File:  [Isaki's NoNo m68k/m88k emulator] / nono / lib / bitmap.cpp
Revision 1.1.1.8 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:05:56 2026 UTC (2 months, 3 weeks ago) by root
Branches: MAIN, Isaki
CVS tags: v027, HEAD
nono 1.7.0

//
// nono
// Copyright (C) 2021 nono project
// Licensed under nono-license.txt
//

//
// ビットマップ
//

#include "bitmap.h"
#include "mainapp.h"
#include <cmath>

//
// ビットマップ (基本クラス)
//

// コンストラクタ
BitmapBase::BitmapBase(uint depth_, uint width_, uint height_, const void *buf_)
	: depth(depth_)
{
	assert(depth == 1 || (depth % 8) == 0);
	Create(width_, height_, buf_);
}

// デストラクタ
BitmapBase::~BitmapBase()
{
}

// サイズを指定してビットマップを作成する。
// width_, height_ は 0 が指定される場合もある(初期状態)。
// buf_ が NULL ならこっちでメモリを確保。
void
BitmapBase::Create(uint width_, uint height_, const void *buf_)
{
	width  = width_;
	height = height_;

	if (depth == 1) {
		stride = (width + 7) / 8;
	} else {
		stride = width * depth / 8;
	}

	owned_buf.Reset();
	if (buf_) {
		buf = static_cast<uint8 *>(const_cast<void *>(buf_));
	} else {
		if (width > 0 && height > 0) {
			owned_buf.Reset(stride * height);
		}
		buf = owned_buf.get();
	}
}

// コピーコンストラクタ
BitmapBase::BitmapBase(const BitmapBase& src)
{
	width  = src.width;
	height = src.height;
	stride = src.stride;
	depth  = src.depth;

	owned_buf = src.owned_buf;
	// src.buf は src.owned_buf かクラス外のどこか (か NULL) を指している。
	if (src.buf == src.owned_buf.get()) {
		buf = owned_buf.get();
	} else {
		buf = src.buf;
	}
}

// コピー代入演算子
BitmapBase&
BitmapBase::operator=(const BitmapBase& src)
{
	owned_buf.Reset();

	width  = src.width;
	height = src.height;
	stride = src.stride;
	depth  = src.depth;

	owned_buf = src.owned_buf;
	// src.buf は src.owned_buf かクラス外のどこか (か NULL) を指している。
	if (src.buf == src.owned_buf.get()) {
		buf = owned_buf.get();
	} else {
		buf = src.buf;
	}

	return *this;
}

// 全域をコピーしてくる。
void
BitmapBase::CopyFrom(const BitmapBase *src)
{
	assert(src);
	assert(stride == src->stride);
	assert(height == src->height);

	memcpy(buf, src->buf, stride * height);
}


//
// 1bpp ビットマップ
//

// コンストラクタ
BitmapI1::BitmapI1()
	: BitmapI1(0, 0) // 移譲
{
}

// コンストラクタ
BitmapI1::BitmapI1(uint width_, uint height_)
	: BitmapI1(NULL, width_, height_) // 移譲
{
}

// コンストラクタ
BitmapI1::BitmapI1(const void *buf_, uint width_, uint height_)
	: inherited(1, width_, height_, buf_)
{
}

// デストラクタ
BitmapI1::~BitmapI1()
{
}

// (x, y) のアドレスを返す関数だが、I1 ではサポートしていない。
uint8 *
BitmapI1::GetPtr(int x, int y) const
{
	assertmsg(false, "GetPtr() not work for BitmapI1");
	return NULL;
}

// 1ラスタを読み込む/書き出すマクロ。副作用もりもり。
#define Load(bits, src)	do {	\
		bits = 0;	\
		for (int x = 0; x < stride; x++) {	\
			bits <<= 8;	\
			bits |= *src++;	\
		}	\
} while (0)
#define Store(bits, dst)	do {	\
		for (int x = stride - 1; x >= 0; x--) {	\
			*dst++ = (bits >> (x * 8)) & 0xff;	\
		}	\
} while (0)

// このビットマップ(フォント)をボールドにした新しいビットマップを返す。
BitmapI1
BitmapI1::ConvertToBold() const
{
	assert(stride <= 8);

	BitmapI1 dst(width, height);
	const uint8 *s = buf;
	uint8 *d = dst.buf;
	for (int y = 0; y < height; y++) {
		uint64 bits;
		Load(bits, s);
		bits |= bits >> 1;
		Store(bits, d);
	}
	return dst;
}

