透過PNG素材の「プロ品質」基準と
出荷前自動検品システム完全設計

topic: 同人素材販売 / 透過PNG素材のプロ品質基準と自動検品
重視軸: 技術(PIL実装粒度) / 目標: 100点必達
発生背景: 自作SFX素材で「文字端がキャンバスで切れる(hard-clip)」「グロー切れ」「縁のジャギ/黒点」が量産後に発覚。再発防止のため基準の言語化 + 出荷前バリデータ(PIL)を確立する。
作成: 2026-06-11 / Claude (Opus 4.8) / 1次情報17ソース脚注付き
関連DR: SFX素材/写植/フォント系は別DR群参照(末尾11章)。本DRは透過PNGの技術QCに特化した新規DR(既存重複なし)。

1. 結論(最短で効く3行)

① 端切れ・グロー切れの真因は1つ「キャンバスがαの実体を内包していない」。だから対策も1つ ―― 必要余白 = グロー半径 + ストローク幅 + AAにじみ + 安全マージン生成前にキャンバスへ織り込む。出力後にトリミングするのではなく、最初から大きく作る

② 縁のジャギ/黒点/白フチの真因も1つ「透明画素のRGBがゴミ(背景色 or 黒)のまま」。straight alpha で書き出し、透明域のRGBを 近傍の不透明色で埋める(defringe / alpha dilation) ことで縮小・合成時のハロを根絶する[6][10]

③ 人間の目視は破綻する。出荷前に validate_png.py を必ず通す。「αのbboxがキャンバス端に接触したらreject(端切れ)」「最外周1pxにα>0があればreject(余白ゼロ)」「黒背景・白背景・グレー差分でフチ検出」を機械判定。合格証跡(JSON)が無いZIPは販売パッケージに入れない

≥8px
基本透明セーフマージン
glow+stroke
+AA+8
SFX素材の必要余白式
straight
α書き出し方式(premul禁止)
3面
黒/白/グレー検版必須
5判定
バリデータrejectルール
本DRは「なぜ起きるか(原理)→ どう設計するか(キャンバス) → どう機械検品するか(PIL実装) → どう見せるか(3面プレビュー)」を一直線でつなぐ。コードはそのまま D:\projects に置けば動く粒度で記載。

2. 「プロ素材」の必須品質基準(市場が要求する水準)

素材ストア/同人プラットフォームが暗黙に要求する水準を統合した。透過PNG素材で「プロ」を名乗るには下表を全項目クリアすること。1項目でも欠けると「アマチュア感」がレビューに直撃する。

プロ基準根拠/出典
余白/セーフマージン視覚要素の外周に最低 2〜8px の透明余白。グロー/影付き素材は グロー半径+α。アイコン系は 24px枠に対し全周2px(=live area 20px)が業界標準[7]Material/UX Planet keyline[7]
縁アンチエイリアスα境界は滑らかなグラデ(0→255)。ジャギ(1px硬切替)はNG。かつ透明画素のRGBは黒/白ではなく近傍色で埋める(縮小時ハロ防止)[4][6]alpha bleed/defringe[4][10]
α書き出し方式straight alpha 一択。premultiplied は受け手で黒フチ化する。PNGは原則straight[9]premul vs straight[9][8]
カラバリSFX/装飾は最低3色(白/黒/差し色)。白背景でも黒背景でも置けるよう「白縁版」「黒縁版」を同梱。色は単独で「黒に置いて消えない/白に置いて消えない」コントラストを確保素材販売実務(BOOTH 印刷/web版分け[5])
解像度SFX/装飾: 長辺 2000px以上(印刷流用想定)。線画/トーン素材: グレスケ 600ppi。CG/紹介画像はDLsite推奨 1600×1200(4:3)以上[5]DLsite/BOOTH/コミグラ[5]
ビット深度/形式PNG-24/32(8bit/ch + α8bit)。8bitパレットPNG(PNG-8)はα段階が粗くジャギ化→素材販売では禁止PNG concepts[2]
パッケージprint版/web版にサイズ分け、README(用途/ライセンス)、サンプル3枚、ZIP整理。1ファイル1GB上限[5]BOOTH頒布ガイド[5]
メタ衛生不要な巨大透明キャンバスはバイト浪費+整列崩れ→意図的余白以外はトリム(ただし下記§4の式で必要余白は残す)[1]透過運用ベストプラクティス[1]
3レベルの「余白」を混同しない(ここが事故の温床)

