package test import ( domain "RenewCMS/internal/domain/image" "RenewCMS/internal/infrastructure/imaging" "bytes" "image/color" "strconv" "testing" ) func variantSettings(width int, height int, fit domain.VariantFit, format domain.VariantFormat) domain.VariantSettings { settings := domain.VariantSettings{Fit: fit, Format: format} if width > 0 { settings.Width = &width } if height > 0 { settings.Height = &height } return settings } func renderVariant(t *testing.T, source []byte, settings domain.VariantSettings) domain.RenderedVariant { t.Helper() rendered, err := imaging.NewVariantRenderer().Render(bytes.NewReader(source), settings) if err != nil { t.Fatalf("render failed: %v", err) } return rendered } func TestVariantOutputGeometry(t *testing.T) { rows := []struct { sourceWidth, sourceHeight, width, height int fit domain.VariantFit wantWidth, wantHeight int }{ {4032, 3024, 400, 300, domain.FitCover, 400, 300}, {4032, 3024, 800, 0, domain.FitCover, 800, 600}, {4032, 3024, 0, 600, domain.FitContain, 800, 600}, {4032, 3024, 1000, 1000, domain.FitInside, 1000, 750}, {4032, 3024, 1000, 1000, domain.FitCover, 1000, 1000}, {4032, 3024, 1000, 1000, domain.FitContain, 1000, 1000}, {300, 200, 800, 0, domain.FitCover, 300, 200}, {300, 200, 0, 800, domain.FitInside, 300, 200}, {300, 200, 400, 300, domain.FitCover, 267, 200}, {300, 200, 400, 300, domain.FitContain, 400, 300}, {300, 200, 1200, 1200, domain.FitContain, 1200, 1200}, {300, 200, 1200, 1200, domain.FitInside, 300, 200}, } for _, row := range rows { name := strconv.Itoa(row.sourceWidth) + "x" + strconv.Itoa(row.sourceHeight) + " to " + strconv.Itoa(row.width) + "x" + strconv.Itoa(row.height) + " " + string(row.fit) t.Run(name, func(t *testing.T) { source := solidPNG(t, row.sourceWidth, row.sourceHeight) rendered := renderVariant(t, source, variantSettings(row.width, row.height, row.fit, domain.FormatPNG)) width, height := decodedSize(t, rendered.Content) if width != row.wantWidth || height != row.wantHeight { t.Errorf("output = %dx%d, want %dx%d", width, height, row.wantWidth, row.wantHeight) } }) } } func TestVariantFitPixels(t *testing.T) { t.Run("Cover crops around the centre", func(t *testing.T) { rendered := renderVariant(t, encodePNG(t, stripedImage(t)), variantSettings(20, 20, domain.FitCover, domain.FormatPNG)) decoded, _ := decodeImage(t, rendered.Content) if decoded.Bounds().Dx() != 20 || decoded.Bounds().Dy() != 20 { t.Fatalf("output = %v", decoded.Bounds()) } for y := 0; y < 20; y++ { for x := 0; x < 20; x++ { if got := pixelAt(decoded, x, y); got != variantGreen { t.Fatalf("pixel %d,%d = %v, want green", x, y, got) } } } }) t.Run("Contain pads transparent on png", func(t *testing.T) { source := encodePNG(t, variantSolidImage(40, 20, variantRed)) rendered := renderVariant(t, source, variantSettings(40, 40, domain.FitContain, domain.FormatPNG)) decoded, _ := decodeImage(t, rendered.Content) if decoded.Bounds().Dx() != 40 || decoded.Bounds().Dy() != 40 { t.Fatalf("output = %v", decoded.Bounds()) } for x := 0; x < 40; x++ { for _, y := range []int{0, 9, 30, 39} { if got := pixelAt(decoded, x, y); got.A != 0 { t.Fatalf("pixel %d,%d = %v, want transparent", x, y, got) } } for _, y := range []int{10, 19, 29} { if got := pixelAt(decoded, x, y); got != variantRed { t.Fatalf("pixel %d,%d = %v, want red", x, y, got) } } } }) t.Run("Contain never enlarges a small source and centres it on the box", func(t *testing.T) { source := encodePNG(t, variantSolidImage(30, 20, variantRed)) rendered := renderVariant(t, source, variantSettings(40, 30, domain.FitContain, domain.FormatPNG)) decoded, _ := decodeImage(t, rendered.Content) if decoded.Bounds().Dx() != 40 || decoded.Bounds().Dy() != 30 { t.Fatalf("output = %v", decoded.Bounds()) } for _, inside := range [][2]int{{5, 5}, {34, 24}, {20, 15}} { if got := pixelAt(decoded, inside[0], inside[1]); got != variantRed { t.Errorf("pixel %v = %v, want red", inside, got) } } for _, outside := range [][2]int{{4, 15}, {35, 15}, {20, 4}, {20, 25}} { if got := pixelAt(decoded, outside[0], outside[1]); got.A != 0 { t.Errorf("pixel %v = %v, want transparent", outside, got) } } }) t.Run("Contain with a single dimension adds no padding", func(t *testing.T) { source := encodePNG(t, variantSolidImage(40, 20, variantRed)) rendered := renderVariant(t, source, variantSettings(0, 10, domain.FitContain, domain.FormatPNG)) decoded, _ := decodeImage(t, rendered.Content) if decoded.Bounds().Dx() != 20 || decoded.Bounds().Dy() != 10 { t.Fatalf("output = %v", decoded.Bounds()) } for y := 0; y < 10; y++ { if got := pixelAt(decoded, 0, y); got.A == 0 { t.Fatalf("pixel 0,%d is transparent", y) } } }) t.Run("Contain pads transparent on webp", func(t *testing.T) { source := encodePNG(t, variantSolidImage(40, 20, variantRed)) rendered := renderVariant(t, source, variantSettings(40, 40, domain.FitContain, domain.FormatWebP)) decoded, _ := decodeImage(t, rendered.Content) if got := pixelAt(decoded, 20, 2); got.A != 0 { t.Errorf("padded pixel = %v, want transparent", got) } }) t.Run("Contain pads white on jpeg", func(t *testing.T) { source := encodePNG(t, variantSolidImage(40, 20, variantRed)) rendered := renderVariant(t, source, variantSettings(40, 40, domain.FitContain, domain.FormatJPEG)) decoded, _ := decodeImage(t, rendered.Content) for _, y := range []int{0, 5, 9, 30, 35, 39} { assertNearColor(t, pixelAt(decoded, 20, y), color.NRGBA{R: 255, G: 255, B: 255, A: 255}) } }) t.Run("Inside adds no padding", func(t *testing.T) { source := encodePNG(t, variantSolidImage(40, 20, variantRed)) rendered := renderVariant(t, source, variantSettings(40, 40, domain.FitInside, domain.FormatPNG)) decoded, _ := decodeImage(t, rendered.Content) if decoded.Bounds().Dx() != 40 || decoded.Bounds().Dy() != 20 { t.Fatalf("output = %v", decoded.Bounds()) } for y := 0; y < 20; y++ { if got := pixelAt(decoded, 3, y); got.A != 255 { t.Fatalf("pixel 3,%d = %v, want opaque", y, got) } } }) t.Run("A transparent png source flattened to jpeg becomes white", func(t *testing.T) { source := encodePNG(t, variantSolidImage(10, 10, color.NRGBA{})) rendered := renderVariant(t, source, variantSettings(10, 10, domain.FitCover, domain.FormatJPEG)) decoded, _ := decodeImage(t, rendered.Content) for y := 0; y < 10; y++ { for x := 0; x < 10; x++ { assertNearColor(t, pixelAt(decoded, x, y), color.NRGBA{R: 255, G: 255, B: 255, A: 255}) } } }) } func assertNearColor(t *testing.T, got color.NRGBA, want color.NRGBA) { t.Helper() const tolerance = 8 for _, pair := range [][2]uint8{{got.R, want.R}, {got.G, want.G}, {got.B, want.B}} { difference := int(pair[0]) - int(pair[1]) if difference > tolerance || difference < -tolerance { t.Fatalf("color = %v, want near %v", got, want) } } } func TestVariantOutputFormat(t *testing.T) { photo := encodePNG(t, noisyImage(64, 64)) rows := []struct { name string source []byte format domain.VariantFormat contentType string signature string }{ {"png original", photo, domain.FormatOriginal, "image/png", "\x89PNG"}, {"jpeg original", encodeJPEG(t, noisyImage(64, 64)), domain.FormatOriginal, "image/jpeg", "\xff\xd8"}, {"png to jpeg", photo, domain.FormatJPEG, "image/jpeg", "\xff\xd8"}, {"jpeg to png", encodeJPEG(t, noisyImage(64, 64)), domain.FormatPNG, "image/png", "\x89PNG"}, {"png to webp", photo, domain.FormatWebP, "image/webp", "RIFF"}, } for _, row := range rows { t.Run(row.name, func(t *testing.T) { rendered := renderVariant(t, row.source, variantSettings(32, 0, domain.FitCover, row.format)) if rendered.ContentType != row.contentType { t.Errorf("content type = %q, want %q", rendered.ContentType, row.contentType) } if !bytes.HasPrefix(rendered.Content, []byte(row.signature)) { t.Errorf("signature = %q", rendered.Content[:4]) } }) } t.Run("A webp output carries the WEBP marker and the right dimensions", func(t *testing.T) { rendered := renderVariant(t, photo, variantSettings(32, 0, domain.FitCover, domain.FormatWebP)) width, height := webpSize(t, rendered.Content) if string(rendered.Content[8:12]) != "WEBP" || width != 32 || height != 32 { t.Errorf("webp = %q %dx%d", rendered.Content[8:12], width, height) } }) } func TestVariantQuality(t *testing.T) { photo := encodePNG(t, noisyImage(300, 300)) t.Run("Quality changes the size of a lossy output", func(t *testing.T) { for _, format := range []domain.VariantFormat{domain.FormatJPEG, domain.FormatWebP} { low, high := 20, 95 lowSettings := variantSettings(300, 0, domain.FitCover, format) highSettings := lowSettings lowSettings.Quality, highSettings.Quality = &low, &high lowSize := len(renderVariant(t, photo, lowSettings).Content) highSize := len(renderVariant(t, photo, highSettings).Content) if lowSize >= highSize { t.Errorf("%s: quality 20 gave %d bytes, quality 95 gave %d", format, lowSize, highSize) } } }) t.Run("A png output ignores quality", func(t *testing.T) { low, high := 30, 95 settings := variantSettings(100, 0, domain.FitCover, domain.FormatPNG) first, second := settings, settings first.Quality, second.Quality = &low, &high if !bytes.Equal(renderVariant(t, photo, first).Content, renderVariant(t, photo, second).Content) { t.Error("png outputs differ") } }) } func TestVariantSourceHandling(t *testing.T) { t.Run("A webp source still decodes and renders to jpeg", func(t *testing.T) { webpSource := renderVariant(t, solidPNG(t, 40, 40), variantSettings(40, 0, domain.FitCover, domain.FormatWebP)).Content rendered := renderVariant(t, webpSource, variantSettings(20, 0, domain.FitCover, domain.FormatJPEG)) if rendered.ContentType != "image/jpeg" { t.Errorf("content type = %q", rendered.ContentType) } }) t.Run("A webp source with format original stays webp when an encoder exists", func(t *testing.T) { webpSource := renderVariant(t, solidPNG(t, 40, 40), variantSettings(40, 0, domain.FitCover, domain.FormatWebP)).Content rendered := renderVariant(t, webpSource, variantSettings(20, 0, domain.FitCover, domain.FormatOriginal)) if rendered.ContentType != "image/webp" { t.Errorf("content type = %q", rendered.ContentType) } }) t.Run("Without a webp encoder a webp source with format original falls back to png", func(t *testing.T) { webpSource := renderVariant(t, solidPNG(t, 40, 40), variantSettings(40, 0, domain.FitCover, domain.FormatWebP)).Content renderer := imaging.NewVariantRendererEncoding(domain.FormatOriginal, domain.FormatJPEG, domain.FormatPNG) rendered, err := renderer.Render(bytes.NewReader(webpSource), variantSettings(20, 0, domain.FitCover, domain.FormatOriginal)) if err != nil || rendered.ContentType != "image/png" { t.Errorf("rendered = %q, err = %v", rendered.ContentType, err) } }) t.Run("The renderer reports the formats it can encode", func(t *testing.T) { all := imaging.NewVariantRenderer().EncodableFormats() without := imaging.NewVariantRendererEncoding(domain.FormatOriginal, domain.FormatJPEG, domain.FormatPNG).EncodableFormats() if len(all) != 4 || len(without) != 3 { t.Errorf("formats = %v and %v", all, without) } }) t.Run("A one-frame gif renders like any raster source", func(t *testing.T) { rendered := renderVariant(t, stillGIF(t), variantSettings(4, 0, domain.FitCover, domain.FormatPNG)) if rendered.ContentType != "image/png" { t.Errorf("content type = %q", rendered.ContentType) } }) t.Run("Bytes that are not an image fail to render", func(t *testing.T) { _, err := imaging.NewVariantRenderer().Render(bytes.NewReader([]byte("not an image")), variantSettings(4, 0, domain.FitCover, domain.FormatPNG)) if err == nil { t.Error("expected an error") } }) }