// このビットマップ(フォント)をイタリックにした新しいビットマップを返す。
BitmapI1
BitmapI1::ConvertToItalic() const
{
	assert(stride <= 7);

	BitmapI1 dst(width, height);
	int half = height / 2;
	const uint8 *s = buf;
	uint8 *d = dst.buf;
	int y;

	// 上段は、右端が sticky な右シフト。
	for (y = 0; y <= half; y++) {
		uint64 bits;
		Load(bits, s);
		bits = (bits >> 1) | (bits & 1);
		Store(bits, d);
	}

	// 下段は、シフトしないのでコピー。
	memcpy(d, s, stride * (height - y));

	return dst;
}

#undef Load
#undef Store


//
// 8bpp ビットマップ
//

// コンストラクタ
BitmapI8::BitmapI8()
	: BitmapI8(0, 0) // 移譲
{
}

// コンストラクタ
BitmapI8::BitmapI8(uint width_, uint height_)
	: BitmapI8(NULL, width_, height_) // 移譲
{
}

// コンストラクタ
BitmapI8::BitmapI8(const void *buf_, uint width_, uint height_)
	: inherited(8, width_, height_, buf_)
{
}

// デストラクタ
BitmapI8::~BitmapI8()
{
}


//
// 24bpp ビットマップ
//

// コンストラクタ
BitmapRGB::BitmapRGB()
	: BitmapRGB(0, 0) // 移譲
{
}

// コンストラクタ
BitmapRGB::BitmapRGB(uint width_, uint height_)
	: BitmapRGB(NULL, width_, height_) // 移譲
{
}

// コンストラクタ
BitmapRGB::BitmapRGB(const void *buf_, uint width_, uint height_)
	: inherited(24, width_, height_, buf_)
{
}

// デストラクタ
BitmapRGB::~BitmapRGB()
{
}


//
// 32bpp (RGBX) ビットマップ
//

// コンストラクタ
BitmapRGBX::BitmapRGBX()
	: BitmapRGBX(0, 0) // 移譲
{
}

// コンストラクタ
BitmapRGBX::BitmapRGBX(uint width_, uint height_)
	: BitmapRGBX(NULL, width_, height_) // 移譲
{
}

// コンストラクタ
BitmapRGBX::BitmapRGBX(const void *buf_, uint width_, uint height_)
	: inherited(32, width_, height_, buf_)
{
#if defined(HAVE_AVX2)
	enable_avx2 = gMainApp.enable_avx2;
#endif
}

// デストラクタ
BitmapRGBX::~BitmapRGBX()
{
}

// (dx, dy) にビットマップ(I1) src を描画する。
// 描画範囲をはみ出さないこと。
void
BitmapRGBX::DrawBitmapI1(int dx, int dy, const BitmapI1& src,
	const Color *palette)
{
	assert(buf);

	const uint8 *srcbuf = src.GetBuf();

	uint8 data = 0;
	for (uint y = 0; y < src.GetHeight(); y++) {
		uint32 *d = (uint32 *)GetPtr(dx, dy + y);
		for (uint x = 0; x < src.GetWidth(); x++) {
			if (__predict_false(x % 8 == 0)) {
				data = *srcbuf++;
			}
			int cc;
			if ((int8)data < 0) {
				cc = 1;
			} else {
				cc = 0;
			}
			data <<= 1;

			*d++ = palette[cc].u32;
		}
	}
}

// (dx, dy) にビットマップ src を scale 倍にして描画する。
// 描画範囲をはみ出さないこと。
void
BitmapRGBX::DrawBitmapI1Scale(int dx, int dy, const BitmapI1& src,
	const Color *palette, int scale)
{
	assert(buf);

	const uint8 *srcbuf = src.GetBuf();
	uint8 data = 0;
	uint dx0 = dx;

	for (uint y = 0; y < src.GetHeight(); y++) {
		dx = dx0;
		for (uint x = 0; x < src.GetWidth(); x++) {
			if (__predict_false(x % 8 == 0)) {
				data = *srcbuf++;
			}
			int cc;
			if ((int8)data < 0) {
				cc = 1;
			} else {
				cc = 0;
			}
			data <<= 1;

			FillRect(palette[cc], dx, dy, scale, scale);
			dx += scale;
		}
		dy += scale;
	}
}