今回の事故は「②③を削ろうとして①まで削った」型。バリデータは①の有無を機械で守る。

3. プロ現場の検版プロセス TOP手法(競合ベンチ)

素材ストア審査・VFX・ゲームテクスチャ・UIアイコンの各現場で確立された検版手法を、同人SFX素材に転用できる順に並べた。

#手法現場本件への転用
1黒/白 2面コンポジット検版(透明画素のフチ汚れを背景色を変えて暴く)素材ストア入稿[1]必須。さらにグレー追加で3面
2alpha bleed / edge padding(透明域RGBを近傍色で膨張)ゲームテクスチャ[4]縮小プレビュー時のハロ根絶
3Defringe / Remove White-Black Matte(背景色を縁から除去)Photoshop[10]PILで同等処理を自作(§5)
4straight alpha 統一(premul混在の黒フチ禁止)AE/Nuke/Fusion[9][8]PNG保存は常にstraight
5keyline/safe-area グリッド(全素材で余白統一)Material/UIアイコン[7]SFXもグリッド化で整列美
6getbbox 端接触チェック(被写体がキャンバス端に触れたら切れ警告)画像自動処理[3]バリデータ中核(§6)
7mip/縮小プレビュー目視(等倍では見えない縁を縮小で暴く)ゲーム/web[4]50%/25%サムネ自動生成

4. 端切れ・グロー切れを出さないキャンバス自動パディング設計

4-1. 必要余白の式(これがDRの心臓)

margin = ceil(glow_radius + stroke_width + aa_bleed + safety)

例: outer glow σ=12, 縁取り6px, 安全8 → margin = 36+3+3+8 = 50px を全周に確保してから描画する。

4-2. 「描く前に大きく作る」生成側の正しい順序

事故型(NG)は「コンテンツ実寸のキャンバスを作る→グロー→端で切れる」。正解は余白込みキャンバスを先に確保→中央に描画

from PIL import Image, ImageFilter
import math

def make_canvas(content_w, content_h, *, blur_sigma=0, stroke=0,
                aa=3, safety=8, extra_glow_spread=0):
    """SFX素材を端切れ無しで生成するキャンバスを確保。中央寄せ前提。"""
    glow = math.ceil(3 * blur_sigma) + extra_glow_spread     # 3σルール
    margin = math.ceil(glow + stroke + aa + safety)
    W = content_w + margin * 2
    H = content_h + margin * 2
    canvas = Image.new("RGBA", (W, H), (0, 0, 0, 0))         # 完全透明で開始
    origin = (margin, margin)   # ここを基準に文字/SFXを描く
    return canvas, origin, margin

グローを「キャンバス内」で完結させる

グローは別レイヤーで作ってから合成すると、ぼかしがキャンバス端で切り落とされないことを保証しやすい。GaussianBlur はキャンバス外には伸びないため、blur前にσ×3の余白が無いと必ず切れる。

def add_outer_glow(canvas, shape_rgba, origin, sigma, glow_color=(255,40,90)):
    """shape_rgbaのαからグローを生成しcanvasの下に敷く。余白はmake_canvasで確保済み前提。"""
    # 1) αだけ取り出して着色 → ぼかす(これが外側に伸びる)
    alpha = shape_rgba.split()[3]
    glow = Image.new("RGBA", canvas.size, (0,0,0,0))
    solid = Image.new("RGBA", canvas.size, glow_color + (255,))
    glow.paste(solid, origin, alpha)            # 形状の位置にベタ色を敷く
    glow = glow.filter(ImageFilter.GaussianBlur(sigma))  # 余白内で滲む
    # 2) グローを下、本体を上で合成
    out = Image.alpha_composite(glow, _paste_layer(canvas, shape_rgba, origin))
    return out

