Test that processed images carry no metadata (closes #82)
check / check (push) Failing after 3m2s
check / check (push) Failing after 3m2s
Failing tests first: GPS EXIF must not survive into any output format, an image with EXIF orientation 6 must come out upright (also when only a width is asked for), and a Display P3 image must come out as sRGB with no ICC profile. Three JPEG fixtures of about 1 KB each are added under internal/imageprocessor/testdata/. Model: opus-5-5
This commit is contained in:
@@ -9,7 +9,9 @@ import (
|
||||
"image/jpeg"
|
||||
"image/png"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/davidbyttow/govips/v2/vips"
|
||||
@@ -561,3 +563,152 @@ func TestImageProcessor_EncodeAVIF(t *testing.T) {
|
||||
|
||||
encodeAndCheck(t, FormatAVIF, 85, mimeAVIF)
|
||||
}
|
||||
|
||||
// processAndDecode runs input through Process and decodes the output with
|
||||
// vips, so a test can inspect the image a client would receive.
|
||||
func processAndDecode(t *testing.T, input []byte, req *Request) *vips.ImageRef {
|
||||
t.Helper()
|
||||
|
||||
result, err := New(Params{}).Process(
|
||||
context.Background(), bytes.NewReader(input), req,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Process() error = %v", err)
|
||||
}
|
||||
|
||||
defer func() { _ = result.Content.Close() }()
|
||||
|
||||
data, err := io.ReadAll(result.Content)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read result: %v", err)
|
||||
}
|
||||
|
||||
output, err := vips.NewImageFromBuffer(data)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to decode output: %v", err)
|
||||
}
|
||||
|
||||
t.Cleanup(output.Close)
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
func TestImageProcessor_StripsEXIF(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// gps-exif.jpg carries GPS coordinates, a camera make, model and serial
|
||||
// number, and a capture time.
|
||||
input, err := os.ReadFile("testdata/gps-exif.jpg")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read test JPEG: %v", err)
|
||||
}
|
||||
|
||||
fixture, err := vips.NewImageFromBuffer(input)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to decode test JPEG: %v", err)
|
||||
}
|
||||
|
||||
t.Cleanup(fixture.Close)
|
||||
|
||||
if !slices.Contains(fixture.GetFields(), "exif-ifd3-GPSLatitude") {
|
||||
t.Fatal("testdata/gps-exif.jpg has no GPS latitude")
|
||||
}
|
||||
|
||||
formats := []Format{
|
||||
FormatJPEG, FormatPNG, FormatWebP, FormatAVIF, FormatGIF, FormatOriginal,
|
||||
}
|
||||
|
||||
for _, format := range formats {
|
||||
t.Run(string(format), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
output := processAndDecode(t, input, &Request{Format: format})
|
||||
|
||||
if output.HasExif() {
|
||||
t.Errorf("output has EXIF: %v", output.GetExif())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestImageProcessor_AppliesEXIFOrientation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// orientation-6.jpg is stored 16x8, red on the left and blue on the
|
||||
// right, with EXIF orientation 6 (turn 90 degrees clockwise to view).
|
||||
// Upright it is 8x16, red on top and blue below.
|
||||
input, err := os.ReadFile("testdata/orientation-6.jpg")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read test JPEG: %v", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
size Size
|
||||
wantW int
|
||||
wantH int
|
||||
}{
|
||||
{name: "original size", size: Size{}, wantW: 8, wantH: 16},
|
||||
{name: "width only", size: Size{Width: 4}, wantW: 4, wantH: 8},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
output := processAndDecode(t, input, &Request{
|
||||
Size: tt.size,
|
||||
Format: FormatPNG,
|
||||
})
|
||||
|
||||
if output.Width() != tt.wantW || output.Height() != tt.wantH {
|
||||
t.Fatalf("output is %dx%d, want %dx%d",
|
||||
output.Width(), output.Height(), tt.wantW, tt.wantH)
|
||||
}
|
||||
|
||||
top, err := output.GetPoint(tt.wantW/2, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("GetPoint() error = %v", err)
|
||||
}
|
||||
|
||||
bottom, err := output.GetPoint(tt.wantW/2, tt.wantH-1)
|
||||
if err != nil {
|
||||
t.Fatalf("GetPoint() error = %v", err)
|
||||
}
|
||||
|
||||
if top[0] <= top[2] || bottom[2] <= bottom[0] {
|
||||
t.Errorf("top pixel = %v, bottom pixel = %v, want red above blue",
|
||||
top, bottom)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestImageProcessor_ConvertsWideGamutToSRGB(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// display-p3.jpg is a flat 8x8 image with the Display P3 profile
|
||||
// embedded, filled with Display P3 (234, 51, 35), which is sRGB red.
|
||||
input, err := os.ReadFile("testdata/display-p3.jpg")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read test JPEG: %v", err)
|
||||
}
|
||||
|
||||
output := processAndDecode(t, input, &Request{Format: FormatPNG})
|
||||
|
||||
if output.HasICCProfile() {
|
||||
t.Error("output has an ICC profile")
|
||||
}
|
||||
|
||||
pixel, err := output.GetPoint(4, 4)
|
||||
if err != nil {
|
||||
t.Fatalf("GetPoint() error = %v", err)
|
||||
}
|
||||
|
||||
want := []float64{255, 0, 0}
|
||||
for i := range want {
|
||||
if math.Abs(pixel[i]-want[i]) > 5 {
|
||||
t.Fatalf("pixel = %v, want within 5 of %v", pixel, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user