0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

画像変換の成功条件を分ける:Pillow で座標・画素・色数を検証する

0
Posted at

何を検証するか

画像の「変換成功」と「必要な性質が残った」は別の判定です。切り抜きは座標、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 モジュールの公式ドキュメント を参照してください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?