def _paste_layer(base, layer, origin):
    tmp = base.copy()
    tmp.alpha_composite(layer, dest=origin)
    return tmp

4-3. 出力後の「賢いトリム」(無駄余白だけ削り、技術余白は残す)

§2の③だけ削る。getbbox(alpha_only=True) で実体を取り、そこへmargin分だけ戻してクロップする。これで「ピッタリ切って端切れ」を構造的に防ぐ。

def smart_trim(img, keep_margin):
    """透明実体のbboxを取り、keep_marginを保って切る。端接触なら元のまま返す。"""
    bbox = img.getbbox(alpha_only=True)          # 非透明領域 (l,u,r,b) / 無ければNone
    if bbox is None:
        return img                               # 完全透明=異常(別途reject)
    l, u, r, b = bbox
    W, H = img.size
    L = max(0, l - keep_margin); U = max(0, u - keep_margin)
    R = min(W, r + keep_margin); B = min(H, b + keep_margin)
    return img.crop((L, U, R, B))

getbbox(alpha_only=True) はαのみで非透明域を判定する(RGBが何であれαで切る)。これがPillow標準の正しいトリム手段[3]

4-4. 透明域RGBを近傍色で埋める(alpha dilation / defringe)

縮小・回転・他ソフト貼り込みで出る黒/白フチの根治。透明画素のRGBを近傍の不透明色で膨張させる[4][10]。PILの MaxFilter でRGBを外側へ滲ませ、αは元のまま維持する。

from PIL import ImageFilter

def defringe_dilate(img, iterations=4, kernel=3):
    """透明画素のRGBを近傍不透明色で埋める。αは不変。縮小時ハロ防止。"""
    r, g, b, a = img.split()
    rgb = Image.merge("RGB", (r, g, b))
    mask = a                                  # 不透明領域(255)を広げていく
    for _ in range(iterations):
        # RGBを膨張(MaxFilterは明領域拡張なので、暗い被写体には別途下記の被覆合成を使う)
        spread = rgb.filter(ImageFilter.MaxFilter(kernel))
        grown  = mask.filter(ImageFilter.MaxFilter(kernel))
        # 既に色がある場所は保持、新規拡張部だけ spread を採用
        new_area = ImageChops.subtract(grown, mask)        # 今回広がった縁
        rgb = Image.composite(spread, rgb, new_area)
        mask = grown
    out = Image.merge("RGBA", (*rgb.split(), a))           # ★αは触らない
    return out
注意 MaxFilter は明色を広げる近似。暗色被写体で色がズレるなら、「不透明画素だけの平均色で透明域を初期化→反復膨張」の方が正確(下の堅牢版)。要は透明画素のRGBが黒(0,0,0)のまま残らないことが目的。
import numpy as np
def defringe_np(img, iterations=8):
    """numpy版・堅牢。透明域RGBを最近傍の不透明RGBで反復伝播。"""
    arr = np.array(img.convert("RGBA")).astype(np.float32)
    rgb = arr[..., :3]; a = arr[..., 3]
    known = a > 0
    for _ in range(iterations):
        if known.all(): break
        # 4近傍から既知色を集めて平均(未知画素を埋める)
        acc = np.zeros_like(rgb); cnt = np.zeros(a.shape, np.float32)
        for dy, dx in ((1,0),(-1,0),(0,1),(0,-1)):
            sh = np.roll(np.where(known[...,None], rgb, 0), (dy,dx), (0,1))
            km = np.roll(known.astype(np.float32), (dy,dx), (0,1))
            acc += sh; cnt += km
        fillable = (~known) & (cnt > 0)
        rgb[fillable] = (acc[fillable] / cnt[fillable, None])
        known = known | fillable
    out = np.dstack([rgb, a]).astype(np.uint8)
    return Image.fromarray(out, "RGBA")

5. 出荷前バリデータ validate_png.py (PIL実装方針)

