--- nono/lib/bitmap.cpp 2026/04/29 17:05:15 1.1.1.3 +++ nono/lib/bitmap.cpp 2026/04/29 17:05:47 1.1.1.7 @@ -9,6 +9,7 @@ // #include "bitmap.h" +#include "mainapp.h" #include // @@ -16,14 +17,11 @@ // // コンストラクタ -BitmapBase::BitmapBase() +BitmapBase::BitmapBase(uint depth_, uint width_, uint height_, const void *buf_) + : depth(depth_) { -} - -// コンストラクタ -BitmapBase::BitmapBase(int width_, int height_, int depth_) -{ - Create(width_, height_, depth_); + assert(depth == 1 || (depth % 8) == 0); + Create(width_, height_, buf_); } // デストラクタ @@ -31,22 +29,80 @@ BitmapBase::~BitmapBase() { } -// 作成。width_ * height_ が面積を持っていれば true を返す。 -bool -BitmapBase::Create(int width_, int height_, int depth_) +// サイズを指定してビットマップを作成する。 +// width_, height_ は 0 が指定される場合もある(初期状態)。 +// buf_ が NULL ならこっちでメモリを確保。 +void +BitmapBase::Create(uint width_, uint height_, const void *buf_) { - assert(depth_ == 1 || depth_ == 8 || depth_ == 24); - - width = width_; + width = width_; height = height_; - if (depth_ == 1) { + if (depth == 1) { stride = (width + 7) / 8; } else { - stride = width * depth_ / 8; + stride = width * depth / 8; + } + + owned_buf.Reset(); + if (buf_) { + buf = static_cast(const_cast(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); - return (width > 0 && height > 0); + memcpy(buf, src->buf, stride * height); } @@ -56,20 +112,20 @@ BitmapBase::Create(int width_, int heigh // コンストラクタ BitmapI1::BitmapI1() + : BitmapI1(0, 0) // 移譲 { } // コンストラクタ -BitmapI1::BitmapI1(int width_, int height_) +BitmapI1::BitmapI1(uint width_, uint height_) + : BitmapI1(NULL, width_, height_) // 移譲 { - Create(width_, height_); } // コンストラクタ -BitmapI1::BitmapI1(const void *buf_, int width_, int height_) - : inherited(width_, height_, 1) +BitmapI1::BitmapI1(const void *buf_, uint width_, uint height_) + : inherited(1, width_, height_, buf_) { - buf = static_cast(const_cast(buf_)); } // デストラクタ @@ -77,29 +133,75 @@ BitmapI1::~BitmapI1() { } -// 作成 -void -BitmapI1::Create(int width_, int height_) -{ - if (inherited::Create(width_, height_, 1)) { - owned_buf.reset(new uint8[stride * height]); - buf = owned_buf.get(); - } else { - owned_buf.reset(); - buf = NULL; +// (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); } -} -// カラーコード cc で全体を塗りつぶす。 -// 未使用ビットも変更される。 -void -BitmapI1::Fill(int cc) -{ - assert(buf); + // 下段は、シフトしないのでコピー。 + memcpy(d, s, stride * (height - y)); - memset(buf, (cc ? 0xff : 0), stride * height); + return dst; } +#undef Load +#undef Store + // // 8bpp ビットマップ @@ -107,19 +209,20 @@ BitmapI1::Fill(int cc) // コンストラクタ BitmapI8::BitmapI8() + : BitmapI8(0, 0) // 移譲 { } // コンストラクタ -BitmapI8::BitmapI8(int width_, int height_) +BitmapI8::BitmapI8(uint width_, uint height_) + : BitmapI8(NULL, width_, height_) // 移譲 { - Create(width_, height_); } // コンストラクタ -BitmapI8::BitmapI8(const void *buf_, int width_, int height_) +BitmapI8::BitmapI8(const void *buf_, uint width_, uint height_) + : inherited(8, width_, height_, buf_) { - Create(buf_, width_, height_); } // デストラクタ @@ -127,101 +230,79 @@ BitmapI8::~BitmapI8() { } -// 作成 -void -BitmapI8::Create(int width_, int height_) + +// +// 24bpp ビットマップ +// + +// コンストラクタ +BitmapRGB::BitmapRGB() + : BitmapRGB(0, 0) // 移譲 { - if (inherited::Create(width_, height_, 8)) { - owned_buf.reset(new uint8[stride * height]); - buf = owned_buf.get(); - } else { - owned_buf.reset(); - buf = NULL; - } } -// 元データを指定して作成 -void -BitmapI8::Create(const void *buf_, int width_, int height_) +// コンストラクタ +BitmapRGB::BitmapRGB(uint width_, uint height_) + : BitmapRGB(NULL, width_, height_) // 移譲 { - if (inherited::Create(width_, height_, 8)) { - buf = static_cast(const_cast(buf_)); - } else { - buf = NULL; - } } -// カラーコード cc で全体を塗りつぶす。 -// 未使用ビットも変更される。 -void -BitmapI8::Fill(int cc) +// コンストラクタ +BitmapRGB::BitmapRGB(const void *buf_, uint width_, uint height_) + : inherited(24, width_, height_, buf_) { - assert(buf); +} - memset(buf, cc, stride * height); +// デストラクタ +BitmapRGB::~BitmapRGB() +{ } // -// 24bpp ビットマップ +// 32bpp (RGBX) ビットマップ // // コンストラクタ -BitmapRGB::BitmapRGB() +BitmapRGBX::BitmapRGBX() + : BitmapRGBX(0, 0) // 移譲 { } // コンストラクタ -BitmapRGB::BitmapRGB(int width_, int height_) +BitmapRGBX::BitmapRGBX(uint width_, uint height_) + : BitmapRGBX(NULL, width_, height_) // 移譲 { - Create(width_, height_); } // コンストラクタ -BitmapRGB::BitmapRGB(const void *buf_, int width_, int height_) - : inherited(width_, height_, 24) +BitmapRGBX::BitmapRGBX(const void *buf_, uint width_, uint height_) + : inherited(32, width_, height_, buf_) { - buf = static_cast(const_cast(buf_)); +#if defined(HAVE_AVX2) + enable_avx2 = gMainApp.enable_avx2; +#endif } // デストラクタ -BitmapRGB::~BitmapRGB() -{ -} - -// 作成 -void -BitmapRGB::Create(int width_, int height_) +BitmapRGBX::~BitmapRGBX() { - if (inherited::Create(width_, height_, 24)) { - owned_buf.reset(new uint8[stride * height]); - buf = owned_buf.get(); - } else { - owned_buf.reset(); - buf = NULL; - } -} - -// 色 c で全体を塗りつぶす。 -void -BitmapRGB::Fill(Color c) -{ - FillRect(c, 0, 0, width, height); } -// (dx, dy) にビットマップ src を描画する。 +// (dx, dy) にビットマップ(I1) src を描画する。 // 描画範囲をはみ出さないこと。 void -BitmapRGB::DrawBitmap(int dx, int dy, const BitmapI1& src, const Color *palette) +BitmapRGBX::DrawBitmapI1(int dx, int dy, const BitmapI1& src, + const Color *palette) { assert(buf); const uint8 *srcbuf = src.GetBuf(); uint8 data = 0; - for (int y = 0; y < src.GetHeight(); y++) { - uint8 *d = GetPtr(dx, dy + y); - for (int x = 0; x < src.GetWidth(); x++) { + 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++; } @@ -233,57 +314,7 @@ BitmapRGB::DrawBitmap(int dx, int dy, co } data <<= 1; - Color c = palette[cc]; - *d++ = c.r; - *d++ = c.g; - *d++ = c.b; - } - } -} - -// (dx, dy) にビットマップ src の全域を描画する。 -// 描画範囲をはみ出さないこと。 -void -BitmapRGB::DrawBitmap(int dx, int dy, const BitmapI8& src, const Color *palette) -{ - Rect sr(0, 0, src.GetWidth(), src.GetHeight()); - DrawBitmap(dx, dy, src, palette, sr); -} - -// (dx, dy) にビットマップ src の範囲 sr を描画する。 -// 描画範囲をはみ出さないこと。 -void -BitmapRGB::DrawBitmap(int dx, int dy, const BitmapI8& src, const Color *palette, - const Rect& sr) -{ - for (int y = 0; y < sr.h; y++) { - uint8 *d = GetPtr(dx, dy + y); - const uint8 *s = src.GetPtr(sr.x, sr.y + y); - for (int x = 0; x < sr.w; x++) { - int cc = *s++; - Color c = palette[cc]; - *d++ = c.r; - *d++ = c.g; - *d++ = c.b; - } - } -} - -// (dx, dy) にビットマップ src を描画する。 -// 描画範囲をはみ出さないこと。 -void -BitmapRGB::DrawBitmap(int dx, int dy, const BitmapRGB& src) -{ - assert(buf); - - const uint8 *s = src.GetBuf(); - - for (int y = 0; y < src.GetHeight(); y++) { - uint8 *d = GetPtr(dx, dy + y); - for (int x = 0; x < src.GetWidth(); x++) { - *d++ = *s++; - *d++ = *s++; - *d++ = *s++; + *d++ = palette[cc].u32; } } } @@ -291,18 +322,18 @@ BitmapRGB::DrawBitmap(int dx, int dy, co // (dx, dy) にビットマップ src を scale 倍にして描画する。 // 描画範囲をはみ出さないこと。 void -BitmapRGB::DrawScaleUp(int dx, int dy, int scale, const BitmapI1& src, - const Color *palette) +BitmapRGBX::DrawBitmapI1Scale(int dx, int dy, const BitmapI1& src, + const Color *palette, int scale) { assert(buf); const uint8 *srcbuf = src.GetBuf(); uint8 data = 0; - int dx0 = dx; + uint dx0 = dx; - for (int y = 0; y < src.GetHeight(); y++) { + for (uint y = 0; y < src.GetHeight(); y++) { dx = dx0; - for (int x = 0; x < src.GetWidth(); x++) { + for (uint x = 0; x < src.GetWidth(); x++) { if (__predict_false(x % 8 == 0)) { data = *srcbuf++; } @@ -321,13 +352,114 @@ BitmapRGB::DrawScaleUp(int dx, int dy, i } } +// 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 -BitmapRGB::DrawScale(const Rect& dr, const BitmapI8& src, const Color *palette, +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); @@ -348,8 +480,8 @@ BitmapRGB::DrawScale(const Rect& dr, con int sx; int sy = syI - 1; - for (int y = 0; y < dr.h; y++) { - uint8 *d = GetPtr(dr.x, dr.y + y); + for (uint y = 0; y < dr.h; y++) { + uint32 *d = (uint32 *)GetPtr(dr.x, dr.y + y); if (sy != syI) { sy = syI; @@ -359,16 +491,13 @@ BitmapRGB::DrawScale(const Rect& dr, con sxN = sxN0; uint8 cc = 0; - for (int x = 0; x < dr.w; x++) { + for (uint x = 0; x < dr.w; x++) { if (sx != sxI) { sx = sxI; const uint8 *s = src.GetPtr(sx, sy); cc = *s; } - const Color& c = palette[cc]; - *d++ = c.r; - *d++ = c.g; - *d++ = c.b; + *d++ = palette[cc].u32; sxN += sxS; if (sxN >= sxD) { @@ -378,7 +507,7 @@ BitmapRGB::DrawScale(const Rect& dr, con } } else { // 前のラスタの中からコピーすればよい - memcpy(d, d - stride, dr.w * 3); + memcpy(d, (uint8 *)d - stride, dr.w * 4); } syN += syS; @@ -389,26 +518,319 @@ BitmapRGB::DrawScale(const Rect& dr, con } } +// (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 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(dx, 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 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(dx, 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 *)GetRowPtr(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 -BitmapRGB::Mean(const RectD& sr) const +BitmapRGBX::Mean(const RectF& sr) const { - int x1 = std::floor(sr.x); - int y1 = std::floor(sr.y); - int x2 = std::ceil(sr.x + sr.w); - int y2 = std::ceil(sr.y + sr.h); - if (__predict_false(x2 >= GetWidth())) { + 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())) { + if (__predict_false(y2 > GetHeight())) { y2 = GetHeight(); } - ColorD sum; + float sum_r = 0; + float sum_g = 0; + float sum_b = 0; + // サブピクセルの辺の長さ - double rx, ry; - double s = 0; - for (int y = y1; y < y2; y++) { + 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) { @@ -417,72 +839,47 @@ BitmapRGB::Mean(const RectD& sr) const ry = 1; } - const uint8 *p = GetPtr(x1, y); - for (int x = x1; x < x2; x++) { + 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 (y == x2 - 1) { + } else if (x == x2 - 1) { rx = 1 - (x2 - (sr.x + sr.w)); } else { rx = 1; } - Color c; - c.r = *p++; - c.g = *p++; - c.b = *p++; - // 面積比をかけて合成 - sum.r += c.r * rx * ry; - sum.g += c.g * rx * ry; - sum.b += c.b * rx * ry; - s += rx * ry; + sum_r += c.r * rx * ry; + sum_g += c.g * rx * ry; + sum_b += c.b * rx * ry; + area += rx * ry; } } // 平均にする - sum.r /= s; - sum.g /= s; - sum.b /= s; + sum_r /= area; + sum_g /= area; + sum_b /= area; - return Color(sum.r, sum.g, sum.b); + return Color(sum_r, sum_g, sum_b); } -// 伸縮して描画 +// (x, y) に点を描画する。 +// 描画範囲をはみ出さないこと。 void -BitmapRGB::StretchDraw(Rect dr, const BitmapRGB& src) +BitmapRGBX::DrawPoint(Color c, int x, int y) { - if (__predict_false(dr.w == src.GetWidth() && dr.h == src.GetHeight())) { - DrawBitmap(dr.x, dr.y, src); - return; - } - - // dst の 1 pixel が src の 何 pixel に相当するか - double tx = (double)src.GetWidth() / dr.w; - double ty = (double)src.GetHeight() / dr.h; - - // src 上を動くウィンドウ - RectD sr(0, 0, tx, ty); - - for (int y = 0; y < dr.h; y++) { - uint8 *d = GetRowPtr(y); - sr.x = 0; - for (int x = 0; x < dr.w; x++) { - Color c = src.Mean(sr); - - *d++ = c.r; - *d++ = c.g; - *d++ = c.b; - sr.x += tx; - } - sr.y += ty; - } + uint32 *d = (uint32 *)GetPtr(x, y); + *d = c.u32; } // 直線 (x1, y1) - (x2, y2) を描画する。終点は開区間。 // 描画範囲をはみ出さないこと。 void -BitmapRGB::DrawLine(Color c, int x1, int y1, int x2, int y2) +BitmapRGBX::DrawLine(Color c, int x1, int y1, int x2, int y2) { if (y1 == y2) { DrawLineH(c, x1, y1, x2); @@ -496,7 +893,7 @@ BitmapRGB::DrawLine(Color c, int x1, int // 水平の直線 (x1, y1) - (x2, y1) を描画する。終点は開区間。 // 描画範囲をはみ出さないこと。 void -BitmapRGB::DrawLineH(Color c, int x1, int y1, int x2) +BitmapRGBX::DrawLineH(Color c, int x1, int y1, int x2) { assert(buf); @@ -506,43 +903,16 @@ BitmapRGB::DrawLineH(Color c, int x1, in x2++; } - uint8 *d = GetPtr(x1, y1); - - // 12バイト境界に整列するまで - for (; (uintptr_t)d % 4 != 0 && x1 < x2 ; x1++) { - *d++ = c.r; - *d++ = c.g; - *d++ = c.b; - } - - // 12バイト境界から 12バイト以上残ってる間 - int cnt = (x2 - x1) / 4; - if (__predict_true(cnt > 0)) { - uint32 pat[3]; - for (uint8 *p = (uint8 *)pat, *pend = p + sizeof(pat); p < pend; ) { - *p++ = c.r; - *p++ = c.g; - *p++ = c.b; - } - for (int i = 0; i < cnt; i++) { - memcpy(d, pat, sizeof(pat)); - d += sizeof(pat); - } - x1 += cnt * sizeof(pat) / 3; - } - - // 残り - for (; x1 < x2; x1++) { - *d++ = c.r; - *d++ = c.g; - *d++ = c.b; + uint32 *d = (uint32 *)GetPtr(x1, y1); + for (int x = x1; x < x2; x++) { + *d++ = c.u32; } } // 垂直の直線 (x1, y1) - (x1, y2) を描画する。終点は開区間。 // 描画範囲をはみ出さないこと。 void -BitmapRGB::DrawLineV(Color c, int x1, int y1, int y2) +BitmapRGBX::DrawLineV(Color c, int x1, int y1, int y2) { assert(buf); @@ -552,56 +922,41 @@ BitmapRGB::DrawLineV(Color c, int x1, in y2++; } - for (; y1 < y2; y1++) { - uint8 *d = GetPtr(x1, y1); - *d++ = c.r; - *d++ = c.g; - *d++ = c.b; + for (int y = y1; y < y2; y++) { + uint32 *d = (uint32 *)GetPtr(x1, y); + *d = c.u32; } } -// 矩形 (dx, dy) - (dw * dh) を色 c で塗りつぶす。 -// 描画範囲をはみ出さないこと。 -void -BitmapRGB::FillRect(Color c, int dx, int dy, int dw, int dh) -{ - FillRect(c, Rect(dx, dy, dw, dh)); -} - // 矩形 rect を色 c で塗りつぶす。 // 描画範囲をはみ出さないこと。 void -BitmapRGB::FillRect(Color c, const Rect& rect) +BitmapRGBX::FillRect(Color c, const Rect& rect) { assert(buf); - const int l = rect.x; - const int t = rect.y; - - DrawLineH(c, l, t, l + rect.w); + DrawLineH(c, rect.x, rect.y, rect.x + rect.w); + CopyFromTop(rect); +} - uint8 *s = GetPtr(l, t); +// rect の一番上のラスターを rect の残りのラスターにコピーする。 +void +BitmapRGBX::CopyFromTop(const Rect& rect) +{ + uint8 *s = GetPtr(rect.x, rect.y); uint8 *d = s + stride; - const int len = rect.w * 3; + const uint len = rect.w * 4; - for (int y = t + 1; y < t + rect.h; y++) { + for (uint y = rect.y + 1; y < rect.y + rect.h; y++) { memcpy(d, s, len); d += stride; } } -// 矩形 (dx, dy) - (dw * dh) を色 c で描画する。 -// 描画範囲をはみ出さないこと。 -void -BitmapRGB::DrawRect(Color c, int dx, int dy, int dw, int dh) -{ - DrawRect(c, Rect(dx, dy, dw, dh)); -} - // 矩形 rect の辺を色 c で描画する。 // 描画範囲をはみ出さないこと。 void -BitmapRGB::DrawRect(Color c, const Rect& rect) +BitmapRGBX::DrawRect(Color c, const Rect& rect) { assert(buf); @@ -615,3 +970,127 @@ BitmapRGB::DrawRect(Color c, const Rect& 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; + } +}