// BitmapI8 内の s8 から BitmapRGBX 内の d32 に width ピクセルをコピー (C++ 版)
// width は双方の画像をはみ出さないこと。
void
BitmapRGBX::CopyLineI8_gen(uint32 *d32, const uint8 *s8, uint width,
	const Color *palette)
{
	uint x = 0;

	if (__predict_true((((uintptr_t)s8) & 3) == 0)) {
		// 4ピクセルずつ処理する
		uint w1 = width & ~3;
		for (; x < w1; x += 4) {
			uint32 cc4 = *(const uint32 *)s8;
			s8 += 4;

			// 4ピクセルをそれぞれ Color に変換
#if BYTE_ORDER == LITTLE_ENDIAN
			// cc4 = $I3I2I1I0
			Color c0 = palette[ cc4        & 0xff];
			Color c1 = palette[(cc4 >>  8) & 0xff];
			Color c2 = palette[(cc4 >> 16) & 0xff];
			Color c3 = palette[ cc4 >> 24        ];
#else
			// cc4 = $I0I1I2I3
			Color c0 = palette[ cc4 >> 24        ];
			Color c1 = palette[(cc4 >> 16) & 0xff];
			Color c2 = palette[(cc4 >>  8) & 0xff];
			Color c3 = palette[ cc4        & 0xff];
#endif

			*d32++ = c0.u32;
			*d32++ = c1.u32;
			*d32++ = c2.u32;
			*d32++ = c3.u32;
		}
	}

	for (; x < width; x++) {
		uint cc = *s8++;
		Color c = palette[cc];
		*d32++ = c.u32;
	}
}

// (dx, dy) にビットマップ(I8) src の範囲 sr を描画する。
// 描画範囲をはみ出さないこと。
void
BitmapRGBX::DrawBitmapI8(int dx, int dy, const BitmapI8& src,
	const Color *palette, const Rect& sr)
{
#if defined(HAVE_AVX2)
	if (__predict_true(enable_avx2)) {
		for (uint y = 0; y < sr.h; y++) {
			uint32 *d = (uint32 *)GetPtr(dx, dy + y);
			const uint8 *s = src.GetPtr(sr.x, sr.y + y);
			CopyLineI8_avx2(d, s, sr.w, palette);
		}
	} else
#endif
	{
		for (uint y = 0; y < sr.h; y++) {
			uint32 *d = (uint32 *)GetPtr(dx, dy + y);
			const uint8 *s = src.GetPtr(sr.x, sr.y + y);
			CopyLineI8_gen(d, s, sr.w, palette);
		}
	}
}

// ビットマップ(I8) src の全域を指定のラスターのみ描画する。
void
BitmapRGBX::DrawBitmapI8Raster(const BitmapI8& src, const Color *palette,
	const uint8 *update_raster)
{
	uint width = src.GetWidth();
	uint height = src.GetHeight();

#if defined(HAVE_AVX2)
	if (__predict_true(enable_avx2)) {
		for (uint y = 0; y < height; y++) {
			if (update_raster[y] == 0) {
				continue;
			}
			uint32 *d = (uint32 *)GetRowPtr(y);
			const uint8 *s = src.GetRowPtr(y);
			CopyLineI8_avx2(d, s, width, palette);
		}
	} else
#endif
	{
		for (uint y = 0; y < height; y++) {
			if (update_raster[y] == 0) {
				continue;
			}
			uint32 *d = (uint32 *)GetRowPtr(y);
			const uint8 *s = src.GetRowPtr(y);
			CopyLineI8_gen(d, s, width, palette);
		}
	}
}

