from io import BytesIO import pytest from PIL import Image from pigway_frame_flow.models import DisplayGeometry from pigway_frame_flow.providers.photo_names import folder_caption from pigway_frame_flow.providers.photo_sources import PhotoSourceInput from pigway_frame_flow.renderers.frame_geometry import frame_canvas from pigway_frame_flow.renderers.photo_caption import render_captioned_photo @pytest.mark.parametrize("rotation", [0, 90, 180, 270]) @pytest.mark.parametrize("mode", [None, "auto", "portrait", "landscape"]) def test_canvas_respects_rotation_and_explicit_direction(rotation, mode): display = DisplayGeometry( width=1200, height=1600, rotation=rotation, color_mode="color" ) expected = ( (1600, 1200) if mode == "landscape" or (mode in (None, "auto") and rotation in (90, 270)) else (1200, 1600) ) assert frame_canvas(display, mode) == expected @pytest.mark.parametrize("profile", ["color", "grayscale"]) def test_landscape_caption_uses_compact_watermark(service, profile): out = BytesIO() Image.new("RGB", (1600, 900), "blue").save(out, "JPEG") data = render_captioned_photo( out.getvalue(), folder_caption("20261005 家庭出游@示例地点"), service.renderer.font, profile=profile, size=(1600, 1200), ) with Image.open(BytesIO(data)) as image: assert image.size == (1600, 1200) assert image.crop((30, 1128, 1570, 1200)).convert("L").getextrema()[0] < 50 assert image.getpixel((20, 1100))[0] < 230 assert image.getpixel((20, 1190))[0] > 240 pixel = image.getpixel((800, 480)) assert ( len(set(pixel)) <= 2 if profile == "grayscale" else pixel[2] > pixel[0] + 100 ) @pytest.mark.parametrize( "mode,expected", [("auto", (1600, 1200)), ("landscape", (1600, 1200)), ("portrait", (1200, 1600))], ) def test_select_and_render_use_same_direction( client, service, settings, mode, expected ): root = settings.data_dir.parent / "photos" settings.photo_root = root folder = root / "20261005 出游@金山" folder.mkdir(parents=True) for name, size in [("wide.jpg", (300, 200)), ("tall.jpg", (200, 300))]: Image.new("RGB", size, "blue").save(folder / name) service.photo_sources.save( "family", PhotoSourceInput( name="Family", kind="local", location=".", folder_caption=True, orientation=mode, ), ) rows = client.get("/api/v1/photo-sources/family/images").json()["items"] assert len(rows) == 1 and rows[0]["name"] == ( "wide.jpg" if expected[0] > expected[1] else "tall.jpg" ) before = service.registry.get("living-room").get_current_image() accepted = client.post( "/api/v1/devices/living-room/upload", json={"type": "photo_source", "source_id": "family"}, ) assert accepted.status_code == 202 and service.process_one() op = client.get("/api/v1/operations/" + accepted.json()["operation_id"]).json() assert op["status"] == "COMPLETED" with Image.open(BytesIO(client.get(op["result"]["url"]).content)) as image: assert image.size == expected assert service.registry.get("living-room").get_current_image() == before def test_auto_catalog_cache_is_not_shared_between_opposite_canvases(service, settings): root = settings.data_dir.parent / "photos" settings.photo_root = root root.mkdir() for name, size in [("wide.jpg", (300, 200)), ("tall.jpg", (200, 300))]: Image.new("RGB", size).save(root / name) p = service.photo_sources p.save( "family", PhotoSourceInput(name="Family", kind="local", location=".", orientation="auto"), ) assert p.catalog("family", target_size=(1600, 1200))[0]["name"] == "wide.jpg" assert p.catalog("family", target_size=(1200, 1600))[0]["name"] == "tall.jpg" def test_auto_preview_uses_requested_device_not_default( client, service, settings, monkeypatch ): import copy driver = service.registry.get("living-room") other = copy.copy(driver) model = driver.device().model_copy( update={ "id": "bedroom", "display": driver.device().display.model_copy(update={"rotation": 0}), } ) monkeypatch.setattr(other, "device", lambda: model) monkeypatch.setitem(service.registry.drivers, "bedroom", other) root = settings.data_dir.parent / "photos" root.mkdir() settings.photo_root = root for name, size in [("wide.jpg", (300, 200)), ("tall.jpg", (200, 300))]: Image.new("RGB", size).save(root / name) service.photo_sources.save( "family", PhotoSourceInput(name="Family", kind="local", location=".", orientation="auto"), ) r = client.get("/api/v1/photo-sources/family/images?device_id=bedroom") assert r.status_code == 200 and r.json()["items"][0]["name"] == "tall.jpg" accepted = client.post( "/api/v1/devices/bedroom/upload", json={"type": "photo_source", "source_id": "family"}, ) assert accepted.status_code == 202 and service.process_one() op = client.get("/api/v1/operations/" + accepted.json()["operation_id"]).json() assert op["status"] == "COMPLETED" with Image.open(BytesIO(client.get(op["result"]["url"]).content)) as image: assert image.size == (1200, 1600)