From 2ee944909dc8ca63f65734fc5166493ed2d99780 Mon Sep 17 00:00:00 2001 From: sneak Date: Thu, 15 Oct 2026 21:31:59 +0000 Subject: [PATCH] Test that fit=cover serves a thin source and keeps its centre (part of #282) An 8192x64 or 8192x1 source, or one turned on its side, requested at 8192x8192 with fit=cover must be served at that size, and fit=cover must keep the centre of an ordinary source. Model: opus-5-5 --- .../imageprocessor/fit_cover_internal_test.go | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 internal/imageprocessor/fit_cover_internal_test.go diff --git a/internal/imageprocessor/fit_cover_internal_test.go b/internal/imageprocessor/fit_cover_internal_test.go new file mode 100644 index 0000000..50bdc30 --- /dev/null +++ b/internal/imageprocessor/fit_cover_internal_test.go @@ -0,0 +1,148 @@ +package imageprocessor + +import ( + "bytes" + "image" + "image/color" + "image/png" + "testing" +) + +// TestImageProcessor_FitCoverServesThinSource verifies that FitCover serves +// a source of 8192x64 or 8192x1, or one of those turned on its side, at +// 8192x8192. Scaling such a source to cover that size makes it 128 or 8192 +// times as long as the output on its long side. +func TestImageProcessor_FitCoverServesThinSource(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + inputWidth, inputHeight int + }{ + {"8192x64", 8192, 64}, + {"8192x1", 8192, 1}, + {"64x8192", 64, 8192}, + {"1x8192", 1, 8192}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + input := createTestJPEG(t, tt.inputWidth, tt.inputHeight) + + result, err := New(Params{}).Process(t.Context(), + bytes.NewReader(input), + &Request{ + Size: Size{ + Width: MaxOutputDimension, + Height: MaxOutputDimension, + }, + Format: FormatJPEG, + FitMode: FitCover, + }) + if err != nil { + t.Fatalf("Process() error = %v", err) + } + + defer func() { _ = result.Content.Close() }() + + t.Logf("Process() made %dx%d", result.Width, result.Height) + + if result.Width != MaxOutputDimension || + result.Height != MaxOutputDimension { + t.Errorf("Process() made %dx%d, want %dx%d", + result.Width, result.Height, + MaxOutputDimension, MaxOutputDimension) + } + }) + } +} + +// createBandsPNG creates a PNG of three equal bands, red, green and blue, +// side by side when it is wider than tall and top to bottom otherwise. +func createBandsPNG(t *testing.T, width, height int) []byte { + t.Helper() + + bands := []color.RGBA{ + {R: 255, A: 255}, + {G: 255, A: 255}, + {B: 255, A: 255}, + } + + img := image.NewRGBA(image.Rect(0, 0, width, height)) + for y := range height { + for x := range width { + band := 3 * y / height + if width > height { + band = 3 * x / width + } + + img.Set(x, y, bands[band]) + } + } + + return encodePNG(t, img) +} + +// TestImageProcessor_FitCoverCropsTheCentre verifies that FitCover keeps the +// centre of the source: a 300x100 source of red, green and blue bands side +// by side, or a 100x300 one with them top to bottom, comes out at 50x50 as +// its green band. +func TestImageProcessor_FitCoverCropsTheCentre(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + inputWidth, inputHeight int + }{ + {"bands side by side", 300, 100}, + {"bands top to bottom", 100, 300}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + input := createBandsPNG(t, tt.inputWidth, tt.inputHeight) + + result, err := New(Params{}).Process(t.Context(), + bytes.NewReader(input), + &Request{ + Size: Size{Width: 50, Height: 50}, + Format: FormatPNG, + FitMode: FitCover, + }) + if err != nil { + t.Fatalf("Process() error = %v", err) + } + + defer func() { _ = result.Content.Close() }() + + output, err := png.Decode(result.Content) + if err != nil { + t.Fatalf("failed to decode output: %v", err) + } + + bounds := output.Bounds() + t.Logf("Process() made %dx%d", bounds.Dx(), bounds.Dy()) + + if bounds.Dx() != 50 || bounds.Dy() != 50 { + t.Fatalf("Process() made %dx%d, want 50x50", + bounds.Dx(), bounds.Dy()) + } + + // Resampling may blend a little of the bands beside the + // green one into the output's edges. + for y := bounds.Min.Y; y < bounds.Max.Y; y++ { + for x := bounds.Min.X; x < bounds.Max.X; x++ { + r, g, b, _ := output.At(x, y).RGBA() + if r>>8 > 64 || g>>8 < 192 || b>>8 > 64 { + t.Fatalf("output pixel %d,%d is %d,%d,%d, want green", + x, y, r>>8, g>>8, b>>8) + } + } + } + }) + } +}