何を検証するか
画像の「変換成功」と「必要な性質が残った」は別の判定です。切り抜きは座標、PNG 保存は画素、GIF 化は色数とフレーム数、WebP は可逆・非可逆の設定を分けて確認します。
開示:QoTool の運営側からの投稿です。自社ツールへのリンクを含み、文章とコードは AI を用いて作成しました。以下の合成画像テストは Pillow 12.3.0 で実行して PASS を確認しています。実画像や個人情報は使いません。これは Pillow の処理を検証する例であり、リンク先のブラウザ実装を自動テストした結果ではありません。
小さい画像をメモリ内で作る
Python と Pillow が必要です。次を check_image.py として保存し、python check_image.py で実行します。ファイルは書き出さず、BytesIO で保存後の画像を読み直します。
from io import BytesIO
from PIL import Image
source = Image.new("RGB", (64, 32))
source.putdata([(x * 4, y * 8, (x + y) * 2)
for y in range(32) for x in range(64)])
cropped = source.crop((8, 4, 40, 20))
assert cropped.size == (32, 16)
assert cropped.getpixel((0, 0)) == source.getpixel((8, 4))
assert cropped.getpixel((31, 15)) == source.getpixel((39, 19))
png = BytesIO()
cropped.save(png, format="PNG")
png.seek(0)
with Image.open(png) as decoded:
assert decoded.convert("RGB").tobytes() == cropped.tobytes()
indexed = cropped.quantize(colors=16, dither=Image.Dither.NONE)
gif = BytesIO()
indexed.save(gif, format="GIF")
gif.seek(0)
with Image.open(gif) as decoded:
assert decoded.size == cropped.size
assert getattr(decoded, "n_frames", 1) == 1
rgb = decoded.convert("RGB")
assert rgb.getcolors(maxcolors=16) is not None
assert rgb.tobytes() != cropped.tobytes()
webp = BytesIO()
cropped.save(webp, format="WEBP", lossless=True)
webp.seek(0)
with Image.open(webp) as decoded:
assert decoded.convert("RGB").tobytes() == cropped.tobytes()
print("PASS: crop coordinates, PNG pixels, still GIF palette, lossless WebP")
結果を読む
切り抜き範囲 (8, 4, 40, 20) は幅 32、高さ 16 です。右端と下端を含めないため、出力の最後の画素は入力の (39, 19) に対応します。見た目だけで一画素のずれを探すより、両端の対応を assert で固定できます。GUI で png crop を行う場合も、期待する幅と高さを先にメモしてから保存結果と比べます。
GIF の例では意図的に 16 色へ減色しています。画像のサイズとフレーム数が合っていても、RGB の値は元と一致しません。保存に成功しただけでは「無劣化」とはいえないことを、この合成画像で確認しています。
AVIF を入力にする avif to gif でも、静止画が欲しいのか、アニメーションを保持したいのかを先に決めます。QoTool のこの変換は静止 GIF 出力用です。アニメーション全体の保持には使えません。上のサンプル自体は AVIF の読み込みやアニメーションの変換をテストしていません。
WebP の例は lossless=True を明示しています。非可逆設定では画素の完全一致を合格条件にしてはいけません。webp compressor などの GUI を使う場合も、元画像を残し、文字の縁やグラデーションを拡大して比較します。このコードはサイズ削減率や GUI の可逆性を保証しません。
実データに広げるときの注意
このテストは小さな RGB 静止画だけを扱います。透明度、ICC プロファイル、EXIF の向き、HDR、アニメーションの時間情報は対象外です。透過ロゴなら半透明の境界を、写真なら肌や空の色を、アニメーションならフレーム数と表示時間を別のテストとして追加してください。
再保存したファイルのバイト列が違っていても、復号した画素が同じ場合があります。逆に寸法と拡張子が同じでも、色が変わっていることがあります。どの性質を維持したいかを書いてから、その性質に対応する検査を選ぶと、比較の目的が明確になります。
API の仕様は Pillow Image モジュールの公式ドキュメント を参照してください。