package imageprocessor import ( "bytes" "image" "image/color" "image/png" "testing" "github.com/davidbyttow/govips/v2/vips" ) // processedSize runs input through Process and returns the output's size in // bytes. func processedSize(t *testing.T, input []byte, req *Request) int64 { t.Helper() result, err := New(Params{}).Process(t.Context(), bytes.NewReader(input), req) if err != nil { t.Fatalf("Process() error = %v", err) } _ = result.Content.Close() return result.ContentLength } // encodePNG encodes img as PNG with Go's encoder. func encodePNG(t *testing.T, img image.Image) []byte { t.Helper() var buf bytes.Buffer err := png.Encode(&buf, img) if err != nil { t.Fatalf("failed to encode test PNG: %v", err) } return buf.Bytes() } // decode decodes input with vips, as Process does. func decode(t *testing.T, input []byte) *vips.ImageRef { t.Helper() img, err := vips.NewImageFromBuffer(input) if err != nil { t.Fatalf("failed to decode input: %v", err) } t.Cleanup(img.Close) return img } // TestImageProcessor_PNGIsCompressed verifies that a PNG output is // compressed: a flat 200x150 image, 90,000 bytes of raw pixels, comes out at // a small fraction of that. func TestImageProcessor_PNGIsCompressed(t *testing.T) { t.Parallel() const width, height = 200, 150 flat := image.NewRGBA(image.Rect(0, 0, width, height)) for y := range height { for x := range width { flat.Set(x, y, color.RGBA{R: 40, G: 120, B: 200, A: 255}) } } size := processedSize(t, encodePNG(t, flat), &Request{Format: FormatPNG}) const rawBytes = width * height * 3 if size > rawBytes/10 { t.Errorf("PNG output is %d bytes, want under a tenth of its %d raw", size, rawBytes) } } // TestImageProcessor_WebPAtDefaultEffort verifies that WebP is saved at // libvips' default effort, 4: the output is the size of the same image saved // at that effort. func TestImageProcessor_WebPAtDefaultEffort(t *testing.T) { t.Parallel() input := createTestJPEG(t, 200, 150) size := processedSize(t, input, &Request{Format: FormatWebP, Quality: 85}) want, _, err := decode(t, input).ExportWebp(&vips.WebpExportParams{ StripMetadata: true, Quality: 85, ReductionEffort: 4, }) if err != nil { t.Fatalf("ExportWebp() error = %v", err) } if size != int64(len(want)) { t.Errorf("WebP output is %d bytes, want %d, the size at effort 4", size, len(want)) } } // TestImageProcessor_AVIFAtEffort1 verifies that AVIF is saved at effort 1 // with 8 bits per sample: the output is the size of the same image saved // with those settings. The source is 16-bit, which libvips saves with 12 // bits when it is not given a bit depth. func TestImageProcessor_AVIFAtEffort1(t *testing.T) { t.Parallel() const width, height = 64, 48 source := image.NewRGBA64(image.Rect(0, 0, width, height)) for y := range height { for x := range width { source.Set(x, y, color.RGBA64{ R: uint16((x * 65535 / width) & 0xffff), G: uint16((y * 65535 / height) & 0xffff), B: 32768, A: 65535, }) } } input := encodePNG(t, source) size := processedSize(t, input, &Request{Format: FormatAVIF, Quality: 85}) want, _, err := decode(t, input).ExportAvif(&vips.AvifExportParams{ StripMetadata: true, Quality: 85, Effort: 1, Bitdepth: 8, }) if err != nil { t.Fatalf("ExportAvif() error = %v", err) } if size != int64(len(want)) { t.Errorf("AVIF output is %d bytes, want %d, the size at effort 1 "+ "and 8 bits", size, len(want)) } }