From 0bff28381fd060fec8080e7fa05ad0066108737e Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sat, 11 Jul 2015 19:45:10 +0100 Subject: [PATCH] fix(#46): transform to proper image size --- README.md | 6 ++-- options.go | 1 + resize.go | 97 ++++++++++++++++++++++++++------------------------ resize_test.go | 40 +++++++++++++++++++++ vips.go | 5 +-- vips_test.go | 2 +- 6 files changed, 99 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index f302ebd..f7f4e1c 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ The [install script](https://github.com/lovell/sharp/blob/master/preinstall.sh) For platform specific installations, see [Mac OS](https://github.com/lovell/sharp/blob/master/README.md#mac-os-tips) tips or [Windows](https://github.com/lovell/sharp/blob/master/README.md#windows) tips -## Supported image operations +## Features - Resize - Enlarge @@ -52,6 +52,7 @@ For platform specific installations, see [Mac OS](https://github.com/lovell/sha - Thumbnail - Extract area - Watermark (text-based) +- Custom output color space (RGB, grayscale...) - Format conversion (with additional quality/compression settings) - EXIF metadata (size, alpha channel, profile, orientation...) @@ -65,7 +66,7 @@ Here you can see some performance test comparisons for multiple scenarios: #### Benchmarks -Tested using Go 1.4.2 and libvips-7.42.3 in OSX i7 2.7Ghz +Tested using Go 1.4.1 and libvips-7.42.3 in OSX i7 2.7Ghz ``` BenchmarkResizeLargeJpeg 50 43400480 ns/op BenchmarkResizePng 20 57592174 ns/op @@ -688,6 +689,7 @@ type Options struct { Embed bool Flip bool Flop bool + Force bool NoAutoRotate bool NoProfile bool Interlace bool diff --git a/options.go b/options.go index 1162335..8cf00cf 100644 --- a/options.go +++ b/options.go @@ -105,6 +105,7 @@ type Options struct { Embed bool Flip bool Flop bool + Force bool NoAutoRotate bool NoProfile bool Interlace bool diff --git a/resize.go b/resize.go index e77a2c1..185a997 100644 --- a/resize.go +++ b/resize.go @@ -45,7 +45,7 @@ func Resize(buf []byte, o Options) ([]byte, error) { // Do not enlarge the output if the input width or height // are already less than the required dimensions - if o.Enlarge == false { + if !o.Enlarge && !o.Force { if inWidth < o.Width && inHeight < o.Height { factor = 1.0 shrink = 1 @@ -81,30 +81,8 @@ func Resize(buf []byte, o Options) ([]byte, error) { } // Transform image, if necessary - transformImage := o.Width != inWidth || o.Height != inHeight || o.AreaWidth > 0 || o.AreaHeight > 0 - - if transformImage { - // Use vips_shrink with the integral reduction - if shrink > 1 { - image, residual, err = shrinkImage(image, o, residual, shrink) - if err != nil { - return nil, err - } - } - - debug("Transform: factor=%v, shrink=%v, residual=%v, interpolator=%v", - factor, shrink, residual, o.Interpolator.String()) - - // Affine with the remaining float part - if residual != 0 { - image, err = affineImage(image, o, residual) - if err != nil { - return nil, err - } - } - - // Extract area from image - image, err = extractImage(image, o) + if shouldTransformImage(o, inWidth, inHeight) { + image, err = transformImage(image, o, shrink, residual) if err != nil { return nil, err } @@ -150,16 +128,57 @@ func applyDefaults(o *Options, imageType ImageType) { } func normalizeOperation(o *Options, inWidth, inHeight int) { - if o.Crop == false && o.Enlarge == false && o.Rotate == 0 && (o.Width > 0 || o.Height > 0) { - if inWidth > o.Width || inHeight > o.Height { - o.Crop = true - } else { - o.Enlarge = true + if !o.Force && !o.Crop && !o.Embed && !o.Enlarge && o.Rotate == 0 && (o.Width > 0 || o.Height > 0) { + o.Force = true + } +} + +func shouldTransformImage(o Options, inWidth, inHeight int) bool { + return o.Force || (o.Width > 0 && o.Width != inWidth) || + (o.Height > 0 && o.Height != inHeight) || o.AreaWidth > 0 || o.AreaHeight > 0 +} + +func transformImage(image *C.struct__VipsImage, o Options, shrink int, residual float64) (*C.struct__VipsImage, error) { + var err error + + // Use vips_shrink with the integral reduction + if shrink > 1 { + image, residual, err = shrinkImage(image, o, residual, shrink) + if err != nil { + return nil, err } } + + residualx, residualy := residual, residual + if o.Force { + residualx = float64(o.Width) / float64(image.Xsize) + residualy = float64(o.Height) / float64(image.Ysize) + } + + if o.Force || residual != 0 { + image, err = vipsAffine(image, residualx, residualy, o.Interpolator) + if err != nil { + return nil, err + } + } + + if o.Force { + o.Crop = false + o.Embed = false + } + + image, err = extractOrEmbedImage(image, o) + if err != nil { + return nil, err + } + + debug("Transform: shrink=%v, residual=%v, interpolator=%v", + shrink, residual, o.Interpolator.String()) + + return image, nil } -func extractImage(image *C.struct__VipsImage, o Options) (*C.struct__VipsImage, error) { +func extractOrEmbedImage(image *C.struct__VipsImage, o Options) (*C.struct__VipsImage, error) { var err error = nil inWidth := int(image.Xsize) inHeight := int(image.Ysize) @@ -263,22 +282,6 @@ func zoomImage(image *C.struct__VipsImage, zoom int) (*C.struct__VipsImage, erro return vipsZoom(image, zoom+1) } -func affineImage(image *C.struct__VipsImage, o Options, residual float64) (*C.struct__VipsImage, error) { - newImage, err := vipsAffine(image, residual, o.Interpolator) - if err != nil { - C.g_object_unref(C.gpointer(image)) - return nil, err - } - - if o.Enlarge || o.Width > 0 || (o.Width > int(newImage.Xsize) && o.Height > int(newImage.Ysize)) { - C.g_object_unref(C.gpointer(image)) - return newImage, nil - } - - C.g_object_unref(C.gpointer(newImage)) - return image, nil -} - func shrinkImage(image *C.struct__VipsImage, o Options, residual float64, shrink int) (*C.struct__VipsImage, float64, error) { // Use vips_shrink with the integral reduction image, err := vipsShrink(image, shrink) diff --git a/resize_test.go b/resize_test.go index 89bb6ae..c419b4a 100644 --- a/resize_test.go +++ b/resize_test.go @@ -28,6 +28,46 @@ func TestResize(t *testing.T) { Write("fixtures/test_out.jpg", newImg) } +func TestResizeCustomSizes(t *testing.T) { + tests := []struct { + file string + format ImageType + options Options + }{ + {"test.jpg", JPEG, Options{Width: 800, Height: 600}}, + {"test.jpg", JPEG, Options{Width: 1000, Height: 1000}}, + {"test.jpg", JPEG, Options{Width: 100, Height: 50}}, + {"test.jpg", JPEG, Options{Width: 2000, Height: 2000}}, + {"test.jpg", JPEG, Options{Width: 500, Height: 1000}}, + {"test.jpg", JPEG, Options{Width: 500}}, + {"test.jpg", JPEG, Options{Height: 500}}, + {"test.jpg", JPEG, Options{Crop: true, Width: 500, Height: 1000}}, + {"test.jpg", JPEG, Options{Crop: true, Enlarge: true, Width: 2000, Height: 1400}}, + {"test.jpg", JPEG, Options{Enlarge: true, Force: true, Width: 2000, Height: 2000}}, + {"test.jpg", JPEG, Options{Force: true, Width: 2000, Height: 2000}}, + } + + for _, test := range tests { + buf, _ := Read("fixtures/" + test.file) + image, err := Resize(buf, test.options) + if err != nil { + t.Errorf("Resize(imgData, %#v) error: %#v", test.options, err) + } + + if DetermineImageType(image) != test.format { + t.Fatal("Image format is invalid. Expected: %s", test.format) + } + + size, _ := Size(image) + if test.options.Height > 0 && size.Height != test.options.Height { + t.Fatalf("Invalid height: %d", size.Height) + } + if test.options.Width > 0 && size.Width != test.options.Width { + t.Fatalf("Invalid width: %d", size.Width) + } + } +} + func TestRotate(t *testing.T) { options := Options{Width: 800, Height: 600, Rotate: 270} buf, _ := Read("fixtures/test.jpg") diff --git a/vips.go b/vips.go index 93be07b..d9504a7 100644 --- a/vips.go +++ b/vips.go @@ -367,15 +367,16 @@ func vipsEmbed(input *C.struct__VipsImage, left, top, width, height, extend int) return image, nil } -func vipsAffine(input *C.struct__VipsImage, residual float64, i Interpolator) (*C.struct__VipsImage, error) { +func vipsAffine(input *C.struct__VipsImage, residualx, residualy float64, i Interpolator) (*C.struct__VipsImage, error) { var image *C.struct__VipsImage cstring := C.CString(i.String()) interpolator := C.vips_interpolate_new(cstring) defer C.free(unsafe.Pointer(cstring)) + defer C.g_object_unref(C.gpointer(input)) defer C.g_object_unref(C.gpointer(interpolator)) - err := C.vips_affine_interpolator(input, &image, C.double(residual), 0, 0, C.double(residual), interpolator) + err := C.vips_affine_interpolator(input, &image, C.double(residualx), 0, 0, C.double(residualy), interpolator) if err != 0 { return nil, catchVipsError() } diff --git a/vips_test.go b/vips_test.go index b448d69..da5cea5 100644 --- a/vips_test.go +++ b/vips_test.go @@ -58,7 +58,7 @@ func TestVipsRotate(t *testing.T) { func TestVipsZoom(t *testing.T) { image, _, _ := vipsRead(readImage("test.jpg")) - newImg, err := vipsRotate(image, D90) + newImg, err := vipsZoom(image, 1) if err != nil { t.Fatal("Cannot save the image") }