package imageext import ( "bytes" "image" "image/color" "image/jpeg" "image/png" "strings" "testing" "git.blackforestbytes.com/BlackForestBytes/goext/tst" ) // makeRGBA creates a solid-color RGBA image of the given size. func makeRGBA(w, h int, c color.Color) *image.RGBA { img := image.NewRGBA(image.Rect(0, 0, w, h)) for y := 0; y < h; y++ { for x := 0; x < w; x++ { img.Set(x, y, c) } } return img } // makeGradient creates an RGBA image with a gradient pattern. // Useful for codecs that may behave oddly with uniform colors. func makeGradient(w, h int) *image.RGBA { img := image.NewRGBA(image.Rect(0, 0, w, h)) for y := 0; y < h; y++ { for x := 0; x < w; x++ { img.Set(x, y, color.RGBA{ R: uint8((x * 255) / max1(w-1)), G: uint8((y * 255) / max1(h-1)), B: uint8(((x + y) * 255) / max1(w+h-2)), A: 255, }) } } return img } func max1(v int) int { if v <= 0 { return 1 } return v } func TestCropImage_HalfRegion(t *testing.T) { src := makeGradient(100, 80) out, err := CropImage(src, 0.0, 0.0, 0.5, 0.5) tst.AssertNoErr(t, err) tst.AssertEqual(t, out.Bounds().Dx(), 50) tst.AssertEqual(t, out.Bounds().Dy(), 40) } func TestCropImage_Offset(t *testing.T) { src := makeGradient(200, 100) out, err := CropImage(src, 0.25, 0.5, 0.5, 0.5) tst.AssertNoErr(t, err) // SubImage preserves coordinates of the parent image. tst.AssertEqual(t, out.Bounds().Min.X, 50) tst.AssertEqual(t, out.Bounds().Min.Y, 50) tst.AssertEqual(t, out.Bounds().Dx(), 100) tst.AssertEqual(t, out.Bounds().Dy(), 50) } func TestCropImage_Full(t *testing.T) { src := makeGradient(40, 40) out, err := CropImage(src, 0.0, 0.0, 1.0, 1.0) tst.AssertNoErr(t, err) tst.AssertEqual(t, out.Bounds().Dx(), 40) tst.AssertEqual(t, out.Bounds().Dy(), 40) } func TestEncodeImage_AllCompressions(t *testing.T) { src := makeGradient(20, 16) cases := []struct { comp ImageCompresson mime string signature []byte }{ {CompressionPNGNone, "image/png", []byte{0x89, 'P', 'N', 'G'}}, {CompressionPNGSpeed, "image/png", []byte{0x89, 'P', 'N', 'G'}}, {CompressionPNGBest, "image/png", []byte{0x89, 'P', 'N', 'G'}}, {CompressionJPEG100, "image/jpeg", []byte{0xFF, 0xD8, 0xFF}}, {CompressionJPEG90, "image/jpeg", []byte{0xFF, 0xD8, 0xFF}}, {CompressionJPEG80, "image/jpeg", []byte{0xFF, 0xD8, 0xFF}}, {CompressionJPEG70, "image/jpeg", []byte{0xFF, 0xD8, 0xFF}}, {CompressionJPEG60, "image/jpeg", []byte{0xFF, 0xD8, 0xFF}}, {CompressionJPEG50, "image/jpeg", []byte{0xFF, 0xD8, 0xFF}}, {CompressionJPEG25, "image/jpeg", []byte{0xFF, 0xD8, 0xFF}}, {CompressionJPEG10, "image/jpeg", []byte{0xFF, 0xD8, 0xFF}}, {CompressionJPEG1, "image/jpeg", []byte{0xFF, 0xD8, 0xFF}}, } for _, c := range cases { t.Run(string(c.comp), func(t *testing.T) { buf, mime, err := EncodeImage(src, c.comp) tst.AssertNoErr(t, err) tst.AssertEqual(t, mime, c.mime) tst.AssertTrue(t, buf.Len() > 0) data := buf.Bytes() tst.AssertTrue(t, len(data) >= len(c.signature)) tst.AssertTrue(t, bytes.Equal(data[:len(c.signature)], c.signature)) // Round-trip decode to confirm the bytes form a valid image. var dec image.Image var derr error if c.mime == "image/png" { dec, derr = png.Decode(bytes.NewReader(data)) } else { dec, derr = jpeg.Decode(bytes.NewReader(data)) } tst.AssertNoErr(t, derr) tst.AssertEqual(t, dec.Bounds().Dx(), 20) tst.AssertEqual(t, dec.Bounds().Dy(), 16) }) } } func TestEncodeImage_UnknownCompression(t *testing.T) { src := makeRGBA(4, 4, color.White) buf, mime, err := EncodeImage(src, ImageCompresson("UNKNOWN")) tst.AssertTrue(t, err != nil) tst.AssertEqual(t, mime, "") tst.AssertEqual(t, buf.Len(), 0) } func TestObjectFitImage_Cover_SmallerThanBB(t *testing.T) { // Image (100x100) is smaller than the BB (200x100), so the output is // scaled down to the smaller-axis factor of 0.5: 100x50. src := makeGradient(100, 100) out, rect, err := ObjectFitImage(src, 200, 100, ImageFitCover, color.Transparent) tst.AssertNoErr(t, err) tst.AssertEqual(t, out.Bounds().Dx(), 100) tst.AssertEqual(t, out.Bounds().Dy(), 50) tst.AssertDeepEqual(t, rect, PercentageRectangle{0, 0, 1, 1}) } func TestObjectFitImage_Cover_LargerThanBB(t *testing.T) { // Image (400x200) is larger than the BB (200x100); fac is capped at 1, so // the output is exactly the BB size. src := makeGradient(400, 200) out, _, err := ObjectFitImage(src, 200, 100, ImageFitCover, color.Transparent) tst.AssertNoErr(t, err) tst.AssertEqual(t, out.Bounds().Dx(), 200) tst.AssertEqual(t, out.Bounds().Dy(), 100) } func TestObjectFitImage_ContainCenter(t *testing.T) { // Image 100x100 in a BB of 200x100 -> output 200x100, drawn rect 100x100 centered. src := makeGradient(100, 100) out, rect, err := ObjectFitImage(src, 200, 100, ImageFitContainCenter, color.Black) tst.AssertNoErr(t, err) tst.AssertEqual(t, out.Bounds().Dx(), 200) tst.AssertEqual(t, out.Bounds().Dy(), 100) // (200-100)/2 = 50 -> X=50/200 = 0.25, W=100/200=0.5, Y=0, H=1 tst.AssertDeepEqual(t, rect, PercentageRectangle{X: 0.25, Y: 0, W: 0.5, H: 1}) } func TestObjectFitImage_ContainTopLeft(t *testing.T) { src := makeGradient(100, 100) out, rect, err := ObjectFitImage(src, 200, 100, ImageFitContainTopLeft, color.Black) tst.AssertNoErr(t, err) tst.AssertEqual(t, out.Bounds().Dx(), 200) tst.AssertEqual(t, out.Bounds().Dy(), 100) tst.AssertDeepEqual(t, rect, PercentageRectangle{X: 0, Y: 0, W: 0.5, H: 1}) } func TestObjectFitImage_ContainTopRight(t *testing.T) { src := makeGradient(100, 100) out, rect, err := ObjectFitImage(src, 200, 100, ImageFitContainTopRight, color.Black) tst.AssertNoErr(t, err) tst.AssertEqual(t, out.Bounds().Dx(), 200) tst.AssertEqual(t, out.Bounds().Dy(), 100) tst.AssertDeepEqual(t, rect, PercentageRectangle{X: 0.5, Y: 0, W: 0.5, H: 1}) } func TestObjectFitImage_ContainBottomLeft(t *testing.T) { // Image 200x100 in a BB of 100x100 (image is bigger so facOut is capped at 1) src := makeGradient(200, 100) out, rect, err := ObjectFitImage(src, 100, 100, ImageFitContainBottomLeft, color.Black) tst.AssertNoErr(t, err) tst.AssertEqual(t, out.Bounds().Dx(), 100) tst.AssertEqual(t, out.Bounds().Dy(), 100) // dw=100, dh=50 -> bottom-left rect: (0, 50, 100, 100) -> Y=0.5, H=0.5 tst.AssertDeepEqual(t, rect, PercentageRectangle{X: 0, Y: 0.5, W: 1, H: 0.5}) } func TestObjectFitImage_ContainBottomRight(t *testing.T) { src := makeGradient(200, 100) out, rect, err := ObjectFitImage(src, 100, 100, ImageFitContainBottomRight, color.Black) tst.AssertNoErr(t, err) tst.AssertEqual(t, out.Bounds().Dx(), 100) tst.AssertEqual(t, out.Bounds().Dy(), 100) tst.AssertDeepEqual(t, rect, PercentageRectangle{X: 0, Y: 0.5, W: 1, H: 0.5}) } func TestObjectFitImage_Stretch(t *testing.T) { // Image 100x100 in BB 200x100 -> uses max(facW=0.5, facH=1.0) capped at 1, so result is 200x100. src := makeGradient(100, 100) out, rect, err := ObjectFitImage(src, 200, 100, ImageFitStretch, color.Black) tst.AssertNoErr(t, err) tst.AssertEqual(t, out.Bounds().Dx(), 200) tst.AssertEqual(t, out.Bounds().Dy(), 100) tst.AssertDeepEqual(t, rect, PercentageRectangle{0, 0, 1, 1}) } func TestObjectFitImage_Stretch_SmallImage(t *testing.T) { // Image 50x25 in BB 200x100 -> max(0.25, 0.25) = 0.25, output 50x25. src := makeGradient(50, 25) out, _, err := ObjectFitImage(src, 200, 100, ImageFitStretch, color.Black) tst.AssertNoErr(t, err) tst.AssertEqual(t, out.Bounds().Dx(), 50) tst.AssertEqual(t, out.Bounds().Dy(), 25) } func TestObjectFitImage_UnknownFit(t *testing.T) { src := makeGradient(20, 20) out, _, err := ObjectFitImage(src, 100, 100, ImageFit("BOGUS"), color.Black) tst.AssertTrue(t, err != nil) if out != nil { t.Errorf("expected nil image on error, got %v", out) } } func TestVerifyAndDecodeImage_PNG(t *testing.T) { src := makeGradient(12, 8) buf := bytes.Buffer{} tst.AssertNoErr(t, png.Encode(&buf, src)) out, err := VerifyAndDecodeImage(&buf, "image/png") tst.AssertNoErr(t, err) tst.AssertEqual(t, out.Bounds().Dx(), 12) tst.AssertEqual(t, out.Bounds().Dy(), 8) } func TestVerifyAndDecodeImage_JPEG(t *testing.T) { src := makeGradient(16, 16) buf := bytes.Buffer{} tst.AssertNoErr(t, jpeg.Encode(&buf, src, &jpeg.Options{Quality: 90})) out, err := VerifyAndDecodeImage(&buf, "image/jpeg") tst.AssertNoErr(t, err) tst.AssertEqual(t, out.Bounds().Dx(), 16) tst.AssertEqual(t, out.Bounds().Dy(), 16) } func TestVerifyAndDecodeImage_UnknownMime(t *testing.T) { out, err := VerifyAndDecodeImage(strings.NewReader("whatever"), "image/gif") tst.AssertTrue(t, err != nil) if out != nil { t.Errorf("expected nil image on error, got %v", out) } } func TestVerifyAndDecodeImage_BadPNG(t *testing.T) { out, err := VerifyAndDecodeImage(strings.NewReader("not a png"), "image/png") tst.AssertTrue(t, err != nil) if out != nil { t.Errorf("expected nil image on error, got %v", out) } } func TestVerifyAndDecodeImage_BadJPEG(t *testing.T) { out, err := VerifyAndDecodeImage(strings.NewReader("not a jpeg"), "image/jpeg") tst.AssertTrue(t, err != nil) if out != nil { t.Errorf("expected nil image on error, got %v", out) } }