From eda9a0df23a1c0fd2bd6c102b569049301635e47 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Tue, 24 Feb 2026 15:46:26 -0800 Subject: [PATCH] fix: luminance-masked screen blend + cache bypass param - Screen blend now uses brightness mask from the design so only non-dark pixels show through. Prevents visible dark rectangle when design has its own dark background (e.g. DefectFi tee). - Add ?fresh=1 query param to /mockup endpoint for cache bypass. Co-Authored-By: Claude Opus 4.6 --- backend/app/api/designs.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/backend/app/api/designs.py b/backend/app/api/designs.py index 0946c5b..d908234 100644 --- a/backend/app/api/designs.py +++ b/backend/app/api/designs.py @@ -86,7 +86,7 @@ async def get_design_image(slug: str): @router.get("/{slug}/mockup") -async def get_design_mockup(slug: str, type: str = "shirt"): +async def get_design_mockup(slug: str, type: str = "shirt", fresh: bool = False): """Serve the design composited onto a product mockup template. For Printful-provider designs: fetches photorealistic mockup from @@ -95,9 +95,10 @@ async def get_design_mockup(slug: str, type: str = "shirt"): Query params: type: Product type — "shirt", "sticker", or "print" (default: shirt) + fresh: If true, bypass cache and regenerate mockup """ cache_key = (slug, type) - if cache_key in _mockup_cache: + if not fresh and cache_key in _mockup_cache: return StreamingResponse( io.BytesIO(_mockup_cache[cache_key]), media_type="image/png", @@ -225,10 +226,11 @@ async def _pillow_mockup(slug: str, type: str) -> StreamingResponse: blend_mode = template_config.get("blend", "paste") if blend_mode == "screen": - # Screen blend: makes light designs appear printed on dark fabric. - # Formula: 1 - (1-base)(1-overlay). Preserves fabric texture. + # Screen blend for light designs on dark fabric. + # We use a brightness-based mask so only non-dark pixels from + # the design show through, preventing a visible dark rectangle + # when the design has its own dark background. design_rgb = design_resized.convert("RGB") - alpha = design_resized.split()[3] if design_resized.mode == "RGBA" else None # Extract the region under the design region = canvas.crop((dx, dy, dx + dw, dy + dh)) @@ -236,14 +238,15 @@ async def _pillow_mockup(slug: str, type: str) -> StreamingResponse: # Screen blend the design onto the fabric region blended = ImageChops.screen(region, design_rgb) - # If design has alpha, composite using it as mask - if alpha: - # Convert alpha to 3-channel for masking - region_arr = region.copy() - blended_with_alpha = Image.composite(blended, region_arr, alpha) - canvas.paste(blended_with_alpha, (dx, dy)) - else: - canvas.paste(blended, (dx, dy)) + # Create a luminance mask from the design — only bright pixels blend in. + # This prevents the design's dark background from creating a visible box. + lum = design_rgb.convert("L") + # Boost contrast so only clearly visible parts of the design show + lum = lum.point(lambda p: min(255, int(p * 1.5))) + + # Composite: use luminance as mask (bright pixels = show blended, dark = keep original) + result = Image.composite(blended, region, lum) + canvas.paste(result, (dx, dy)) else: # Direct paste — for stickers/prints where design goes on a light surface if design_resized.mode == "RGBA":