mirror of
https://github.com/talgo-cloud/bimg.git
synced 2026-03-13 09:20:29 -07:00
Merge pull request #198 from greut/webpload
Add shrink-on-load for webp.
This commit is contained in:
commit
480ea8ab1a
5 changed files with 155 additions and 90 deletions
BIN
fixtures/vertical.webp
Normal file
BIN
fixtures/vertical.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2 MiB |
51
resizer.go
51
resizer.go
|
|
@ -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 {
|
||||||
|
|
|
||||||
176
resizer_test.go
176
resizer_test.go
|
|
@ -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
13
vips.go
|
|
@ -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
5
vips.h
|
|
@ -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);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue