check / check (push) Waiting to run
WebP is saved at effort 2 and JPEG XL at effort 4, and an AVIF output of more than 16,777,216 pixels is refused with 400 before anything is decoded, as AVIF is already at the lowest effort govips can set. With one libvips thread, at any quality, 8192x8192 random pixels with an alpha channel take up to about 23 seconds as WebP and 27 as JPEG XL, and 4096x4096, 8192x2048 or 2048x8192 up to about 34 as AVIF. Resizing with fit=cover already decodes the input, so the output's dimensions are now worked out before resizing, in outputDimensions. Reading the input moves into readInput to keep Process within the linter's limits. TestImageProcessor_WebPAtDefaultEffort becomes TestImageProcessor_WebPAtEffort2, as #280 (comment) allows. Model: opus-5-5
85 lines
2.9 KiB
Go
85 lines
2.9 KiB
Go
package imageprocessor
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"os"
|
|
"testing"
|
|
)
|
|
|
|
// TestImageProcessor_RefusesAVIFAboveMaxPixels verifies that an AVIF output
|
|
// of more than MaxAVIFOutputPixels is refused with ErrAVIFOutputTooLarge
|
|
// before anything is decoded, whatever size and fit give it, the source's own
|
|
// size included, and that an AVIF output of at most that many pixels, or one
|
|
// in another format, is not. One input is a 64x32 PNG cut off in its pixel
|
|
// data. The other is testdata/red.avif, a 4x4 AVIF, with the size in its
|
|
// header changed to 4097x4096. libvips reads each input's size from its
|
|
// header but fails when it decodes its pixel data, so a request that gets
|
|
// that failure was not refused, and a refusal came before any decoding.
|
|
func TestImageProcessor_RefusesAVIFAboveMaxPixels(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
whole := createTestPNG(t, 64, 32)
|
|
cutOff := whole[:len(whole)/2]
|
|
|
|
largeAVIF, err := os.ReadFile("testdata/red.avif")
|
|
if err != nil {
|
|
t.Fatalf("failed to read test AVIF: %v", err)
|
|
}
|
|
|
|
// The ispe box gives the width and then the height, 4 bytes each, after
|
|
// the box's type and its 4 bytes of version and flags
|
|
ispe := bytes.Index(largeAVIF, []byte("ispe"))
|
|
binary.BigEndian.PutUint32(largeAVIF[ispe+8:], 4097)
|
|
binary.BigEndian.PutUint32(largeAVIF[ispe+12:], 4096)
|
|
|
|
tests := []struct {
|
|
name string
|
|
input []byte
|
|
format Format
|
|
width, height int
|
|
fit FitMode
|
|
refused bool
|
|
}{
|
|
{"AVIF at the limit", cutOff, FormatAVIF, 4096, 4096, FitCover, false},
|
|
{"AVIF above the limit", cutOff, FormatAVIF, 4096, 4097, FitCover, true},
|
|
{"WebP above the AVIF limit", cutOff, FormatWebP, 4096, 4097, FitCover, false},
|
|
// FitInside does not enlarge, so the output stays 64x32
|
|
{"AVIF inside a larger size", cutOff, FormatAVIF, 8192, 8192, FitInside, false},
|
|
// FitOutside scales 64x32 by 128 to cover 8192x1: 8192x4096
|
|
{"AVIF outside a smaller size", cutOff, FormatAVIF, 8192, 1, FitOutside, true},
|
|
// With no size, the output keeps the source's 4097x4096
|
|
{"AVIF of the source's size", largeAVIF, FormatAVIF, 0, 0, FitCover, true},
|
|
{"orig of an AVIF source", largeAVIF, FormatOriginal, 0, 0, FitCover, true},
|
|
{"JPEG of the source's size", largeAVIF, FormatJPEG, 0, 0, FitCover, false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
_, err := New(Params{}).Process(t.Context(), bytes.NewReader(tt.input),
|
|
&Request{
|
|
Size: Size{Width: tt.width, Height: tt.height},
|
|
Format: tt.format,
|
|
FitMode: tt.fit,
|
|
})
|
|
t.Logf("Process() error = %v", err)
|
|
|
|
if tt.refused {
|
|
if !errors.Is(err, ErrAVIFOutputTooLarge) {
|
|
t.Errorf("Process() error = %v, want %v", err, ErrAVIFOutputTooLarge)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
if err == nil || errors.Is(err, ErrAVIFOutputTooLarge) {
|
|
t.Errorf("Process() error = %v, want the failure to decode "+
|
|
"the input", err)
|
|
}
|
|
})
|
|
}
|
|
}
|