// 分数で表現可能な拡大縮小描画。
// dr にビットマップ src の (sxN / sxD, syN / syD) からを
// dst の 1 ピクセルに対して (sxS / sxD, syS / syD) ずつ動かした位置を
// 読み出し位置として描画する。
// 描画範囲をはみ出さないこと。
void
BitmapRGBX::DrawBitmapI8Scale(const Rect& dr, const BitmapI8& src,
	const Color *palette,
	int sxN, int sxD, int sxS, int syN, int syD, int syS)
{
	assert(buf);
	assert(sxD >= 1);
	assert(syD >= 1);
	assert(sxS != 0);
	assert(syS != 0);

	int sxI = sxN / sxD;
	int syI = syN / syD;

	sxN %= sxD;
	syN %= syD;

	int sxI0 = sxI;
	int sxN0 = sxN;

	int sx;
	int sy = syI - 1;

	for (uint y = 0; y < dr.h; y++) {
		uint32 *d = (uint32 *)GetPtr(dr.x, dr.y + y);

		if (sy != syI) {
			sy = syI;

			sx = sxI0 - 1;
			sxI = sxI0;
			sxN = sxN0;

			uint8 cc = 0;
			for (uint x = 0; x < dr.w; x++) {
				if (sx != sxI) {
					sx = sxI;
					const uint8 *s = src.GetPtr(sx, sy);
					cc = *s;
				}
				*d++ = palette[cc].u32;

				sxN += sxS;
				if (sxN >= sxD) {
					sxI += sxN / sxD;
					sxN %= sxD;
				}
			}
		} else {
			// 前のラスタの中からコピーすればよい
			memcpy(d, (uint8 *)d - stride, dr.w * 4);
		}

		syN += syS;
		if (syN >= syD) {
			syI += syN / syD;
			syN %= syD;
		}
	}
}

// (dx, dy) にビットマップ(RGBX) src の範囲 sr を描画する。
// 描画範囲をはみ出さないこと。
void
BitmapRGBX::DrawBitmap(int dx, int dy, const BitmapRGBX& src, const Rect& sr)
{
	assert(buf);

	for (uint y = 0; y < sr.h; y++) {
		uint32 *d = (uint32 *)GetPtr(dx, dy + y);
		const uint32 *s = (const uint32 *)src.GetPtr(sr.x, sr.y + y);
		for (uint x = 0; x < sr.w; x++) {
			*d++ = *s++;
		}
	}
}

// src 全域が dr のサイズになるよう拡大縮小して描画。
// 拡大、縮小、等倍すべて可能。
void
BitmapRGBX::DrawBitmapStretch(const Rect& dr, const BitmapRGBX& src)
{
	uint srcw = src.GetWidth();
	uint srch = src.GetHeight();

	if (dr.w < srcw && dr.h < srch) {
		uint w1 = srcw - dr.w;
		uint h1 = srch - dr.h;
		uint wn = dr.w / w1;
		uint wr = dr.w % w1;
		uint hn = dr.h / h1;
		uint hr = dr.h % h1;
		if (wr == 0 && hr == 0 && wn == hn && wn < 256) {
			// (N-1)/N 倍に縮小。
			DrawBitmapNtoNm1(dr, src, wn + 1);
			return;
		}
	}
	if (dr.w > srcw && dr.h > srch) {
		uint w1 = dr.w - srcw;
		uint h1 = dr.h - srch;
		uint wn = dr.w / w1;
		uint wr = dr.w % w1;
		uint hn = dr.h / h1;
		uint hr = dr.h % h1;
		if (wr == 0 && hr == 0 && wn == hn && wn < 16) {
			// N/(N-1) 倍に拡大。
			DrawBitmapNm1toN(dr, src, wn);
			return;
		}
	}
	if (__predict_false(dr.w == srcw && dr.h == srch)) {
		// 等倍。
		DrawBitmap(dr.x, dr.y, src);
		return;
	}

	// 任意倍率の拡大縮小。
	DrawBitmapMean(dr, src);
}

