- 技術 💻
画像をドット絵に変換する方法|減色・ディザの実測比較と自作変換ツール
結論
画像をドット絵にするには、「小さく縮める→色を減らす→ぼかさずに拡大する」の3段階で変換します。写真なら横32〜64ドット・8〜16色から試し、拡大は最近傍法(ニアレストネイバー)で行うと、ドットがくっきり残ります。
この記事でわかること
- ブラウザだけで動く変換ツールのコード全文(画像は外へ送らない)
- Pillow で減色4方式・ディザ3方式・ドット数5段階を比べた実測
- ぼやける・色がくすむ・透過が消える原因と、エラー原文つきの直し方
ドット絵への変換は、どのツールでも中身は同じ流れです。違いは「どう縮めるか」「どの色を残すか」「ディザリング(点を混ぜて中間色に見せる処理)を使うか」の選び方にあります。
flowchart TD
A[元の画像] --> B[1. 縮小<br>面積平均で横32〜64ドット]
B --> C[2. 減色<br>8〜16色のパレットを作る]
C --> D{ディザリング}
D -->|グラデーションが多い| E[点を混ぜて中間色]
D -->|くっきりさせたい| F[近い色に置き換えるだけ]
E --> G[3. 拡大<br>最近傍法で整数倍]
F --> G
G --> H[PNGで保存]
やりたいことから、いちばん早い方法を選べるようにまとめます。
| やりたいこと | いちばん早い方法 | 詳しい節 |
|---|---|---|
| とにかく1枚、画像をドット絵に変換したい | 下のブラウザ変換ツールを保存して開き、画像を選ぶ | ブラウザだけで動くドット絵変換ツール |
| たくさんの画像をまとめて変換したい | Python(Pillow)の pixelate.py をループで回す |
Pythonで画像をドット絵に変換する |
| 変換したらぼやけた | 拡大を最近傍法にする。表示は image-rendering: pixelated |
よくあるつまずき |
| 色がくすむ・小さな部分の色が消える | 減色の方式を k-means か八分木に替え、色数を増やす | 減色の方法を比べる |
| レトロゲーム風の色にしたい | 決まったパレット(16色など)に置き換え、ディザを足す | 決まったパレットで変換する |
| キャラクター素材のように輪郭をつけたい | 透明の外側1ドットを濃い色で塗る | 透過PNGと輪郭線 |
| ビーズ・クロスステッチの図案にしたい | 手持ちの色をパレットにし、ディザなしで変換 | 目的別の判断表 |
| 手で描くドット絵の作り方を知りたい | 16×16から描き始め、変換結果を下絵に手で直す | ドット絵の作り方 |
確認環境:Ubuntu 24.04.5 LTS の上で、Python 3.13.18・Pillow 12.3.0・NumPy 2.5.3・scikit-learn 1.9.1 を使い、自分で作った見本画像3枚(512×512の写真風の風景、256×64のグラデーション、256×256の透過スプライト。どれも図形とグラデーションから自作)を変換しました。ブラウザの変換ツールは 127.0.0.1 に置いた自分のページを Chromium 153.0.8010.12(Chrome for Testing、Playwright 1.63.0 で操作)で開き、Canvas から画素を読み出して色数と時間を測っています。時間は2コアの仮想マシンでの最良値で、同じ処理でも機械によって変わります。Pillow の仕様は Pillow 公式ドキュメント、Canvas と CSS は MDN、Aseprite・Pixelorama・Piskel・GIMP は各公式サイトとマニュアル、著作権は文化庁の解説で確かめました。図は mermaid 11.16 で描画を確かめています(確認日 2026年10月29日)。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「画像をドット絵に変換する方法|減色・ディザの実測比較と自作変換ツール」
ドット絵変換の仕組み(縮小・減色・拡大の3段階)
この節では、画像をドット絵に変換するときに、中で何が起きているかが分かります。
ドット絵らしさは「ドットが大きく見えること」と「色が少ないこと」の2つで決まります。どちらか片方だけでは、ただの粗い画像かポスターのような画像になります。変換ツールの設定項目も、ほとんどがこの2つのどちらかを調整しています。
1縮小:ドットの数を決める
512×512の画像を48×48に縮めると、元の約10×10画素が1ドットになる。1ドットの色は、その範囲の平均(面積平均)で決めると、ノイズが目立たない。
2減色:使う色を決める
縮めた画像には数千色が残る。これを8〜16色程度のパレット(使う色の一覧)に置き換える。この処理を量子化とも呼ぶ。
3拡大:ぼかさずに大きくする
48×48のままでは小さすぎるので、8倍などの整数倍に拡大する。拡大は最近傍法(隣の色をそのまま使う)で行い、補間でぼかさない。
ディザリングと輪郭線は、この3段階の間に足す「仕上げ」です。ディザリングは減色のときに、輪郭線は減色のあとに入れます。
縮めただけでは1,599色が残り、ドット絵というより「解像度の低い写真」に見えます。減色まで行って、はじめて色の面がそろい、ドット絵らしい見た目になります。
ブラウザだけで動くドット絵変換ツール(コード全文)
この節では、インストールなしでブラウザだけで画像をドット絵に変換する、小さなツールの作り方と使い方が分かります。
次のコードを pixel.html という名前で保存してブラウザで開くと、画像を選ぶだけでドット絵に変換できます。処理はすべてブラウザの中(Canvas)で行い、画像はどこにも送りません。社外に出したくない画像でも、手元だけで変換できます。
コードを保存して開く
下のコードをテキストエディタに貼り、文字コード UTF-8 で pixel.html として保存する。ファイルをダブルクリックするか、ブラウザにドラッグして開く。
画像を選ぶ
「ファイルを選択」で PNG・JPG・WebP などを選ぶ。選んだ直後に、横48ドット・16色で変換した結果が出る。
ドット数と色数を変える
荒くしたいならドット数を32に、細かくしたいなら64に。色数は8〜16から試す。パレットを選ぶとレトロゲーム風の色になる。
ディザリング・輪郭線を足す
空や肌のようなグラデーションが段々になるならディザリングを、透過PNGのキャラクターには輪郭線をオンにする。
PNGで保存する
「PNGで保存」を押す。拡大の倍率(既定8倍)のまま保存されるので、SNS などで縮小されてもドットが崩れにくい。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>ドット絵変換(ブラウザだけ)</title>
<style>
canvas, img { image-rendering: pixelated; }
#out { border: 1px solid #ccc; max-width: 100%; }
</style>
</head>
<body>
<main>
<p><input type="file" id="file" accept="image/*"></p>
<p>
<label>横のドット数 <input type="number" id="w" value="48" min="8" max="256"></label>
<label>色数 <input type="number" id="k" value="16" min="2" max="64"></label>
<label>パレット
<select id="pal">
<option value="auto">自動(k-means)</option>
<option value="pico8">PICO-8 16色</option>
<option value="gb">緑4階調</option>
<option value="mono">白黒</option>
</select></label>
<label><input type="checkbox" id="dither"> ディザリング</label>
<label><input type="checkbox" id="line"> 輪郭線</label>
<label>拡大 <input type="number" id="scale" value="8" min="1" max="32"></label>
</p>
<canvas id="out"></canvas>
<p><a id="dl" download="pixel.png">PNGで保存</a> <span id="info"></span></p>
</main>
<script>
const PALS = {
pico8: ['000000','1D2B53','7E2553','008751','AB5236','5F574F','C2C3C7','FFF1E8',
'FF004D','FFA300','FFEC27','00E436','29ADFF','83769C','FF77A8','FFCCAA'],
gb: ['0F380F','306230','8BAC0F','9BBC0F'],
mono: ['000000','FFFFFF']
};
const hex = h => [0, 2, 4].map(i => parseInt(h.slice(i, i + 2), 16));
const $ = id => document.getElementById(id);
let src = null;
// 1. 縮小:1ドットに入る元の画素を平均する(面積平均)
function shrink(img, w) {
const h = Math.max(1, Math.round(img.height * w / img.width));
const c = new OffscreenCanvas(img.width, img.height);
const x = c.getContext('2d'); x.drawImage(img, 0, 0);
const s = x.getImageData(0, 0, img.width, img.height).data;
const d = new ImageData(w, h);
for (let j = 0; j < h; j++) for (let i = 0; i < w; i++) {
const x0 = Math.floor(i * img.width / w), x1 = Math.max(x0 + 1, Math.floor((i + 1) * img.width / w));
const y0 = Math.floor(j * img.height / h), y1 = Math.max(y0 + 1, Math.floor((j + 1) * img.height / h));
let r = 0, g = 0, b = 0, a = 0, n = 0;
for (let y = y0; y < y1; y++) for (let x = x0; x < x1; x++) {
const p = (y * img.width + x) * 4, al = s[p + 3];
r += s[p] * al; g += s[p + 1] * al; b += s[p + 2] * al; a += al; n++;
}
const q = (j * w + i) * 4;
d.data[q] = a ? r / a : 0; d.data[q + 1] = a ? g / a : 0; d.data[q + 2] = a ? b / a : 0;
d.data[q + 3] = a / n >= 128 ? 255 : 0; // 透明は「ある・ない」の2つにする
}
return d;
}
// 2. 減色:不透明な画素だけで k-means(初期値は明るさの順に等間隔)
function kmeans(d, k) {
const px = [];
for (let p = 0; p < d.data.length; p += 4) if (d.data[p + 3]) px.push([d.data[p], d.data[p + 1], d.data[p + 2]]);
if (!px.length) return [[0, 0, 0]];
px.sort((a, b) => (a[0] + a[1] + a[2]) - (b[0] + b[1] + b[2]));
let c = Array.from({ length: Math.min(k, px.length) }, (_, i) => px[Math.floor((i + 0.5) * px.length / k)].slice());
for (let it = 0; it < 12; it++) {
const sum = c.map(() => [0, 0, 0, 0]);
for (const v of px) { const i = nearest(c, v); sum[i][0] += v[0]; sum[i][1] += v[1]; sum[i][2] += v[2]; sum[i][3]++; }
c = c.map((o, i) => sum[i][3] ? [sum[i][0] / sum[i][3], sum[i][1] / sum[i][3], sum[i][2] / sum[i][3]] : o);
}
return c.map(v => v.map(Math.round));
}
function nearest(pal, v) {
let best = 0, bd = Infinity;
for (let i = 0; i < pal.length; i++) {
const dr = pal[i][0] - v[0], dg = pal[i][1] - v[1], db = pal[i][2] - v[2];
const dd = dr * dr + dg * dg + db * db; if (dd < bd) { bd = dd; best = i; }
}
return best;
}
// 3. パレットに置き換える(Floyd-Steinberg で誤差を右と下へ配る)
function apply(d, pal, dither) {
const w = d.width, h = d.height, f = Float32Array.from(d.data);
for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) {
const p = (y * w + x) * 4; if (!d.data[p + 3]) continue;
const old = [f[p], f[p + 1], f[p + 2]], c = pal[nearest(pal, old)];
for (let k = 0; k < 3; k++) {
d.data[p + k] = c[k];
if (!dither) continue;
const e = old[k] - c[k];
const add = (dx, dy, r) => { const xx = x + dx, yy = y + dy;
if (xx >= 0 && xx < w && yy < h && d.data[(yy * w + xx) * 4 + 3]) f[(yy * w + xx) * 4 + k] += e * r; };
add(1, 0, 7 / 16); add(-1, 1, 3 / 16); add(0, 1, 5 / 16); add(1, 1, 1 / 16);
}
}
}
// 4. 輪郭線:透明な画素のうち、上下左右に不透明な画素があるものを塗る
function outline(d, rgb) {
const w = d.width, h = d.height, a = d.data.filter((_, i) => i % 4 === 3);
for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) {
if (a[y * w + x]) continue;
const near = [[1, 0], [-1, 0], [0, 1], [0, -1]].some(([dx, dy]) =>
x + dx >= 0 && x + dx < w && y + dy >= 0 && y + dy < h && a[(y + dy) * w + x + dx]);
if (near) d.data.set([...rgb, 255], (y * w + x) * 4);
}
}
function run() {
if (!src) return;
const t0 = performance.now();
const d = shrink(src, +$('w').value);
const pal = $('pal').value === 'auto' ? kmeans(d, +$('k').value) : PALS[$('pal').value].map(hex);
apply(d, pal, $('dither').checked);
if ($('line').checked) outline(d, [40, 20, 30]);
const small = new OffscreenCanvas(d.width, d.height);
small.getContext('2d').putImageData(d, 0, 0);
const s = +$('scale').value, out = $('out');
out.width = d.width * s; out.height = d.height * s;
const ctx = out.getContext('2d');
ctx.imageSmoothingEnabled = false; // ここを忘れると拡大でぼやける
ctx.drawImage(small, 0, 0, out.width, out.height);
out.toBlob(b => {
if ($('dl').href.startsWith('blob:')) URL.revokeObjectURL($('dl').href); // 前の URL を解放
$('dl').href = URL.createObjectURL(b);
});
$('info').textContent = `${d.width}×${d.height}ドット・${pal.length}色・${(performance.now() - t0).toFixed(1)}ms`;
}
$('file').onchange = async e => { src = await createImageBitmap(e.target.files[0]); run(); };
for (const id of ['w', 'k', 'pal', 'dither', 'line', 'scale']) $(id).onchange = run;
</script>
</body>
</html>
このツールは、縮小(面積平均)→減色(不透明な画素だけで k-means)→置き換え(必要ならディザ)→輪郭線→拡大(平滑化なし)の順に処理しています。外部のライブラリは使っていません。
Chromium で動かした結果
127.0.0.1 に置いたこのページを Chromium 153 で開き、見本画像を選んで設定を変えながら、出来上がった Canvas の画素を数えました。
| 画像・設定 | 出力の大きさ | 使われた色 | 変換の時間 |
|---|---|---|---|
| 写真風・16ドット・16色 | 128×128 | 16 | 75.9ms |
| 写真風・48ドット・16色 | 384×384 | 16 | 95.3ms |
| 写真風・128ドット・16色 | 1024×1024 | 16 | 251.0ms |
| 写真風・48ドット・16色・ディザ | 384×384 | 16 | 170.1ms |
| 写真風・48ドット・PICO-8 16色 | 384×384 | 9 | 124.3ms |
| 写真風・48ドット・緑4階調・ディザ | 384×384 | 2 | 241.0ms |
| 透過スプライト・32ドット・輪郭線 | 256×256 | 14(+透明) | 19.1ms |
保存リンクを押すと pixel.png(2,954バイト)がダウンロードされ、透明部分は透明のまま残りました。拡大後の Canvas でも色の数は16のままで、拡大で中間色が生まれていないことも確かめられます。
ひとつ気になる結果は、緑4階調にディザを付けたとき、写真で2色しか使われなかったことです。青い空や湖が、緑のどれにも近くないためです。対処は「決まったパレットで変換する」の節で扱います。
Pythonで画像をドット絵に変換する(Pillowのコード)
この節では、Python の画像ライブラリ Pillow で、画像をドット絵に変換するスクリプトと実行結果が分かります。
大量の画像を同じ設定で変換するなら、Python が向いています。Pillow だけで動き、1枚あたり10ミリ秒台で変換できました(512×512の画像、手元の計測)。
python3 -m venv .venv
. .venv/bin/activate
pip install pillow
"""画像をドット絵に変換する(Pillow だけ)
使い方: python3 pixelate.py 入力 出力.png --dots 48 --colors 16 [--dither] [--outline] [--scale 8]
"""
import argparse
from PIL import Image, ImageChops, ImageFilter
def pixelate(src, dots=48, colors=16, dither=False, outline=False, scale=8):
im = Image.open(src).convert('RGBA')
h = max(1, round(im.height * dots / im.width))
small = im.resize((dots, h), Image.Resampling.BOX) # 1. 縮小(面積平均)
alpha = small.getchannel('A').point(lambda a: 255 if a >= 128 else 0) # 透明は2値に
rgb = Image.new('RGB', small.size, (0, 0, 0))
rgb.paste(small, mask=alpha)
pal = rgb.quantize(colors, Image.Quantize.FASTOCTREE) # 2. 減色(パレットを作る)
d = Image.Dither.FLOYDSTEINBERG if dither else Image.Dither.NONE
out = rgb.quantize(palette=pal, dither=d).convert('RGBA') # 3. 置き換え(ディザはここで効く)
out.putalpha(alpha)
if outline: # 4. 輪郭線(透明の外側1ドット)
ring = alpha.filter(ImageFilter.MaxFilter(3)) # 不透明を1ドット太らせる
edge = ImageChops.subtract(ring, alpha) # 太った分=外側の1ドット
line = Image.new('RGBA', out.size, (40, 20, 30, 255))
out = Image.composite(line, out, edge)
return out.resize((out.width * scale, out.height * scale), Image.Resampling.NEAREST) # 5. 拡大
if __name__ == '__main__':
p = argparse.ArgumentParser()
p.add_argument('src'); p.add_argument('dst')
p.add_argument('--dots', type=int, default=48); p.add_argument('--colors', type=int, default=16)
p.add_argument('--dither', action='store_true'); p.add_argument('--outline', action='store_true')
p.add_argument('--scale', type=int, default=8)
a = p.parse_args()
r = pixelate(a.src, a.dots, a.colors, a.dither, a.outline, a.scale)
r.save(a.dst)
used = len(r.getcolors(1 << 16))
print(f'{a.dst}: {r.width}x{r.height}px({r.width // a.scale}x{r.height // a.scale}ドット・{used}色・透明を含む)')
実行した結果です。
$ python3 pixelate.py photo.png p1.png
p1.png: 384x384px(48x48ドット・16色・透明を含む)
$ python3 pixelate.py photo.png p2.png --dots 32 --colors 8 --dither
p2.png: 256x256px(32x32ドット・8色・透明を含む)
$ python3 pixelate.py sprite.png p3.png --dots 32 --colors 8 --outline --scale 10
p3.png: 320x320px(32x32ドット・10色・透明を含む)
3つ目は、8色+透明+輪郭線の色で10色です。32ドット・8色にした2つ目では、湖の手前の小さな家が周りの色に吸収されて消えました。小さな部分を残したいときは、ドット数か色数を増やします。
コードの要所
- 縮小は
Image.Resampling.BOX:1ドットに入る画素の平均を取る。理由は次の節の実測で示します。 - 透明は128を境に「ある・ない」の2つにする:半透明の縁を残すと、減色で変な色が増えます。
- 減色は2段階:
quantize(colors)でパレットを作り、quantize(palette=…, dither=…)で置き換えます。1段階だとディザの指定が効きません(後の節で実測)。 - 拡大は
Image.Resampling.NEAREST:省くと補間でぼやけます。
Pillow のリサイズや形式変換の基本は、関連記事「Python Pillowで画像処理|リサイズ・トリミング・形式変換のコード集」で扱っています。
縮小と拡大の方式でドット絵の見え方が変わる
この節では、縮小と拡大のフィルター(再標本化の方式)で、ドット絵の仕上がりがどう変わるかが実測で分かります。
縮小は面積平均(BOX)、拡大は最近傍法(NEAREST)が基本です。逆に、縮小を NEAREST にするとノイズを拾い、拡大を BICUBIC にするとぼやけます。
縮小の方式(512→64)
写真風の画像を64×64に縮め、8倍に戻して元の画像との差(1画素あたりの色の差の平均。0〜255で小さいほど元に近い)を測りました。
| 縮小の方式 | 縮小後の色数 | 元との差 | 時間 |
|---|---|---|---|
| NEAREST(1画素だけ拾う) | 4,002 | 7.60 | 0.01ms |
| BOX(面積平均) | 1,599 | 5.76 | 0.13ms |
| BILINEAR | 1,206 | 5.89 | 0.37ms |
| LANCZOS | 1,722 | 5.87 | 1.17ms |
NEAREST は速いものの、8×8画素の中から1画素だけを拾うため、ノイズの粒がそのままドットになり、色数も最も多くなりました。BOX は元との差が最も小さく、色数も少ないので、次の減色がきれいに決まります。
拡大の方式(64→512)
64×64の画像を512×512に拡大し、「1ドットにあたる8×8のブロックが全部同じ色か」を数えました。100%なら、すべてのドットがくっきりしています。
| 拡大の方式 | 拡大後の色数 | 1ドットが単色のブロック |
|---|---|---|
| NEAREST | 1,599 | 100.0% |
| BILINEAR | 16,681 | 5.3% |
| BICUBIC | 19,129 | 1.0% |
| LANCZOS | 21,374 | 0.0% |
手元の Pillow 12.3 では、resize() の方式を省いた結果は BICUBIC と同じでした。拡大のときに方式を書き忘れると、ドットの境目に中間色が1万色以上生まれ、ぼやけた画像になります。
拡大の書き方
small.resize((512, 512))。方式を省くと BICUBIC になり、ドットの境目がぼけるsmall.resize((512, 512), Image.Resampling.NEAREST)。倍率は8倍などの整数倍にする見分け方:拡大後の色数が、拡大前と同じなら正しい
減色の方法を比べる(中央値分割・k-means・八分木)
この節では、色を減らす方式(減色アルゴリズム)ごとの色の残り方・誤差・時間の違いが分かります。
色のくすみを避けるなら k-means か八分木(FASTOCTREE)が向いています。Pillow の既定の中央値分割(MEDIANCUT)は、平均の誤差は小さめでも、太陽や木のような小さく鮮やかな部分の色を失いました。
| 方式 | しくみ | Pillow での指定 |
|---|---|---|
| 中央値分割 | 色の範囲を、画素数が半分ずつになるよう切り分ける | Quantize.MEDIANCUT(RGB の既定) |
| 最大範囲 | 色の範囲が広い箱から切り分ける | Quantize.MAXCOVERAGE |
| 八分木 | 色を木の形に分類し、似た枝をまとめる | Quantize.FASTOCTREE(RGBA の既定) |
| k-means | 色をk個の集まりに分け、各集まりの平均を代表色にする | scikit-learn などで行う |
| libimagequant | 高品質な減色ライブラリ | Quantize.LIBIMAGEQUANT(組み込み時のみ) |
64×64に縮めた写真風画像を、色数を変えて減色しました。差は減色前の64×64との差です。
| 色数 | MEDIANCUT | MAXCOVERAGE | FASTOCTREE | k-means |
|---|---|---|---|---|
| 4色 | 12.98(1.64ms) | 19.92(0.67ms) | 13.16(0.36ms) | 9.65(8.48ms) |
| 8色 | 9.51(1.75ms) | 13.75(1.07ms) | 8.69(0.24ms) | 5.26(13.22ms) |
| 16色 | 5.14(1.76ms) | 10.43(0.95ms) | 5.95(0.23ms) | 2.69(21.38ms) |
| 32色 | 3.45(1.83ms) | 4.50(1.19ms) | 2.21(0.25ms) | 1.52(38.22ms) |
誤差は k-means が最も小さく、時間は八分木が最も短くなりました。k-means でも64×64なら数十ミリ秒で、1枚ずつ変換するなら待つほどではありません。
数字だけでは分からない「色が消える」問題
16色に減色した結果から、太陽・家の壁・木の1ドットの色を取り出しました。
| 場所 | 減色前 | MEDIANCUT | FASTOCTREE | k-means |
|---|---|---|---|---|
| 太陽 | (252,230,150) | (184,173,160) | (248,226,150) | (248,226,150) |
| 家の壁 | (201,78,59) | (123,81,68) | (199,79,59) | (193,79,60) |
| 木 | (30,89,39) | (69,99,90) | (33,93,45) | (35,93,48) |
MEDIANCUT では、黄色い太陽が灰色に、赤い壁が茶色に、木が山と同じ色になりました。面積の大きい空と湖に色の枠を多く使い、面積の小さい鮮やかな色に枠が回らなかったためです。平均の誤差(5.14)は八分木(5.95)より小さいのに、見た目は最もくすみました。ドット絵では小さな目印の色が大事なので、誤差の数字だけで方式を選ばないようにします。
この環境の Pillow は libimagequant を組み込んでいないため、指定すると次のエラーになりました。
ValueError: dependency required by this method was not enabled at compile time
色の表し方(RGB・16進のカラーコード)は、関連記事「CSSの色指定まとめ|rgb・hsl・カラーコードと透明度の書き方」で扱っています。
決まったパレットで変換する(レトロゲーム風・ファミコン風)
この節では、自動で色を選ぶのではなく、あらかじめ決めた色の一覧(パレット)に合わせてドット絵に変換する方法と、その注意点が分かります。
決まったパレットに変換すると、複数の画像の色がそろい、レトロゲーム風の見た目になります。一方で、元の画像に近い色がパレットに無いと、大きく色が変わります。
from PIL import Image
PICO8 = ['000000', '1D2B53', '7E2553', '008751', 'AB5236', '5F574F', 'C2C3C7', 'FFF1E8',
'FF004D', 'FFA300', 'FFEC27', '00E436', '29ADFF', '83769C', 'FF77A8', 'FFCCAA']
def palette_image(hexes):
flat = [int(h[i:i + 2], 16) for h in hexes for i in (0, 2, 4)]
p = Image.new('P', (1, 1))
p.putpalette(flat + flat[:3] * (256 - len(hexes))) # 残りの枠は1色目で埋める
return p
small = Image.open('photo.png').convert('RGB').resize((64, 64), Image.Resampling.BOX)
out = small.quantize(palette=palette_image(PICO8), dither=Image.Dither.FLOYDSTEINBERG)
out.resize((512, 512), Image.Resampling.NEAREST).save('pico8.png')
PICO-8(Lexaloffle のゲーム制作環境)の16色の値は PICO-8 Wiki の Palette のページに載っているものを使いました。緑4階調は、携帯ゲーム機の液晶を思わせる緑の4色を自分で決めたものです。
| パレット | ディザ | 1ドットの差 | ぼかして見た差 | 使われた色 |
|---|---|---|---|---|
| PICO-8 16色 | なし | 22.96 | 21.75 | 9 |
| PICO-8 16色 | あり | 36.60 | 5.12 | 16 |
| 緑4階調 | なし | 50.75 | 49.96 | 3 |
| 緑4階調 | あり | 54.44 | 45.01 | 3 |
| 白黒2色 | なし | 106.76 | 99.51 | 2 |
| 白黒2色 | あり | 121.39 | 27.33 | 2 |
「ぼかして見た差」は、結果と元を16×16まで縮めてから比べた差で、少し離れて見たときの近さを表します。ディザありは1ドットの差が大きくなる一方、離れて見た差は PICO-8 で21.75→5.12、白黒で99.51→27.33と大きく縮みました。
色合いの違うパレットは「明るさ」で割り当てる
緑4階調では、青い空や湖がどの緑にも近くないため、近い色を探す方法では4色のうち3色しか使われませんでした(ブラウザのツールでは2色)。色合いがまったく違うパレットでは、色の近さではなく明るさの順に割り当てると、4色すべてが使われました。
import numpy as np
GREEN4 = np.array([(15, 56, 15), (48, 98, 48), (139, 172, 15), (155, 188, 15)], 'uint8') # 暗い順
lum = np.asarray(small.convert('L'), 'float32') # 明るさ 0〜255
idx = np.clip((lum / 256 * 4).astype(int), 0, 3) # 4段階に分ける
Image.fromarray(GREEN4[idx]).resize((512, 512), Image.Resampling.NEAREST).save('green4.png')
ファミコン風の色にしたいとき
ファミコン風の色は、多くのツールやエミュレーターが「NES パレット」などの名前でパレットファイルを用意しています。ただしツールごとに値が少しずつ違うため、「この値が正しいファミコンの色」と言い切れる一覧は見当たりませんでした。使うツールのパレットファイルを読み込み、上のコードの PICO8 の代わりに渡すのが確実です。本物のゲーム機の制約(1つの区画に使える色数など)まで再現したい場合は、変換後に手で直す前提になります。
ディザリングあり・なしの違い
この節では、ディザリング(色の点を混ぜて中間色に見せる処理)の種類と、使う・使わないの判断が分かります。
グラデーションが段々になるならディザリングを使い、くっきりした絵柄ならディザリングを使いません。ディザリングは、離れて見たときの色を元に近づける代わりに、1ドットずつはざらつきます。
横グラデーション(256×64)を64×16に縮め、同じ4色のパレットに3つの方法で置き換えました。
| 方法 | 1ドットの差 | ぼかして見た差 | 横1行の色の切り替わり |
|---|---|---|---|
| ディザなし | 12.80 | 10.40 | 4回(4本の帯) |
| 誤差拡散(Floyd–Steinberg) | 15.35 | 4.67 | 24回 |
| 組織的ディザ(Bayer 4×4) | 13.35 | 8.65 | 10回 |
誤差拡散(Floyd–Steinberg)
置き換えで出た色の差を、右と下の画素へ配る
離れて見た色は最も元に近い(4.67)
点の並びが不規則で、写真に向く
Pillow・GIMP・多くの変換ツールが対応
組織的ディザ(Bayer)
決まった模様の表で、色を切り替えるかどうかを決める
格子状の規則的な模様になり、昔のゲームらしく見える
アニメーションで点がちらつきにくい
Pillow には無く、自分で書く(下のコード)
import numpy as np
from PIL import Image
BAYER4 = np.array([[0, 8, 2, 10], [12, 4, 14, 6], [3, 11, 1, 9], [15, 7, 13, 5]]) / 16 - 0.5
def bayer_dither(im, pal, strength=64):
"""im: RGB の Image、pal: (n,3) の色の配列。strength で模様の強さを変える"""
a = np.asarray(im, 'float32')
h, w, _ = a.shape
th = np.tile(BAYER4, (h // 4 + 1, w // 4 + 1))[:h, :w, None] * strength
d = (((a + th)[:, :, None, :] - pal[None, None]) ** 2).sum(-1) # 各色との距離
return Image.fromarray(pal[d.argmin(-1)].astype('uint8'))
Pillow の quantize ではディザの指定が効かない場合がある
手元の Pillow 12.3 では、quantize(8, dither=Image.Dither.FLOYDSTEINBERG) と dither=Image.Dither.NONE の結果が1画素も違いませんでした。公式ドキュメントでは dither の既定は FLOYDSTEINBERG ですが、色数を指定してパレットを作る呼び方では、ディザがかかっていません。palette= にパレットを渡したときは既定でディザがかかりました。
quantize(8) と dither=NONE が同じ: True
quantize(8) と dither=FS が同じ: True
palette=を渡すと既定でディザ: True
ディザなし: 1ドットの差=9.57 ぼかして見た差=7.96 PNG=519B
FS(palette=指定): 1ドットの差=11.26 ぼかして見た差=7.08 PNG=947B
ディザを確実に効かせるには、この記事の pixelate.py のように「パレットを作る」と「パレットに置き換える」を分けます。ディザを入れると点の並びが複雑になり、PNG の容量は519バイトから947バイトへ約1.8倍になりました。
ドットの大きさ(16・32・64px)ごとの見え方と容量
この節では、横のドット数をいくつにすると、どの程度の細かさのドット絵になるかが、見え方・容量・時間の実測で分かります。
写真や風景なら横32〜64ドットが目安です。16ドットはアイコン向けで形がかなり崩れ、128ドットはほぼ元の絵に戻ってドット絵らしさが薄れます。
512×512の写真風画像を、pixelate.py(八分木・16色・ディザなし)で変換しました。拡大後はどれも512×512にそろえています。
| 横のドット数 | 1ドットの大きさ(512px表示) | 見え方 | PNG(等倍/512px) | 時間 |
|---|---|---|---|---|
| 16 | 32px | 木が1つの塊、家は4ドットほど、太陽は四角 | 342B/2,168B | 12.3ms |
| 32 | 16px | 木が縦の棒に分かれ、家の屋根と扉が分かる | 515B/2,452B | 16.9ms |
| 48 | 約10.7px | 雪をかぶった山頂・木の本数が数えられる | 737B/2,735B | 12.9ms |
| 64 | 8px | 木が1本ずつ三角に、太陽が丸く見える | 870B/2,810B | 10.4ms |
| 128 | 4px | ほぼ元の絵。ドットは近づかないと見えない | 1,902B/3,956B | 11.1ms |
16ドットでは太陽の色が (229,212,152)、家の壁が (143,83,69) と、周りの色と混ざってくすみました。1ドットに入る範囲が広いほど、小さな部分は平均で薄まります。32ドット以上では、太陽・壁・木の色はほぼ元のまま残りました。
時間はどれも10ミリ秒台で、ドット数による差は小さく、ほとんどが元画像の読み込みと縮小にかかっています。
大きい画像を変換するときの時間
スマホの写真のような大きな画像では、どこで時間がかかるかも測りました。先に縮小してから減色すると、元の大きさのまま減色するより約140倍速くなりました。4000×3000の画像で、処理ごとの時間を測っています。
| 処理(4000×3000から) | 時間 |
|---|---|
| BOX で64×48に縮小 | 8.85ms |
| LANCZOS で64×48に縮小 | 26.56ms |
reduce(62) で縮小 | 32.00ms |
| BOX で縮小→MEDIANCUT 16色 | 10.91ms |
| BOX で縮小→k-means 16色 | 48.04ms |
| 元の大きさのまま MEDIANCUT 16色 | 1,517.53ms |
ドット絵では最後に必ず縮めるので、減色は縮めたあとに行います。k-means も縮めたあとなら50ミリ秒ほどで済みます。ブラウザのツールでは、元画像の画素を読み出す処理が時間の多くを占め、512×512で約100ミリ秒でした。スマホの写真をそのまま読み込むと重くなるため、Webの変換ツールには、読み込み時に1500px以下へ自動で縮めるものもあります。
ドット絵を16×16の文字で見る
透過スプライトを16×16・4色にした結果を、色ごとの文字で書き出しました(「.」が透明)。小さなドット絵の中身が、ただの色番号の並びであることが分かります。
A=#E9753C B=#F89C61 C=#674032 D=#389F4B
................
................
.........DD.....
........D.......
......AAAA......
....ABBBAAAA....
....BBBBBAAA....
...ABBABBCAAA...
...AABCCACAAA...
...AAAAAACAAA...
...AAAAAAAAAA...
....AAAAAAAA....
....AAAAAAAA....
......AAAA......
................
................
16×16では目(C)は1〜2ドットしか残らず、左右で形が違ってしまいました。アイコンの大きさでは、変換後に手で目の形を整える作業がほぼ必ず入ります。
透過PNGをドット絵に変換するときの注意と輪郭線の足し方
この節では、背景が透明な画像(透過PNG)を変換するときに起きる問題と、ゲーム素材のような輪郭線を足す方法が分かります。
透過PNGは「透明の扱い」を先に決めないと、背景の黒がパレットの色を1つ奪ったり、縁が汚れたりします。
透明部分の黒がパレットを使ってしまう
透明な画素にも RGB の値(この見本では (0,0,0))が入っています。RGB に変換してから減色すると、この黒も「よく使われる色」として数えられます。
全体で減色したパレット ['#D98547', '#CA6735', '#7F0000', '#000000']
不透明だけで減色したパレット ['#E9753C', '#674032', '#F89C61', '#389F4B']
全体で減色すると4色のうち2色が黒と暗い赤に使われ、葉の緑がパレットから消えました。不透明な画素だけで色を選ぶと、葉の緑(#389F4B)が残りました。ブラウザのツールも、不透明な画素だけで k-means をかけています。
半透明の縁と、Pillow のエラー
見本のスプライトには、縁に半透明の画素が2,762個あり、32×32に縮めた直後も116個残りました。ドット絵では透明は「ある・ない」の2つにそろえるのが基本で、128を境に分けると半透明は0個になりました。
RGBA のまま Pillow で減色や保存をすると、次のエラーになります。
ValueError: Fast Octree (method == 2) and libimagequant (method == 3) are the only valid methods for quantizing RGBA images
OSError: cannot write mode RGBA as JPEG
1つ目は、RGBA の画像に MEDIANCUT を指定したときです。公式ドキュメントにも、RGBA の既定は FASTOCTREE と書かれています。2つ目は、透過のある画像を JPEG で保存しようとしたときです。JPEG は透明を持てないため、ドット絵は PNG で保存します。
輪郭線を足す
輪郭線は「不透明な部分を1ドット太らせ、増えた部分を濃い色で塗る」と作れます。Pillow では MaxFilter(3) で太らせ、元との差を取ります。32×32のスプライトでは、輪郭線は100ドットになりました。
- 輪郭線の色は真っ黒より、濃い茶や濃い紺:この記事では (40,20,30) を使いました。真っ黒は硬く見えやすくなります。
- 輪郭線の分だけ外側に余白が要る:画像の端まで絵があると、外側に線が引けません。
- 写真には付けない:輪郭線は透明の境目に引くので、背景のある写真では何も起きません。
透過を持てる画像形式の違いは、関連記事「WebP・HEICとは|JPG・PNGへの変換と画像圧縮を実測で解説」で扱っています。
ドット絵の保存形式(PNG・GIF・JPEG)
変換したドット絵をどの形式で保存すると色と容量が保てるかを、同じ画像で比べました。
ドット絵は PNG(可逆圧縮)で保存します。JPEG で保存すると、ドットの境目に中間色のノイズが入り、色数が数千に増えます。
| 形式 | 容量(384×384・16色) | 保存後の色数 | 透明 |
|---|---|---|---|
| PNG | 1,826B | 16 | 持てる |
| WebP(ロスレス) | 482B | 16 | 持てる |
| GIF | 4,434B | 16 | 1色だけ透明にできる |
| JPEG(品質95) | 5,378B | 1,032 | 持てない |
| JPEG(品質75) | 3,804B | 2,132 | 持てない |
JPEG は PNG より大きく、しかも色が崩れました。色の少ない画像では、PNG やロスレスの WebP の方が小さくなります。アニメーションにするなら GIF か、APNG・WebP のアニメーションを使います。
もうひとつ、ドット絵は小さく保存して、表示のときに拡大する方法もあります。そのときは表示側で平滑化を切る必要があります(次の節)。画像を拡大しても崩れない別の形式として、関連記事「SVGファイルとは|PNGとの違い・開き方・作り方を実測で解説」も参考になります。
変換したドット絵がぼやける・色が汚い・透過が消えるときの直し方
この節では、ドット絵への変換でよくあるつまずきの原因と直し方が、エラー原文と実測つきで分かります。
| 症状・メッセージ | 原因 | 直し方 |
|---|---|---|
| 拡大したらぼやけた | 拡大が BICUBIC など補間あり(Pillow の既定) | Image.Resampling.NEAREST で整数倍に拡大 |
| Web ページに置いたらぼやけた | ブラウザが画像を滑らかに拡大している | CSS で image-rendering: pixelated |
| Canvas に描いたらぼやけた | imageSmoothingEnabled の既定が true | 描く前に ctx.imageSmoothingEnabled = false |
| ドットの大きさがそろわない | 拡大の倍率が整数でない(例:1.5倍) | 8倍・10倍などの整数倍で保存・表示 |
| 色がくすむ・小さな色が消える | MEDIANCUT で小さな鮮やかな部分に色が回らない | FASTOCTREE か k-means に替える・色数を増やす |
| ざらざらして汚い | ディザが強い・ドット数が多い | ディザを切る・ドット数を減らす |
| グラデーションが縞になる | 色数が少なくディザなし | ディザを入れる・色数を増やす |
| 背景の透明が黒くなった | RGB に変換して透明の情報が消えた | アルファを取り出して最後に戻す |
OSError: cannot write mode RGBA as JPEG | 透明のある画像を JPEG で保存 | PNG で保存する |
ValueError: … are the only valid methods for quantizing RGBA images | RGBA に MEDIANCUT を指定 | RGB にしてから減色し、アルファは別に戻す |
| ノイズの粒がドットになった | 縮小を NEAREST で行った | 縮小は BOX(面積平均) |
ブラウザでの表示がぼやける(実測)
32×32のドット絵を、Chromium で320×320に拡大して表示し、画面に出た色の数を数えました。
| 表示のしかた | 画面上の色数 |
|---|---|
<img> を CSS で320px に(既定) | 2,358 |
<img> に image-rendering: pixelated | 9 |
Canvas に drawImage(既定の平滑化あり) | 2,363 |
Canvas に imageSmoothingEnabled = false | 9 |
元のドット絵は透明を含めて9色です。既定のままでは2,000色以上に増えて境目がにじみ、設定を変えると9色のままくっきり表示されました。MDN は pixelated を、最近傍法などで元の大きさの最も近い整数倍に拡大する値と説明しています。
img.pixel-art {
image-rendering: pixelated; /* ドット絵をぼかさずに拡大する */
}
Canvas の画素をくり返し読み出すと、Chromium のコンソールに次の注意が出ました。動作には影響しませんが、何度も読み出すなら getContext('2d', { willReadFrequently: true }) を指定します。
Canvas2D: Multiple readback operations using getImageData are faster with the willReadFrequently attribute set to true.
無料のアプリ・Webサービス・ドット絵ツールの選び方
この節では、自分で作らずに既存の変換サービスやドット絵ツールを使うときに、何を見て選ぶかが分かります。
変換だけなら、画像をアップロードしない(ブラウザの中で処理する)サービスを選ぶのが安全です。仕事の資料や家族の写真は、外のサーバーに送らない方法を選びます。
- 1画像を外へ送るか「ブラウザ内で処理」「アップロードしない」と書かれているか。書かれていなければ送られる前提で考える
- 2ドット数・色数・パレットを選べるか横8〜128ドット程度、色数、決まったパレット、ディザのオン・オフがあると調整しやすい
- 3透過PNGで書き出せるかゲーム素材・アイコンにするなら必須。輪郭線の強さを選べるものもある
- 4書き出しの倍率とサイズの上限8倍・16倍などの整数倍で出せるか。入力のファイルサイズや大きさの上限も見る
- 5利用規約と登録の要否商用利用の可否、透かしの有無、会員登録や有料プランが要るかを規約で確かめる
手で描く・直すためのドット絵ツール
変換した結果を仕上げるには、ドット絵専用のツールが便利です。どれも1ドット単位で描け、パレットやアニメーションを扱えます(各公式サイトの記載、2026年10月29日時点)。
Aseprite
アニメーション対応のドット絵エディタ。公式 FAQ では Windows・macOS・Ubuntu 向けの配布物を19.99米ドルで提供し、ソースコードから自分でビルドして使うこともできる(ビルドしたものの再配布は不可)。
有料・多機能
Pixelorama
無料・オープンソースのドット絵ツール。レイヤー、アニメーション、パレット、タイルマップ、Aseprite 形式の読み込みなどに対応。Steam・itch.io・GitHub などで配布。
無料・多機能
Piskel
ブラウザで動く無料のドット絵エディタ。Windows・macOS・Linux 向けのオフライン版もあり、コードは GitHub で公開されている。公式はPCのブラウザでの利用を勧めている。
無料・インストール不要
GIMP
画像編集ソフト。「画像→モード→インデックス」で色数・パレット・ディザを選んで減色でき、「フィルター→ぼかし→Pixelize」でモザイク状にできる(GIMP 2.10 のマニュアルで確認)。
無料・変換も編集も
2Dゲームの素材として使うなら、ゲームエンジン側でも平滑化を切る設定が要ります。ゲームの作り方は、関連記事「Godot Engineとは|始め方・GDScript・2Dゲームの作り方を解説」で扱っています。
他人の画像をドット絵に変換して公開するときの注意
変換に使う画像の権利について、公開前に押さえておく一般的な注意です。個別の判断は、権利者や専門家への相談が確実です。
他人が撮った写真や描いたイラストをドット絵に変換しても、元の作品の特徴が残っていれば、元の作品の権利が関わります。文化庁の解説では、家庭内など私的に使う目的なら、複製に加えて翻案(作り変え)も認められる、とされています。一方、SNS への投稿や販売など、私的な範囲を超えて公開する場合は、この例外に当たりません。
- 自分で撮った写真・自分で描いた絵を使う:この記事の見本画像も、図形とグラデーションから自作しています。
- 素材サイトの画像は利用規約を読む:加工の可否、商用利用、クレジット表記の条件を確かめます。
- アニメやゲームのキャラクターは、ドット絵にしても元の作品の権利が残る:公式がファンアートの指針を出している場合は、それに従います。
- 人が写った写真は肖像にも配慮する:本人の了解を得てから公開します。
- 変換サービスの規約も見る:出力画像の扱い(透かし・利用範囲)がサービスごとに決まっていることがあります。
ドット絵の作り方:変換したあとに手で直すポイント
この節では、自動変換だけでは足りない部分と、ドット絵を手で描く・直すときの順番が分かります。
自動変換は「下絵づくり」と考えると、仕上がりがよくなります。実測でも、16×16では目の左右の形がそろわず、32ドット・8色では小さな家が消えました。人が見て大事な部分は、変換では守られません。
- 1大きさを決めるアイコンなら16×16か32×32、キャラクターなら32×32〜64×64から始める
- 2シルエット(外形)を1色で描く変換結果を下絵にして、形が分かるかだけを見る
- 3色を3〜4色に分ける基本の色・影・明るい部分。色数は少ないほどまとまる
- 4目・口など大事な点を手で置く左右をそろえ、1ドットの違いで表情を調整する
- 5ジャギー(線の段差)を整える斜めの線は、段の長さを規則的にそろえると滑らかに見える
- 6ぽつんと浮いた1ドットを消す変換やディザで出た孤立点は、周りの色に塗り替える
- 7輪郭線と仕上げ輪郭の色を部分ごとに少し変えると、硬さが和らぐ
変換ツールのドット数を描きたい大きさに合わせておくと、変換結果をそのまま下絵としてドット絵ツールに読み込めます。グリッド(ドットの境目の線)を表示できるツールなら、どこを直すかが見つけやすくなります。
目的別の判断表(ドット数・色数・ディザ・方式)
この節では、作りたいもの別に、どの設定から始めればよいかが一覧で分かります。
| 作りたいもの | 横のドット数 | 色数・パレット | ディザ | 補足 |
|---|---|---|---|---|
| SNS のアイコン | 32〜48 | 8〜16色(自動) | なし | 顔の目・口は手で直す |
| 風景写真をドット絵風に | 48〜64 | 16〜32色(k-means・八分木) | 空が縞ならあり | 家や木など小さな物は64以上で残る |
| ゲームのキャラクター素材 | 32〜64 | 8〜16色(作品で共通のパレット) | なし | 透過PNG・輪郭線あり |
| レトロゲーム風の画面 | 64〜128 | 決まった16色など | あり(組織的ディザも合う) | 色合いが違えば明るさで割り当て |
| 携帯ゲーム機の液晶風 | 48〜64 | 緑4階調 | あり | 明るさで割り当てる |
| ビーズ・クロスステッチの図案 | 図案のマス目の数 | 手持ちの色だけをパレットに | なし | 孤立した1ドットを消すと作りやすい |
| 白黒の印刷・スタンプ | 64〜128 | 白黒2色 | あり | ディザで灰色の濃淡を出す |
| 大量の画像を同じ見た目に | そろえる | 同じ決まったパレット | そろえる | Python でまとめて変換 |
迷ったら、ブラウザのツールで48ドット・16色・ディザなしから始め、ドット数→色数→ディザの順に1つずつ変えると、違いが見分けやすくなります。
よくある質問
この節では、画像をドット絵に変換するときによく調べられる疑問に答えます。
Q. 写真をドット絵にする一番簡単な方法は?
A. この記事のブラウザ変換ツールを pixel.html として保存して開き、画像を選ぶ方法です。インストールが要らず、画像は外へ送られません。
Q. ドット絵に変換するのにおすすめの設定は?
A. 写真なら横48ドット・16色・ディザなしから始めます。荒くしたいなら32ドット、細かくしたいなら64ドットに変えます。
Q. ドット絵がぼやけて見えるのはなぜ?
A. 拡大するときに補間(平滑化)がかかっているためです。保存時は最近傍法で整数倍に拡大し、Web では image-rendering: pixelated を付けます。画像ビューアーやSNSの縮小表示でぼやけることもあります。
Q. ディザリングとは何ですか?
A. 少ない色の点を混ぜて並べ、離れて見たときに中間の色に見せる処理です。実測では、離れて見たときの元との差が、白黒2色で99.51から27.33に減りました。
Q. パレットとは何ですか?
A. ドット絵に使う色の一覧です。自動で画像から選ぶ方法と、決まった色(16色など)を使う方法があり、決まった色を使うと複数の画像の色調がそろいます。
Q. 変換すると色がくすむのはなぜ?
A. 減色の方式によっては、面積の小さい鮮やかな色にパレットの枠が回らないためです。Pillow の既定の MEDIANCUT で太陽が灰色になりました。FASTOCTREE か k-means に替えると残りました。
Q. 背景を透明のままドット絵にできますか?
A. できます。透明を128を境に「ある・ない」に分け、不透明な部分だけで減色し、PNG で保存します。JPEG は透明を持てません。
Q. ビーズやクロスステッチの図案にできますか?
A. できます。横のドット数を図案のマス目の数に合わせ、手持ちのビーズや糸の色だけをパレットにして、ディザなしで変換します。
Q. 変換したドット絵をドット絵ツールで編集できますか?
A. できます。等倍(拡大前)の PNG を保存しておくと、Aseprite・Pixelorama・Piskel などで1ドットずつ直せます。拡大後の画像を読み込むと、1ドットが8×8画素などに分かれて編集しにくくなります。
Q. オンラインの変換サービスに画像を送っても問題ない?
A. 画像がサーバーに送られるサービスもあります。機密の資料や個人の写真は、ブラウザ内で処理するツールか、手元の Python で変換するのが安全です。
Q. ファミコン風の色にするには?
A. ツールが用意している NES パレットなどのパレットファイルを読み込み、決まったパレットへの変換を行います。値はツールによって少し違います。
まとめ
この節では、画像をドット絵に変換する要点を5つにまとめます。
- 変換は「縮小→減色→拡大」の3段階:縮小は面積平均(BOX)、拡大は最近傍法(NEAREST)で整数倍にする。
- 写真は横32〜64ドット・8〜16色から試す:16ドットでは小さな部分の色が平均で薄まり、128ドットではドット絵らしさが薄れた。
- 減色の方式で色の残り方が変わる:MEDIANCUT は太陽を灰色にした。くすむなら八分木か k-means。
- ディザはグラデーション向け:離れて見た差は大きく減るが、ざらつきと容量(約1.8倍)が増える。Pillow ではパレットを渡したときに効く。
- ぼやけ・透過のつまずきは設定で直る:
image-rendering: pixelated・imageSmoothingEnabled = false・透明の2値化・PNG 保存。
参考・出典
この節では、この記事で確かめた公式の資料をまとめます(確認日 2026年10月29日)。
- Pillow「Image Module」(quantize・resize・reduce):https://pillow.readthedocs.io/en/stable/reference/Image.html
- MDN「image-rendering」:https://developer.mozilla.org/ja/docs/Web/CSS/image-rendering
- MDN「CanvasRenderingContext2D: imageSmoothingEnabled」:https://developer.mozilla.org/ja/docs/Web/API/CanvasRenderingContext2D/imageSmoothingEnabled
- Aseprite 公式サイト:https://www.aseprite.org/
- Aseprite「FAQ」:https://www.aseprite.org/faq/
- Pixelorama 公式サイト:https://pixelorama.org/
- Piskel 公式サイト:https://www.piskelapp.com/
- GIMP 2.10 マニュアル「インデックスモードへの変換」:https://docs.gimp.org/2.10/ja/gimp-image-convert-indexed.html
- GIMP 2.10 マニュアル「Pixelize」:https://docs.gimp.org/2.10/ja/gimp-filter-pixelize.html
- PICO-8 Wiki「Palette」:https://pico-8.fandom.com/wiki/Palette
- 文化庁「著作物が自由に使える場合」:https://www.bunka.go.jp/seisaku/chosakuken/seidokaisetsu/gaiyo/chosakubutsu_jiyu.html
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「画像をドット絵に変換する方法|減色・ディザの実測比較と自作変換ツール」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。