Merge pull request #198 from greut/webpload

Add shrink-on-load for webp.
This commit is contained in:
Tomás Aparicio 2017-10-05 13:28:50 +02:00 committed by GitHub
commit 480ea8ab1a
5 changed files with 155 additions and 90 deletions

BIN
fixtures/vertical.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 MiB

View file

@ -8,6 +8,7 @@ import "C"
import ( import (
"errors" "errors"
"fmt"
"math" "math"
) )
@ -67,9 +68,11 @@ func resizer(buf []byte, o Options) ([]byte, error) {
} }
} }
// Try to use libjpeg shrink-on-load // Try to use libjpeg/libwebp shrink-on-load
if imageType == JPEG && shrink >= 2 { supportsShrinkOnLoad := imageType == WEBP && VipsMajorVersion >= 8 && VipsMinorVersion >= 3
tmpImage, factor, err := shrinkJpegImage(buf, image, factor, shrink) supportsShrinkOnLoad = supportsShrinkOnLoad || imageType == JPEG
if supportsShrinkOnLoad && shrink >= 2 {
tmpImage, factor, err := shrinkOnLoad(buf, image, imageType, factor, shrink)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -412,27 +415,31 @@ func shrinkImage(image *C.VipsImage, o Options, residual float64, shrink int) (*
return image, residual, nil return image, residual, nil
} }
func shrinkJpegImage(buf []byte, input *C.VipsImage, factor float64, shrink int) (*C.VipsImage, float64, error) { func shrinkOnLoad(buf []byte, input *C.VipsImage, imageType ImageType, factor float64, shrink int) (*C.VipsImage, float64, error) {
var image *C.VipsImage var image *C.VipsImage
var err error var err error
shrinkOnLoad := 1
// Recalculate integral shrink and double residual
switch {
case shrink >= 8:
factor = factor / 8
shrinkOnLoad = 8
case shrink >= 4:
factor = factor / 4
shrinkOnLoad = 4
case shrink >= 2:
factor = factor / 2
shrinkOnLoad = 2
}
// Reload input using shrink-on-load // Reload input using shrink-on-load
if shrinkOnLoad > 1 { if imageType == JPEG && shrink >= 2 {
shrinkOnLoad := 1
// Recalculate integral shrink and double residual
switch {
case shrink >= 8:
factor = factor / 8
shrinkOnLoad = 8
case shrink >= 4:
factor = factor / 4
shrinkOnLoad = 4
case shrink >= 2:
factor = factor / 2
shrinkOnLoad = 2
}
image, err = vipsShrinkJpeg(buf, input, shrinkOnLoad) image, err = vipsShrinkJpeg(buf, input, shrinkOnLoad)
} else if imageType == WEBP {
image, err = vipsShrinkWebp(buf, input, shrink)
} else {
return nil, 0, fmt.Errorf("%v doesn't support shrink on load", ImageTypeName(imageType))
} }
return image, factor, err return image, factor, err
@ -446,11 +453,7 @@ func imageCalculations(o *Options, inWidth, inHeight int) float64 {
switch { switch {
// Fixed width and height // Fixed width and height
case o.Width > 0 && o.Height > 0: case o.Width > 0 && o.Height > 0:
if o.Crop { factor = math.Min(xfactor, yfactor)
factor = math.Min(xfactor, yfactor)
} else {
factor = math.Max(xfactor, yfactor)
}
// Fixed width, auto height // Fixed width, auto height
case o.Width > 0: case o.Width > 0:
if o.Crop { if o.Crop {

View file

@ -9,7 +9,6 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path" "path"
"strconv"
"testing" "testing"
) )
@ -35,84 +34,129 @@ func TestResize(t *testing.T) {
} }
func TestResizeVerticalImage(t *testing.T) { func TestResizeVerticalImage(t *testing.T) {
tests := []struct { tests := []Options{
format ImageType {Width: 800, Height: 600},
options Options {Width: 1000, Height: 1000},
}{ {Width: 1000, Height: 1500},
{JPEG, Options{Width: 800, Height: 600}}, {Width: 1000},
{JPEG, Options{Width: 1000, Height: 1000}}, {Height: 1500},
{JPEG, Options{Width: 1000, Height: 1500}}, {Width: 100, Height: 50},
{JPEG, Options{Width: 1000}}, {Width: 2000, Height: 2000},
{JPEG, Options{Height: 1500}}, {Width: 500, Height: 1000},
{JPEG, Options{Width: 100, Height: 50}}, {Width: 500},
{JPEG, Options{Width: 2000, Height: 2000}}, {Height: 500},
{JPEG, Options{Width: 500, Height: 1000}}, {Crop: true, Width: 500, Height: 1000},
{JPEG, Options{Width: 500}}, {Crop: true, Enlarge: true, Width: 2000, Height: 1400},
{JPEG, Options{Height: 500}}, {Enlarge: true, Force: true, Width: 2000, Height: 2000},
{JPEG, Options{Crop: true, Width: 500, Height: 1000}}, {Force: true, Width: 2000, Height: 2000},
{JPEG, Options{Crop: true, Enlarge: true, Width: 2000, Height: 1400}},
{JPEG, Options{Enlarge: true, Force: true, Width: 2000, Height: 2000}},
{JPEG, Options{Force: true, Width: 2000, Height: 2000}},
} }
buf, _ := Read("fixtures/vertical.jpg") bufJpeg, err := Read("fixtures/vertical.jpg")
for _, test := range tests { if err != nil {
image, err := Resize(buf, test.options) t.Fatal(err)
if err != nil { }
t.Errorf("Resize(imgData, %#v) error: %#v", test.options, err) bufWebp, err := Read("fixtures/vertical.webp")
} if err != nil {
t.Fatal(err)
}
if DetermineImageType(image) != test.format { images := []struct {
t.Fatalf("Image format is invalid. Expected: %#v", test.format) format ImageType
} buf []byte
}{
{JPEG, bufJpeg},
{WEBP, bufWebp},
}
size, _ := Size(image) for _, source := range images {
if test.options.Height > 0 && size.Height != test.options.Height { for _, options := range tests {
t.Fatalf("Invalid height: %d", size.Height) image, err := Resize(source.buf, options)
} if err != nil {
if test.options.Width > 0 && size.Width != test.options.Width { t.Errorf("Resize(imgData, %#v) error: %#v", options, err)
t.Fatalf("Invalid width: %d", size.Width) }
}
Write("fixtures/test_vertical_"+strconv.Itoa(test.options.Width)+"x"+strconv.Itoa(test.options.Height)+"_out.jpg", image) format := DetermineImageType(image)
if format != source.format {
t.Fatalf("Image format is invalid. Expected: %#v got %v", ImageTypeName(source.format), ImageTypeName(format))
}
size, _ := Size(image)
if options.Height > 0 && size.Height != options.Height {
t.Fatalf("Invalid height: %d", size.Height)
}
if options.Width > 0 && size.Width != options.Width {
t.Fatalf("Invalid width: %d", size.Width)
}
Write(
fmt.Sprintf(
"fixtures/test_vertical_%dx%d_out.%s",
options.Width,
options.Height,
ImageTypeName(source.format)),
image)
}
} }
} }
func TestResizeCustomSizes(t *testing.T) { func TestResizeCustomSizes(t *testing.T) {
tests := []struct { tests := []Options{
format ImageType {Width: 800, Height: 600},
options Options {Width: 1000, Height: 1000},
}{ {Width: 100, Height: 50},
{JPEG, Options{Width: 800, Height: 600}}, {Width: 2000, Height: 2000},
{JPEG, Options{Width: 1000, Height: 1000}}, {Width: 500, Height: 1000},
{JPEG, Options{Width: 100, Height: 50}}, {Width: 500},
{JPEG, Options{Width: 2000, Height: 2000}}, {Height: 500},
{JPEG, Options{Width: 500, Height: 1000}}, {Crop: true, Width: 500, Height: 1000},
{JPEG, Options{Width: 500}}, {Crop: true, Enlarge: true, Width: 2000, Height: 1400},
{JPEG, Options{Height: 500}}, {Enlarge: true, Force: true, Width: 2000, Height: 2000},
{JPEG, Options{Crop: true, Width: 500, Height: 1000}}, {Force: true, Width: 2000, Height: 2000},
{JPEG, Options{Crop: true, Enlarge: true, Width: 2000, Height: 1400}},
{JPEG, Options{Enlarge: true, Force: true, Width: 2000, Height: 2000}},
{JPEG, Options{Force: true, Width: 2000, Height: 2000}},
} }
buf, _ := Read("fixtures/test.jpg") bufJpeg, err := Read("fixtures/test.jpg")
for _, test := range tests { if err != nil {
image, err := Resize(buf, test.options) t.Fatal(err)
if err != nil { }
t.Errorf("Resize(imgData, %#v) error: %#v", test.options, err) bufWebp, err := Read("fixtures/test.webp")
} if err != nil {
t.Fatal(err)
}
if DetermineImageType(image) != test.format { images := []struct {
t.Fatalf("Image format is invalid. Expected: %#v", test.format) format ImageType
} buf []byte
}{
{JPEG, bufJpeg},
{WEBP, bufWebp},
}
size, _ := Size(image) for _, source := range images {
if test.options.Height > 0 && size.Height != test.options.Height { for _, options := range tests {
t.Fatalf("Invalid height: %d", size.Height) image, err := Resize(source.buf, options)
} if err != nil {
if test.options.Width > 0 && size.Width != test.options.Width { t.Errorf("Resize(imgData, %#v) error: %#v", options, err)
t.Fatalf("Invalid width: %d", size.Width) }
if DetermineImageType(image) != source.format {
t.Fatalf("Image format is invalid. Expected: %#v", source.format)
}
size, _ := Size(image)
invalidHeight := options.Height > 0 && size.Height != options.Height
if !options.Crop && invalidHeight {
t.Fatalf("Invalid height: %d, expected %d", size.Height, options.Height)
}
invalidWidth := options.Width > 0 && size.Width != options.Width
if !options.Crop && invalidWidth {
t.Fatalf("Invalid width: %d, expected %d", size.Width, options.Width)
}
if options.Crop && invalidHeight && invalidWidth {
t.Fatalf("Invalid width or height: %dx%d, expected %dx%d (crop)", size.Width, size.Height, options.Width, options.Height)
}
} }
} }
} }

13
vips.go
View file

@ -532,6 +532,19 @@ func vipsShrinkJpeg(buf []byte, input *C.VipsImage, shrink int) (*C.VipsImage, e
return image, nil return image, nil
} }
func vipsShrinkWebp(buf []byte, input *C.VipsImage, shrink int) (*C.VipsImage, error) {
var image *C.VipsImage
var ptr = unsafe.Pointer(&buf[0])
defer C.g_object_unref(C.gpointer(input))
err := C.vips_webpload_buffer_shrink(ptr, C.size_t(len(buf)), &image, C.int(shrink))
if err != 0 {
return nil, catchVipsError()
}
return image, nil
}
func vipsShrink(input *C.VipsImage, shrink int) (*C.VipsImage, error) { func vipsShrink(input *C.VipsImage, shrink int) (*C.VipsImage, error) {
var image *C.VipsImage var image *C.VipsImage
defer C.g_object_unref(C.gpointer(input)) defer C.g_object_unref(C.gpointer(input))

5
vips.h
View file

@ -110,6 +110,11 @@ vips_jpegload_buffer_shrink(void *buf, size_t len, VipsImage **out, int shrink)
return vips_jpegload_buffer(buf, len, out, "shrink", shrink, NULL); return vips_jpegload_buffer(buf, len, out, "shrink", shrink, NULL);
} }
int
vips_webpload_buffer_shrink(void *buf, size_t len, VipsImage **out, int shrink) {
return vips_webpload_buffer(buf, len, out, "shrink", shrink, NULL);
}
int int
vips_flip_bridge(VipsImage *in, VipsImage **out, int direction) { vips_flip_bridge(VipsImage *in, VipsImage **out, int direction) {
return vips_flip(in, out, direction, NULL); return vips_flip(in, out, direction, NULL);