// src 全域を (N-1)/N 倍に縮小描画。
// dr.w == src.Width * (N-1)/N
// dr.h == src.Height * (N-1)/N
// で呼び出すこと。
void
BitmapRGBX::DrawBitmapNtoNm1(const Rect& dr, const BitmapRGBX& src, uint N)
{
	// 画像を 3/4 倍に縮小する場合 (N=4、N1=3、N2=2)。
	//
	// ※ この説明中では二次元座標の x, y を A(y, x) マクロの順に揃えてあり
	//    数学で通常使う (x, y) の順ではない点に注意。
	//
	// dst の (0, 0) は src の (0, 0), (0, 1), (1, 0), (1, 1) の4点から、
	// dst の (0, 1) は src の (0, 1), (0, 2), (1, 1), (1, 2) の4点から、
	// dst の (0, 2) は src の (0, 2), (0, 3), (1, 2), (1, 3) の4点から、
	// :
	// 適当な割合で合成することになる。
	// この時どの dst に対しても src が4点なことがポイント。
	//
	// 出力 3x3 ピクセルに対する入力の 4x4 ピクセル内部を各 2x2 に小分けに
	// した計 8x8 の行列を以下のようにすると、この (空白で区切られた) 各 4x4
	// ピクセル内の 4値の合計はいずれも 9。
	//
	//  0 0  0 0  0 0  0 0
	//  0 9  3 6  6 3  9 0
	//
	//  0 3  1 2  2 1  3 0
	//  0 6  2 4  4 2  6 0
	//
	//  0 6  2 4  4 2  6 0
	//  0 3  1 2  2 1  3 0
	//
	//  0 9  3 6  6 3  9 0
	//  0 0  0 0  0 0  0 0
	//
	// これを外周の 0 を取り除いた 6x6 行列に読み替えると、
	// この (空白で区切られた) 各 3x3 ピクセル内の4値の合計はいずれも 16 に
	// なっており、元の 4x4 ピクセルの成分を均等に分配できる。
	//
	//  9 3  6 6  3 9
	//  3 1  2 2  1 3
	//
	//  6 2  4 4  2 6
	//  6 2  4 4  2 6
	//
	//  3 1  2 2  1 3
	//  9 3  6 6  3 9
	//
	//
	// この計 6x6 の行列は 3x3 (N1 x N1) 行列の A[] から、オフセットを
	// 移動させながら求めることが出来る。
	//
	//     | 1  2  3 |
	// A = | 2  4  6 | / 16
	//     | 3  6  9 |
	//
	// 具体的には dst(0, 0), dst(0, 1), dst(0, 2) の変換行列はそれぞれ
	// 次のようになる。
	//
	//           | 9  3 |                | 6  6 |                | 3  9 |
	// a_(0,0) = | 3  1 |/16,  a_(0,1) = | 2  2 |/16,  a_(0,2) = | 1  3 |/16
	//
	// よって例えば dst(0, 2) の点の明るさは a_(0,2) を用いて
	//
	//  dst(0, 2) = src(0, 2) * 3/16 + src(0, 3) * 9/16
	//            + src(1, 2) * 1/16 + src(1, 3) * 3/16
	//
	// で求められる。

	assertmsg(N >= 2, "N=%u", N);
	uint N1 = N - 1;
	uint N2 = N - 2;
	std::vector<uint> a(N1 * N1);
#define A(y, x) a[(y) * N1 + (x)]

	// 小数部 8bit の固定小数点数で面積比変換行列を計算。
	for (uint y = 0; y < N1; y++) {
		for (uint x = 0; x < N1; x++) {
			A(y, x) = (x + 1) * (y + 1) * 256 / (N * N);
		}
	}

	// N to (N-1) 変換なので、dst 側 N-1 ピクセルごとに
	// src 側を N ピクセル参照すればよく、境界条件を簡略化できる。
	const uint32 stride32 = src.GetStride() / sizeof(uint32);
	for (uint sy = 0, dy = 0; dy < dr.h; sy += N, dy += N1) {
		for (uint sx = 0, dx = 0; dx < dr.w; sx += N, dx += N1) {
			const uint32 *s0 = (const uint32 *)src.GetPtr(sx, sy);
			for (uint ty = 0; ty < N1; ty++) {
				const uint32 *s1 = s0 + stride32;
				uint32 *d = (uint32 *)GetPtr(dr.x + dx, dr.y + dy + ty);
				for (uint tx = 0; tx < N1; tx++) {
					uint a00 = A(N2 - ty, N2 - tx);
					uint a01 = A(N2 - ty,      tx);
					uint a10 = A(     ty, N2 - tx);
					uint a11 = A(     ty,      tx);

					Color c00(s0[tx + 0]);
					Color c01(s0[tx + 1]);
					Color c10(s1[tx + 0]);
					Color c11(s1[tx + 1]);

					uint r, g, b;
					r = a00 * c00.r
					  + a01 * c01.r
					  + a10 * c10.r
					  + a11 * c11.r;
					r >>= 8;

					g = a00 * c00.g
					  + a01 * c01.g
					  + a10 * c10.g
					  + a11 * c11.g;
					g >>= 8;

					b = a00 * c00.b
					  + a01 * c01.b
					  + a10 * c10.b
					  + a11 * c11.b;
					b >>= 8;

					Color c(r, g, b);
					d[tx] = c.u32;
				}
				s0 += stride32;
			}
		}
	}
#undef A
}