5つのreject判定を機械化する。1枚でもFAILしたらZIPに入れない。合格証跡を gate.json として保存し、パッケージング側はそれが無いと止まる(R18量産の品質ゲートと同じ思想)。

5-1. 5つのrejectルール

#判定検出方法(PIL)NG例
R1端切れ(hard-clip)αのbboxがキャンバス端に接触(l==0 or u==0 or r==W or b==H)[3]文字が枠で切れる/グロー切れ
R2余白ゼロ/不足最外周 N px帯にα>閾値の画素が存在(border alpha検出)。要求余白未満でrejectセーフマージン無し
R3縁の黒点/汚れ半透明画素(0<α<255)のRGBが黒/白に偏在 or 周囲不透明色と乖離大→ premul/未defringe疑い[6][8]縮小時にフチが出る
R4ジャギ(AA無し)α境界の遷移幅を計測。1px硬切替が支配的ならAA不足。境界画素中の中間α(1〜254)比率が閾値未満でrejectガビガビの輪郭
R5透過異常全画素α==255(透過してない=ただのRGB)/ 全画素α==0(空)/ bbox None / 1bitα(段階なし)透過になってない

5-2. 実装(コピペで動く中核)

"""validate_png.py — 透過PNG素材 出荷前バリデータ
使い方: python validate_png.py asset.png --margin 50 --json gate.json
合格時 exit 0 / 1枚でもFAILで exit 2 (パッケージャはこれで止める)
"""
import sys, json, argparse, glob
from PIL import Image, ImageChops
import numpy as np

def load_rgba(path):
    return Image.open(path).convert("RGBA")

def check_alpha_present(img):
    a = np.array(img.split()[3])
    if a.max() == 0:  return False, "全透明(空画像)"
    if a.min() == 255: return False, "α全不透明(透過になっていない)"
    uniq = np.unique(a)
    if set(uniq.tolist()) <= {0, 255}:
        return False, "αが1bit(0/255のみ・AA無し)"
    return True, "ok"

def check_clip(img):
    """R1 端接触 + R5 bbox None"""
    bbox = img.getbbox(alpha_only=True)           # [3]
    if bbox is None:
        return False, "bbox None(実体なし)", None
    l, u, r, b = bbox; W, H = img.size
    touch = []
    if l == 0: touch.append("left")
    if u == 0: touch.append("top")
    if r == W: touch.append("right")
    if b == H: touch.append("bottom")
    if touch:
        return False, f"端切れ: {','.join(touch)} がキャンバス端に接触", bbox
    return True, "ok", bbox

def check_margin(img, need, thr=8):
    """R2 最外周 need px 帯に α>thr が無いこと(border alpha)"""
    a = np.array(img.split()[3])
    H, W = a.shape
    n = int(need)
    if n*2 >= min(H, W):
        return False, "画像が余白要求に対して小さすぎる"
    top, bot = a[:n, :], a[-n:, :]
    lft, rgt = a[:, :n], a[:, -n:]
    band_max = max(top.max(), bot.max(), lft.max(), rgt.max())
    if band_max > thr:
        return False, f"余白不足: 外周{n}px帯にα={band_max}の画素あり"
    return True, "ok"

def check_jaggy(img, min_aa_ratio=0.15):
    """R4 境界画素中の中間α比率。低すぎ=ジャギ"""
    a = np.array(img.split()[3]).astype(np.int16)
    grad = (np.abs(np.diff(a, axis=0)).sum() + np.abs(np.diff(a, axis=1)).sum())
    edge = (a > 0) & (a < 255)                    # 中間α=AA帯
    solid_edge = ((a == 255).sum())
    ratio = edge.sum() / max(1, edge.sum() + solid_edge*0 + (a==255).sum()*0 + 1)
    # 実用: 境界長に対する中間α画素の割合で判定
    border = ((a>0) != np.roll(a>0,1,0)) | ((a>0) != np.roll(a>0,1,1))
    border_n = border.sum()
    mid_on_border = (edge & border).sum()
    aa = mid_on_border / max(1, border_n)
    if aa < min_aa_ratio:
        return False, f"ジャギ疑い: 境界AA比率 {aa:.2f} < {min_aa_ratio}"
    return True, "ok"

