ピクセル・アート
2026.03.03
今回はイラストを読込み、縮小してピクセルアート化してみました。

画像を処理するので、2MB PSRAM実装のESP32-S2マイコンを使用しています。
土台のロボットカーは電力供給車です。
詳細は記事、micro:bit & pico拡張ボード中の単なる電力供給車として使うという項目をご覧ください。

micro:bit & Pico 拡張ボード
●画像ファイルの読込み
ネット上で見つけたアリスの画像を使いました。
240x240ピクセルのBMP画像ファイルを読込み表示します。
BMPやTGA形式画像ファイルのヘッダ情報中にある画像サイズなどはリトルエンディアン形式になっています。
意識する必要はありませんが、ESP32中で使われる数値変数もまたリトルエンディアン形式でメモリ中に格納されています。
typedef struct {
uint8_t R;
uint8_t G;
uint8_t B;
} RGB;
typedef struct {
uint8_t model;
uint32_t Width;
uint32_t Height;
uint16_t BitPerPixel;
uint32_t FileSize;
uint32_t DataOffset;
uint32_t ImageSize;
uint32_t ColorsUsed;
RGB *RGBs;
} IMAGE_INFO;
fileSD.read((uint8_t*)&bmp,sizeof(BMP_FORMAT));
if (swap32((uint8_t*)&bmp.Compression)!=0) return false;
img->FileSize = swap32((uint8_t*)&bmp.FileSize);
img->DataOffset = swap32((uint8_t*)&bmp.DataOffset);
img->Width = swap32((uint8_t*)&bmp.Width);
img->Height = swap32((uint8_t*)&bmp.Height);
img->BitPerPixel = swap32((uint8_t*)&bmp.BitPerPixel);
img->ImageSize = swap32((uint8_t*)&bmp.ImageSize);
img->ColorsUsed = swap32((uint8_t*)&bmp.ColorsUsed);
img->RGBs = (RGB*)malloc(img->Width * img->Height * sizeof(RGB));
long pos;
uint8_t mod = (img->Width % 4) * 3;
for(int y = img->Height - 1; y >= 0; y--){
pos = img->Width * y;
for(int x = 0; x < img->Width; x++){
fileSD.read(nBuf,3);
img->RGBs[pos].B = nBuf[0];
img->RGBs[pos].G = nBuf[1];
img->RGBs[pos].R = nBuf[2];
pos++;
}
if (mod>0) fileSD.read(nBuf,mod);
}
●バイキュービック(Bicubic)縮小
縮小化は画素を間引く作業ですが、いきなり間引いてしまうと輪郭線などが消えてしまうことがあります。
そこで前処理としてバイキュービック法を用います。
バイキュービックでは画像を滲ませ、輪郭線などの濃い色が周囲の画素に反映されます。
逆に輪郭線なども周囲の画素から影響を受けるため、ぼかしの効果が表れてしまいます。
バイキュービック処理は、画像の拡大・縮小において優れた性能を持つアルゴリズムです。
注目画素を中心として周辺画素を参照し、注目画素を補正します。
通常は注目画素に対して16の点を参照した重み付けを行いますが、今回は参照画素数を8としています。
注目画素周囲の画素RGB値に1/16を掛け、注目画素には1/2を掛けて足し込み、その値で注目画素RGB値を更新します。
long near[8];
long pos = 0;
RGB *pixs;
long R, G, B;
long malloc_size = sizeof(RGB) * img->Width * img->Height;
if (!(pixs = (RGB*)malloc(malloc_size))) return false;
near[0] = -1 - img->Width;
near[1] = 0 - img->Width;
near[2] = 1 - img->Width;
near[3] = -1;
near[4] = 1;
near[5] = -1 + img->Width;
near[6] = 0 + img->Width;
near[7] = 1 + img->Width;
for(int y=0; y < img->Height; y++) {
for(int x=0; x < img->Width; x++,pos++) {
if ( (x > 0)&&(x < (img->Width - 1))
&&(y > 0)&&(y < (img->Height - 1))) {
R = 0; G = 0; B = 0;
for (int i = 0; i < 8; i++) {
R += img->RGBs[pos+near[i]].R;
G += img->RGBs[pos+near[i]].G;
B += img->RGBs[pos+near[i]].B;
}
pixs[pos].R = (0.0625 * R) + (0.5 * img->RGBs[pos].R);
pixs[pos].G = (0.0625 * G) + (0.5 * img->RGBs[pos].G);
pixs[pos].B = (0.0625 * B) + (0.5 * img->RGBs[pos].B);
} else {
memcpy(&pixs[pos],&img->RGBs[pos],3);
}
}
}
memcpy(img->RGBs,pixs,malloc_size);
free(pixs);
●最近傍縮小(64x64)
NearestNeighbor 最近傍縮小はもっともシンプルな縮小方法です。
例えば幅240ドットの画像を1/2の120ドットに縮小する場合は1ドットおきに画素を間引いて繋ぎ合わせます。
1/2のように割切れる場合ではなくても、複雑な条件文を付加することなく縮小することが可能です。
縮小したあとで画素の引き延ばしを行い、画素間に1ドットの隙間を空けてピクセルアート風にしています。
縮小処理のコードです
float ratio_x = (float)toWidth/img->Width;
float ratio_y = (float)toHeight/img->Height;
long pos = 0;
long target, offset_y;
for(int y=0; y < img->Height; y++) {
offset_y = toWidth * round(ratio_y * y);
for(int x=0; x < img->Width; x++,pos++) {
target = offset_y + round(ratio_x * x);
memcpy(&img->RGBs[target],&img->RGBs[pos],sizeof(RGB));
}
}
ピクセル・アート風に表示するコードです
int unit_size = (img->Width / scale) - 1;
int offset = (img->Width - (scale * (unit_size + 1)) + 1) / 2;
long pos;
int offset_y = offset;
int offset_x;
tft.init(240,240);
tft.setRotation(0);
tft.fillScreen(ST77XX_BLACK);
for(int y = 0; y < scale; y++) {
for(int v = 0; v < unit_size; v++, offset_y++) {
offset_x = offset;
pos = scale * y;
for(int x = 0; x < scale; x++, pos++) {
for(int h = 0; h < unit_size; h++, offset_x++) {
tft.drawPixel(offset_x,offset_y,
tft.color565(img->RGBs[pos].R,
img->RGBs[pos].G,
img->RGBs[pos].B));
}
offset_x++;
}
}
offset_y++;
}
●最近傍縮小(48x48)
縦横240ドットの画像を48ドットに縮小した画像です。普通に認識できます。
●最近傍縮小(32x32)
縦横240ドットの画像を32ドットに縮小した画像です。ピクセル・アートの雰囲気が漂いはじめます。
●最近傍縮小(24x24)
縦横240ドットの画像を24ドットに縮小した画像です。丁度よいピクセル・アート的な感じです。
●最近傍縮小(16x16)
縦横240ドットの画像を16ドットに縮小した画像です。元画像を知らなければちょっと悩んでしまいます。
●最近傍縮小(16x16)/コントラスト補正
コントラストを強調してみたところ、益々わかりずらくなってしまいました。
|