// src 全域を N/(N-1) 倍に拡大描画。
// dr.w == src.Width * N/(N-1)
// dr.h == src.Height * N/(N-1)
// で呼び出すこと。
void
BitmapRGBX::DrawBitmapNm1toN(const Rect& dr, const BitmapRGBX& src, uint N)
{
	assertmsg(N >= 2, "N=%u", N);
	uint N1 = N - 1;
	std::vector<uint> a(N * N);
#define A(y, x) a[(y) * N + (x)]

	// 小数部 8bit の固定小数点数で面積比変換行列を計算。
	for (uint y = 0; y < N; y++) {
		for (uint x = 0; x < N; x++) {
			A(y, x) = x * y * 256 / (N1 * N1);
		}
	}

	const uint sstride32 = src.GetStride() / sizeof(uint32);
	const uint dstride32 = GetStride() / sizeof(uint32);
	// N to (N+1) 変換なので、
	for (uint sy = 0, dy = 0; dy < dr.h; sy += N1, dy += N) {
		for (uint sx = 0, dx = 0; dx < dr.w; sx += N1, dx += N) {
			const uint32 *s0i = (const uint32 *)src.GetPtr(sx, sy);
			uint32 *d = (uint32 *)GetPtr(dr.x + dx, dr.y + dy);
			for (uint ty = 0; ty < N; ty++) {
				const uint32 *s1 = (const uint32 *)(s0i + ty * sstride32);
				const uint32 *s0 = (const uint32 *)(s1 - sstride32);
				for (uint tx = 0; tx < N; tx++, s0++, s1++) {
					uint a00 = A(     ty,      tx);
					uint a01 = A(     ty, N1 - tx);
					uint a10 = A(N1 - ty,      tx);
					uint a11 = A(N1 - ty, N1 - tx);

					uint32 v00 = a00 ? s0[-1] : 0;
					uint32 v01 = a01 ? s0[ 0] : 0;
					uint32 v10 = a10 ? s1[-1] : 0;
					uint32 v11 = a11 ? s1[ 0] : 0;

					Color c00(v00);
					Color c01(v01);
					Color c10(v10);
					Color c11(v11);

					uint r, g, b;

					r = a00 * c00.r
					  + a01 * c01.r
					  + a10 * c10.r
					  + a11 * c11.r;
					r >>= 8;

					g = a00 * c00.g
					  + a01 * c01.g
					  + a10 * c10.g
					  + a11 * c11.g;
					g >>= 8;

					b = a00 * c00.b
					  + a01 * c01.b
					  + a10 * c10.b
					  + a11 * c11.b;
					b >>= 8;

					Color c(r, g, b);
					d[tx] = c.u32;
				}
				d += dstride32;
			}
		}
	}
#undef A
}

// src 全域が dr のサイズになるよう面積平均法で拡大縮小して描画。
// (等倍も無駄だけど動作はするはず)
void
BitmapRGBX::DrawBitmapMean(const Rect& dr, const BitmapRGBX& src)
{
	// dst の 1 pixel が src の 何 pixel に相当するか
	float tx = (float)src.GetWidth() / dr.w;
	float ty = (float)src.GetHeight() / dr.h;

	// src 上を動くウィンドウ
	RectF sr(0, 0, tx, ty);

	for (uint y = 0; y < dr.h; y++) {
		uint32 *d = (uint32 *)GetPtr(dr.x, dr.y + y);
		sr.x = 0;
		for (uint x = 0; x < dr.w; x++) {
			Color c = src.Mean(sr);
			*d++ = c.u32;
			sr.x += tx;
		}
		sr.y += ty;
	}
}