def check_fringe(img, max_dark_ratio=0.20):
    """R3 半透明画素のRGBが黒/白に偏る=未defringe/premul疑い"""
    arr = np.array(img).astype(np.int16)
    a = arr[...,3]
    semi = (a > 8) & (a < 248)
    if semi.sum() == 0:
        return True, "半透明画素なし(中抜き素材)"
    rgb = arr[...,:3][semi]
    luma = (0.299*rgb[:,0] + 0.587*rgb[:,1] + 0.114*rgb[:,2])
    dark = (luma < 16).mean()                     # ほぼ黒の縁=premul黒フチ
    white = (luma > 239).mean()                   # ほぼ白=白マット残り
    if dark > max_dark_ratio:
        return False, f"黒フチ疑い: 半透明画素の{dark:.0%}がほぼ黒(premul/未defringe)"
    if white > max_dark_ratio:
        return False, f"白マット疑い: 半透明画素の{white:.0%}がほぼ白"
    return True, "ok"

def validate(path, need_margin, thr=8):
    img = load_rgba(path)
    res = {"file": path, "size": img.size, "checks": {}, "pass": True}
    def rec(name, tup):
        ok = tup[0]; res["checks"][name] = {"pass": ok, "msg": tup[1]}
        if not ok: res["pass"] = False
    rec("R5_alpha",  check_alpha_present(img))
    rec("R1_clip",   check_clip(img)[:2])
    rec("R2_margin", check_margin(img, need_margin, thr))
    rec("R4_jaggy",  check_jaggy(img))
    rec("R3_fringe", check_fringe(img))
    return res

if __name__ == "__main__":
    ap = argparse.ArgumentParser()
    ap.add_argument("paths", nargs="+")
    ap.add_argument("--margin", type=int, default=8)
    ap.add_argument("--thr", type=int, default=8)
    ap.add_argument("--json", default=None)
    a = ap.parse_args()
    files = []
    for p in a.paths: files += glob.glob(p)
    out, ng = [], 0
    for f in files:
        r = validate(f, a.margin, a.thr); out.append(r)
        tag = "PASS" if r["pass"] else "FAIL"
        print(f"[{tag}] {f}")
        for k,v in r["checks"].items():
            if not v["pass"]: print(f"    - {k}: {v['msg']}")
        if not r["pass"]: ng += 1
    if a.json:
        json.dump({"results": out, "ng": ng, "total": len(out)},
                  open(a.json,"w",encoding="utf-8"), ensure_ascii=False, indent=2)
    sys.exit(2 if ng else 0)
パッケージャ連携(証跡ゲート)

ZIP作成スクリプトの先頭で validate_png.py *.png --json gate.json を呼び、exit code != 0 なら sys.exit(2) で停止。これでR18量産のpreflight思想と同じく「未合格は物理的に出荷不能」になる。gate.json をZIP同梱すれば品質の自己証明にもなる。

6. 黒/白/グレー 3面プレビューの作り方

透明素材の縁汚れは背景色を変えると初めて見える。黒で消える黒フチ、白で消える白マットを同時に暴くため3面コンポジットを自動生成する[1]

BLACK #000000
GRAY #808080
WHITE #FFFFFF
CHECKER
def make_3up(img, bg_colors=((0,0,0),(128,128,128),(255,255,255)), pad=24):
    """黒/白/グレー(+市松)の検版コンタクトシートを1枚に。"""
    w, h = img.size
    panel_w = w + pad*2
    sheet = Image.new("RGB", (panel_w*len(bg_colors), h+pad*2), (40,40,40))
    for i, c in enumerate(bg_colors):
        panel = Image.new("RGBA", (panel_w, h+pad*2), c+(255,))
        panel.alpha_composite(img, dest=(pad, pad))
        sheet.paste(panel.convert("RGB"), (panel_w*i, 0))
    return sheet

