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/jpeg"
|
||||||
"image/png"
|
"image/png"
|
||||||
"io"
|
"io"
|
||||||
|
"math"
|
||||||
"os"
|
"os"
|
||||||
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/davidbyttow/govips/v2/vips"
|
"github.com/davidbyttow/govips/v2/vips"
|
||||||
@@ -561,3 +563,152 @@ func TestImageProcessor_EncodeAVIF(t *testing.T) {
|
|||||||
|
|
||||||
encodeAndCheck(t, FormatAVIF, 85, mimeAVIF)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 811 B |
Reference in New Issue
Block a user