116 lines
4.4 KiB
Python
116 lines
4.4 KiB
Python
import hashlib
|
|
import json
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
from PIL import Image
|
|
|
|
SCRIPTS = Path(__file__).resolve().parents[1] / "scripts"
|
|
sys.path.insert(0, str(SCRIPTS))
|
|
|
|
from asset_quality import analyze_asset
|
|
from build_scroll_skins import (
|
|
build_asset,
|
|
remove_chroma_background,
|
|
sanitize_output_edges,
|
|
stretch_safe_center,
|
|
)
|
|
|
|
|
|
class BuildScrollSkinsTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.directory = tempfile.TemporaryDirectory()
|
|
self.addCleanup(self.directory.cleanup)
|
|
self.root = Path(self.directory.name)
|
|
|
|
def test_chroma_background_becomes_transparent_black(self):
|
|
image = Image.new("RGB", (4, 2), (0, 255, 0))
|
|
image.putpixel((1, 0), (180, 30, 20))
|
|
cleaned = remove_chroma_background(image, (0, 255, 0), tolerance=80)
|
|
self.assertEqual((0, 0, 0, 0), cleaned.getpixel((0, 0)))
|
|
self.assertEqual((180, 30, 20, 255), cleaned.getpixel((1, 0)))
|
|
|
|
def test_safe_center_stretch_preserves_caps_and_exact_size(self):
|
|
image = Image.new("RGBA", (30, 10), (150, 20, 10, 255))
|
|
for y in range(10):
|
|
for x in range(5):
|
|
image.putpixel((x, y), (220, 170, 40, 255))
|
|
image.putpixel((29 - x, y), (220, 170, 40, 255))
|
|
|
|
output = stretch_safe_center(image, output_size=(60, 20), cap_width=5, padding=2)
|
|
self.assertEqual((60, 20), output.size)
|
|
self.assertEqual((0, 0, 0, 0), output.getpixel((0, 0)))
|
|
self.assertEqual((220, 170, 40, 255), output.getpixel((3, 10)))
|
|
self.assertEqual((150, 20, 10, 255), output.getpixel((30, 10)))
|
|
self.assertEqual((220, 170, 40, 255), output.getpixel((56, 10)))
|
|
|
|
def test_sanitizes_only_forbidden_edge_artifacts(self):
|
|
image = Image.new("RGBA", (3, 1), (248, 240, 220, 255))
|
|
image.putpixel((0, 0), (0, 255, 0, 128))
|
|
image.putpixel((1, 0), (250, 250, 250, 96))
|
|
cleaned = sanitize_output_edges(image)
|
|
self.assertEqual((0, 0, 0, 0), cleaned.getpixel((0, 0)))
|
|
self.assertEqual((0, 0, 0, 0), cleaned.getpixel((1, 0)))
|
|
self.assertEqual((248, 240, 220, 255), cleaned.getpixel((2, 0)))
|
|
|
|
def asset(self, asset_id):
|
|
"""返回与现行判别式 schema 一致的最小透明卷轴测试资产。"""
|
|
return {
|
|
"id": asset_id,
|
|
"source": "source.png",
|
|
"output": "output.png",
|
|
"sourcePixels": {"width": 32, "height": 16},
|
|
"outputPixels": {"width": 60, "height": 30},
|
|
"alpha": {"required": True, "transparentOuterPadding": 1, "cornerMaxAlpha": 0},
|
|
"edge": {
|
|
"forbidChromaResidue": True,
|
|
"forbidLightFringe": True,
|
|
"premultipliedAlphaCheck": True,
|
|
},
|
|
"quality": {"maxBytes": 10000, "colorSpace": "sRGB"},
|
|
"processing": {
|
|
"mode": "chroma-stretch",
|
|
"capWidth": 4,
|
|
"keyColor": "#00FF00",
|
|
"keyTolerance": 80,
|
|
"paletteColors": 8,
|
|
},
|
|
}
|
|
|
|
def save_test_source(self):
|
|
source = Image.new("RGB", (32, 16), (0, 255, 0))
|
|
for y in range(1, 15):
|
|
for x in range(1, 31):
|
|
source.putpixel((x, y), (120 + (x % 30) * 4, 20 + y, 10))
|
|
source.save(self.root / "source.png")
|
|
|
|
def test_build_asset_honors_declared_palette_size(self):
|
|
self.save_test_source()
|
|
build_asset(self.asset("palette-test"), self.root)
|
|
with Image.open(self.root / "output.png") as output:
|
|
visible_colors = {
|
|
pixel[:3] for pixel in output.convert("RGBA").get_flattened_data() if pixel[3] > 0
|
|
}
|
|
self.assertLessEqual(len(visible_colors), 8)
|
|
|
|
def test_same_input_produces_identical_bytes_and_report_twice(self):
|
|
self.save_test_source()
|
|
asset = self.asset("deterministic-test")
|
|
snapshots = []
|
|
for _ in range(2):
|
|
build_asset(asset, self.root)
|
|
output = self.root / "output.png"
|
|
snapshots.append(
|
|
(
|
|
hashlib.sha256(output.read_bytes()).hexdigest(),
|
|
json.dumps(analyze_asset(output, asset, self.root), ensure_ascii=False, sort_keys=True),
|
|
)
|
|
)
|
|
self.assertEqual(snapshots[0], snapshots[1])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|