feat(image): preserve transparency for alpha images end to end
Skip WebP re-encoding for PNG/AVIF/BMP uploads and any bitmap that actually contains alpha, tag transparency-capable canvas uploads with the transparent-image tone, and detect alpha in merged layers so the composed node keeps a transparent tone. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -129,6 +129,71 @@ func TestMergeLayersFlattensTextOverImage(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeLayersPreservesTransparentPixels(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
repo := memory.NewProjectRepository()
|
||||
service := NewDesignService(repo, nil, nil, nil, nil)
|
||||
transparentLayer := image.NewNRGBA(image.Rect(0, 0, 3, 2))
|
||||
transparentLayer.SetNRGBA(1, 0, color.NRGBA{R: 255, A: 255})
|
||||
project := design.Project{
|
||||
ID: "project-merge-transparent",
|
||||
Title: "Merge transparent",
|
||||
Status: design.StatusReady,
|
||||
UpdatedAt: time.Now(),
|
||||
Nodes: []design.Node{
|
||||
{
|
||||
ID: "transparent",
|
||||
Type: design.NodeTypeImage,
|
||||
Title: "Transparent",
|
||||
X: 0,
|
||||
Y: 0,
|
||||
Width: 3,
|
||||
Height: 2,
|
||||
Content: testPNGDataURLFromImage(transparentLayer),
|
||||
Status: "success",
|
||||
},
|
||||
{
|
||||
ID: "blue",
|
||||
Type: design.NodeTypeImage,
|
||||
Title: "Blue",
|
||||
X: 2,
|
||||
Y: 1,
|
||||
Width: 1,
|
||||
Height: 1,
|
||||
Content: testPNGDataURL(color.NRGBA{B: 255, A: 255}, 1, 1),
|
||||
Status: "success",
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := repo.Save(ctx, project); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := service.MergeLayers(ctx, project.ID, []string{"transparent", "blue"}, "Transparent merge"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
merged := waitForMergedProject(t, repo, project.ID)
|
||||
node := merged.Nodes[0]
|
||||
if node.Tone != "transparent-image" {
|
||||
t.Fatalf("expected transparent merged image tone, got %q", node.Tone)
|
||||
}
|
||||
imageData, _, _, ok, err := decodeDataImage(node.Content)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatalf("expected merged content data URL, got %s", node.Content)
|
||||
}
|
||||
decoded, err := png.Decode(bytes.NewReader(imageData))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertPixel(t, decoded, 0, 0, color.NRGBA{})
|
||||
assertPixel(t, decoded, 1, 0, color.NRGBA{R: 255, A: 255})
|
||||
assertPixel(t, decoded, 2, 1, color.NRGBA{B: 255, A: 255})
|
||||
}
|
||||
|
||||
func waitForMergedProject(t *testing.T, repo design.Repository, projectID string) design.Project {
|
||||
t.Helper()
|
||||
deadline := time.After(2 * time.Second)
|
||||
@@ -160,6 +225,10 @@ func testPNGDataURL(fill color.NRGBA, width int, height int) string {
|
||||
img.SetNRGBA(x, y, fill)
|
||||
}
|
||||
}
|
||||
return testPNGDataURLFromImage(img)
|
||||
}
|
||||
|
||||
func testPNGDataURLFromImage(img image.Image) string {
|
||||
var buf bytes.Buffer
|
||||
if err := png.Encode(&buf, img); err != nil {
|
||||
panic(err)
|
||||
|
||||
Reference in New Issue
Block a user