func TestCrop(t *testing.T) { img := image("watermelon.jpg") m, err := format.MetadataBytes(img) assert.Nil(t, err) assert.Equal(t, m.Width, 398) assert.Equal(t, m.Height, 536) // Verify cropping to fit. thumb, err := Thumbnail(img, Options{Width: 300, Height: 400, Crop: true}) if assert.Nil(t, err) { assert.Nil(t, isSize(thumb, format.Jpeg, 300, 400, false)) } // Verify cropping to fit, too big. thumb, err = Thumbnail(img, Options{Width: 2000, Height: 1500, Crop: true}) if assert.Nil(t, err) { assert.Nil(t, isSize(thumb, format.Jpeg, 398, 299, false)) } // Verify cropping to fit, same result size. thumb, err = Thumbnail(img, Options{Width: 796, Height: 1072, Crop: true}) if assert.Nil(t, err) { assert.Nil(t, isSize(thumb, format.Jpeg, 398, 536, false)) } }
func TestThumbnail(t *testing.T) { img := image("watermelon.jpg") m, err := format.MetadataBytes(img) if !assert.Nil(t, err) { return } assert.Equal(t, m.Width, 398) assert.Equal(t, m.Height, 536) // Verify scaling down to fit completely into box. thumb, err := Thumbnail(img, Options{Width: 200, Height: 300}) if assert.Nil(t, err) { assert.Nil(t, isSize(thumb, format.Jpeg, 200, 270, false)) } // Verify scaling down to have width fit. thumb, err = Thumbnail(img, Options{Width: 200}) if assert.Nil(t, err) { assert.Nil(t, isSize(thumb, format.Jpeg, 200, 270, false)) } // Verify scaling down to have height fit. thumb, err = Thumbnail(img, Options{Height: 300}) if assert.Nil(t, err) { assert.Nil(t, isSize(thumb, format.Jpeg, 223, 300, false)) } // Verify that we don't scale up. thumb, err = Thumbnail(img, Options{Width: 2048, Height: 2048}) if assert.Nil(t, err) { assert.Nil(t, isSize(thumb, format.Jpeg, 398, 536, false)) } }
func TestConversion(t *testing.T) { var formatTest = []struct { filename string in format.Format outLossless format.Format }{ {"2px.gif", format.Gif, format.Png}, {"2px.png", format.Png, format.Png}, {"2px.jpg", format.Jpeg, format.Jpeg}, {"2px.webp", format.Webp, format.Png}, } for _, f := range formatTest { img := image(f.filename) m, err := format.MetadataBytes(img) if assert.Nil(t, err, "format: %s", f.in) { assert.Equal(t, m.Format, f.in, "format: %s", f.in) assert.Equal(t, m.Width, 2, "format: %s", f.in) assert.Equal(t, m.Height, 3, "format: %s", f.in) for _, of := range []format.Format{format.Png, format.Jpeg, format.Webp} { // If we ask for a specific format, it should return that. thumb, err := Thumbnail(img, Options{Width: 1024, Height: 1024, Save: format.SaveOptions{Format: of}}) if assert.Nil(t, err, "formats: %s -> %s", f.in, of) { assert.Nil(t, isSize(thumb, of, 2, 3, false), "formats: %s -> %s", f.in, of) } } } } }
func isSize(image []byte, f format.Format, width, height int, alpha bool) error { m, err := format.MetadataBytes(image) if err != nil { return err } if m.Width != width || m.Height != height { return fmt.Errorf("Got %dx%d != want %dx%d", m.Width, m.Height, width, height) } if m.Format != f { return fmt.Errorf("Format %s!=%s", m.Format, f) } if m.HasAlpha != alpha { return fmt.Errorf("HasAlpha %t!=%t", m.HasAlpha, alpha) } return nil }
func isSize(filename string, f format.Format, width, height int) error { image, code := fetch(filename) if code != 200 { return fmt.Errorf("HTTP error %d", code) } m, err := format.MetadataBytes(image) if err != nil { return err } if m.Width != width || m.Height != height { return fmt.Errorf("Width %d!=%d or Height %d!=%d", m.Width, width, m.Height, height) } if m.Format != f { return fmt.Errorf("Format %s!=%s", m.Format, f) } return nil }
func TestRotation(t *testing.T) { for i := 0; i <= 8; i++ { // Verify that New() correctly translates dimensions. img := image("orient" + strconv.Itoa(i) + ".jpg") m, err := format.MetadataBytes(img) if !assert.Nil(t, err) { continue } assert.Equal(t, m.Width, 48) assert.Equal(t, m.Height, 80) // Verify that img.Thumbnail() maintains orientation. thumb, err := Thumbnail(img, Options{Width: 40, Height: 40}) if assert.Nil(t, err) { assert.Nil(t, isSize(thumb, format.Jpeg, 24, 40, false)) } // TODO: Figure out how to test crop. } }
// Thumbnail scales or crops a compressed image blob according to the // Options specified in o and returns a compressed image. // Should be called from a thread pool with runtime.LockOSThread() locked. func Thumbnail(blob []byte, o Options) ([]byte, error) { if o.MaxProcessingDuration > 0 { timer := time.AfterFunc(o.MaxProcessingDuration, func() { panic(fmt.Sprintf("Thumbnail took longer than %v", o.MaxProcessingDuration)) }) defer timer.Stop() } // Free some thread-local caches. Safe to call unnecessarily. defer vips.ThreadShutdown() m, err := format.MetadataBytes(blob) if err != nil { return nil, err } o, err = o.Check(m) if err != nil { return nil, err } // If source image is lossy, disable lossless. if m.Format == format.Jpeg { o.Save.Lossless = false } // Figure out size to scale image down to. For crop, this is the // intermediate size the original image would have to be scaled to // be cropped to requested size. iw, ih, trustWidth := scaleAspect(m.Width, m.Height, o.Width, o.Height, !o.Crop) // Are we shrinking by more than 2.5%? shrinking := iw < m.Width-m.Width/40 && ih < m.Height-m.Height/40 // Figure out the jpeg/webp shrink factor and load image. // Jpeg shrink rounds up the number of pixels. psf := preShrinkFactor(m.Width, m.Height, iw, ih, trustWidth, o.FastResize, m.Format == format.Jpeg) image, err := load(blob, m.Format, psf) if err != nil { return nil, err } defer image.Close() if err := srgb(image); err != nil { return nil, err } if err := resize(image, iw, ih, o.FastResize, o.BlurSigma, o.Sharpen && shrinking); err != nil { return nil, err } // Make sure we generate images with 8 bits per channel. Do this before the // rotate to reduce the amount of data that needs to be copied. if image.ImageGetBandFormat() != vips.BandFormatUchar { if err := image.Cast(vips.BandFormatUchar); err != nil { return nil, err } } if o.Crop { if err = crop(image, o.Width, o.Height); err != nil { return nil, err } } if image.HasAlpha() { if min, err := minTransparency(image); err == nil && min >= 0.9 { if err := image.Flatten(); err != nil { return nil, err } } } if err := m.Orientation.Apply(image); err != nil { return nil, err } return format.Save(image, o.Save) }