
Transparent Visual Assets
- 59 installs
- 58 repo stars
- Updated May 7, 2026
- dwdestiny/codex-visual-asset-skills
Generates transparent-background PNG assets by prompting Codex image generation with a solid-color background, then removing it with a cleanup script.
About
This skill produces transparent PNG stickers, icons, mascots, and UI assets by generating on a solid chroma background and cleaning it deterministically. A developer uses it for web, presentation, or app visual elements needing transparency.
- Codex image generation with chroma-key background
- Deterministic background removal and edge cropping script
Transparent Visual Assets by the numbers
- 59 all-time installs (skills.sh)
- Ranked #867 of 1,335 Generative Media skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/dwdestiny/codex-visual-asset-skills --skill transparent-visual-assetsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 59 |
|---|---|
| repo stars | ★ 58 |
| Last updated | May 7, 2026 |
| Repository | dwdestiny/codex-visual-asset-skills ↗ |
What it does
Generates transparent-background PNG assets by prompting Codex image generation with a solid-color background, then removing it with a cleanup script.
Files
Transparent Visual Assets
核心判断
先让 Codex 生图得到“容易抠干净”的图,再用脚本做确定性清理。不要要求模型直接画棋盘格透明背景;棋盘格会变成图像内容。优先让模型使用纯色背景,例如 #FF00FF 或 #00FF00,并明确主体不能使用接近背景的颜色。
适合输出:透明 PNG、网页素材、贴纸、图标、单帧 sprite、角色/物体素材。
不适合输出:多帧动画、spritesheet、视频;这些转给 $sprite-animation-assets。
典型使用场景
- 网站设计素材:Landing page、产品官网、博客插画、功能区装饰、空状态插画。
- PPT / 汇报图表:路演页、数据页、业务复盘、图表旁的视觉解释元素。
- App / 产品引导:onboarding、权限说明、功能引导、发布说明里的产品插画。
- 电商 / 社媒贴纸:商品页角标、促销贴纸、社媒封面、公众号配图元素。
- 游戏 / 互动 UI:小角色、道具、背包图标、按钮素材、单帧 sprite。
- 视频 / 海报叠加:封面人物、栏目贴片、透明 logo-like 装饰、片头片尾元素。
工作流
1. 明确素材用途、尺寸、主体边界和禁用元素。 2. 调用 Codex 生图能力生成带纯色背景的候选图。 3. 目视检查:主体完整、没有阴影地面、没有漂浮杂点、主体颜色不接近背景色。 4. 用 scripts/prepare_transparent_asset.py 清理纯色背景、裁切透明边缘、输出 PNG。 5. 打开结果或检查报告,确认透明角落、主体不被误删。
生图提示词要点
使用简短、强约束的提示词:
Create a single centered web asset of <subject>.
Use a perfectly flat #FF00FF background for later chroma key removal.
The subject must not contain magenta, pink, or colors close to #FF00FF.
No shadows, floor, scenery, text, labels, checkerboard pattern, glow, blur, or detached decorative particles.
Keep the full subject inside the canvas with clear spacing.如果主体天然包含粉色或品红,换成 #00FF00、#00FFFF 或其他不会出现在主体里的纯色。
更多提示词和 QA 细节见 references/prompt-and-cleanup.md。
清理脚本
python "${CODEX_HOME:-$HOME/.codex}/skills/transparent-visual-assets/scripts/prepare_transparent_asset.py" \
--input /absolute/path/source.png \
--output /absolute/path/asset.png \
--background "#FF00FF" \
--threshold 24 \
--feather-threshold 48 \
--trim \
--padding 8 \
--report /absolute/path/report.json参数说明:
--background:要移除的纯色背景;不传时用四角颜色估算。--threshold:颜色容差。背景边缘残留就调大;主体被吃掉就调小或重生图换背景色。--feather-threshold:可选的边缘柔化容差,用来淡化生图抗锯齿留下的背景色毛边。--trim和--padding:裁掉透明边缘并保留一点安全留白。
脚本只做背景清理,不会补画、重绘、扩图或创造新素材。
验收标准
- 输出必须是
.png,并带 alpha 透明通道。 - 四角应透明,主体边缘不缺块。
- 主体外没有阴影、地面、光晕、漂浮符号、文字或背景色残留。
- 素材在深色、浅色、棋盘格背景上都能读清。
- 如果清理后主体明显破损,重新生图或换背景色,不要靠脚本硬修。
interface:
display_name: "Transparent Asset Generation"
short_description: "Generate and clean transparent image assets"
default_prompt: "Use $transparent-visual-assets to create a transparent-background PNG asset from this image concept."
透明素材提示词与清理参考
背景色选择
- 优先使用主体中不会出现的高饱和纯色:
#FF00FF、#00FF00、#00FFFF。 - 如果主体有品牌色、衣服、发色或高光接近背景色,立即换背景色。
- 不要使用白色、黑色、灰色或渐变背景;这些颜色常常会混进主体边缘。
生图禁止项
- checkerboard transparency、transparent grid、alpha background。
- 投影、地面、环境、框线、文字、标签、编号。
- 光晕、雾、半透明效果、散落粒子、运动线。
- 贴近画布边缘或被裁切的主体。
清理后 QA
1. 看四角 alpha 是否为 0。 2. 看主体边缘是否被吃掉。 3. 看主体内部是否出现透明洞。 4. 放到白底、深底、棋盘格三种背景上预览。 5. 需要网页使用时,保留 4 到 16 px 安全留白,不要把主体裁到贴边。
失败处理
- 背景残留:提高
--threshold,或换更远的背景色重生图。 - 边缘有背景色毛边:加
--feather-threshold,通常设置为--threshold往上 40 到 70。 - 主体破损:降低
--threshold,或重生图并禁止主体使用背景色。 - 边缘半透明脏:让生图用“hard-edged, flat color, no glow, no blur”。
- 阴影无法抠干净:重生图,明确 “no shadow, no floor, no contact shadow”。
#!/usr/bin/env python3
"""Clean a flat-background image into a transparent PNG asset."""
from __future__ import annotations
import argparse
import json
import math
import re
from pathlib import Path
from PIL import Image
def parse_hex_color(raw_value: str) -> tuple[int, int, int]:
value = raw_value.strip()
if not re.fullmatch(r"#[0-9a-fA-F]{6}", value):
raise SystemExit(f"invalid --background value: {raw_value}; expected #RRGGBB")
return tuple(int(value[index : index + 2], 16) for index in (1, 3, 5))
def color_distance(pixel: tuple[int, int, int], background_rgb: tuple[int, int, int]) -> float:
red, green, blue = pixel
return math.sqrt(
(red - background_rgb[0]) ** 2
+ (green - background_rgb[1]) ** 2
+ (blue - background_rgb[2]) ** 2
)
def infer_corner_background(image: Image.Image) -> tuple[int, int, int]:
rgb_image = image.convert("RGB")
width, height = rgb_image.size
sample_points = [
(0, 0),
(width - 1, 0),
(0, height - 1),
(width - 1, height - 1),
]
samples = [rgb_image.getpixel(point) for point in sample_points]
return tuple(round(sum(pixel[channel] for pixel in samples) / len(samples)) for channel in range(3))
def remove_background(
image: Image.Image,
*,
background_rgb: tuple[int, int, int],
threshold: float,
feather_threshold: float | None,
) -> tuple[Image.Image, int, int]:
rgba_image = image.convert("RGBA")
pixels = rgba_image.load()
transparent_pixels = 0
opaque_pixels = 0
feather_span = None
if feather_threshold is not None:
if feather_threshold < threshold:
raise SystemExit("--feather-threshold must be >= --threshold")
feather_span = max(1.0, feather_threshold - threshold)
for y_index in range(rgba_image.height):
for x_index in range(rgba_image.width):
red, green, blue, alpha = pixels[x_index, y_index]
distance = color_distance((red, green, blue), background_rgb)
if alpha == 0:
pixels[x_index, y_index] = (0, 0, 0, 0)
transparent_pixels += 1
continue
if distance <= threshold:
pixels[x_index, y_index] = (0, 0, 0, 0)
transparent_pixels += 1
elif feather_threshold is not None and distance <= feather_threshold:
softness = (distance - threshold) / feather_span
softened_alpha = max(1, min(255, round(alpha * softness * softness)))
pixels[x_index, y_index] = (red, green, blue, softened_alpha)
opaque_pixels += 1
else:
opaque_pixels += 1
return rgba_image, transparent_pixels, opaque_pixels
def trim_image(image: Image.Image, padding: int) -> Image.Image:
bbox = image.getchannel("A").getbbox()
if bbox is None:
return image
left, top, right, bottom = bbox
left = max(0, left - padding)
top = max(0, top - padding)
right = min(image.width, right + padding)
bottom = min(image.height, bottom + padding)
return image.crop((left, top, right, bottom))
def positive_float(raw_value: str) -> float:
value = float(raw_value)
if value < 0:
raise argparse.ArgumentTypeError("value must be >= 0")
return value
def non_negative_int(raw_value: str) -> int:
value = int(raw_value)
if value < 0:
raise argparse.ArgumentTypeError("value must be >= 0")
return value
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--input", required=True, help="Source image with a flat removable background.")
parser.add_argument("--output", required=True, help="Output transparent PNG path.")
parser.add_argument(
"--background",
help="Background color to remove, as #RRGGBB. Defaults to the average corner color.",
)
parser.add_argument("--threshold", type=positive_float, default=24.0)
parser.add_argument(
"--feather-threshold",
type=positive_float,
help="Optional wider color distance for softening anti-aliased background fringes.",
)
parser.add_argument("--trim", action="store_true", help="Crop transparent edges after cleanup.")
parser.add_argument("--padding", type=non_negative_int, default=0)
parser.add_argument("--report", help="Optional JSON report path.")
args = parser.parse_args()
input_path = Path(args.input).expanduser().resolve()
output_path = Path(args.output).expanduser().resolve()
report_path = Path(args.report).expanduser().resolve() if args.report else None
if not input_path.is_file():
raise SystemExit(f"input image does not exist: {input_path}")
if output_path.suffix.lower() != ".png":
raise SystemExit("output must be a .png file so transparency is preserved")
try:
with Image.open(input_path) as opened_image:
source_image = opened_image.convert("RGBA")
except Exception as exc: # noqa: BLE001
raise SystemExit(f"could not open input image: {exc}") from exc
background_rgb = parse_hex_color(args.background) if args.background else infer_corner_background(source_image)
cleaned_image, transparent_pixels, opaque_pixels = remove_background(
source_image,
background_rgb=background_rgb,
threshold=args.threshold,
feather_threshold=args.feather_threshold,
)
final_image = trim_image(cleaned_image, args.padding) if args.trim else cleaned_image
output_path.parent.mkdir(parents=True, exist_ok=True)
final_image.save(output_path)
result = {
"ok": opaque_pixels > 0,
"input": str(input_path),
"output": str(output_path),
"background_rgb": list(background_rgb),
"threshold": args.threshold,
"feather_threshold": args.feather_threshold,
"trimmed": bool(args.trim),
"padding": args.padding,
"width": final_image.width,
"height": final_image.height,
"transparent_pixels": transparent_pixels,
"opaque_pixels": opaque_pixels,
}
if report_path:
report_path.parent.mkdir(parents=True, exist_ok=True)
report_path.write_text(json.dumps(result, indent=2) + "\n", encoding="utf-8")
print(json.dumps(result, indent=2))
raise SystemExit(0 if result["ok"] else 1)
if __name__ == "__main__":
main()