def make_checker(size, cell=16, c1=(200,200,200), c2=(245,245,245)):
    """半透明グラデの確認に最適な市松背景。"""
    w,h = size; bg = Image.new("RGB",(w,h),c1)
    for y in range(0,h,cell):
        for x in range(0,w,cell):
            if (x//cell + y//cell) % 2:
                bg.paste(Image.new("RGB",(cell,cell),c2),(x,y))
    return bg

def composite_on(img, bg):
    base = bg.convert("RGBA"); base.alpha_composite(img); return base.convert("RGB")
検版の見方(目視の最後の砦)

さらに 50%/25%縮小プレビューも同時生成すると、等倍で見えないmip由来のハロが出る[4]img.resize((w//2,h//2), Image.LANCZOS) を3面に並べる。

7. 30日実装プラン(自作SFX素材ラインに組込)

期間やること完了条件
Day1-3make_canvas の余白式を既存SFX生成scriptに前置。glow/stroke値から自動算出に置換新規生成は端切れ0(R1自動PASS)
Day4-7validate_png.py 実装→既存在庫を全数スキャン。FAIL一覧を出す在庫の不良率が数値化
Day8-14defringe_np + smart_trim を再加工バッチ化。FAIL分を一括補修R3/R2が一括PASS化
Day15-213面+縮小プレビュー自動生成をパイプ末尾に。サンプル画像も兼用商品サンプル3枚が自動出力
Day22-26パッケージャに gate.json 必須化(未合格ZIP生成不能に)出荷=全数合格保証
Day27-30白縁/黒縁カラバリ自動量産 + README/ライセンス雛形をテンプレ化プロ品質パッケージ完成

8. リスク・落とし穴・撤退ライン

8-1. 技術的落とし穴

症状回避
生成後トリムで端切れgetbboxピッタリで切ってグロー消失必ず keep_margin を戻す(§4-3)
MaxFilterでαまで膨張輪郭が太る/にじむdefringeはRGBのみ触りαは不変(§4-4)
premul保存白背景で黒フチPILは標準straight。saveでpremul指定しない[8][9]
PNG-8で保存αが段階化しジャギ必ずRGBA(PNG-32)で保存[2]
JPEG中間挟むα消失中間保存もPNG/TIFFのみ[12]
Windowsで show() 確認BMP変換でα消えて「透過してない」と誤判断必ずファイル保存して3面で確認[11]
R4ジャギ閾値が厳しすぎ正常な中抜き素材を誤reject素材種別でthr調整・半透明皆無なら除外

8-2. 撤退ライン

9. 既存資産・社内資産の活用

10. 品質投資のリターン(なぜ検品に時間を割くか)

11. 関連DR一覧(D:\市場調査資料\)

12. 脚注(全URL・1次情報)

  1. 透過PNG運用ベストプラクティス(黒/白2面テスト・matte色問題・無駄余白トリム) — Cloudinary "Image Transparency Explained": https://cloudinary.com/guides/image-effects/image-transparency
  2. PNGの仕様・ビット深度・α・パレットの概念 — Pillow Concepts: https://pillow.readthedocs.io/en/stable/handbook/concepts.html
  3. getbbox(alpha_only)/crop/split/putalpha — Pillow Image module reference: https://pillow.readthedocs.io/en/stable/reference/Image.html / 解説: https://jdhao.github.io/2019/03/07/pillow_image_alpha_channel/
  4. alpha bleed / edge padding / 透明域RGBの膨張(mip・縮小ハロ防止) — Shahriar Shahrabi "Padding Transparent Textures for MIPs and Game Engines": https://shahriyarshahrabi.medium.com/padding-transparent-textures-fir-mips-and-game-engines-c71c085142fe
  5. 同人素材の販売規定(BOOTH 1GB上限/print・web版分け/サンプル3枚/README) — めいくりむ BOOTH DL販売ガイド: https://makelim.site/boothdl/ / DLsite推奨解像度1600×1200(4:3): https://x.com/ririsu_org/status/1509439633209237506 / 印刷解像度: https://www.graphic.jp/comic/user_guide/data_flow
  6. 白/黒ハロの原因(premul/straightの取り違え)・低解像度αのジャギ — Pillow alpha解説 & Concepts: https://pillow.readthedocs.io/en/stable/handbook/concepts.html
  7. アイコンの余白/セーフエリア標準(24px枠・全周2px・8pxグリッド・keyline) — Material Design System icons: https://m2.material.io/design/iconography/system-icons.html / UX Planet Practical Guide to Icon Design: https://uxplanet.org/practical-guide-to-icon-design-794baf5624c8 / keyline解説: https://minoraxis.medium.com/icon-grids-keylines-demystified-5a228fe08cfd
  8. premultiplied alphaの誤解(premulは「いつ乗算するか」でありRGBの正しさは別問題) — frame.io "The Pitfalls of Exchanging Files: Alpha Channels": https://blog.frame.io/2022/08/18/file-exchange-pitfalls-alpha-channels-data-range/
  9. premul vs straight alpha・黒フチ/白フチの発生機序・PNGはstraight推奨 — ProVideo Coalition "Alpha channels: premultiplied vs straight": https://www.provideocoalition.com/alpha-channels-premultiplied-vs-straight/ / ActionVFX "Mastering Alpha Channels": https://www.actionvfx.com/blog/mastering-alpha-channels
  10. Defringe / Remove White-Black Matte / Color Decontaminate(縁の背景色除去アルゴリズム) — Adobe Photoshop ヘルプ Remove fringe pixels: https://helpx.adobe.com/photoshop/desktop/make-selections/refine-modify-selections/fringe-pixels-around-a-selection.html / PhotoshopCAFE 3 easy ways to remove edge fringes: https://photoshopcafe.com/3-easy-ways-remove-edge-fringes-photoshop-halo-removal-cutout-edges/
  11. Windowsの show() がBMP変換でα消失する罠 — jdhao "Manipulating Images with Alpha Channels in Pillow": https://jdhao.github.io/2019/03/07/pillow_image_alpha_channel/
  12. JPEGはα非対応で透過破棄/保存はPNG必須 — Pillow Image file formats & alpha解説 — tutorialspoint Working With Alpha Channels: https://www.tutorialspoint.com/python_pillow/python_pillow_working_with_alpha_channels.htm / Pillow formats: https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html
  13. ImageChops difference/subtract/lighter/darker(2面合成差分でフチ検出) & ImageFilter MaxFilter/MinFilter(α膨張/収縮) — Pillow ImageChops reference: https://pillow.readthedocs.io/en/stable/reference/ImageChops.html
  14. Adobe Stock 透過PNG入稿要件(αの扱い・縁品質) — Adobe Stock Contributor "Requirements for submitting PNG files with transparent backgrounds": https://helpx.adobe.com/stock/contributor/help/png-with-transparency.html
  15. 透明PNGへのpadding付与(整列余白の標準的扱い) — Online PNG Tools Add Padding: https://onlinepngtools.com/add-padding-to-png
  16. DLsite紹介画像の解像度/ぼやけ対策(300dpi以上) — 生活プラスター "DLsite紹介画像のぼやけ解消法": https://life.awaisora.com/2025/06/19/1fbca9c9-cb1e-4e93-b4e0-6b39b7fd23ce/
  17. 縮小時の dark fringe/halo の実例(RGBA縮小でフチが出る) — Adobe Community "Why I see dark fringe/halo in scaled down RGBA images": https://community.adobe.com/t5/substance-3d-designer-bugs/why-i-see-dark-fringe-halo-in-scaled-down-rgba-imaged-in-designer-and-not-in-any-other-soft/idi-p/15461220

DR_透過PNG素材のプロ品質基準と自動検品_2026-06-11 / 17ソース脚注付き / PIL実装粒度100点級
※コード中 from PIL import ImageChops を validate_png.py 冒頭に追加要(check内で使用)。numpy必須。