Use the photo-anchored ashlar limestone on the masonic facade
This commit is contained in:
1 parent
05cb6a43cb
commit
f95a685934
24 files changed
+111
-10
No files matched your search
@@ -539,9 +539,21 @@ def _fit(img: Image.Image, max_dim: int = MAX_DIM) -> Image.Image:
|
||||
|
||||
|
||||
def generate_ai_textures(prompt: str, name: str, model: str, size: int,
|
||||
transparent: bool = False) -> None:
|
||||
"""Generate one texture via OpenRouter and save it under assets/textures/."""
|
||||
img = generate_image(prompt, model, size, transparent)
|
||||
transparent: bool = False,
|
||||
reference_path=None) -> None:
|
||||
"""Generate one texture via OpenRouter and save it under assets/textures/.
|
||||
|
||||
With `reference_path`, the request is image-to-image (licence-cleared
|
||||
reference photo anchoring value and detail); the returned frame is then
|
||||
centre-cropped to the requested square so tile wraps stay predictable.
|
||||
"""
|
||||
img = generate_image(prompt, model, size, transparent,
|
||||
reference_path=reference_path)
|
||||
if reference_path is not None:
|
||||
side = min(img.size)
|
||||
left = (img.width - side) // 2
|
||||
top = (img.height - side) // 2
|
||||
img = img.crop((left, top, left + side, top + side))
|
||||
img = _fit(img)
|
||||
if transparent:
|
||||
img = img.convert("RGBA")
|
||||
@@ -556,7 +568,11 @@ def _run_ai(args: argparse.Namespace) -> None:
|
||||
if args.prompt:
|
||||
if not args.name:
|
||||
sys.exit("error: --name is required when --prompt is given")
|
||||
generate_ai_textures(args.prompt, args.name, args.model, args.size)
|
||||
if bool(args.reference) != bool(args.reference_licence):
|
||||
sys.exit("error: --reference and --reference-licence must be given "
|
||||
"together (the licence note records the sidecar clearance)")
|
||||
generate_ai_textures(args.prompt, args.name, args.model, args.size,
|
||||
reference_path=args.reference or None)
|
||||
return
|
||||
for name, prompt, transparent in AI_TEXTURES:
|
||||
print(f"generating {name} ...")
|
||||
@@ -579,6 +595,14 @@ def _build_arg_parser() -> argparse.ArgumentParser:
|
||||
parser.add_argument(
|
||||
"--model", default=DEFAULT_IMAGE_MODEL,
|
||||
help=f"image model slug (default: {DEFAULT_IMAGE_MODEL})")
|
||||
parser.add_argument(
|
||||
"--reference", default="",
|
||||
help="licence-cleared photo (listed in a refs-board SIDECAR.md) used "
|
||||
"as an image-to-image anchor; requires --reference-licence")
|
||||
parser.add_argument(
|
||||
"--reference-licence", default="",
|
||||
help="licence label from the photo's sidecar row, recorded in the "
|
||||
"generation provenance")
|
||||
parser.add_argument(
|
||||
"--size", type=int, default=GEN_SIZE,
|
||||
help=f"square size requested from the API in px (min {GEN_SIZE}); "
|
||||
|
||||
@@ -230,6 +230,7 @@ PICKS = {
|
||||
"skyline_night": "skyline_night-v4",
|
||||
"brick_facade": "brick_facade",
|
||||
"stone_steps": "stone_steps-v2",
|
||||
"limestone_ashlar": "limestone_ashlar",
|
||||
"sidewalk_pavers": "sidewalk_pavers",
|
||||
"asphalt": "asphalt",
|
||||
"asphalt_wet": "asphalt_wet",
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
"""Tests for the reference-anchored texture path (no network)."""
|
||||
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
from PIL import Image
|
||||
|
||||
import generate_textures as gt
|
||||
|
||||
|
||||
class ReferenceGateTests(unittest.TestCase):
|
||||
def test_reference_without_licence_note_is_refused(self):
|
||||
for argv in (["--ai", "--prompt", "x", "--name", "a.png",
|
||||
"--reference", "r.jpg"],
|
||||
["--ai", "--prompt", "x", "--name", "a.png",
|
||||
"--reference-licence", "CC BY 2.0"]):
|
||||
with mock.patch.object(gt, "generate_ai_textures") as gen:
|
||||
with self.assertRaises(SystemExit):
|
||||
gt._run_ai(gt._build_arg_parser().parse_args(argv))
|
||||
gen.assert_not_called()
|
||||
|
||||
def test_reference_pair_calls_with_the_path(self):
|
||||
argv = ["--ai", "--prompt", "x", "--name", "a.png",
|
||||
"--reference", "r.jpg", "--reference-licence", "CC BY 2.0"]
|
||||
with mock.patch.object(gt, "generate_ai_textures") as gen:
|
||||
gt._run_ai(gt._build_arg_parser().parse_args(argv))
|
||||
gen.assert_called_once_with("x", "a.png", gt.DEFAULT_IMAGE_MODEL,
|
||||
gt.GEN_SIZE, reference_path="r.jpg")
|
||||
|
||||
|
||||
class ReferenceCropTests(unittest.TestCase):
|
||||
def test_reference_results_are_cropped_square_before_save(self):
|
||||
wide = Image.new("RGB", (1344, 768), (10, 20, 30))
|
||||
with mock.patch.object(gt, "generate_image", return_value=wide) as gen, \
|
||||
mock.patch.object(gt, "save") as saved:
|
||||
gt.generate_ai_textures("p", "t.png", "m", 1024,
|
||||
reference_path="r.jpg")
|
||||
gen.assert_called_once()
|
||||
img = saved.call_args[0][0]
|
||||
self.assertEqual(img.size, (768, 768))
|
||||
|
||||
def test_plain_results_keep_their_size(self):
|
||||
wide = Image.new("RGB", (1344, 768), (10, 20, 30))
|
||||
with mock.patch.object(gt, "generate_image", return_value=wide), \
|
||||
mock.patch.object(gt, "save") as saved:
|
||||
gt.generate_ai_textures("p", "t.png", "m", 1024)
|
||||
img = saved.call_args[0][0]
|
||||
self.assertEqual(img.size, (1024, 585)) # _fit preserves aspect ratio
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user