Add new game assets and update asset paths in code
No files matched your search
|
After Width: | Height: | Size: 597 KiB |
|
After Width: | Height: | Size: 398 KiB |
|
After Width: | Height: | Size: 503 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 521 KiB |
|
After Width: | Height: | Size: 927 B |
|
After Width: | Height: | Size: 921 B |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 982 B |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 15 KiB |
@@ -5,16 +5,16 @@ extends CanvasLayer
|
||||
|
||||
const Rules := preload("res://src/core/rules.gd")
|
||||
const Strings := preload("res://src/core/strings.gd")
|
||||
const T_VIGNETTE := preload("res://assets/textures/vignette.png")
|
||||
const T_ICON_FLASH := preload("res://assets/textures/icons/flashlight_on.png")
|
||||
const T_ICON_DOOR := preload("res://assets/textures/icons/door_locked.png")
|
||||
const T_ICON_WAND := preload("res://assets/textures/icons/wand.png")
|
||||
const T_ICON_REC := preload("res://assets/textures/icons/rec_dot.png")
|
||||
const T_CARD_ID := preload("res://assets/textures/cards/id_card.png")
|
||||
const T_CARD_WRIST := preload("res://assets/textures/cards/wristband.png")
|
||||
const T_CARD_TICKET := preload("res://assets/textures/cards/ticket.png")
|
||||
const T_CARD_CRED := preload("res://assets/textures/cards/credential.png")
|
||||
const T_CARD_GUEST := preload("res://assets/textures/cards/guest_list.png")
|
||||
const T_VIGNETTE := preload("res://assets/textures/ai/game_ready/vignette.png")
|
||||
const T_ICON_FLASH := preload("res://assets/textures/ai/game_ready/icons/flashlight_on.png")
|
||||
const T_ICON_DOOR := preload("res://assets/textures/ai/game_ready/icons/door_locked.png")
|
||||
const T_ICON_WAND := preload("res://assets/textures/ai/game_ready/icons/wand.png")
|
||||
const T_ICON_REC := preload("res://assets/textures/ai/game_ready/icons/rec_dot.png")
|
||||
const T_CARD_ID := preload("res://assets/textures/ai/game_ready/cards/id_card.png")
|
||||
const T_CARD_WRIST := preload("res://assets/textures/ai/game_ready/cards/wristband.png")
|
||||
const T_CARD_TICKET := preload("res://assets/textures/ai/game_ready/cards/ticket.png")
|
||||
const T_CARD_CRED := preload("res://assets/textures/ai/game_ready/cards/credential.png")
|
||||
const T_CARD_GUEST := preload("res://assets/textures/ai/game_ready/cards/guest_list.png")
|
||||
const CARD_TEXTURES := {
|
||||
Rules.AccessKind.ID: T_CARD_ID,
|
||||
Rules.AccessKind.WRISTBAND: T_CARD_WRIST,
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
class_name WorldBuilder
|
||||
## Procedurally assembles a minimal Hollywood Boulevard entrance prototype:
|
||||
|
||||
const T_BRICK := preload("res://assets/textures/brick_facade.png")
|
||||
const T_CONCRETE := preload("res://assets/textures/concrete.png")
|
||||
const T_ASPHALT := preload("res://assets/textures/asphalt.png")
|
||||
const T_AWNING := preload("res://assets/textures/awning.png")
|
||||
const T_SKYLINE := preload("res://assets/textures/skyline.png")
|
||||
const T_BRICK := preload("res://assets/textures/ai/game_ready/brick_facade.png")
|
||||
const T_CONCRETE := preload("res://assets/textures/ai/game_ready/concrete.png")
|
||||
const T_ASPHALT := preload("res://assets/textures/ai/game_ready/asphalt.png")
|
||||
const T_AWNING := preload("res://assets/textures/ai/game_ready/awning.png")
|
||||
const T_SKYLINE := preload("res://assets/textures/ai/game_ready/skyline.png")
|
||||
## steps, building facade, restricted door, sidewalk, pavement, and street edge.
|
||||
## No art assets required; all geometry is BoxMesh with flat materials.
|
||||
|
||||
|
||||
@@ -12,6 +12,12 @@ import generate_textures
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".webp", ".bmp", ".tif", ".tiff"}
|
||||
DEFAULT_ROLE_IMAGE_MODEL = "openai/gpt-5-image-mini"
|
||||
API_ASPECTS = {
|
||||
"texture": "1:1", "tile": "1:1", "icon": "1:1", "app_icon": "1:1",
|
||||
"overlay": "1:1", "card": "3:2", "banner": "3:2", "backdrop": "3:2",
|
||||
"background": "3:2",
|
||||
}
|
||||
|
||||
PROFILES = {
|
||||
"texture": {"target": (512, 512), "min": (256, 256), "max": (1024, 1024),
|
||||
@@ -24,15 +30,20 @@ PROFILES = {
|
||||
"app_icon": {"target": (1024, 1024), "min": (512, 512), "max": (1024, 1024),
|
||||
"aspect": 1.0, "alpha": None, "view": "square, centered composition"},
|
||||
"overlay": {"target": (512, 512), "min": (256, 256), "max": (1024, 1024),
|
||||
"aspect": 1.0, "alpha": None, "view": "screen-space overlay"},
|
||||
"aspect": 1.0, "alpha": None, "view": "screen-space overlay",
|
||||
"min_contrast": 5.0},
|
||||
"card": {"target": (256, 128), "min": (128, 64), "max": (1024, 512),
|
||||
"aspect": 2.0, "alpha": None, "view": "flat, front-facing card"},
|
||||
"banner": {"target": (1024, 256), "min": (512, 128), "max": (2048, 512),
|
||||
"aspect": 4.0, "alpha": None, "view": "flat, front-facing banner"},
|
||||
"backdrop": {"target": (1024, 256), "min": (1024, 256), "max": (8192, 2048),
|
||||
"aspect": 4.0, "alpha": None, "view": "straight-on wide backdrop"},
|
||||
"aspect": 4.0, "alpha": True,
|
||||
"view": "straight-on wide backdrop on transparency"},
|
||||
"background": {"target": (1920, 1080), "min": (1280, 720), "max": (8192, 4320),
|
||||
"aspect": 16 / 9, "alpha": None, "view": "intended game camera and horizon"},
|
||||
"screenshot": {"target": (1280, 720), "min": (1280, 720), "max": (3840, 2160),
|
||||
"aspect": 16 / 9, "alpha": None, "view": "gameplay frame, venue, and UI state",
|
||||
"min_contrast": 5.0},
|
||||
}
|
||||
|
||||
ROLE_GUIDANCE = {
|
||||
@@ -41,10 +52,10 @@ ROLE_GUIDANCE = {
|
||||
"icon": "single centered flat UI glyph, clean silhouette, transparent background, no mockup or perspective",
|
||||
"app_icon": "square game application icon, centered composition, no perspective",
|
||||
"overlay": "screen-space overlay texture with transparent edges, no perspective",
|
||||
"card": "single front-facing flat game UI card, no perspective, no surrounding mockup",
|
||||
"banner": "wide front-facing game banner texture, no perspective, no surrounding scene",
|
||||
"backdrop": "wide seamless-looking game environment backdrop, straight-on view, no foreground objects",
|
||||
"background": "wide 16:9 game background environment with a coherent horizon and straight camera view",
|
||||
"card": "single wide landscape game UI card filling the frame, front-facing and flat, no perspective, no surrounding mockup or whitespace",
|
||||
"banner": "wide front-facing game banner texture, keep important details in a narrow horizontal band through the image center for final cropping, no perspective, no surrounding scene",
|
||||
"backdrop": "wide isolated skyline silhouette across a low horizon in a narrow horizontal band through the image center, transparent background, no foreground objects",
|
||||
"background": "wide 16:9 game background with a coherent horizon and straight camera view, keep important details in the central horizontal band for final cropping",
|
||||
}
|
||||
|
||||
|
||||
@@ -137,6 +148,8 @@ def resize_image(source, output, width, height, fit="contain", background=(0, 0,
|
||||
def infer_role(path):
|
||||
parts = [part.lower() for part in path.parts]
|
||||
stem = path.stem.lower()
|
||||
if "screenshots" in parts or "screenshot" in stem:
|
||||
return "screenshot"
|
||||
if "icons" in parts:
|
||||
return "icon"
|
||||
if "cards" in parts:
|
||||
@@ -193,6 +206,8 @@ def audit_image(path, role="auto", seam_threshold=18.0):
|
||||
f"wrong aspect ratio: {actual_aspect:.3f}; expected about {aspect:.3f}")
|
||||
if profile.get("alpha") is True and not alpha:
|
||||
errors.append("transparency required but image is opaque")
|
||||
if alpha and image.convert("RGBA").getchannel("A").getbbox() is None:
|
||||
errors.append("image is fully transparent")
|
||||
elif profile.get("alpha") is False and alpha:
|
||||
warnings.append(
|
||||
"image has an alpha channel although this role is normally opaque")
|
||||
@@ -204,20 +219,27 @@ def audit_image(path, role="auto", seam_threshold=18.0):
|
||||
if edge_difference > seam_threshold:
|
||||
errors.append(
|
||||
f"tile edges differ by {edge_difference:.1f} (limit {seam_threshold:.1f})")
|
||||
contrast = None
|
||||
if profile.get("min_contrast") is not None:
|
||||
contrast = ImageStat.Stat(image.convert("L")).stddev[0]
|
||||
if contrast < profile["min_contrast"]:
|
||||
errors.append(
|
||||
f"near-blank frame: contrast {contrast:.1f} is below {profile['min_contrast']:.1f}")
|
||||
manual_review = [
|
||||
f"Confirm {profile.get('view', 'intended')} perspective/projection visually; "
|
||||
"automated checks cannot infer it reliably."
|
||||
]
|
||||
return {
|
||||
"path": str(path), "role": chosen_role, "size": [width, height],
|
||||
"alpha": alpha, "edge_difference": edge_difference,
|
||||
"alpha": alpha, "edge_difference": edge_difference, "contrast": contrast,
|
||||
"quality": "bad" if errors else "good",
|
||||
"errors": errors, "warnings": warnings, "manual_review": manual_review,
|
||||
}
|
||||
except (OSError, ValueError) as exc:
|
||||
return {
|
||||
"path": str(path), "role": chosen_role, "size": None, "alpha": None,
|
||||
"edge_difference": None, "quality": "bad", "errors": [f"cannot read image: {exc}"],
|
||||
"edge_difference": None, "contrast": None, "quality": "bad",
|
||||
"errors": [f"cannot read image: {exc}"],
|
||||
"warnings": [], "manual_review": [],
|
||||
}
|
||||
|
||||
@@ -246,13 +268,19 @@ def _generate(args):
|
||||
"alpha") is True
|
||||
prompt = f"{args.prompt.strip()}. {ROLE_GUIDANCE[args.role]}."
|
||||
image = generate_textures.generate_image(
|
||||
prompt, args.model, args.request_size, transparent)
|
||||
prompt, args.model, args.request_size, transparent,
|
||||
aspect_ratio=API_ASPECTS[args.role])
|
||||
foreground = getattr(args, "foreground", None)
|
||||
if foreground:
|
||||
image = colorize_alpha(image, foreground)
|
||||
if args.role == "icon" and has_alpha(image):
|
||||
image = resize_image_from_image(
|
||||
image, width, height, "contain", (0, 0, 0, 0))
|
||||
else:
|
||||
image = ImageOps.fit(image, (width, height),
|
||||
method=Image.Resampling.LANCZOS)
|
||||
if args.role == "tile":
|
||||
image = make_seamless(image)
|
||||
save_image(image, resolve_path(args.output))
|
||||
|
||||
|
||||
@@ -273,6 +301,53 @@ def resize_image_from_image(image, width, height, fit, background):
|
||||
return result
|
||||
|
||||
|
||||
def colorize_alpha(image, color):
|
||||
if not has_alpha(image):
|
||||
raise ValueError(
|
||||
"foreground color requires an image with transparency")
|
||||
result = Image.new("RGBA", image.size, color[:3])
|
||||
result.putalpha(image.convert("RGBA").getchannel("A"))
|
||||
return result
|
||||
|
||||
|
||||
def make_seamless(image, band=32):
|
||||
original_mode = image.mode
|
||||
result = image.convert("RGBA")
|
||||
width, height = result.size
|
||||
pixels = result.load()
|
||||
band = min(band, width // 4, height // 4)
|
||||
|
||||
def blend_edges(first_index, last_index, length, across_length, horizontal):
|
||||
for offset in range(band):
|
||||
weight = 1.0 - offset / band
|
||||
first = first_index(offset)
|
||||
last = last_index(offset)
|
||||
for across in range(across_length):
|
||||
first_pos = (first, across) if horizontal else (across, first)
|
||||
last_pos = (last, across) if horizontal else (across, last)
|
||||
first_color = pixels[first_pos]
|
||||
last_color = pixels[last_pos]
|
||||
average = tuple((a + b) / 2 for a,
|
||||
b in zip(first_color, last_color))
|
||||
pixels[first_pos] = tuple(round(a * (1 - weight) + b * weight)
|
||||
for a, b in zip(first_color, average))
|
||||
pixels[last_pos] = tuple(round(a * (1 - weight) + b * weight)
|
||||
for a, b in zip(last_color, average))
|
||||
|
||||
blend_edges(lambda offset: offset, lambda offset: width - 1 - offset,
|
||||
width, height, True)
|
||||
blend_edges(lambda offset: offset, lambda offset: height - 1 - offset,
|
||||
height, width, False)
|
||||
return result if has_alpha(image) else result.convert(original_mode)
|
||||
|
||||
|
||||
def seamless_image(source, output):
|
||||
with Image.open(source) as image:
|
||||
image.load()
|
||||
result = make_seamless(image)
|
||||
save_image(result, output)
|
||||
|
||||
|
||||
def build_parser():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Create and validate game image assets.")
|
||||
@@ -283,13 +358,16 @@ def build_parser():
|
||||
generate.add_argument("--prompt", required=True)
|
||||
generate.add_argument("--output", required=True,
|
||||
help="output path, relative to the project root or absolute")
|
||||
generate.add_argument("--role", choices=PROFILES, required=True)
|
||||
generate.add_argument("--role", choices=API_ASPECTS, required=True)
|
||||
generate.add_argument(
|
||||
"--model", default=generate_textures.DEFAULT_IMAGE_MODEL)
|
||||
"--model", default=DEFAULT_ROLE_IMAGE_MODEL)
|
||||
generate.add_argument("--request-size", type=int,
|
||||
default=generate_textures.GEN_SIZE)
|
||||
generate.add_argument("--width", type=int)
|
||||
generate.add_argument("--height", type=int)
|
||||
generate.add_argument(
|
||||
"--foreground", type=parse_color,
|
||||
help="replace visible RGB with this color while preserving alpha")
|
||||
alpha = generate.add_mutually_exclusive_group()
|
||||
alpha.add_argument("--transparent", dest="transparent",
|
||||
action="store_true")
|
||||
@@ -320,6 +398,13 @@ def build_parser():
|
||||
resize.set_defaults(handler=lambda args: resize_image(resolve_path(args.input), resolve_path(args.output),
|
||||
args.width, args.height, args.fit, args.background))
|
||||
|
||||
seamless = commands.add_parser(
|
||||
"seamless", help="blend opposite edges of a texture for tiling")
|
||||
seamless.add_argument("input")
|
||||
seamless.add_argument("output")
|
||||
seamless.set_defaults(handler=lambda args: seamless_image(
|
||||
resolve_path(args.input), resolve_path(args.output)))
|
||||
|
||||
audit = commands.add_parser(
|
||||
"audit", help="classify images as good/bad using their game-asset role")
|
||||
audit.add_argument("paths", nargs="+", help="image files or directories")
|
||||
|
||||
@@ -373,23 +373,32 @@ def _api_key() -> str:
|
||||
|
||||
|
||||
def generate_image(prompt: str, model: str, size: int,
|
||||
transparent: bool = False) -> Image.Image:
|
||||
transparent: bool = False,
|
||||
aspect_ratio: str = "1:1") -> Image.Image:
|
||||
"""Call the OpenRouter image API and return the decoded PIL image.
|
||||
|
||||
Requests a square image at `size` px and decodes the base64 PNG/JPEG/WebP
|
||||
bytes returned in `data[].b64_json`. The requested size is clamped to at
|
||||
least GEN_SIZE because some models (e.g. seedream) reject small outputs;
|
||||
callers downscale the result to MAX_DIM afterwards. When `transparent` is
|
||||
true, the API is asked for a transparent background (PNG alpha).
|
||||
Requests a square image at `size` px or a role-shaped image at the matching
|
||||
resolution tier and decodes `data[].b64_json`. Seedream's square requests
|
||||
retain the existing GEN_SIZE floor. When `transparent` is true, the API is
|
||||
asked for a transparent PNG background.
|
||||
"""
|
||||
size = max(GEN_SIZE, int(size))
|
||||
size = max(1, int(size))
|
||||
if model == DEFAULT_IMAGE_MODEL:
|
||||
size = max(GEN_SIZE, size)
|
||||
body = {
|
||||
"model": model,
|
||||
"prompt": prompt,
|
||||
"size": f"{size}x{size}",
|
||||
"output_format": "png",
|
||||
"n": 1,
|
||||
}
|
||||
if aspect_ratio == "1:1":
|
||||
body["size"] = f"{size}x{size}"
|
||||
else:
|
||||
body["aspect_ratio"] = aspect_ratio
|
||||
body["resolution"] = next(
|
||||
(tier for limit, tier in ((512, "512"), (768, "768"), (1024, "1K"),
|
||||
(1536, "1.5K"), (2048, "2K"), (4096, "4K"))
|
||||
if size <= limit), "4K")
|
||||
if transparent:
|
||||
body["background"] = "transparent"
|
||||
req = urllib.request.Request(
|
||||
|
||||
@@ -3,7 +3,7 @@ import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from PIL import Image
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
import asset_pipeline
|
||||
|
||||
@@ -51,6 +51,38 @@ class AssetPipelineTests(unittest.TestCase):
|
||||
self.assertEqual(asset_pipeline.audit_image(
|
||||
bad, "tile")["quality"], "bad")
|
||||
|
||||
def test_seamless_transform_makes_tile_edges_continuous(self):
|
||||
image_path = self.root / "tile.png"
|
||||
output = self.root / "seamless.png"
|
||||
image = Image.new("RGB", (512, 512))
|
||||
for x in range(512):
|
||||
for y in range(512):
|
||||
image.putpixel((x, y), (x // 2, y // 2, (x + y) // 4))
|
||||
image.save(image_path)
|
||||
|
||||
asset_pipeline.seamless_image(image_path, output)
|
||||
|
||||
self.assertEqual(asset_pipeline.audit_image(
|
||||
output, "tile")["quality"], "good")
|
||||
|
||||
def test_tile_generation_applies_seamless_transform(self):
|
||||
output = self.root / "generated_tile.png"
|
||||
args = type("Args", (), {
|
||||
"prompt": "brick", "role": "tile", "output": str(output),
|
||||
"width": None, "height": None, "transparent": False,
|
||||
"model": "test-model", "request_size": 1024,
|
||||
})()
|
||||
generated = Image.new("RGB", (512, 512), (80, 80, 80))
|
||||
for y in range(512):
|
||||
generated.putpixel((0, y), (255, 0, 0))
|
||||
|
||||
with patch.object(asset_pipeline.generate_textures, "generate_image",
|
||||
return_value=generated):
|
||||
asset_pipeline._generate(args)
|
||||
|
||||
self.assertEqual(asset_pipeline.audit_image(
|
||||
output, "tile")["quality"], "good")
|
||||
|
||||
def test_background_audit_rejects_small_image(self):
|
||||
image_path = self.root / "small.png"
|
||||
Image.new("RGB", (640, 360)).save(image_path)
|
||||
@@ -63,11 +95,27 @@ class AssetPipelineTests(unittest.TestCase):
|
||||
|
||||
def test_grayscale_vignette_is_valid_without_alpha(self):
|
||||
image_path = self.root / "vignette.png"
|
||||
Image.new("L", (512, 512), 0).save(image_path)
|
||||
image = Image.new("L", (512, 512), 0)
|
||||
draw = ImageDraw.Draw(image)
|
||||
for radius in range(255, -1, -1):
|
||||
value = int(255 * (1 - radius / 255))
|
||||
draw.ellipse((256 - radius, 256 - radius,
|
||||
256 + radius, 256 + radius), fill=value)
|
||||
image.save(image_path)
|
||||
|
||||
self.assertEqual(asset_pipeline.audit_image(
|
||||
image_path, "overlay")["quality"], "good")
|
||||
|
||||
def test_overlay_audit_rejects_blank_transparent_image(self):
|
||||
image_path = self.root / "blank_overlay.png"
|
||||
Image.new("RGBA", (512, 512), (0, 0, 0, 0)).save(image_path)
|
||||
|
||||
result = asset_pipeline.audit_image(image_path, "overlay")
|
||||
|
||||
self.assertEqual(result["quality"], "bad")
|
||||
self.assertTrue(any("near-blank frame" in error
|
||||
for error in result["errors"]))
|
||||
|
||||
def test_icon_audit_rejects_opaque_rgba_images(self):
|
||||
image_path = self.root / "opaque_icon.png"
|
||||
Image.new("RGBA", (64, 64), (255, 255, 255, 255)).save(image_path)
|
||||
@@ -78,6 +126,16 @@ class AssetPipelineTests(unittest.TestCase):
|
||||
self.assertTrue(any("transparency required" in error
|
||||
for error in result["errors"]))
|
||||
|
||||
def test_backdrop_audit_requires_transparency(self):
|
||||
image_path = self.root / "skyline.png"
|
||||
Image.new("RGB", (1024, 256), (20, 30, 40)).save(image_path)
|
||||
|
||||
result = asset_pipeline.audit_image(image_path, "backdrop")
|
||||
|
||||
self.assertEqual(result["quality"], "bad")
|
||||
self.assertTrue(any("transparency required" in error
|
||||
for error in result["errors"]))
|
||||
|
||||
def test_generation_reuses_existing_backend_and_targets_role_dimensions(self):
|
||||
output = self.root / "background.png"
|
||||
args = type("Args", (), {
|
||||
@@ -93,9 +151,43 @@ class AssetPipelineTests(unittest.TestCase):
|
||||
|
||||
self.assertIn("Hollywood at night", backend.call_args.args[0])
|
||||
self.assertIn("wide 16:9", backend.call_args.args[0])
|
||||
self.assertEqual(backend.call_args.kwargs["aspect_ratio"], "3:2")
|
||||
with Image.open(output) as image:
|
||||
self.assertEqual(image.size, (1920, 1080))
|
||||
|
||||
def test_generation_can_recolor_transparent_glyph(self):
|
||||
output = self.root / "icon.png"
|
||||
args = type("Args", (), {
|
||||
"prompt": "flashlight", "role": "icon", "output": str(output),
|
||||
"width": None, "height": None, "transparent": True,
|
||||
"model": "test-model", "request_size": 1024,
|
||||
"foreground": (255, 255, 255, 255),
|
||||
})()
|
||||
generated = Image.new("RGBA", (64, 64), (0, 0, 0, 0))
|
||||
generated.putpixel((32, 32), (20, 20, 20, 255))
|
||||
|
||||
with patch.object(asset_pipeline.generate_textures, "generate_image",
|
||||
return_value=generated):
|
||||
asset_pipeline._generate(args)
|
||||
|
||||
with Image.open(output) as image:
|
||||
self.assertEqual(image.getpixel((32, 32)), (255, 255, 255, 255))
|
||||
self.assertEqual(image.getpixel((0, 0))[3], 0)
|
||||
|
||||
def test_screenshot_audit_accepts_game_frame_and_rejects_blank_capture(self):
|
||||
valid = self.root / "frame.png"
|
||||
blank = self.root / "blank.png"
|
||||
frame = Image.new("RGB", (1280, 720), (0, 0, 0))
|
||||
ImageDraw.Draw(frame).rectangle(
|
||||
(640, 0, 1279, 719), fill=(255, 255, 255))
|
||||
frame.save(valid)
|
||||
Image.new("RGB", (1280, 720), (0, 0, 0)).save(blank)
|
||||
|
||||
self.assertEqual(asset_pipeline.audit_image(
|
||||
valid, "screenshot")["quality"], "good")
|
||||
self.assertEqual(asset_pipeline.audit_image(
|
||||
blank, "screenshot")["quality"], "bad")
|
||||
|
||||
def test_role_inference_uses_game_asset_paths(self):
|
||||
self.assertEqual(asset_pipeline.infer_role(
|
||||
Path("assets/textures/icons/wand.png")), "icon")
|
||||
@@ -104,6 +196,12 @@ class AssetPipelineTests(unittest.TestCase):
|
||||
self.assertEqual(asset_pipeline.infer_role(
|
||||
Path("assets/textures/skyline.png")), "backdrop")
|
||||
|
||||
def test_role_generator_defaults_to_validated_image_model(self):
|
||||
args = asset_pipeline.build_parser().parse_args([
|
||||
"generate", "--prompt", "test", "--role", "tile", "--output", "tile.png"])
|
||||
|
||||
self.assertEqual(args.model, asset_pipeline.DEFAULT_ROLE_IMAGE_MODEL)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||