// sr 範囲の色の平均を返す
Color
BitmapRGBX::Mean(const RectF& sr) const
{
	uint x1 = std::floor(sr.x);
	uint y1 = std::floor(sr.y);
	uint x2 = std::ceil(sr.x + sr.w);
	uint y2 = std::ceil(sr.y + sr.h);
	if (__predict_false(x2 > GetWidth())) {
		x2 = GetWidth();
	}
	if (__predict_false(y2 > GetHeight())) {
		y2 = GetHeight();
	}

	float sum_r = 0;
	float sum_g = 0;
	float sum_b = 0;

	// サブピクセルの辺の長さ
	float rx, ry;
	float area = 0;
	for (uint y = y1; y < y2; y++) {
		if (y == y1) {
			ry = 1 - (sr.y - y1);
		} else if (y == y2 - 1) {
			ry = 1 - (y2 - (sr.y + sr.h));
		} else {
			ry = 1;
		}

		const uint32 *p = (const uint32 *)GetPtr(x1, y);
		for (uint x = x1; x < x2; x++) {
			Color c(*p++);

			if (x == x1) {
				rx = 1 - (sr.x - x1);
			} else if (x == x2 - 1) {
				rx = 1 - (x2 - (sr.x + sr.w));
			} else {
				rx = 1;
			}

			// 面積比をかけて合成
			sum_r += c.r * rx * ry;
			sum_g += c.g * rx * ry;
			sum_b += c.b * rx * ry;
			area += rx * ry;
		}
	}

	// 平均にする
	sum_r /= area;
	sum_g /= area;
	sum_b /= area;

	return Color(sum_r, sum_g, sum_b);
}

// (x, y) に点を描画する。
// 描画範囲をはみ出さないこと。
void
BitmapRGBX::DrawPoint(Color c, int x, int y)
{
	uint32 *d = (uint32 *)GetPtr(x, y);
	*d = c.u32;
}

// 直線 (x1, y1) - (x2, y2) を描画する。終点は開区間。
// 描画範囲をはみ出さないこと。
void
BitmapRGBX::DrawLine(Color c, int x1, int y1, int x2, int y2)
{
	if (y1 == y2) {
		DrawLineH(c, x1, y1, x2);
	} else if (x1 == x2) {
		DrawLineV(c, x1, y1, y2);
	} else {
		assert(false);
	}
}

// 水平の直線 (x1, y1) - (x2, y1) を描画する。終点は開区間。
// 描画範囲をはみ出さないこと。
void
BitmapRGBX::DrawLineH(Color c, int x1, int y1, int x2)
{
	assert(buf);

	if (__predict_false(x1 > x2)) {
		std::swap(x1, x2);
		x1++;
		x2++;
	}

	uint32 *d = (uint32 *)GetPtr(x1, y1);
	for (int x = x1; x < x2; x++) {
		*d++ = c.u32;
	}
}

// 垂直の直線 (x1, y1) - (x1, y2) を描画する。終点は開区間。
// 描画範囲をはみ出さないこと。
void
BitmapRGBX::DrawLineV(Color c, int x1, int y1, int y2)
{
	assert(buf);

	if (__predict_false(y1 > y2)) {
		std::swap(y1, y2);
		y1++;
		y2++;
	}

	for (int y = y1; y < y2; y++) {
		uint32 *d = (uint32 *)GetPtr(x1, y);
		*d = c.u32;
	}
}

// 矩形 rect を色 c で塗りつぶす。
// 描画範囲をはみ出さないこと。
void
BitmapRGBX::FillRect(Color c, const Rect& rect)
{
	assert(buf);

	DrawLineH(c, rect.x, rect.y, rect.x + rect.w);
	CopyFromTop(rect);
}

// rect の一番上のラスターを rect の残りのラスターにコピーする。
void
BitmapRGBX::CopyFromTop(const Rect& rect)
{
	uint8 *s = GetPtr(rect.x, rect.y);
	uint8 *d = s + stride;
	const uint len = rect.w * 4;

	for (uint y = rect.y + 1; y < rect.y + rect.h; y++) {
		memcpy(d, s, len);
		d += stride;
	}
}

// 矩形 rect の辺を色 c で描画する。
// 描画範囲をはみ出さないこと。
void
BitmapRGBX::DrawRect(Color c, const Rect& rect)
{
	assert(buf);

	int l = rect.x;
	int t = rect.y;
	int r = rect.GetRight();
	int b = rect.GetBottom();

	DrawLineH(c, l, t, r);	// 上辺→
	DrawLineV(c, r, t, b);	// 右辺↓
	DrawLineH(c, r, b, l);	// 下辺←
	DrawLineV(c, l, b, t);	// 左辺↑
}

