From ef10d7d7ec1fe7534f85f6d2a5b7acc3d79a568b Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sun, 5 Apr 2015 23:39:15 +0200 Subject: [PATCH] feat: support multiple outputs --- image.go | 28 ++++++++++++----- image_test.go | 20 ++++++++++++ options.go | 2 ++ resize.go | 21 +++++++++++-- resize_test.go | 28 +++++++++++++++++ type.go | 17 +++++++++-- type_test.go | 50 ++++++++++++++++++++++++++++++ vips.go | 30 +++++++++++++----- vips.h | 82 +++++++++++++++++++++++++++----------------------- 9 files changed, 220 insertions(+), 58 deletions(-) create mode 100644 image_test.go create mode 100644 type_test.go diff --git a/image.go b/image.go index 7ee0f8e..489a411 100644 --- a/image.go +++ b/image.go @@ -9,7 +9,7 @@ func (i *Image) Resize(width int, height int) ([]byte, error) { Width: width, Height: height, } - return Resize(i.buffer, options) + return i.Process(options) } func (i *Image) Extract(top int, left int, width int, height int) ([]byte, error) { @@ -19,22 +19,36 @@ func (i *Image) Extract(top int, left int, width int, height int) ([]byte, error Top: top, Left: left, } - return Resize(i.buffer, options) + return i.Process(options) } -func (i *Image) Rotate(degrees Angle) ([]byte, error) { - options := Options{Rotate: degrees} - return Resize(i.buffer, options) +func (i *Image) Rotate(a Angle) ([]byte, error) { + options := Options{Rotate: a} + return i.Process(options) } func (i *Image) Flip() ([]byte, error) { options := Options{Flip: VERTICAL} - return Resize(i.buffer, options) + return i.Process(options) } func (i *Image) Flop() ([]byte, error) { options := Options{Flip: HORIZONTAL} - return Resize(i.buffer, options) + return i.Process(options) +} + +func (i *Image) Convert(t ImageType) ([]byte, error) { + options := Options{Type: t} + return i.Process(options) +} + +func (i *Image) Process(o Options) ([]byte, error) { + image, err := Resize(i.buffer, o) + if err != nil { + return nil, err + } + i.buffer = image + return image, nil } func (i *Image) Type() string { diff --git a/image_test.go b/image_test.go new file mode 100644 index 0000000..0d6789c --- /dev/null +++ b/image_test.go @@ -0,0 +1,20 @@ +package bimg + +import ( + "io/ioutil" + "os" + "path" + "testing" +) + +func TestImageResize(t *testing.T) { + data, _ := os.Open(path.Join("fixtures/test.jpg")) + buf, err := ioutil.ReadAll(data) + + image := NewImage(buf) + + buf, err = image.Resize(300, 240) + if err != nil { + t.Errorf("Cannot process the image: %#v", err) + } +} diff --git a/options.go b/options.go index 60db5b8..1884fe9 100644 --- a/options.go +++ b/options.go @@ -48,6 +48,8 @@ type Options struct { Extend int Embed bool Quality int + Compression int + Type ImageType Rotate Angle Flip Direction Gravity Gravity diff --git a/resize.go b/resize.go index 8938f34..72d946f 100644 --- a/resize.go +++ b/resize.go @@ -7,6 +7,7 @@ package bimg import "C" import ( + "errors" "math" ) @@ -27,7 +28,7 @@ const ( func Resize(buf []byte, o Options) ([]byte, error) { defer C.vips_thread_shutdown() - image, err := vipsRead(buf) + image, imageType, err := vipsRead(buf) if err != nil { return nil, err } @@ -36,6 +37,12 @@ func Resize(buf []byte, o Options) ([]byte, error) { if o.Quality == 0 { o.Quality = QUALITY } + if o.Compression == 0 { + o.Compression = 6 + } + if o.Type == 0 { + o.Type = imageType + } // get WxH inWidth := int(image.Xsize) @@ -74,7 +81,17 @@ func Resize(buf []byte, o Options) ([]byte, error) { } } - buf, err = vipsSave(image, vipsSaveOptions{Quality: o.Quality}) + if IsTypeSupported(o.Type) == false { + return nil, errors.New("Unsupported image output type") + } + + saveOptions := vipsSaveOptions{ + Quality: o.Quality, + Type: o.Type, + Compression: o.Compression, + } + + buf, err = vipsSave(image, saveOptions) if err != nil { return nil, err } diff --git a/resize_test.go b/resize_test.go index 124b394..7c146c8 100644 --- a/resize_test.go +++ b/resize_test.go @@ -33,3 +33,31 @@ func TestResize(t *testing.T) { t.Fatal("Cannot save the image") } } + +func TestConvert(t *testing.T) { + options := Options{Width: 640, Height: 480, Crop: false, Type: PNG} + img, err := os.Open("fixtures/test.jpg") + if err != nil { + t.Fatal(err) + } + defer img.Close() + + buf, err := ioutil.ReadAll(img) + if err != nil { + t.Fatal(err) + } + + newImg, err := Resize(buf, options) + if err != nil { + t.Errorf("Resize(imgData, %#v) error: %#v", options, err) + } + + if DetermineImageType(newImg) != PNG { + t.Fatal("Image is not png") + } + + err = ioutil.WriteFile("result.png", newImg, 0644) + if err != nil { + t.Fatal("Cannot save the image") + } +} diff --git a/type.go b/type.go index c908df9..a491b11 100644 --- a/type.go +++ b/type.go @@ -1,7 +1,9 @@ package bimg +type ImageType int + const ( - UNKNOWN = iota + UNKNOWN ImageType = iota JPEG WEBP PNG @@ -9,7 +11,7 @@ const ( MAGICK ) -func DetermineImageType(buf []byte) int { +func DetermineImageType(buf []byte) ImageType { return vipsImageType(buf) } @@ -28,7 +30,7 @@ func DetermineImageTypeName(buf []byte) string { imageType = "png" break case imageCode == TIFF: - imageType = "png" + imageType = "tiff" break case imageCode == MAGICK: imageType = "magick" @@ -37,3 +39,12 @@ func DetermineImageTypeName(buf []byte) string { return imageType } + +func IsTypeSupported(t ImageType) bool { + return t == JPEG || t == PNG || t == WEBP +} + +func IsTypeNameSupported(t string) bool { + return t == "jpeg" || t == "jpg" || + t == "png" || t == "webp" +} diff --git a/type_test.go b/type_test.go new file mode 100644 index 0000000..4da508d --- /dev/null +++ b/type_test.go @@ -0,0 +1,50 @@ +package bimg + +import ( + "io/ioutil" + "os" + "path" + "testing" +) + +func TestDeterminateImageType(t *testing.T) { + files := []struct { + name string + expected ImageType + }{ + {"test.jpg", JPEG}, + {"test.png", PNG}, + {"test.webp", WEBP}, + } + + for _, file := range files { + img, _ := os.Open(path.Join("fixtures", file.name)) + buf, _ := ioutil.ReadAll(img) + defer img.Close() + + if DetermineImageType(buf) != file.expected { + t.Fatal("Image type is not valid") + } + } +} + +func TestDeterminateImageTypeName(t *testing.T) { + files := []struct { + name string + expected string + }{ + {"test.jpg", "jpeg"}, + {"test.png", "png"}, + {"test.webp", "webp"}, + } + + for _, file := range files { + img, _ := os.Open(path.Join("fixtures", file.name)) + buf, _ := ioutil.ReadAll(img) + defer img.Close() + + if DetermineImageTypeName(buf) != file.expected { + t.Fatal("Image type is not valid") + } + } +} diff --git a/vips.go b/vips.go index 57c1e05..2216e9c 100644 --- a/vips.go +++ b/vips.go @@ -60,12 +60,12 @@ func vipsFlip(image *C.struct__VipsImage, direction Direction) (*C.struct__VipsI return out, nil } -func vipsRead(buf []byte) (*C.struct__VipsImage, error) { +func vipsRead(buf []byte) (*C.struct__VipsImage, ImageType, error) { var image *C.struct__VipsImage imageType := vipsImageType(buf) if imageType == UNKNOWN { - return nil, errors.New("Input buffer contains unsupported image format") + return nil, UNKNOWN, errors.New("Input buffer contains unsupported image format") } // feed it @@ -75,10 +75,10 @@ func vipsRead(buf []byte) (*C.struct__VipsImage, error) { err := C.vips_init_image(imageBuf, length, imageTypeC, &image) if err != 0 { - return nil, catchVipsError() + return nil, UNKNOWN, catchVipsError() } - return image, nil + return image, imageType, nil } func vipsExtract(image *C.struct__VipsImage, left int, top int, width int, height int) (*C.struct__VipsImage, error) { @@ -93,7 +93,7 @@ func vipsExtract(image *C.struct__VipsImage, left int, top int, width int, heigh return buf, nil } -func vipsImageType(buf []byte) int { +func vipsImageType(buf []byte) ImageType { imageType := UNKNOWN length := C.size_t(len(buf)) @@ -126,18 +126,32 @@ func vipsExifOrientation(image *C.struct__VipsImage) int { } type vipsSaveOptions struct { - Quality int + Quality int + Compression int + Type ImageType } func vipsSave(image *C.struct__VipsImage, o vipsSaveOptions) ([]byte, error) { var ptr unsafe.Pointer length := C.size_t(0) + err := C.int(0) - err := C.vips_jpegsave_custom(image, &ptr, &length, 1, C.int(o.Quality), 0) + switch { + case o.Type == PNG: + err = C.vips_pngsave_custom(image, &ptr, &length, 1, C.int(o.Compression), C.int(o.Quality), 0) + break + case o.Type == WEBP: + err = C.vips_webpsave_custom(image, &ptr, &length, 1, C.int(o.Quality), 0) + break + default: + err = C.vips_jpegsave_custom(image, &ptr, &length, 1, C.int(o.Quality), 0) + break + } + + C.g_object_unref(C.gpointer(image)) if err != 0 { return nil, catchVipsError() } - C.g_object_unref(C.gpointer(image)) buf := C.GoBytes(ptr, C.int(length)) // cleanup diff --git a/vips.h b/vips.h index 92ab5f9..d8c199f 100644 --- a/vips.h +++ b/vips.h @@ -36,24 +36,12 @@ vips_jpegload_buffer_shrink(void *buf, size_t len, VipsImage **out, int shrink) return vips_jpegload_buffer(buf, len, out, "shrink", shrink, NULL); }; -int -vips_webpload_buffer_seq(void *buf, size_t len, VipsImage **out) -{ - return vips_webpload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL); -}; - int vips_flip_seq(VipsImage *in, VipsImage **out) { return vips_flip(in, out, VIPS_DIRECTION_HORIZONTAL, NULL); }; -int -vips_pngload_buffer_seq(void *buf, size_t len, VipsImage **out) -{ - return vips_pngload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL); -}; - int vips_shrink_0(VipsImage *in, VipsImage **out, double xshrink, double yshrink) { @@ -82,32 +70,6 @@ vips_rotate(VipsImage *in, VipsImage **buf, int angle) return vips_rot(in, buf, rotate, NULL); }; -int -vips_init_image(void *buf, size_t len, int imageType, VipsImage **out) { - int code = 1; - - if (imageType == JPEG) { - code = vips_jpegload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL); - } else if (imageType == PNG) { - code = vips_pngload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL); - } else if (imageType == WEBP) { - code = vips_webpload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL); - } else if (imageType == TIFF) { - code = vips_tiffload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL); -#if (VIPS_MAJOR_VERSION >= 8) - } else if (imageType == MAGICK) { - code = vips_magickload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL); -#endif - } - - if (out != NULL) { - // Listen for "postclose" signal to delete input buffer - //g_signal_connect(out, "postclose", G_CALLBACK(vips_malloc_cb), buf); - } - - return code; -}; - int vips_exif_orientation(VipsImage *image) { int orientation = 0; @@ -144,3 +106,47 @@ vips_jpegsave_custom(VipsImage *in, void **buf, size_t *len, int strip, int qual { return vips_jpegsave_buffer(in, buf, len, "strip", strip, "Q", quality, "optimize_coding", TRUE, "interlace", interlace, NULL); }; + +int +vips_pngsave_custom(VipsImage *in, void **buf, size_t *len, int strip, int compression, int quality, int interlace) +{ +#if (VIPS_MAJOR_VERSION >= 8 || (VIPS_MAJOR_VERSION >= 7 && VIPS_MINOR_VERSION >= 42)) + return vips_pngsave_buffer(in, buf, len, "strip", FALSE, "compression", compression, + "interlace", interlace, "filter", VIPS_FOREIGN_PNG_FILTER_NONE, NULL); +#else + return vips_pngsave_buffer(image, buf, len, "strip", FALSE, "compression", compression, + "interlace", interlace, NULL); +#endif +}; + +int +vips_webpsave_custom(VipsImage *in, void **buf, size_t *len, int strip, int quality, int interlace) +{ + return vips_webpsave_buffer(in, buf, len, "strip", strip, "Q", quality, "optimize_coding", TRUE, "interlace", interlace, NULL); +}; + +int +vips_init_image(void *buf, size_t len, int imageType, VipsImage **out) { + int code = 1; + + if (imageType == JPEG) { + code = vips_jpegload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL); + } else if (imageType == PNG) { + code = vips_pngload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL); + } else if (imageType == WEBP) { + code = vips_webpload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL); + } else if (imageType == TIFF) { + code = vips_tiffload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL); +#if (VIPS_MAJOR_VERSION >= 8) + } else if (imageType == MAGICK) { + code = vips_magickload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL); +#endif + } + + if (out != NULL) { + // Listen for "postclose" signal to delete input buffer + //g_signal_connect(out, "postclose", G_CALLBACK(vips_malloc_cb), buf); + } + + return code; +};