// 矩形 rect に内接する正円あるいは楕円を描画して中を塗りつぶす。
// 描画範囲をはみ出さないこと。
void
BitmapRGBX::DrawFillCircle(Color border, Color fill, const Rect& rect)
{
	int D = rect.h;				// 高さを基準にした直径
	float r = D / 2;
	float r2 = r * (r + 0.5);	// 半径の二乗
	float prev_d = -1;

	// Y 方向を 1 px ずつステップしながら上下両側を対称に描画する。
	// 奇数個のときは真ん中を 2 回書くため、D&1 で補正。
	for (int i = 0, end = r + (D & 1); i < end; i++) {
		// x^2 + y^2 = r^2 を解いて、楕円の係数を掛ける。
		float d = std::sqrt(r2 - r * r) * rect.w / rect.h;
		r--;

		for (int j = 0; j < 2; j++) {
			int y;
			if (j == 0) {
				y = rect.y + i;
			} else {
				y = rect.GetBottom() - i;
			}

			int x1 = rect.x + rect.w / 2 - d;
			int x2 = rect.x + rect.w / 2 + d;

			DrawLineH(fill, x1, y, x2);

			DrawPoint(border, x1, y);
			DrawPoint(border, x2, y);

			int prev_x1 = rect.x + rect.w / 2 - prev_d;
			int prev_x2 = rect.x + rect.w / 2 + prev_d;
			DrawLineH(border, x1, y, prev_x1);
			DrawLineH(border, x2, y, prev_x2);
		}

		prev_d = d;
	}
}

// 全域を RGB に変換。
void
BitmapRGBX::ConvertToRGB(BitmapRGB& dst) const
{
	assertmsg(dst.GetWidth() == GetWidth(),
		"dst.width=%u, src.width=%u", dst.GetWidth(), GetWidth());
	assertmsg(dst.GetHeight() == GetHeight(),
		"dst.height=%u, src.height=%u", dst.GetWidth(), GetWidth());

#if defined(HAVE_AVX2)
	if (__predict_true(enable_avx2)) {
		ConvertToRGB_avx2(dst);
	} else
#endif
	{
		ConvertToRGB_gen(dst);
	}
}

// RGBX から RGB への変換。(C++ 版)
void
BitmapRGBX::ConvertToRGB_gen(BitmapRGB& dst) const
{
	const BitmapRGBX& src = *this;

	// この変換は一次元配列とみなしても行える。
	// モニタ等のパネルでも呼ばれるので何ピクセルで割り切れるかの前提は不可。
	uint total  = GetWidth() * GetHeight();
	uint packed = total & ~3;

	const uint32 *s32 = (const uint32 *)src.GetRowPtr(0);
	const uint32 *s32total  = s32 + total;
	const uint32 *s32packed = s32 + packed;

	uint32 *d32 = (uint32 *)dst.GetRowPtr(0);
	for (; s32 < s32packed; ) {
		// 4ピクセルずつ処理する
		uint32 c0 = *s32++;
		uint32 c1 = *s32++;
		uint32 c2 = *s32++;
		uint32 c3 = *s32++;

#if BYTE_ORDER == LITTLE_ENDIAN
		// c0   = $X0B0G0R0
		// c1   = $X1B1G1R1
		// c2   = $X2B2G2R2
		// c3   = $X3B3G3R3
		//
		// d[0] = $00B0G0R0 | $R1000000 = $R1B0G0R0
		// d[1] = $0000B1G1 | $G2R20000 = $G2R2B1G1
		// d[2] = $000000B2 | $B3G3R300 = $B3G3R3B2

		*d32++ = (c0      ) | (c1 << 24);
		*d32++ = (c1 >>  8) | (c2 << 16);
		*d32++ = (c2 >> 16) | (c3 <<  8);
#else
		// c0   = $R0G0B0X0
		// c1   = $R1G1B1X1
		// c2   = $R2G2B2X2
		// c3   = $R3G3B3X3
		//
		// d[0] = $R0G0B000 | $000000R1 = $R0G0B0R1
		// d[1] = $G1B10000 | $0000R2G2 = $G1B1R2G2
		// d[2] = $B2000000 | $00R3G3B3 = $B2R3G3B3

		*d32++ = (c0      ) | (c1 >> 24);
		*d32++ = (c1 <<  8) | (c2 >> 16);
		*d32++ = (c2 << 16) | (c3 >>  8);
#endif
	}

	uint8 *d8 = (uint8 *)d32;
	for (; s32 < s32total; ) {
		// 残りを1ピクセルずつ処理する
		Color col(*s32++);
		*d8++ = col.r;
		*d8++ = col.g;
		*d8++ = col.b;
	}
}

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.