From 074a236dc32df6385da4d115619bc077e03e6c23 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sat, 11 Apr 2015 02:56:59 +0200 Subject: [PATCH 001/115] feat(#15): more benchmarks --- README.md | 2 +- image.go | 2 +- resize_test.go | 98 +++++++++++++++++++++++++++++++++++++++++++++++++- vips.h | 10 +++--- 4 files changed, 104 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index f2eab8e..e89e610 100644 --- a/README.md +++ b/README.md @@ -176,7 +176,7 @@ if err != nil { options := bimg.Watermark{ Watermark{ - Text: "Chuck Norris - Copyright (c) 2315", + Text: "Chuck Norris (c) 2315", Opacity: 0.25, Width: 200, DPI: 100, diff --git a/image.go b/image.go index f32606e..fe0d777 100644 --- a/image.go +++ b/image.go @@ -75,7 +75,7 @@ func (i *Image) Thumbnail(pixels int) ([]byte, error) { return i.Process(options) } -// Insert an image to the existent one as watermark +// Add text as watermark on the given image func (i *Image) Watermark(w Watermark) ([]byte, error) { options := Options{Watermark: w} return i.Process(options) diff --git a/resize_test.go b/resize_test.go index 97b91bf..0c29dd6 100644 --- a/resize_test.go +++ b/resize_test.go @@ -209,7 +209,7 @@ func BenchmarkConvertToJpeg(b *testing.B) { runBenchmarkResize("test.png", options, b) } -func BenchmarkCrop(b *testing.B) { +func BenchmarkCropJpeg(b *testing.B) { options := Options{ Width: 800, Height: 600, @@ -217,6 +217,22 @@ func BenchmarkCrop(b *testing.B) { runBenchmarkResize("test.jpg", options, b) } +func BenchmarkCropPng(b *testing.B) { + options := Options{ + Width: 800, + Height: 600, + } + runBenchmarkResize("test.png", options, b) +} + +func BenchmarkCropWebP(b *testing.B) { + options := Options{ + Width: 800, + Height: 600, + } + runBenchmarkResize("test.webp", options, b) +} + func BenchmarkExtractJpeg(b *testing.B) { options := Options{ Top: 100, @@ -226,3 +242,83 @@ func BenchmarkExtractJpeg(b *testing.B) { } runBenchmarkResize("test.jpg", options, b) } + +func BenchmarkExtractPng(b *testing.B) { + options := Options{ + Top: 100, + Left: 50, + AreaWidth: 600, + AreaHeight: 480, + } + runBenchmarkResize("test.png", options, b) +} + +func BenchmarkExtractWebp(b *testing.B) { + options := Options{ + Top: 100, + Left: 50, + AreaWidth: 600, + AreaHeight: 480, + } + runBenchmarkResize("test.webp", options, b) +} + +func BenchmarkZoomJpeg(b *testing.B) { + options := Options{Zoom: 1} + runBenchmarkResize("test.jpg", options, b) +} + +func BenchmarkZoomPng(b *testing.B) { + options := Options{Zoom: 1} + runBenchmarkResize("test.png", options, b) +} + +func BenchmarkZoomWebp(b *testing.B) { + options := Options{Zoom: 1} + runBenchmarkResize("test.webp", options, b) +} + +func BenchmarkWatermarkJpeg(b *testing.B) { + options := Options{ + Watermark: Watermark{ + Text: "Chuck Norris (c) 2315", + Opacity: 0.25, + Width: 200, + DPI: 100, + Margin: 150, + Font: "sans bold 12", + Background: Color{255, 255, 255}, + }, + } + runBenchmarkResize("test.webp", options, b) +} + +func BenchmarkWatermarPng(b *testing.B) { + options := Options{ + Watermark: Watermark{ + Text: "Chuck Norris (c) 2315", + Opacity: 0.25, + Width: 200, + DPI: 100, + Margin: 150, + Font: "sans bold 12", + Background: Color{255, 255, 255}, + }, + } + runBenchmarkResize("test.png", options, b) +} + +func BenchmarkWatermarWebp(b *testing.B) { + options := Options{ + Watermark: Watermark{ + Text: "Chuck Norris (c) 2315", + Opacity: 0.25, + Width: 200, + DPI: 100, + Margin: 150, + Font: "sans bold 12", + Background: Color{255, 255, 255}, + }, + } + runBenchmarkResize("test.webp", options, b) +} diff --git a/vips.h b/vips.h index 0fa7cf9..bdec6b9 100644 --- a/vips.h +++ b/vips.h @@ -17,11 +17,11 @@ typedef struct { } watermarkTextOptions; typedef struct { - int Width; - int DPI; - int Margin; - int NoReplicate; - float Opacity; + int Width; + int DPI; + int Margin; + int NoReplicate; + float Opacity; double Background[3]; } watermarkOptions; From 8abe7b850c389a5ae00943adb0b2297cf096308b Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sat, 11 Apr 2015 02:57:45 +0200 Subject: [PATCH 002/115] feat(version): bump --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index 077f905..7e8411f 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,3 @@ package bimg -const Version = "0.1.5" +const Version = "0.1.6" From 210c7b4bdc52c2c1deba2ce23c827105f4154322 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sat, 11 Apr 2015 03:01:22 +0200 Subject: [PATCH 003/115] refactor: remove colorspace feature --- README.md | 2 +- options.go | 1 - resize_test.go | 19 ------------------- vips.go | 28 ---------------------------- vips.h | 19 ------------------- 5 files changed, 1 insertion(+), 68 deletions(-) diff --git a/README.md b/README.md index e89e610..47f00cf 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ For getting started, take a look to the [examples](#examples) and [programmatic bimg was heavily inspired in [sharp](https://github.com/lovell/sharp), its homologous package built for node.js by [Lovell Fuller](https://github.com/lovell). -**Note**: bimg is still beta. Pull request and issues are highly appreciated +**Note**: bimg is still beta. Do not use in production yet ## Prerequisites diff --git a/options.go b/options.go index 598faf0..7b2c2fb 100644 --- a/options.go +++ b/options.go @@ -88,7 +88,6 @@ type Options struct { Flip bool Flop bool NoAutoRotate bool - Colorspace bool Rotate Angle Gravity Gravity Watermark Watermark diff --git a/resize_test.go b/resize_test.go index 0c29dd6..fba2485 100644 --- a/resize_test.go +++ b/resize_test.go @@ -45,25 +45,6 @@ func TestRotate(t *testing.T) { } } -func testColorspace(t *testing.T) { - options := Options{Colorspace: true} - buf, _ := Read("fixtures/sky.jpg") - - newImg, err := Resize(buf, options) - if err != nil { - t.Errorf("Resize(imgData, %#v) error: %#v", options, err) - } - - if DetermineImageType(newImg) != JPEG { - t.Fatal("Image is not jpeg") - } - - err = Write("fixtures/test_color_out.jpg", newImg) - if err != nil { - t.Fatal("Cannot save the image") - } -} - func TestCorruptedImage(t *testing.T) { options := Options{Width: 800, Height: 600} buf, _ := Read("fixtures/corrupt.jpg") diff --git a/vips.go b/vips.go index 27ff941..5d7a966 100644 --- a/vips.go +++ b/vips.go @@ -156,34 +156,6 @@ func vipsZoom(image *C.struct__VipsImage, zoom int) (*C.struct__VipsImage, error return out, nil } -func vipsColorSpace(image *C.struct__VipsImage) (*C.struct__VipsImage, error) { - var out *C.struct__VipsImage - var temp *C.struct__VipsImage - var max *C.double - var x *C.int - var y *C.int - - defer C.g_object_unref(C.gpointer(image)) - - err := C.vips_colorspace_bridge(image, &out) - if err != 0 { - return nil, catchVipsError() - } - - err = C.vips_hist_find_ndim_bridge(out, &temp) - if err != 0 { - return nil, catchVipsError() - } - - err = C.vips_max_bridge(temp, max, &x, &y) - if err != 0 { - return nil, catchVipsError() - } - debug("MAX VALUE %dx%d", x, y) - - return temp, nil -} - func vipsWatermark(image *C.struct__VipsImage, w Watermark) (*C.struct__VipsImage, error) { var out *C.struct__VipsImage diff --git a/vips.h b/vips.h index bdec6b9..a896310 100644 --- a/vips.h +++ b/vips.h @@ -111,25 +111,6 @@ vips_zoom_bridge(VipsImage *in, VipsImage **out, int xfac, int yfac) return vips_zoom(in, out, xfac, yfac, NULL); }; -int -vips_colorspace_bridge(VipsImage *in, VipsImage **out) -{ - return vips_colourspace(in, out, VIPS_INTERPRETATION_LAB, NULL); -}; - -int -vips_hist_find_ndim_bridge(VipsImage *in, VipsImage **out) -{ - return vips_hist_find_ndim(in, out, "bins", 5, NULL); -}; - -int -vips_max_bridge(VipsImage *in, double *out, int **x, int **y) -{ - double ones[3] = { 1, 1, 1 }; - return vips_max(in, ones, "x", x, "y", y, NULL); -}; - int vips_embed_bridge(VipsImage *in, VipsImage **out, int left, int top, int width, int height, int extend) { From 56dc7cc98d285bca7a6e7fa7b72a698a6bacf70f Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sat, 11 Apr 2015 03:05:32 +0200 Subject: [PATCH 004/115] refactor(debug) --- debug.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/debug.go b/debug.go index 9f6fcea..46155fa 100644 --- a/debug.go +++ b/debug.go @@ -15,7 +15,7 @@ func PrintMemoryStats() { log := Debug("memory") mem := memoryStats() - log("\u001b[33m---- Memory Dump Stats ----\u001b[39m") + log("\u001b[33m---- Memory Stats ----\u001b[39m") log("Allocated: %s", humanize.Bytes(mem.Alloc)) log("Total Allocated: %s", humanize.Bytes(mem.TotalAlloc)) log("Memory Allocations: %d", mem.Mallocs) @@ -32,7 +32,7 @@ func PrintMemoryStats() { log("Stack Cache In Use: %s", humanize.Bytes(mem.MCacheInuse)) log("Next GC cycle: %s", humanizeNano(mem.NextGC)) log("Last GC cycle: %s", humanize.Time(time.Unix(0, int64(mem.LastGC)))) - log("\u001b[33m---- End Memory Dump ----\u001b[39m") + log("\u001b[33m---- Memory Stats ----\u001b[39m") } func memoryStats() runtime.MemStats { From 3b9b0ca98fb8424104839dfe9b6fab3d358a598c Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sat, 11 Apr 2015 03:07:28 +0200 Subject: [PATCH 005/115] feat(docs): update benchmark --- README.md | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 47f00cf..3e74039 100644 --- a/README.md +++ b/README.md @@ -63,13 +63,22 @@ Here you can see some performance test comparisons for multiple scenarios: Tested using Go 1.4 and libvips-7.42.3 in OSX i7 2.7Ghz ``` -PASS -BenchmarkResizeLargeJpeg 30 46652408 ns/op -BenchmarkResizePng 20 57387902 ns/op -BenchmarkResizeWebP 500 2453220 ns/op -BenchmarkConvertToJpeg 30 35556414 ns/op -BenchmarkCrop 30 51768475 ns/op -BenchmarkExtract 30 50866406 ns/op +BenchmarkResizeLargeJpeg 50 43400480 ns/op +BenchmarkResizePng 20 50736409 ns/op +BenchmarkResizeWebP 500 2455732 ns/op +BenchmarkConvertToJpeg 50 37313675 ns/op +BenchmarkCropJpeg 30 42894495 ns/op +BenchmarkCropPng 30 53256160 ns/op +BenchmarkCropWebP 5000 242998 ns/op +BenchmarkExtractJpeg 50 26182413 ns/op +BenchmarkExtractPng 2000 800507 ns/op +BenchmarkExtractWebp 3000 553045 ns/op +BenchmarkZoomJpeg 10 166818184 ns/op +BenchmarkZoomPng 20 67196268 ns/op +BenchmarkZoomWebp 5000 364657 ns/op +BenchmarkWatermarkJpeg 100 10085519 ns/op +BenchmarkWatermarPng 200 7591993 ns/op +BenchmarkWatermarWebp 100 10150068 ns/op ok 9.424s ``` From 6f5f7ff6448d8a1335f6506aa17b6bf43bfee588 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sat, 11 Apr 2015 03:09:13 +0200 Subject: [PATCH 006/115] refactor(resize) --- resize.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/resize.go b/resize.go index 1bfe48d..53880c9 100644 --- a/resize.go +++ b/resize.go @@ -118,18 +118,18 @@ func Resize(buf []byte, o Options) ([]byte, error) { return nil, err } + // Add watermark if necessary + image, err = watermakImage(image, o.Watermark) + if err != nil { + return nil, err + } + saveOptions := vipsSaveOptions{ Quality: o.Quality, Type: o.Type, Compression: o.Compression, } - // watermark - image, err = watermakImage(image, o.Watermark) - if err != nil { - return nil, err - } - // Finally save as buffer buf, err = vipsSave(image, saveOptions) if err != nil { From b55834edf283a171b27b98fababa7145634c5c7f Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sat, 11 Apr 2015 03:12:49 +0200 Subject: [PATCH 007/115] refactor(vips.h) --- vips.h | 111 +++++++++++++++++++++++++++------------------------------ 1 file changed, 52 insertions(+), 59 deletions(-) diff --git a/vips.h b/vips.h index a896310..4886056 100644 --- a/vips.h +++ b/vips.h @@ -171,63 +171,56 @@ vips_init_image(void *buf, size_t len, int imageType, VipsImage **out) { int vips_watermark(VipsImage *in, VipsImage **out, watermarkTextOptions *to, watermarkOptions *o) { - double ones[3] = { 1, 1, 1 }; - - VipsImage *base = vips_image_new(); - VipsImage **t = (VipsImage **) vips_object_local_array(VIPS_OBJECT(base), 12); - t[0] = in; - - // Make the mask. - if ( - vips_text(&t[1], to->Text, - "width", o->Width, - "dpi", o->DPI, - "font", to->Font, - NULL) || - vips_linear1(t[1], &t[2], o->Opacity, 0.0, NULL) || - vips_cast(t[2], &t[3], VIPS_FORMAT_UCHAR, NULL) || - vips_embed(t[3], &t[4], 100, 100, - t[3]->Xsize + o->Margin, t[3]->Ysize + o->Margin, NULL) - ) { - g_object_unref(base); - return (1); - } - - // Replicate if necessary - if (o->NoReplicate != 1 && ( - vips_replicate(t[4], &t[5], - 1 + t[0]->Xsize / t[4]->Xsize, - 1 + t[0]->Ysize / t[4]->Ysize, NULL) || - vips_crop(t[5], &t[6], 0, 0, - t[0]->Xsize, t[0]->Ysize, NULL) - )) { - g_object_unref(base); - return (1); - } - - // Make the constant image to paint the text with. - if ( - vips_black(&t[7], 1, 1, NULL) || - vips_linear( t[7], &t[8], ones, o->Background, 3, NULL) || - vips_cast(t[8], &t[9], VIPS_FORMAT_UCHAR, NULL) || - vips_copy(t[9], &t[10], - "interpretation", t[0]->Type, - NULL) || - vips_embed(t[10], &t[11], 0, 0, - t[0]->Xsize, t[0]->Ysize, - "extend", VIPS_EXTEND_COPY, - NULL) - ) { - g_object_unref(base); - return (1); - } - - // Blend the mask and text and write to output. - if (vips_ifthenelse(t[6], t[11], t[0], out, "blend", TRUE, NULL)) { - g_object_unref(base); - return (1); - } - - g_object_unref(base); - return (0); + double ones[3] = { 1, 1, 1 }; + + VipsImage *base = vips_image_new(); + VipsImage **t = (VipsImage **) vips_object_local_array(VIPS_OBJECT(base), 12); + t[0] = in; + + // Make the mask. + if ( + vips_text(&t[1], to->Text, + "width", o->Width, + "dpi", o->DPI, + "font", to->Font, + NULL) || + vips_linear1(t[1], &t[2], o->Opacity, 0.0, NULL) || + vips_cast(t[2], &t[3], VIPS_FORMAT_UCHAR, NULL) || + vips_embed(t[3], &t[4], 100, 100, t[3]->Xsize + o->Margin, t[3]->Ysize + o->Margin, NULL) + ) { + g_object_unref(base); + return (1); + } + + // Replicate if necessary + if (o->NoReplicate != 1 && ( + vips_replicate(t[4], &t[5], + 1 + t[0]->Xsize / t[4]->Xsize, + 1 + t[0]->Ysize / t[4]->Ysize, NULL) || + vips_crop(t[5], &t[6], 0, 0, t[0]->Xsize, t[0]->Ysize, NULL)) + ) { + g_object_unref(base); + return (1); + } + + // Make the constant image to paint the text with. + if ( + vips_black(&t[7], 1, 1, NULL) || + vips_linear( t[7], &t[8], ones, o->Background, 3, NULL) || + vips_cast(t[8], &t[9], VIPS_FORMAT_UCHAR, NULL) || + vips_copy(t[9], &t[10], "interpretation", t[0]->Type, NULL) || + vips_embed(t[10], &t[11], 0, 0, t[0]->Xsize, t[0]->Ysize, "extend", VIPS_EXTEND_COPY, NULL) + ) { + g_object_unref(base); + return (1); + } + + // Blend the mask and text and write to output. + if (vips_ifthenelse(t[6], t[11], t[0], out, "blend", TRUE, NULL)) { + g_object_unref(base); + return (1); + } + + g_object_unref(base); + return (0); }; From 51c320fab7fcc864825214cfb0f75aa1c26eaa0f Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sat, 11 Apr 2015 12:12:29 +0200 Subject: [PATCH 008/115] refactor: vips.h, fix(docs): --- README.md | 38 ++++++++++++++++++++------------------ resize_test.go | 10 ++++++++++ vips.h | 4 ++-- 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 3e74039..2b678ab 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ bimg is designed to be a small and efficient library with a generic and useful s It uses internally libvips, which requires a [low memory footprint](http://www.vips.ecs.soton.ac.uk/index.php?title=Speed_and_Memory_Use) and it's typically 4x faster than using the quickest ImageMagick and GraphicsMagick settings or Go native `image` package, and in some cases it's even 8x faster processing JPEG images. -It can read JPEG, PNG, WEBP and TIFF formats and output to JPEG, PNG and WEBP. It supports common [image transformation](#supported-image-operations) operations such as crop, resize, rotate, zoom, watermark... and conversion between multiple formats. +It can read JPEG, PNG, WEBP and TIFF formats and output to JPEG, PNG and WEBP, including conversion betweem them. It supports common [image operations](#supported-image-operations) such as crop, resize, rotate, zoom, watermark... For getting started, take a look to the [examples](#examples) and [programmatic API](https://godoc.org/github.com/h2non/bimg) documentation. @@ -64,22 +64,24 @@ Here you can see some performance test comparisons for multiple scenarios: Tested using Go 1.4 and libvips-7.42.3 in OSX i7 2.7Ghz ``` BenchmarkResizeLargeJpeg 50 43400480 ns/op -BenchmarkResizePng 20 50736409 ns/op -BenchmarkResizeWebP 500 2455732 ns/op -BenchmarkConvertToJpeg 50 37313675 ns/op -BenchmarkCropJpeg 30 42894495 ns/op -BenchmarkCropPng 30 53256160 ns/op -BenchmarkCropWebP 5000 242998 ns/op -BenchmarkExtractJpeg 50 26182413 ns/op -BenchmarkExtractPng 2000 800507 ns/op -BenchmarkExtractWebp 3000 553045 ns/op -BenchmarkZoomJpeg 10 166818184 ns/op -BenchmarkZoomPng 20 67196268 ns/op -BenchmarkZoomWebp 5000 364657 ns/op -BenchmarkWatermarkJpeg 100 10085519 ns/op -BenchmarkWatermarPng 200 7591993 ns/op -BenchmarkWatermarWebp 100 10150068 ns/op -ok 9.424s +BenchmarkResizePng 20 57592174 ns/op +BenchmarkResizeWebP 500 2872295 ns/op +BenchmarkConvertToJpeg 30 41835497 ns/op +BenchmarkConvertToPng 10 153382204 ns/op +BenchmarkConvertToWebp 10000 264542 ns/op +BenchmarkCropJpeg 30 52267699 ns/op +BenchmarkCropPng 30 56477454 ns/op +BenchmarkCropWebP 5000 274302 ns/op +BenchmarkExtractJpeg 50 27827670 ns/op +BenchmarkExtractPng 2000 769761 ns/op +BenchmarkExtractWebp 3000 513954 ns/op +BenchmarkZoomJpeg 10 159272494 ns/op +BenchmarkZoomPng 20 65771476 ns/op +BenchmarkZoomWebp 5000 368327 ns/op +BenchmarkWatermarkJpeg 100 10026033 ns/op +BenchmarkWatermarPng 200 7350821 ns/op +BenchmarkWatermarWebp 200 9014197 ns/op +ok 30.698s ``` ## API @@ -195,7 +197,7 @@ options := bimg.Watermark{ } } -newImage, err := bimg.NewImage(buffer).Watermark() +newImage, err := bimg.NewImage(buffer).Watermark(options) if err != nil { fmt.Fprintln(os.Stderr, err) } diff --git a/resize_test.go b/resize_test.go index fba2485..4fcc091 100644 --- a/resize_test.go +++ b/resize_test.go @@ -190,6 +190,16 @@ func BenchmarkConvertToJpeg(b *testing.B) { runBenchmarkResize("test.png", options, b) } +func BenchmarkConvertToPng(b *testing.B) { + options := Options{Type: PNG} + runBenchmarkResize("test.jpg", options, b) +} + +func BenchmarkConvertToWebp(b *testing.B) { + options := Options{Type: WEBP} + runBenchmarkResize("test.jpg", options, b) +} + func BenchmarkCropJpeg(b *testing.B) { options := Options{ Width: 800, diff --git a/vips.h b/vips.h index 4886056..b012916 100644 --- a/vips.h +++ b/vips.h @@ -12,8 +12,8 @@ enum types { }; typedef struct { - char *Text; - char *Font; + const char *Text; + const char *Font; } watermarkTextOptions; typedef struct { From b065e902ed0c4eae1726e15d65d8054fa245b80f Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sat, 11 Apr 2015 12:58:15 +0200 Subject: [PATCH 009/115] refactor(vips.h): watermark replicate --- image_test.go | 31 ++++++++++++++++++++++++++++ vips.h | 57 ++++++++++++++++++++++++++++++++++----------------- 2 files changed, 69 insertions(+), 19 deletions(-) diff --git a/image_test.go b/image_test.go index ab0bb08..cc8d8b6 100644 --- a/image_test.go +++ b/image_test.go @@ -134,6 +134,37 @@ func TestImageWatermark(t *testing.T) { Write("fixtures/test_watermark_out.jpg", buf) } +func TestImageWatermarkNoReplicate(t *testing.T) { + image := initImage("test.jpg") + _, err := image.Crop(800, 600, NORTH) + if err != nil { + t.Errorf("Cannot process the image: %#v", err) + } + + buf, err := image.Watermark(Watermark{ + Text: "Copy me if you can", + Opacity: 0.5, + Width: 200, + DPI: 100, + NoReplicate: true, + Background: Color{255, 255, 255}, + }) + if err != nil { + t.Error(err) + } + + err = assertSize(buf, 800, 600) + if err != nil { + t.Error(err) + } + + if DetermineImageType(buf) != JPEG { + t.Fatal("Image is not jpeg") + } + + Write("fixtures/test_watermark_replicate_out.jpg", buf) +} + func TestImageZoom(t *testing.T) { buf, err := initImage("test.jpg").Zoom(1) if err != nil { diff --git a/vips.h b/vips.h index b012916..2537cae 100644 --- a/vips.h +++ b/vips.h @@ -168,13 +168,31 @@ vips_init_image(void *buf, size_t len, int imageType, VipsImage **out) { return code; }; +int +vips_watermark_replicate(VipsImage *orig, VipsImage *in, VipsImage **out) +{ + VipsImage *cache = vips_image_new(); + + if ( + vips_replicate(in, &cache, + 1 + orig->Xsize / in->Xsize, + 1 + orig->Ysize / in->Ysize, NULL) || + vips_crop(cache, out, 0, 0, orig->Xsize, orig->Ysize, NULL) + ) { + g_object_unref(cache); + return 1; + } + + return 0; +}; + int vips_watermark(VipsImage *in, VipsImage **out, watermarkTextOptions *to, watermarkOptions *o) { double ones[3] = { 1, 1, 1 }; VipsImage *base = vips_image_new(); - VipsImage **t = (VipsImage **) vips_object_local_array(VIPS_OBJECT(base), 12); + VipsImage **t = (VipsImage **) vips_object_local_array(VIPS_OBJECT(base), 10); t[0] = in; // Make the mask. @@ -189,38 +207,39 @@ vips_watermark(VipsImage *in, VipsImage **out, watermarkTextOptions *to, waterma vips_embed(t[3], &t[4], 100, 100, t[3]->Xsize + o->Margin, t[3]->Ysize + o->Margin, NULL) ) { g_object_unref(base); - return (1); + return 1; } // Replicate if necessary - if (o->NoReplicate != 1 && ( - vips_replicate(t[4], &t[5], - 1 + t[0]->Xsize / t[4]->Xsize, - 1 + t[0]->Ysize / t[4]->Ysize, NULL) || - vips_crop(t[5], &t[6], 0, 0, t[0]->Xsize, t[0]->Ysize, NULL)) - ) { - g_object_unref(base); - return (1); + if (o->NoReplicate != 1) { + VipsImage *cache = vips_image_new(); + if (vips_watermark_replicate(t[0], t[4], &cache)) { + g_object_unref(cache); + g_object_unref(base); + return 1; + } + g_object_unref(t[4]); + t[4] = cache; } // Make the constant image to paint the text with. if ( - vips_black(&t[7], 1, 1, NULL) || - vips_linear( t[7], &t[8], ones, o->Background, 3, NULL) || - vips_cast(t[8], &t[9], VIPS_FORMAT_UCHAR, NULL) || - vips_copy(t[9], &t[10], "interpretation", t[0]->Type, NULL) || - vips_embed(t[10], &t[11], 0, 0, t[0]->Xsize, t[0]->Ysize, "extend", VIPS_EXTEND_COPY, NULL) + vips_black(&t[5], 1, 1, NULL) || + vips_linear(t[5], &t[6], ones, o->Background, 3, NULL) || + vips_cast(t[6], &t[7], VIPS_FORMAT_UCHAR, NULL) || + vips_copy(t[7], &t[8], "interpretation", t[0]->Type, NULL) || + vips_embed(t[8], &t[9], 0, 0, t[0]->Xsize, t[0]->Ysize, "extend", VIPS_EXTEND_COPY, NULL) ) { g_object_unref(base); - return (1); + return 1; } // Blend the mask and text and write to output. - if (vips_ifthenelse(t[6], t[11], t[0], out, "blend", TRUE, NULL)) { + if (vips_ifthenelse(t[4], t[9], t[0], out, "blend", TRUE, NULL)) { g_object_unref(base); - return (1); + return 1; } g_object_unref(base); - return (0); + return 0; }; From 2bc5756fad3d2ad6bb1bca7d634300f70fa24283 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sat, 11 Apr 2015 14:19:15 +0200 Subject: [PATCH 010/115] feat(test): better coverage for vips interface --- resize_test.go | 30 ++++------------- vips.h | 1 + vips_test.go | 91 ++++++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 85 insertions(+), 37 deletions(-) diff --git a/resize_test.go b/resize_test.go index 4fcc091..cb8dfe3 100644 --- a/resize_test.go +++ b/resize_test.go @@ -20,10 +20,7 @@ func TestResize(t *testing.T) { t.Fatal("Image is not jpeg") } - err = Write("fixtures/test_out.jpg", newImg) - if err != nil { - t.Fatal("Cannot save the image") - } + Write("fixtures/test_out.jpg", newImg) } func TestRotate(t *testing.T) { @@ -39,10 +36,7 @@ func TestRotate(t *testing.T) { t.Fatal("Image is not jpeg") } - err = Write("fixtures/test_rotate_out.jpg", newImg) - if err != nil { - t.Fatal("Cannot save the image") - } + Write("fixtures/test_rotate_out.jpg", newImg) } func TestCorruptedImage(t *testing.T) { @@ -58,10 +52,7 @@ func TestCorruptedImage(t *testing.T) { t.Fatal("Image is not jpeg") } - err = Write("fixtures/test_corrupt_out.jpg", newImg) - if err != nil { - t.Fatal("Cannot save the image") - } + Write("fixtures/test_corrupt_out.jpg", newImg) } func TestInvalidRotate(t *testing.T) { @@ -77,10 +68,7 @@ func TestInvalidRotate(t *testing.T) { t.Fatal("Image is not jpeg") } - err = Write("fixtures/test_invalid_rotate_out.jpg", newImg) - if err != nil { - t.Fatal("Cannot save the image") - } + Write("fixtures/test_invalid_rotate_out.jpg", newImg) } func TestConvert(t *testing.T) { @@ -112,10 +100,7 @@ func TestConvert(t *testing.T) { t.Fatal("Invalid image size") } - err = Write("fixtures/test_out.png", newImg) - if err != nil { - t.Fatal("Cannot save the image") - } + Write("fixtures/test_out.png", newImg) } func TestResizePngWithTransparency(t *testing.T) { @@ -147,10 +132,7 @@ func TestResizePngWithTransparency(t *testing.T) { t.Fatal("Invalid image size") } - err = Write("fixtures/transparent_out.png", newImg) - if err != nil { - t.Fatal("Cannot save the image") - } + Write("fixtures/transparent_out.png", newImg) } func runBenchmarkResize(file string, o Options, b *testing.B) { diff --git a/vips.h b/vips.h index 2537cae..23b9613 100644 --- a/vips.h +++ b/vips.h @@ -243,3 +243,4 @@ vips_watermark(VipsImage *in, VipsImage **out, watermarkTextOptions *to, waterma g_object_unref(base); return 0; }; + diff --git a/vips_test.go b/vips_test.go index 533e810..5eab2c3 100644 --- a/vips_test.go +++ b/vips_test.go @@ -18,37 +18,102 @@ func TestVipsRead(t *testing.T) { } for _, file := range files { - img, _ := os.Open(path.Join("fixtures", file.name)) - buf, _ := ioutil.ReadAll(img) - defer img.Close() - - image, imageType, _ := vipsRead(buf) + image, imageType, _ := vipsRead(readImage(file.name)) if image == nil { t.Fatal("Empty image") } if imageType != file.expected { - t.Fatal("Empty image") + t.Fatal("Invalid image type") } } } func TestVipsSave(t *testing.T) { - img, _ := os.Open(path.Join("fixtures", "test.jpg")) - buf, _ := ioutil.ReadAll(img) - defer img.Close() + image, _, _ := vipsRead(readImage("test.jpg")) + options := vipsSaveOptions{Quality: 95, Type: JPEG} - image, _, _ := vipsRead(buf) - if image == nil { + buf, err := vipsSave(image, options) + if err != nil { + t.Fatal("Cannot save the image") + } + if len(buf) == 0 { t.Fatal("Empty image") } +} - options := vipsSaveOptions{Quality: 95, Type: JPEG} +func TestVipsRotate(t *testing.T) { + image, _, _ := vipsRead(readImage("test.jpg")) - buf, err := vipsSave(image, options) + newImg, err := vipsRotate(image, D90) + if err != nil { + t.Fatal("Cannot save the image") + } + + buf, _ := vipsSave(newImg, vipsSaveOptions{Quality: 95}) + if len(buf) == 0 { + t.Fatal("Empty image") + } +} + +func TestVipsZoom(t *testing.T) { + image, _, _ := vipsRead(readImage("test.jpg")) + + newImg, err := vipsRotate(image, D90) if err != nil { t.Fatal("Cannot save the image") } + + buf, _ := vipsSave(newImg, vipsSaveOptions{Quality: 95}) if len(buf) == 0 { t.Fatal("Empty image") } } + +func TestVipsWatermark(t *testing.T) { + image, _, _ := vipsRead(readImage("test.jpg")) + + watermark := Watermark{ + Text: "Copy me if you can", + Font: "sans bold 12", + Opacity: 0.5, + Width: 200, + DPI: 100, + Margin: 100, + Background: Color{255, 255, 255}, + } + + newImg, err := vipsWatermark(image, watermark) + if err != nil { + t.Errorf("Cannot add watermark: %s", err) + } + + buf, _ := vipsSave(newImg, vipsSaveOptions{Quality: 95}) + if len(buf) == 0 { + t.Fatal("Empty image") + } +} + +func TestVipsImageType(t *testing.T) { + imgType := vipsImageType(readImage("test.jpg")) + if imgType != JPEG { + t.Fatal("Invalid image type") + } +} + +func TestVipsMemory(t *testing.T) { + mem := VipsMemory() + + if mem.Memory < 1024 { + t.Fatal("Invalid memory") + } + if mem.Allocations == 0 { + t.Fatal("Invalid memory allocations") + } +} + +func readImage(file string) []byte { + img, _ := os.Open(path.Join("fixtures", file)) + buf, _ := ioutil.ReadAll(img) + defer img.Close() + return buf +} From 16b8301f5ba007d7f35a777397e7bbd5482d0269 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sat, 11 Apr 2015 14:20:26 +0200 Subject: [PATCH 011/115] feat(docs): update docs --- README.md | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2b678ab..572afe6 100644 --- a/README.md +++ b/README.md @@ -243,7 +243,7 @@ Determines the image type format by name (jpeg, png, webp or tiff) ```go func Initialize() ``` -Explicit thread-safe start of libvips. You should only call this function if you +Explicit thread-safe start of libvips. Only call this function if you've previously shutdown libvips #### func IsTypeNameSupported @@ -260,6 +260,13 @@ func IsTypeSupported(t ImageType) bool ``` Check if a given image type is supported +#### func PrintMemoryStats + +```go +func PrintMemoryStats() +``` +Print Go memory and garbage collector stats. Useful for debugging + #### func Read ```go @@ -285,7 +292,7 @@ already initialized, the function is no-op ```go func VipsDebug() ``` -Output to stdout collected data for debugging purposes +Output to stdout vips collected data. Useful for debugging #### func Write @@ -309,6 +316,16 @@ const ( ) ``` +#### type Color + +```go +type Color struct { + R, G, B uint8 +} +``` + +Color represents a traditional RGB color scheme + #### type Direction ```go @@ -411,6 +428,13 @@ func (i *Image) Flop() ([]byte, error) ``` Flop the image about the horizontal X axis +#### func (*Image) Image + +```go +func (i *Image) Image() []byte +``` +Get image buffer + #### func (*Image) Metadata ```go @@ -460,6 +484,20 @@ func (i *Image) Type() string ``` Get image type format (jpeg, png, webp, tiff) +#### func (*Image) Watermark + +```go +func (i *Image) Watermark(w Watermark) ([]byte, error) +``` +Add text as watermark on the given image + +#### func (*Image) Zoom + +```go +func (i *Image) Zoom(level int) ([]byte, error) +``` +Zoom the image by the given factor + #### type ImageMetadata ```go @@ -531,6 +569,7 @@ Determines the image type format (jpeg, png, webp or tiff) type Interpolator int ``` + ```go const ( BICUBIC Interpolator = iota @@ -558,18 +597,55 @@ type Options struct { Extend int Quality int Compression int + Zoom int Crop bool Enlarge bool Embed bool Flip bool Flop bool + NoAutoRotate bool Rotate Angle Gravity Gravity + Watermark Watermark Type ImageType Interpolator Interpolator } ``` + +#### type VipsMemoryInfo + +```go +type VipsMemoryInfo struct { + Memory int64 + MemoryHighwater int64 + Allocations int64 +} +``` + + +#### func VipsMemory + +```go +func VipsMemory() VipsMemoryInfo +``` +Get memory info stats from vips (cache size, memory allocs...) + +#### type Watermark + +```go +type Watermark struct { + Width int + DPI int + Margin int + Opacity float32 + NoReplicate bool + Text string + Font string + Background Color +} +``` + ## Special Thanks - [John Cupitt](https://github.com/jcupitt) From eff3f079af61971c79299bc248663f5c3bc55f80 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sat, 11 Apr 2015 14:21:05 +0200 Subject: [PATCH 012/115] feat(version): bump --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index 7e8411f..9dccc14 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,3 @@ package bimg -const Version = "0.1.6" +const Version = "0.1.7" From 3f063b6a2f4ae39cc19b3fa6aa1b4ce84a79716e Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sat, 11 Apr 2015 15:04:13 +0200 Subject: [PATCH 013/115] refactor: comparse as pure string --- resize.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resize.go b/resize.go index 53880c9..0de04ae 100644 --- a/resize.go +++ b/resize.go @@ -199,12 +199,12 @@ func rotateImage(image *C.struct__VipsImage, o Options) (*C.struct__VipsImage, e } func watermakImage(image *C.struct__VipsImage, w Watermark) (*C.struct__VipsImage, error) { - if len(w.Text) == 0 { + if w.Text == "" { return image, nil } // Defaults - if len(w.Font) == 0 { + if w.Font == "" { w.Font = "sans 10" } if w.Width == 0 { From 924be1d3f434bd4abdb9bad1ce5f95645d2ad786 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sun, 12 Apr 2015 11:44:26 +0200 Subject: [PATCH 014/115] fix(#28): zoom requires extract params --- README.md | 7 ++++--- image.go | 7 ++++--- image_test.go | 25 ++++++++++++++++--------- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 572afe6..85bb97b 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,11 @@ bimg is designed to be a small and efficient library with a generic and useful s It uses internally libvips, which requires a [low memory footprint](http://www.vips.ecs.soton.ac.uk/index.php?title=Speed_and_Memory_Use) and it's typically 4x faster than using the quickest ImageMagick and GraphicsMagick settings or Go native `image` package, and in some cases it's even 8x faster processing JPEG images. -It can read JPEG, PNG, WEBP and TIFF formats and output to JPEG, PNG and WEBP, including conversion betweem them. It supports common [image operations](#supported-image-operations) such as crop, resize, rotate, zoom, watermark... +It can read JPEG, PNG, WEBP and TIFF formats and output to JPEG, PNG and WEBP, including conversion between them. It supports common [image operations](#supported-image-operations) such as crop, resize, rotate, zoom, watermark... For getting started, take a look to the [examples](#examples) and [programmatic API](https://godoc.org/github.com/h2non/bimg) documentation. -bimg was heavily inspired in [sharp](https://github.com/lovell/sharp), -its homologous package built for node.js by [Lovell Fuller](https://github.com/lovell). +bimg was heavily inspired in [sharp](https://github.com/lovell/sharp), its homologous package built for node.js by [Lovell Fuller](https://github.com/lovell). **Note**: bimg is still beta. Do not use in production yet @@ -653,3 +652,5 @@ type Watermark struct { ## License MIT - Tomas Aparicio + +[![views](https://sourcegraph.com/api/repos/github.com/h2non/bimg/.counters/views.svg)](https://sourcegraph.com/github.com/h2non/bimg) diff --git a/image.go b/image.go index fe0d777..85f0b6d 100644 --- a/image.go +++ b/image.go @@ -81,9 +81,10 @@ func (i *Image) Watermark(w Watermark) ([]byte, error) { return i.Process(options) } -// Zoom the image by the given factor -func (i *Image) Zoom(level int) ([]byte, error) { - options := Options{Zoom: level} +// Zoom the image by the given factor. +// You should probably call Extract() before +func (i *Image) Zoom(factor int) ([]byte, error) { + options := Options{Zoom: factor} return i.Process(options) } diff --git a/image_test.go b/image_test.go index cc8d8b6..29e3bd4 100644 --- a/image_test.go +++ b/image_test.go @@ -23,7 +23,7 @@ func TestImageResize(t *testing.T) { func TestImageExtract(t *testing.T) { buf, err := initImage("test.jpg").Extract(100, 100, 300, 300) if err != nil { - t.Errorf("Cannot process the image: %#v", err) + t.Errorf("Cannot process the image: %s", err) } err = assertSize(buf, 300, 300) @@ -51,7 +51,7 @@ func TestImageEnlarge(t *testing.T) { func TestImageCrop(t *testing.T) { buf, err := initImage("test.jpg").Crop(800, 600, NORTH) if err != nil { - t.Errorf("Cannot process the image: %#v", err) + t.Errorf("Cannot process the image: %s", err) } err = assertSize(buf, 800, 600) @@ -65,7 +65,7 @@ func TestImageCrop(t *testing.T) { func TestImageCropByWidth(t *testing.T) { buf, err := initImage("test.jpg").CropByWidth(600) if err != nil { - t.Errorf("Cannot process the image: %#v", err) + t.Errorf("Cannot process the image: %s", err) } err = assertSize(buf, 600, 375) @@ -79,7 +79,7 @@ func TestImageCropByWidth(t *testing.T) { func TestImageCropByHeight(t *testing.T) { buf, err := initImage("test.jpg").CropByHeight(300) if err != nil { - t.Errorf("Cannot process the image: %#v", err) + t.Errorf("Cannot process the image: %s", err) } err = assertSize(buf, 480, 300) @@ -93,7 +93,7 @@ func TestImageCropByHeight(t *testing.T) { func TestImageThumbnail(t *testing.T) { buf, err := initImage("test.jpg").Thumbnail(100) if err != nil { - t.Errorf("Cannot process the image: %#v", err) + t.Errorf("Cannot process the image: %s", err) } err = assertSize(buf, 100, 100) @@ -138,7 +138,7 @@ func TestImageWatermarkNoReplicate(t *testing.T) { image := initImage("test.jpg") _, err := image.Crop(800, 600, NORTH) if err != nil { - t.Errorf("Cannot process the image: %#v", err) + t.Errorf("Cannot process the image: %s", err) } buf, err := image.Watermark(Watermark{ @@ -166,12 +166,19 @@ func TestImageWatermarkNoReplicate(t *testing.T) { } func TestImageZoom(t *testing.T) { - buf, err := initImage("test.jpg").Zoom(1) + image := initImage("test.jpg") + + _, err := image.Extract(100, 100, 400, 300) if err != nil { - t.Errorf("Cannot process the image: %#v", err) + t.Errorf("Cannot extract the image: %s", err) + } + + buf, err := image.Zoom(1) + if err != nil { + t.Errorf("Cannot process the image: %s", err) } - err = assertSize(buf, 3360, 2100) + err = assertSize(buf, 800, 600) if err != nil { t.Error(err) } From 0bff93ddae0cb1ec88d72e29dc244b3828fea8ff Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sun, 12 Apr 2015 11:46:34 +0200 Subject: [PATCH 015/115] fix(#28): zoom requires extract params --- image_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/image_test.go b/image_test.go index 29e3bd4..edb46ee 100644 --- a/image_test.go +++ b/image_test.go @@ -168,7 +168,7 @@ func TestImageWatermarkNoReplicate(t *testing.T) { func TestImageZoom(t *testing.T) { image := initImage("test.jpg") - _, err := image.Extract(100, 100, 400, 300) + _, err := image.Extract(100, 100, 0, 0) if err != nil { t.Errorf("Cannot extract the image: %s", err) } @@ -180,7 +180,7 @@ func TestImageZoom(t *testing.T) { err = assertSize(buf, 800, 600) if err != nil { - t.Error(err) + //t.Error(err) } Write("fixtures/test_zoom_out.jpg", buf) From 4bf5d7391e8bf02a575e1d06e708ab34d86bd643 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sun, 12 Apr 2015 12:39:26 +0200 Subject: [PATCH 016/115] refactor(watermark): auto define width --- image_test.go | 4 ++-- resize.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/image_test.go b/image_test.go index edb46ee..29e3bd4 100644 --- a/image_test.go +++ b/image_test.go @@ -168,7 +168,7 @@ func TestImageWatermarkNoReplicate(t *testing.T) { func TestImageZoom(t *testing.T) { image := initImage("test.jpg") - _, err := image.Extract(100, 100, 0, 0) + _, err := image.Extract(100, 100, 400, 300) if err != nil { t.Errorf("Cannot extract the image: %s", err) } @@ -180,7 +180,7 @@ func TestImageZoom(t *testing.T) { err = assertSize(buf, 800, 600) if err != nil { - //t.Error(err) + t.Error(err) } Write("fixtures/test_zoom_out.jpg", buf) diff --git a/resize.go b/resize.go index 0de04ae..253c626 100644 --- a/resize.go +++ b/resize.go @@ -208,7 +208,7 @@ func watermakImage(image *C.struct__VipsImage, w Watermark) (*C.struct__VipsImag w.Font = "sans 10" } if w.Width == 0 { - w.Width = int(math.Floor(float64(image.Xsize / 8))) + w.Width = int(math.Floor(float64(image.Xsize / 6))) } if w.DPI == 0 { w.DPI = 150 From 61424367437477cb939359b4643ddd84361de1cd Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sun, 12 Apr 2015 17:03:52 +0200 Subject: [PATCH 017/115] fix(vips): panic error on exif orientation --- README.md | 11 +++++++++-- metadata_test.go | 2 +- vips.h | 6 +++--- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 85bb97b..0912ba9 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ Small [Go](http://golang.org) library for blazing fast and efficient image processing based on [libvips](https://github.com/jcupitt/libvips) using C bindings. It provides a clean, simple and fluent [API](https://godoc.org/github.com/h2non/bimg) in pure Go. -bimg is designed to be a small and efficient library with a generic and useful set of features. -It uses internally libvips, which requires a [low memory footprint](http://www.vips.ecs.soton.ac.uk/index.php?title=Speed_and_Memory_Use) +bimg was designed to be a small and efficient library with a generic and useful features. +It uses internally libvips, a powerful library written in C for binary image processing which requires a [low memory footprint](http://www.vips.ecs.soton.ac.uk/index.php?title=Speed_and_Memory_Use) and it's typically 4x faster than using the quickest ImageMagick and GraphicsMagick settings or Go native `image` package, and in some cases it's even 8x faster processing JPEG images. It can read JPEG, PNG, WEBP and TIFF formats and output to JPEG, PNG and WEBP, including conversion between them. It supports common [image operations](#supported-image-operations) such as crop, resize, rotate, zoom, watermark... @@ -230,6 +230,13 @@ if err != nil { bimg.Write("new.jpg", newImage) ``` +#### Debugging + +Run the process passing the `DEBUG` environment variable +``` +DEBUG=* ./app +``` + #### func DetermineImageTypeName ```go diff --git a/metadata_test.go b/metadata_test.go index f5021ec..9698b65 100644 --- a/metadata_test.go +++ b/metadata_test.go @@ -47,7 +47,7 @@ func TestMetadata(t *testing.T) { for _, file := range files { metadata, err := Metadata(readFile(file.name)) if err != nil { - t.Fatalf("Cannot read the image: %#v", err) + t.Fatalf("Cannot read the image: %s -> %s", file.name, err) } if metadata.Type != file.format { diff --git a/vips.h b/vips.h index 23b9613..d0de8e9 100644 --- a/vips.h +++ b/vips.h @@ -68,12 +68,12 @@ vips_rotate(VipsImage *in, VipsImage **buf, int angle) int vips_exif_orientation(VipsImage *image) { int orientation = 0; - const char **exif; + const char *exif; if ( vips_image_get_typeof(image, "exif-ifd0-Orientation") != 0 && - !vips_image_get_string(image, "exif-ifd0-Orientation", exif) + !vips_image_get_string(image, "exif-ifd0-Orientation", &exif) ) { - orientation = atoi(exif[0]); + orientation = atoi(&exif[0]); } return orientation; }; From 1d80105872f0f68444714a177991298387528a76 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sun, 12 Apr 2015 17:04:09 +0200 Subject: [PATCH 018/115] feat(version): bump --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index 9dccc14..545ef0c 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,3 @@ package bimg -const Version = "0.1.7" +const Version = "0.1.8" From 82a805706718634a3ef31874c27c50e78f2ac05c Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sun, 12 Apr 2015 17:05:49 +0200 Subject: [PATCH 019/115] feat(docs): add imaginary link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0912ba9..db51ad0 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ For getting started, take a look to the [examples](#examples) and [programmatic bimg was heavily inspired in [sharp](https://github.com/lovell/sharp), its homologous package built for node.js by [Lovell Fuller](https://github.com/lovell). -**Note**: bimg is still beta. Do not use in production yet +If you're looking for a HTTP-based image processing solution, see [imaginary](https://github.com/h2non/imaginary) ## Prerequisites From e7b430f0d3f826f508381700942a832ca42d2146 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sun, 12 Apr 2015 17:12:08 +0200 Subject: [PATCH 020/115] feat(docs): add imaginary link --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index db51ad0..7bc4623 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,11 @@ It can read JPEG, PNG, WEBP and TIFF formats and output to JPEG, PNG and WEBP, i For getting started, take a look to the [examples](#examples) and [programmatic API](https://godoc.org/github.com/h2non/bimg) documentation. +If you're looking for a HTTP-based image processing solution, see [imaginary](https://github.com/h2non/imaginary) + bimg was heavily inspired in [sharp](https://github.com/lovell/sharp), its homologous package built for node.js by [Lovell Fuller](https://github.com/lovell). -If you're looking for a HTTP-based image processing solution, see [imaginary](https://github.com/h2non/imaginary) +**Note**: bimg is still beta. Do not use in compromised environments yet ## Prerequisites From e7576e085c31a7b9187460b476fbe990a39192d5 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sun, 12 Apr 2015 17:43:11 +0200 Subject: [PATCH 021/115] refactor(file) --- file.go | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/file.go b/file.go index b6c5b04..e84fb7a 100644 --- a/file.go +++ b/file.go @@ -1,23 +1,9 @@ package bimg -import ( - "io/ioutil" - "os" -) +import "io/ioutil" func Read(path string) ([]byte, error) { - file, err := os.Open(path) - if err != nil { - return nil, err - } - defer file.Close() - - buf, err := ioutil.ReadAll(file) - if err != nil { - return nil, err - } - - return buf, nil + return ioutil.ReadFile(path) } func Write(path string, buf []byte) error { From 93c0637ac894f70136e86646f4863fbc5bbfe4dc Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sun, 12 Apr 2015 20:46:53 +0200 Subject: [PATCH 022/115] refactor(docs): update badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7bc4623..2382683 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# bimg [![Build Status](https://travis-ci.org/h2non/bimg.png)](https://travis-ci.org/h2non/bimg) [![GitHub release](https://img.shields.io/github/tag/h2non/bimg.svg)](https://github.com/h2non/bimg/releases) [![GoDoc](https://godoc.org/github.com/h2non/bimg?status.png)](https://godoc.org/github.com/h2non/bimg) [![Coverage Status](https://coveralls.io/repos/h2non/bimg/badge.svg?branch=master)](https://coveralls.io/r/h2non/bimg?branch=master) +# bimg [![Build Status](https://travis-ci.org/h2non/bimg.png)](https://travis-ci.org/h2non/bimg) [![GitHub release](https://img.shields.io/github/tag/h2non/bimg.svg)](https://github.com/h2non/bimg/releases) [![GoDoc](https://godoc.org/github.com/h2non/bimg?status.svg)](https://godoc.org/github.com/h2non/bimg) [![Coverage Status](https://coveralls.io/repos/h2non/bimg/badge.svg?branch=master)](https://coveralls.io/r/h2non/bimg?branch=master) Small [Go](http://golang.org) library for blazing fast and efficient image processing based on [libvips](https://github.com/jcupitt/libvips) using C bindings. It provides a clean, simple and fluent [API](https://godoc.org/github.com/h2non/bimg) in pure Go. From 6dbab52dbd1a61d4c41e19ffd7a989e29cb756dc Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sun, 12 Apr 2015 22:15:56 +0200 Subject: [PATCH 023/115] refactor(docs) --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 2382683..065cb83 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,7 @@ and it's typically 4x faster than using the quickest ImageMagick and GraphicsMag It can read JPEG, PNG, WEBP and TIFF formats and output to JPEG, PNG and WEBP, including conversion between them. It supports common [image operations](#supported-image-operations) such as crop, resize, rotate, zoom, watermark... -For getting started, take a look to the [examples](#examples) and [programmatic API](https://godoc.org/github.com/h2non/bimg) documentation. - -If you're looking for a HTTP-based image processing solution, see [imaginary](https://github.com/h2non/imaginary) +For getting started, take a look to the [examples](#examples) and [programmatic API](https://godoc.org/github.com/h2non/bimg) documentation. If you're looking for a HTTP-based image processing solution, see [imaginary](https://github.com/h2non/imaginary). bimg was heavily inspired in [sharp](https://github.com/lovell/sharp), its homologous package built for node.js by [Lovell Fuller](https://github.com/lovell). From e111d875e744f1601462efd681f03ae1b47c7191 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Wed, 15 Apr 2015 02:25:57 +0200 Subject: [PATCH 024/115] fix(#30): one concurrent thread by default --- README.md | 7 ++++++- vips.go | 23 ++++++++++++++++++----- vips.h | 11 ++++++++--- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 065cb83..609fbf7 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ Here you can see some performance test comparisons for multiple scenarios: #### Benchmarks -Tested using Go 1.4 and libvips-7.42.3 in OSX i7 2.7Ghz +Tested using Go 1.4.2 and libvips-7.42.3 in OSX i7 2.7Ghz ``` BenchmarkResizeLargeJpeg 50 43400480 ns/op BenchmarkResizePng 20 57592174 ns/op @@ -237,6 +237,11 @@ Run the process passing the `DEBUG` environment variable DEBUG=* ./app ``` +Enable libvips traces (note that a lot of data will be written in stdout): +``` +VIPS_TRACE=1 ./app +``` + #### func DetermineImageTypeName ```go diff --git a/vips.go b/vips.go index 5d7a966..d92c1ba 100644 --- a/vips.go +++ b/vips.go @@ -9,6 +9,7 @@ import "C" import ( "errors" + "os" "runtime" "strings" "sync" @@ -68,9 +69,19 @@ func Initialize() { panic("unable to start vips!") } - C.vips_concurrency_set(0) // default - C.vips_cache_set_max_mem(100 * 1024 * 1024) // 100 MB - C.vips_cache_set_max(500) // 500 operations + C.vips_cache_set_max_mem(100 * 1024 * 1024) + C.vips_cache_set_max(500) + + // Explicit concurrency limit to avoid thread-unsafe issues. + // See: https://github.com/jcupitt/libvips/issues/261#issuecomment-92850414 + if os.Getenv("VIPS_CONCURRENCY") == "" { + C.vips_concurrency_set(1) + } + + if os.Getenv("VIPS_TRACE") != "" { + C.vips_enable_cache_set_trace() + } + initialized = true } @@ -113,7 +124,9 @@ func vipsHasProfile(image *C.struct__VipsImage) bool { } func vipsWindowSize(name string) float64 { - return float64(C.interpolator_window_size(C.CString(name))) + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + return float64(C.interpolator_window_size(cname)) } func vipsSpace(image *C.struct__VipsImage) string { @@ -175,7 +188,7 @@ func vipsWatermark(image *C.struct__VipsImage, w Watermark) (*C.struct__VipsImag defer C.free(unsafe.Pointer(text)) defer C.free(unsafe.Pointer(font)) - err := C.vips_watermark(image, &out, (*C.watermarkTextOptions)(unsafe.Pointer(&textOpts)), (*C.watermarkOptions)(unsafe.Pointer(&opts))) + err := C.vips_watermark(image, &out, (*C.WatermarkTextOptions)(unsafe.Pointer(&textOpts)), (*C.WatermarkOptions)(unsafe.Pointer(&opts))) if err != 0 { return nil, catchVipsError() } diff --git a/vips.h b/vips.h index d0de8e9..445a755 100644 --- a/vips.h +++ b/vips.h @@ -14,7 +14,7 @@ enum types { typedef struct { const char *Text; const char *Font; -} watermarkTextOptions; +} WatermarkTextOptions; typedef struct { int Width; @@ -23,7 +23,7 @@ typedef struct { int NoReplicate; float Opacity; double Background[3]; -} watermarkOptions; +} WatermarkOptions; int vips_affine_interpolator(VipsImage *in, VipsImage **out, double a, double b, double c, double d, VipsInterpolate *interpolator) @@ -187,7 +187,7 @@ vips_watermark_replicate(VipsImage *orig, VipsImage *in, VipsImage **out) }; int -vips_watermark(VipsImage *in, VipsImage **out, watermarkTextOptions *to, watermarkOptions *o) +vips_watermark(VipsImage *in, VipsImage **out, WatermarkTextOptions *to, WatermarkOptions *o) { double ones[3] = { 1, 1, 1 }; @@ -244,3 +244,8 @@ vips_watermark(VipsImage *in, VipsImage **out, watermarkTextOptions *to, waterma return 0; }; +void +vips_enable_cache_set_trace() +{ + vips_cache_set_trace(TRUE); +}; From b8925f9f30007164e75cedf1eadc3f93d622d7d4 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Wed, 15 Apr 2015 02:26:41 +0200 Subject: [PATCH 025/115] ffeat(version): bump --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index 545ef0c..3271f26 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,3 @@ package bimg -const Version = "0.1.8" +const Version = "0.1.9" From eb933a8c38d414580745c4b4e14b6f82a8a96aa8 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Wed, 15 Apr 2015 16:21:17 +0200 Subject: [PATCH 026/115] refactor(vips): remove obvious code --- vips.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/vips.go b/vips.go index d92c1ba..53ae1f2 100644 --- a/vips.go +++ b/vips.go @@ -18,7 +18,7 @@ import ( var ( m sync.Mutex - initialized bool = false + initialized bool ) type VipsMemoryInfo struct { @@ -48,16 +48,16 @@ type vipsWatermarkTextOptions struct { } func init() { - if C.VIPS_MAJOR_VERSION <= 7 && C.VIPS_MINOR_VERSION < 40 { - panic("unsupported old vips version!") - } - Initialize() } // Explicit thread-safe start of libvips. // Only call this function if you've previously shutdown libvips func Initialize() { + if C.VIPS_MAJOR_VERSION <= 7 && C.VIPS_MINOR_VERSION < 40 { + panic("unsupported libvips version!") + } + m.Lock() runtime.LockOSThread() defer m.Unlock() @@ -65,7 +65,6 @@ func Initialize() { err := C.vips_init(C.CString("bimg")) if err != 0 { - Shutdown() panic("unable to start vips!") } @@ -85,13 +84,13 @@ func Initialize() { initialized = true } -// Explicit thread-safe libvips shutdown. Call this to drop caches. +// Thread-safe function to shutdown libvips. You could call this to drop caches as well. // If libvips was already initialized, the function is no-op func Shutdown() { m.Lock() defer m.Unlock() - if initialized == true { + if initialized { C.vips_shutdown() initialized = false } From c2a502a611498182bec73f7a935712718d949923 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Thu, 16 Apr 2015 02:24:44 +0200 Subject: [PATCH 027/115] fix(#31) --- resize.go | 28 ++++++++++++++-------------- resize_test.go | 2 +- vips.go | 3 ++- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/resize.go b/resize.go index 253c626..c348a57 100644 --- a/resize.go +++ b/resize.go @@ -48,6 +48,13 @@ func Resize(buf []byte, o Options) ([]byte, error) { shrink := int(math.Max(math.Floor(factor), 1)) residual := float64(shrink) / factor + // Calculate integral box shrink + windowSize := vipsWindowSize(o.Interpolator.String()) + if factor >= 2 && windowSize > 3 { + // Shrink less, affine more with interpolators that use at least 4x4 pixel window, e.g. bicubic + shrink = int(math.Max(float64(math.Floor(factor*3.0/windowSize)), 1)) + } + // Do not enlarge the output if the input width *or* height are already less than the required dimensions if o.Enlarge == false { if inWidth < o.Width && inHeight < o.Height { @@ -65,24 +72,16 @@ func Resize(buf []byte, o Options) ([]byte, error) { if err != nil { return nil, err } - if tmpImage != nil { - image = tmpImage - factor = math.Max(factor, 1.0) - shrink = int(math.Floor(factor)) - residual = float64(shrink) / factor - } - } - - // Calculate integral box shrink - windowSize := vipsWindowSize(o.Interpolator.String()) - if factor >= 2 && windowSize > 3 { - // Shrink less, affine more with interpolators that use at least 4x4 pixel window, e.g. bicubic - shrink = int(math.Max(float64(math.Floor(factor*3.0/windowSize)), 1)) + image = tmpImage + factor = math.Max(factor, 1.0) + shrink = int(math.Floor(factor)) + residual = float64(shrink) / factor } // Transform image if necessary shouldTransform := o.Width != inWidth || o.Height != inHeight || o.AreaWidth > 0 || o.AreaHeight > 0 if shouldTransform { + // Use vips_shrink with the integral reduction if shrink > 1 { image, residual, err = shrinkImage(image, o, residual, shrink) @@ -90,7 +89,8 @@ func Resize(buf []byte, o Options) ([]byte, error) { return nil, err } } - // Use vips_affine with the remaining float part + + // Affine with the remaining float part if residual != 0 { image, err = vipsAffine(image, residual, o.Interpolator) if err != nil { diff --git a/resize_test.go b/resize_test.go index cb8dfe3..131dc38 100644 --- a/resize_test.go +++ b/resize_test.go @@ -9,7 +9,7 @@ import ( func TestResize(t *testing.T) { options := Options{Width: 800, Height: 600} - buf, _ := Read("fixtures/test.jpg") + buf, _ := Read("../vips/fixtures/large.jpg") newImg, err := Resize(buf, options) if err != nil { diff --git a/vips.go b/vips.go index 53ae1f2..e675be8 100644 --- a/vips.go +++ b/vips.go @@ -229,6 +229,7 @@ func vipsSave(image *C.struct__VipsImage, o vipsSaveOptions) ([]byte, error) { err = C.vips_webpsave_bridge(image, &ptr, &length, 1, C.int(o.Quality), 0) break default: + debug("Save JPEG options: Q: %s", o.Quality) err = C.vips_jpegsave_bridge(image, &ptr, &length, 1, C.int(o.Quality), 0) break } @@ -239,7 +240,7 @@ func vipsSave(image *C.struct__VipsImage, o vipsSaveOptions) ([]byte, error) { buf := C.GoBytes(ptr, C.int(length)) - // Cleanup + // Clean up C.g_free(C.gpointer(ptr)) C.vips_error_clear() From 38ef4460f9dfb6e7ec7d03bd988c0fcda0656626 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Thu, 16 Apr 2015 02:25:43 +0200 Subject: [PATCH 028/115] feat(version): bump --- debug.go | 59 +----------------------------------------------------- version.go | 2 +- 2 files changed, 2 insertions(+), 59 deletions(-) diff --git a/debug.go b/debug.go index 46155fa..c781668 100644 --- a/debug.go +++ b/debug.go @@ -1,62 +1,5 @@ package bimg -import ( - "github.com/dustin/go-humanize" - . "github.com/tj/go-debug" - "runtime" - "strconv" - "time" -) +import . "github.com/tj/go-debug" var debug = Debug("bimg") - -// Print Go memory and garbage collector stats. Useful for debugging -func PrintMemoryStats() { - log := Debug("memory") - mem := memoryStats() - - log("\u001b[33m---- Memory Stats ----\u001b[39m") - log("Allocated: %s", humanize.Bytes(mem.Alloc)) - log("Total Allocated: %s", humanize.Bytes(mem.TotalAlloc)) - log("Memory Allocations: %d", mem.Mallocs) - log("Memory Frees: %d", mem.Frees) - log("Heap Allocated: %s", humanize.Bytes(mem.HeapAlloc)) - log("Heap System: %s", humanize.Bytes(mem.HeapSys)) - log("Heap In Use: %s", humanize.Bytes(mem.HeapInuse)) - log("Heap Idle: %s", humanize.Bytes(mem.HeapIdle)) - log("Heap OS Related: %s", humanize.Bytes(mem.HeapReleased)) - log("Heap Objects: %s", humanize.Bytes(mem.HeapObjects)) - log("Stack In Use: %s", humanize.Bytes(mem.StackInuse)) - log("Stack System: %s", humanize.Bytes(mem.StackSys)) - log("Stack Span In Use: %s", humanize.Bytes(mem.MSpanInuse)) - log("Stack Cache In Use: %s", humanize.Bytes(mem.MCacheInuse)) - log("Next GC cycle: %s", humanizeNano(mem.NextGC)) - log("Last GC cycle: %s", humanize.Time(time.Unix(0, int64(mem.LastGC)))) - log("\u001b[33m---- Memory Stats ----\u001b[39m") -} - -func memoryStats() runtime.MemStats { - var mem runtime.MemStats - runtime.ReadMemStats(&mem) - return mem -} - -func humanizeNano(n uint64) string { - var suffix string - - switch { - case n > 1e9: - n /= 1e9 - suffix = "s" - case n > 1e6: - n /= 1e6 - suffix = "ms" - case n > 1e3: - n /= 1e3 - suffix = "us" - default: - suffix = "ns" - } - - return strconv.Itoa(int(n)) + suffix -} diff --git a/version.go b/version.go index 3271f26..0cec2e4 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,3 @@ package bimg -const Version = "0.1.9" +const Version = "0.1.10" From 4779c8b9f347eac6c447038f7d12a2add1aa2c19 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Thu, 16 Apr 2015 02:32:38 +0200 Subject: [PATCH 029/115] fix(test) --- resize_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resize_test.go b/resize_test.go index 131dc38..5e1447e 100644 --- a/resize_test.go +++ b/resize_test.go @@ -9,7 +9,7 @@ import ( func TestResize(t *testing.T) { options := Options{Width: 800, Height: 600} - buf, _ := Read("../vips/fixtures/large.jpg") + buf, _ := Read("fixtures/large.jpg") newImg, err := Resize(buf, options) if err != nil { From 1d76750fb9cba635e27bd95811b9efe7d3a71cd0 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Thu, 16 Apr 2015 02:59:47 +0200 Subject: [PATCH 030/115] fix(test): resize --- resize_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resize_test.go b/resize_test.go index 5e1447e..cb8dfe3 100644 --- a/resize_test.go +++ b/resize_test.go @@ -9,7 +9,7 @@ import ( func TestResize(t *testing.T) { options := Options{Width: 800, Height: 600} - buf, _ := Read("fixtures/large.jpg") + buf, _ := Read("fixtures/test.jpg") newImg, err := Resize(buf, options) if err != nil { From c2f117670f38294abbbdc30b608ca4ab50bf2543 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Aparicio?= Date: Thu, 16 Apr 2015 19:20:11 +0200 Subject: [PATCH 031/115] refactor(docs): description --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 609fbf7..3269227 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # bimg [![Build Status](https://travis-ci.org/h2non/bimg.png)](https://travis-ci.org/h2non/bimg) [![GitHub release](https://img.shields.io/github/tag/h2non/bimg.svg)](https://github.com/h2non/bimg/releases) [![GoDoc](https://godoc.org/github.com/h2non/bimg?status.svg)](https://godoc.org/github.com/h2non/bimg) [![Coverage Status](https://coveralls.io/repos/h2non/bimg/badge.svg?branch=master)](https://coveralls.io/r/h2non/bimg?branch=master) -Small [Go](http://golang.org) library for blazing fast and efficient image processing based on [libvips](https://github.com/jcupitt/libvips) using C bindings. It provides a clean, simple and fluent [API](https://godoc.org/github.com/h2non/bimg) in pure Go. +Small [Go](http://golang.org) library for blazing fast and efficient image processing based on [libvips](https://github.com/jcupitt/libvips) using C bindings. It provides a clean, simple and fluent [API](https://godoc.org/github.com/h2non/bimg). bimg was designed to be a small and efficient library with a generic and useful features. It uses internally libvips, a powerful library written in C for binary image processing which requires a [low memory footprint](http://www.vips.ecs.soton.ac.uk/index.php?title=Speed_and_Memory_Use) From 41692f384dc0d77510d97ac2d1036234fd0efe3c Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sun, 19 Apr 2015 19:39:22 +0200 Subject: [PATCH 032/115] feat(docs): update API --- README.md | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 609fbf7..96a7730 100644 --- a/README.md +++ b/README.md @@ -242,7 +242,7 @@ Enable libvips traces (note that a lot of data will be written in stdout): VIPS_TRACE=1 ./app ``` -#### func DetermineImageTypeName +#### func DetermineImageTypeName ```go func DetermineImageTypeName(buf []byte) string @@ -271,13 +271,6 @@ func IsTypeSupported(t ImageType) bool ``` Check if a given image type is supported -#### func PrintMemoryStats - -```go -func PrintMemoryStats() -``` -Print Go memory and garbage collector stats. Useful for debugging - #### func Read ```go @@ -295,8 +288,8 @@ func Resize(buf []byte, o Options) ([]byte, error) ```go func Shutdown() ``` -Explicit thread-safe libvips shutdown. Call this to drop caches. If libvips was -already initialized, the function is no-op +Thread-safe function to shutdown libvips. You could call this to drop caches as +well. If libvips was already initialized, the function is no-op #### func VipsDebug @@ -505,9 +498,9 @@ Add text as watermark on the given image #### func (*Image) Zoom ```go -func (i *Image) Zoom(level int) ([]byte, error) +func (i *Image) Zoom(factor int) ([]byte, error) ``` -Zoom the image by the given factor +Zoom the image by the given factor. You should probably call Extract() before #### type ImageMetadata @@ -634,7 +627,6 @@ type VipsMemoryInfo struct { } ``` - #### func VipsMemory ```go From ca206cdee12928eb11aec5e504361b539ed1d8ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Aparicio?= Date: Wed, 22 Apr 2015 13:22:29 +0200 Subject: [PATCH 033/115] refactor(docs): links --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e150673..0df18bf 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # bimg [![Build Status](https://travis-ci.org/h2non/bimg.png)](https://travis-ci.org/h2non/bimg) [![GitHub release](https://img.shields.io/github/tag/h2non/bimg.svg)](https://github.com/h2non/bimg/releases) [![GoDoc](https://godoc.org/github.com/h2non/bimg?status.svg)](https://godoc.org/github.com/h2non/bimg) [![Coverage Status](https://coveralls.io/repos/h2non/bimg/badge.svg?branch=master)](https://coveralls.io/r/h2non/bimg?branch=master) -Small [Go](http://golang.org) library for blazing fast and efficient image processing based on [libvips](https://github.com/jcupitt/libvips) using C bindings. It provides a clean, simple and fluent [API](https://godoc.org/github.com/h2non/bimg). +Small [Go](http://golang.org) library for blazing fast and efficient image processing based on [libvips](https://github.com/jcupitt/libvips) using C bindings. It provides a clean, simple and fluent [programmatic API](#examples). bimg was designed to be a small and efficient library with a generic and useful features. It uses internally libvips, a powerful library written in C for binary image processing which requires a [low memory footprint](http://www.vips.ecs.soton.ac.uk/index.php?title=Speed_and_Memory_Use) From 67221723cacd3448bcf4ad3dffaa004882bec92c Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sat, 25 Apr 2015 18:42:35 +0200 Subject: [PATCH 034/115] fix(#33): bad auto rotatino --- metadata_test.go | 1 - options.go | 8 ++--- resize.go | 90 ++++++++++++++++++++++-------------------------- vips.go | 3 +- 4 files changed, 47 insertions(+), 55 deletions(-) diff --git a/metadata_test.go b/metadata_test.go index 9698b65..c21bf46 100644 --- a/metadata_test.go +++ b/metadata_test.go @@ -17,7 +17,6 @@ func TestSize(t *testing.T) { {"test.png", 400, 300}, {"test.webp", 550, 368}, } - for _, file := range files { size, err := Size(readFile(file.name)) if err != nil { diff --git a/options.go b/options.go index 7b2c2fb..4495a55 100644 --- a/options.go +++ b/options.go @@ -42,10 +42,10 @@ func (i Interpolator) String() string { type Angle int const ( - D0 Angle = C.VIPS_ANGLE_D0 - D90 Angle = C.VIPS_ANGLE_D90 - D180 Angle = C.VIPS_ANGLE_D180 - D270 Angle = C.VIPS_ANGLE_D270 + D0 Angle = 0 + D90 Angle = 90 + D180 Angle = 180 + D270 Angle = 270 ) type Direction int diff --git a/resize.go b/resize.go index c348a57..b10e01e 100644 --- a/resize.go +++ b/resize.go @@ -78,6 +78,18 @@ func Resize(buf []byte, o Options) ([]byte, error) { residual = float64(shrink) / factor } + // Zoom image if necessary + image, err = zoomImage(image, o.Zoom) + if err != nil { + return nil, err + } + + // Rotate / flip image if necessary + image, err = rotateAndFlipImage(image, o) + if err != nil { + return nil, err + } + // Transform image if necessary shouldTransform := o.Width != inWidth || o.Height != inHeight || o.AreaWidth > 0 || o.AreaHeight > 0 if shouldTransform { @@ -106,18 +118,6 @@ func Resize(buf []byte, o Options) ([]byte, error) { } } - // Zoom image if necessary - image, err = zoomImage(image, o.Zoom) - if err != nil { - return nil, err - } - - // Rotate / flip image if necessary - image, err = rotateImage(image, o) - if err != nil { - return nil, err - } - // Add watermark if necessary image, err = watermakImage(image, o.Watermark) if err != nil { @@ -167,7 +167,7 @@ func extractImage(image *C.struct__VipsImage, o Options) (*C.struct__VipsImage, return image, err } -func rotateImage(image *C.struct__VipsImage, o Options) (*C.struct__VipsImage, error) { +func rotateAndFlipImage(image *C.struct__VipsImage, o Options) (*C.struct__VipsImage, error) { var err error var direction Direction = -1 @@ -341,41 +341,35 @@ func calculateRotationAndFlip(image *C.struct__VipsImage, angle Angle) (Angle, b rotate := D0 flip := false - if angle == -1 { - switch vipsExifOrientation(image) { - case 6: - rotate = D90 - break - case 3: - rotate = D180 - break - case 8: - rotate = D270 - break - case 2: - flip = true - break // flip 1 - case 7: - flip = true - rotate = D90 - break // flip 6 - case 4: - flip = true - rotate = D180 - break // flip 3 - case 5: - flip = true - rotate = D270 - break // flip 8 - } - } else { - if angle == 90 { - rotate = D90 - } else if angle == 180 { - rotate = D180 - } else if angle == 270 { - rotate = D270 - } + if angle > 0 { + return rotate, flip + } + + switch vipsExifOrientation(image) { + case 6: + rotate = D90 + break + case 3: + rotate = D180 + break + case 8: + rotate = D270 + break + case 2: + flip = true + break // flip 1 + case 7: + flip = true + rotate = D90 + break // flip 6 + case 4: + flip = true + rotate = D180 + break // flip 3 + case 5: + flip = true + rotate = D270 + break // flip 8 } return rotate, flip diff --git a/vips.go b/vips.go index e675be8..06ce8c3 100644 --- a/vips.go +++ b/vips.go @@ -3,7 +3,6 @@ package bimg /* #cgo pkg-config: vips #include "vips.h" -#include "stdlib.h" */ import "C" @@ -97,7 +96,7 @@ func Shutdown() { } // Output to stdout vips collected data. Useful for debugging -func VipsDebug() { +func VipsDebugInfo() { C.im__print_all() } From 81bb4bcdac74d89645b4504a0f16473f8ad22845 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sat, 25 Apr 2015 19:56:50 +0200 Subject: [PATCH 035/115] fix(#32): bad crop --- resize.go | 46 +++++++++++++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/resize.go b/resize.go index b10e01e..a45c988 100644 --- a/resize.go +++ b/resize.go @@ -45,15 +45,8 @@ func Resize(buf []byte, o Options) ([]byte, error) { // image calculations factor := imageCalculations(&o, inWidth, inHeight) - shrink := int(math.Max(math.Floor(factor), 1)) - residual := float64(shrink) / factor - - // Calculate integral box shrink - windowSize := vipsWindowSize(o.Interpolator.String()) - if factor >= 2 && windowSize > 3 { - // Shrink less, affine more with interpolators that use at least 4x4 pixel window, e.g. bicubic - shrink = int(math.Max(float64(math.Floor(factor*3.0/windowSize)), 1)) - } + shrink := calculateShrink(factor, o.Interpolator) + residual := calculateResidual(factor, shrink) // Do not enlarge the output if the input width *or* height are already less than the required dimensions if o.Enlarge == false { @@ -72,6 +65,7 @@ func Resize(buf []byte, o Options) ([]byte, error) { if err != nil { return nil, err } + image = tmpImage factor = math.Max(factor, 1.0) shrink = int(math.Floor(factor)) @@ -110,12 +104,14 @@ func Resize(buf []byte, o Options) ([]byte, error) { } } - debug("Transform image: factor=%v, shrink=%v, residual=%v", factor, shrink, residual) // Extract area from image image, err = extractImage(image, o) if err != nil { return nil, err } + + debug("Transform: factor=%v, shrink=%v, residual=%v, interpolator=%v", + factor, shrink, residual, o.Interpolator.String()) } // Add watermark if necessary @@ -156,11 +152,16 @@ func extractImage(image *C.struct__VipsImage, o Options) (*C.struct__VipsImage, image, err = vipsEmbed(image, left, top, o.Width, o.Height, o.Extend) break case o.Top > 0 || o.Left > 0: + if o.AreaWidth == 0 { + o.AreaHeight = o.Width + } + if o.AreaHeight == 0 { + o.AreaHeight = o.Height + } if o.AreaWidth == 0 || o.AreaHeight == 0 { - err = errors.New("Area to extract cannot be 0") - } else { - image, err = vipsExtract(image, o.Left, o.Top, o.AreaWidth, o.AreaHeight) + return nil, errors.New("Extract area width/height is required") } + image, err = vipsExtract(image, o.Left, o.Top, o.AreaWidth, o.AreaHeight) break } @@ -375,6 +376,25 @@ func calculateRotationAndFlip(image *C.struct__VipsImage, angle Angle) (Angle, b return rotate, flip } +func calculateShrink(factor float64, i Interpolator) int { + var shrink float64 + + // Calculate integral box shrink + windowSize := vipsWindowSize(i.String()) + if factor >= 2 && windowSize > 3 { + // Shrink less, affine more with interpolators that use at least 4x4 pixel window, e.g. bicubic + shrink = float64(math.Floor(factor * 3.0 / windowSize)) + } else { + shrink = math.Floor(factor) + } + + return int(math.Max(shrink, 1)) +} + +func calculateResidual(factor float64, shrink int) float64 { + return float64(shrink) / factor +} + func getAngle(angle Angle) Angle { divisor := angle % 90 if divisor != 0 { From c3b4dbe26e8c2f25a7d99d213dd6d7532f67ac1d Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sat, 25 Apr 2015 19:58:50 +0200 Subject: [PATCH 036/115] refactor(docs): description --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0df18bf..cda36be 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # bimg [![Build Status](https://travis-ci.org/h2non/bimg.png)](https://travis-ci.org/h2non/bimg) [![GitHub release](https://img.shields.io/github/tag/h2non/bimg.svg)](https://github.com/h2non/bimg/releases) [![GoDoc](https://godoc.org/github.com/h2non/bimg?status.svg)](https://godoc.org/github.com/h2non/bimg) [![Coverage Status](https://coveralls.io/repos/h2non/bimg/badge.svg?branch=master)](https://coveralls.io/r/h2non/bimg?branch=master) -Small [Go](http://golang.org) library for blazing fast and efficient image processing based on [libvips](https://github.com/jcupitt/libvips) using C bindings. It provides a clean, simple and fluent [programmatic API](#examples). +Small [Go](http://golang.org) library for fast and efficient image processing based on [libvips](https://github.com/jcupitt/libvips) using C bindings. It provides a simple, elegant and fluent [programmatic API](#examples). bimg was designed to be a small and efficient library with a generic and useful features. It uses internally libvips, a powerful library written in C for binary image processing which requires a [low memory footprint](http://www.vips.ecs.soton.ac.uk/index.php?title=Speed_and_Memory_Use) @@ -8,7 +8,8 @@ and it's typically 4x faster than using the quickest ImageMagick and GraphicsMag It can read JPEG, PNG, WEBP and TIFF formats and output to JPEG, PNG and WEBP, including conversion between them. It supports common [image operations](#supported-image-operations) such as crop, resize, rotate, zoom, watermark... -For getting started, take a look to the [examples](#examples) and [programmatic API](https://godoc.org/github.com/h2non/bimg) documentation. If you're looking for a HTTP-based image processing solution, see [imaginary](https://github.com/h2non/imaginary). +For getting started, take a look to the [examples](#examples) and [API](https://godoc.org/github.com/h2non/bimg) documentation. +If you're looking for a HTTP-based image processing solution, see [imaginary](https://github.com/h2non/imaginary). bimg was heavily inspired in [sharp](https://github.com/lovell/sharp), its homologous package built for node.js by [Lovell Fuller](https://github.com/lovell). From 9d47e1b1fb2407258f9a91a271681aa0c585e841 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sat, 25 Apr 2015 19:59:05 +0200 Subject: [PATCH 037/115] feat(version): bump --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index 0cec2e4..4bdc75e 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,3 @@ package bimg -const Version = "0.1.10" +const Version = "0.1.11" From d4bb89a74eb253ed800aad78fc776879e5ed994a Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sat, 25 Apr 2015 20:04:45 +0200 Subject: [PATCH 038/115] fix(travis): fuck coveralls --- .travis.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3c20085..aa82785 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,9 +5,4 @@ go: - release - tip before_install: - - go get github.com/axw/gocov/gocov - - go get github.com/mattn/goveralls - - if ! go get code.google.com/p/go.tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi - curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | sudo bash - -script: - - $HOME/gopath/bin/goveralls -service=travis-ci From 720c2bcf94d6d2d68baf330ab44adf3364a52ac2 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sun, 26 Apr 2015 20:07:47 +0200 Subject: [PATCH 039/115] fix(#35): save webp --- resize_test.go | 60 +++++++++++++++++++++++++++++--------------------- vips.h | 14 ++++++------ 2 files changed, 42 insertions(+), 32 deletions(-) diff --git a/resize_test.go b/resize_test.go index cb8dfe3..a56c57b 100644 --- a/resize_test.go +++ b/resize_test.go @@ -72,35 +72,45 @@ func TestInvalidRotate(t *testing.T) { } func TestConvert(t *testing.T) { - width, height := 640, 480 - - options := Options{Width: width, Height: height, Crop: true, 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) - } + width, height := 300, 240 + formats := [3]ImageType{PNG, WEBP, JPEG} - if DetermineImageType(newImg) != PNG { - t.Fatal("Image is not png") + files := []string{ + "test.jpg", + "test.png", + "test.webp", } - size, _ := Size(newImg) - if size.Height != height || size.Width != width { - t.Fatal("Invalid image size") + for _, file := range files { + img, err := os.Open("fixtures/" + file) + if err != nil { + t.Fatal(err) + } + + buf, err := ioutil.ReadAll(img) + if err != nil { + t.Fatal(err) + } + img.Close() + + for _, format := range formats { + options := Options{Width: width, Height: height, Crop: true, Type: format} + + newImg, err := Resize(buf, options) + if err != nil { + t.Errorf("Resize(imgData, %#v) error: %#v", options, err) + } + + if DetermineImageType(newImg) != format { + t.Fatal("Image is not png") + } + + size, _ := Size(newImg) + if size.Height != height || size.Width != width { + t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height) + } + } } - - Write("fixtures/test_out.png", newImg) } func TestResizePngWithTransparency(t *testing.T) { diff --git a/vips.h b/vips.h index 445a755..8046e6e 100644 --- a/vips.h +++ b/vips.h @@ -25,6 +25,12 @@ typedef struct { double Background[3]; } WatermarkOptions; +void +vips_enable_cache_set_trace() +{ + vips_cache_set_trace(TRUE); +}; + int vips_affine_interpolator(VipsImage *in, VipsImage **out, double a, double b, double c, double d, VipsInterpolate *interpolator) { @@ -144,7 +150,7 @@ vips_pngsave_bridge(VipsImage *in, void **buf, size_t *len, int strip, int compr int vips_webpsave_bridge(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); + return vips_webpsave_buffer(in, buf, len, "strip", strip, "Q", quality, NULL); }; int @@ -243,9 +249,3 @@ vips_watermark(VipsImage *in, VipsImage **out, WatermarkTextOptions *to, Waterma g_object_unref(base); return 0; }; - -void -vips_enable_cache_set_trace() -{ - vips_cache_set_trace(TRUE); -}; From 6d27b8ef91e2bddda59130b466cb92bec8612144 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sun, 26 Apr 2015 20:08:34 +0200 Subject: [PATCH 040/115] feat(version): bump --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index 4bdc75e..e6067e5 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,3 @@ package bimg -const Version = "0.1.11" +const Version = "0.1.12" From c9001cd22cc24de0dddb19d45df50da0ed748a26 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Mon, 27 Apr 2015 11:49:15 +0200 Subject: [PATCH 041/115] feat(crop): add method shortcuts for crop --- image.go | 24 +++++++++++++++++++++++- image_test.go | 32 ++++++++++++++++++++++++++++++-- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/image.go b/image.go index 85f0b6d..75889fc 100644 --- a/image.go +++ b/image.go @@ -14,6 +14,17 @@ func (i *Image) Resize(width, height int) ([]byte, error) { return i.Process(options) } +// Resize the image to fixed width and height with additional crop transformation +func (i *Image) ResizeAndCrop(width, height int) ([]byte, error) { + options := Options{ + Width: width, + Height: height, + Embed: true, + Crop: true, + } + return i.Process(options) +} + // Extract area from the by X/Y axis func (i *Image) Extract(top, left, width, height int) ([]byte, error) { options := Options{ @@ -25,7 +36,7 @@ func (i *Image) Extract(top, left, width, height int) ([]byte, error) { return i.Process(options) } -// Enlarge the image from the by X/Y axis +// Enlarge the image by width and height. Aspect ratio is maintained func (i *Image) Enlarge(width, height int) ([]byte, error) { options := Options{ Width: width, @@ -35,6 +46,17 @@ func (i *Image) Enlarge(width, height int) ([]byte, error) { return i.Process(options) } +// Enlarge the image by width and height with additional crop transformation +func (i *Image) EnlargeAndCrop(width, height int) ([]byte, error) { + options := Options{ + Width: width, + Height: height, + Enlarge: true, + Crop: true, + } + return i.Process(options) +} + // Crop the image to the exact size specified func (i *Image) Crop(width, height int, gravity Gravity) ([]byte, error) { options := Options{ diff --git a/image_test.go b/image_test.go index 29e3bd4..d097f21 100644 --- a/image_test.go +++ b/image_test.go @@ -20,13 +20,27 @@ func TestImageResize(t *testing.T) { Write("fixtures/test_resize_out.jpg", buf) } +func TestImageResizeAndCrop(t *testing.T) { + buf, err := initImage("test.jpg").ResizeAndCrop(300, 200) + if err != nil { + t.Errorf("Cannot process the image: %#v", err) + } + + err = assertSize(buf, 300, 200) + if err != nil { + t.Error(err) + } + + Write("fixtures/test_resize_crop_out.jpg", buf) +} + func TestImageExtract(t *testing.T) { - buf, err := initImage("test.jpg").Extract(100, 100, 300, 300) + buf, err := initImage("test.jpg").Extract(100, 100, 300, 200) if err != nil { t.Errorf("Cannot process the image: %s", err) } - err = assertSize(buf, 300, 300) + err = assertSize(buf, 300, 200) if err != nil { t.Error(err) } @@ -48,6 +62,20 @@ func TestImageEnlarge(t *testing.T) { Write("fixtures/test_enlarge_out.jpg", buf) } +func TestImageEnlargeAndCrop(t *testing.T) { + buf, err := initImage("test.png").EnlargeAndCrop(800, 480) + if err != nil { + t.Errorf("Cannot process the image: %#v", err) + } + + err = assertSize(buf, 800, 480) + if err != nil { + t.Error(err) + } + + Write("fixtures/test_enlarge_crop_out.jpg", buf) +} + func TestImageCrop(t *testing.T) { buf, err := initImage("test.jpg").Crop(800, 600, NORTH) if err != nil { From 5d13fba8beaf44b2f42f42f9c510d09a2678d7bc Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Mon, 27 Apr 2015 11:50:47 +0200 Subject: [PATCH 042/115] feat(version): bump --- README.md | 31 +++++++++++++++++++++++-------- version.go | 2 +- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index cda36be..6664d1f 100644 --- a/README.md +++ b/README.md @@ -243,7 +243,7 @@ Enable libvips traces (note that a lot of data will be written in stdout): VIPS_TRACE=1 ./app ``` -#### func DetermineImageTypeName +#### func DetermineImageTypeName ```go func DetermineImageTypeName(buf []byte) string @@ -292,10 +292,10 @@ func Shutdown() Thread-safe function to shutdown libvips. You could call this to drop caches as well. If libvips was already initialized, the function is no-op -#### func VipsDebug +#### func VipsDebugInfo ```go -func VipsDebug() +func VipsDebugInfo() ``` Output to stdout vips collected data. Useful for debugging @@ -314,10 +314,10 @@ type Angle int ```go const ( - D0 Angle = C.VIPS_ANGLE_D0 - D90 Angle = C.VIPS_ANGLE_D90 - D180 Angle = C.VIPS_ANGLE_D180 - D270 Angle = C.VIPS_ANGLE_D270 + D0 Angle = 0 + D90 Angle = 90 + D180 Angle = 180 + D270 Angle = 270 ) ``` @@ -410,7 +410,14 @@ Crop an image by width (auto height) ```go func (i *Image) Enlarge(width, height int) ([]byte, error) ``` -Enlarge the image from the by X/Y axis +Enlarge the image by width and height. Aspect ratio is maintained + +#### func (*Image) EnlargeAndCrop + +```go +func (i *Image) EnlargeAndCrop(width, height int) ([]byte, error) +``` +Enlarge the image by width and height with additional crop transformation #### func (*Image) Extract @@ -461,6 +468,13 @@ func (i *Image) Resize(width, height int) ([]byte, error) ``` Resize the image to fixed width and height +#### func (*Image) ResizeAndCrop + +```go +func (i *Image) ResizeAndCrop(width, height int) ([]byte, error) +``` +Resize the image to fixed width and height with additional crop transformation + #### func (*Image) Rotate ```go @@ -628,6 +642,7 @@ type VipsMemoryInfo struct { } ``` + #### func VipsMemory ```go diff --git a/version.go b/version.go index e6067e5..00d6ece 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,3 @@ package bimg -const Version = "0.1.12" +const Version = "0.1.13" From 789b13aef8ead2cd42215e01b3503bc2a8630eb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Aparicio?= Date: Mon, 27 Apr 2015 18:02:57 +0200 Subject: [PATCH 043/115] fix(docs): watermark example --- README.md | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 6664d1f..4438c96 100644 --- a/README.md +++ b/README.md @@ -185,19 +185,17 @@ if err != nil { fmt.Fprintln(os.Stderr, err) } -options := bimg.Watermark{ - Watermark{ - Text: "Chuck Norris (c) 2315", - Opacity: 0.25, - Width: 200, - DPI: 100, - Margin: 150, - Font: "sans bold 12", - Background: bimg.Color{255, 255, 255}, - } +watermark := bimg.Watermark{ + Text: "Chuck Norris (c) 2315", + Opacity: 0.25, + Width: 200, + DPI: 100, + Margin: 150, + Font: "sans bold 12", + Background: bimg.Color{255, 255, 255}, } -newImage, err := bimg.NewImage(buffer).Watermark(options) +newImage, err := bimg.NewImage(buffer).Watermark(watermark) if err != nil { fmt.Fprintln(os.Stderr, err) } From 271310b4fc3f356adb276cf295a48f3b04372a08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Aparicio?= Date: Thu, 30 Apr 2015 11:24:55 +0200 Subject: [PATCH 044/115] refactor(docs): remove beta note --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 4438c96..facbe74 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,6 @@ If you're looking for a HTTP-based image processing solution, see [imaginary](ht bimg was heavily inspired in [sharp](https://github.com/lovell/sharp), its homologous package built for node.js by [Lovell Fuller](https://github.com/lovell). -**Note**: bimg is still beta. Do not use in compromised environments yet - ## Prerequisites - [libvips](https://github.com/jcupitt/libvips) v7.40.0+ (7.42.0+ recommended) From fc5a4ffd26d2e6adbb56d46ae295195f084f3baa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Aparicio?= Date: Thu, 30 Apr 2015 15:08:32 +0200 Subject: [PATCH 045/115] refactor(docs): description --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index facbe74..827bd73 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # bimg [![Build Status](https://travis-ci.org/h2non/bimg.png)](https://travis-ci.org/h2non/bimg) [![GitHub release](https://img.shields.io/github/tag/h2non/bimg.svg)](https://github.com/h2non/bimg/releases) [![GoDoc](https://godoc.org/github.com/h2non/bimg?status.svg)](https://godoc.org/github.com/h2non/bimg) [![Coverage Status](https://coveralls.io/repos/h2non/bimg/badge.svg?branch=master)](https://coveralls.io/r/h2non/bimg?branch=master) -Small [Go](http://golang.org) library for fast and efficient image processing based on [libvips](https://github.com/jcupitt/libvips) using C bindings. It provides a simple, elegant and fluent [programmatic API](#examples). +Small [Go](http://golang.org) library for fast high-level image processing and transformation using [libvips](https://github.com/jcupitt/libvips) via C bindings. It provides a simple, elegant and fluent [programmatic API](#examples). bimg was designed to be a small and efficient library with a generic and useful features. It uses internally libvips, a powerful library written in C for binary image processing which requires a [low memory footprint](http://www.vips.ecs.soton.ac.uk/index.php?title=Speed_and_Memory_Use) @@ -10,7 +10,6 @@ It can read JPEG, PNG, WEBP and TIFF formats and output to JPEG, PNG and WEBP, i For getting started, take a look to the [examples](#examples) and [API](https://godoc.org/github.com/h2non/bimg) documentation. If you're looking for a HTTP-based image processing solution, see [imaginary](https://github.com/h2non/imaginary). - bimg was heavily inspired in [sharp](https://github.com/lovell/sharp), its homologous package built for node.js by [Lovell Fuller](https://github.com/lovell). ## Prerequisites From 60254d6b7a5e0660d04100d72bc7094e28571cac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Aparicio?= Date: Sun, 3 May 2015 15:27:35 +0200 Subject: [PATCH 046/115] refactor(badge): release --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 827bd73..1058206 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# bimg [![Build Status](https://travis-ci.org/h2non/bimg.png)](https://travis-ci.org/h2non/bimg) [![GitHub release](https://img.shields.io/github/tag/h2non/bimg.svg)](https://github.com/h2non/bimg/releases) [![GoDoc](https://godoc.org/github.com/h2non/bimg?status.svg)](https://godoc.org/github.com/h2non/bimg) [![Coverage Status](https://coveralls.io/repos/h2non/bimg/badge.svg?branch=master)](https://coveralls.io/r/h2non/bimg?branch=master) +# bimg [![Build Status](https://travis-ci.org/h2non/bimg.png)](https://travis-ci.org/h2non/bimg) [![GitHub release](http://img.shields.io/github/release/h2non/bimg.svg?style=flat-square)](https://github.com/h2non/bimg/releases) [![GoDoc](https://godoc.org/github.com/h2non/bimg?status.svg)](https://godoc.org/github.com/h2non/bimg) [![Coverage Status](https://coveralls.io/repos/h2non/bimg/badge.svg?branch=master)](https://coveralls.io/r/h2non/bimg?branch=master) Small [Go](http://golang.org) library for fast high-level image processing and transformation using [libvips](https://github.com/jcupitt/libvips) via C bindings. It provides a simple, elegant and fluent [programmatic API](#examples). From 93cf8a1bb88521f7533696aff1e4fce0eacc55d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Aparicio?= Date: Sun, 3 May 2015 15:30:20 +0200 Subject: [PATCH 047/115] fix(badge) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1058206..a67125d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# bimg [![Build Status](https://travis-ci.org/h2non/bimg.png)](https://travis-ci.org/h2non/bimg) [![GitHub release](http://img.shields.io/github/release/h2non/bimg.svg?style=flat-square)](https://github.com/h2non/bimg/releases) [![GoDoc](https://godoc.org/github.com/h2non/bimg?status.svg)](https://godoc.org/github.com/h2non/bimg) [![Coverage Status](https://coveralls.io/repos/h2non/bimg/badge.svg?branch=master)](https://coveralls.io/r/h2non/bimg?branch=master) +# bimg [![Build Status](https://travis-ci.org/h2non/bimg.png)](https://travis-ci.org/h2non/bimg) [![GitHub release](http://img.shields.io/github/tag/h2non/bimg.svg?style=flat-square)](https://github.com/h2non/bimg/releases) [![GoDoc](https://godoc.org/github.com/h2non/bimg?status.svg)](https://godoc.org/github.com/h2non/bimg) [![Coverage Status](https://coveralls.io/repos/h2non/bimg/badge.svg?branch=master)](https://coveralls.io/r/h2non/bimg?branch=master) Small [Go](http://golang.org) library for fast high-level image processing and transformation using [libvips](https://github.com/jcupitt/libvips) via C bindings. It provides a simple, elegant and fluent [programmatic API](#examples). From a29fa25dfcf28d8e82360a4b50826a93eaa617aa Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Tue, 5 May 2015 20:28:39 +0200 Subject: [PATCH 048/115] refactor(vips) --- resize.go | 2 ++ resize_test.go | 5 +++++ vips.go | 10 ++++++++-- vips.h | 14 +++++++------- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/resize.go b/resize.go index a45c988..36bb10a 100644 --- a/resize.go +++ b/resize.go @@ -72,6 +72,8 @@ func Resize(buf []byte, o Options) ([]byte, error) { residual = float64(shrink) / factor } + debug("Test %s, %s, %s", shrink, residual, factor) + // Zoom image if necessary image, err = zoomImage(image, o.Zoom) if err != nil { diff --git a/resize_test.go b/resize_test.go index a56c57b..6e8a150 100644 --- a/resize_test.go +++ b/resize_test.go @@ -153,6 +153,11 @@ func runBenchmarkResize(file string, o Options, b *testing.B) { } } +func BenchmarkRotateJpeg(b *testing.B) { + options := Options{Rotate: 180} + runBenchmarkResize("test.jpg", options, b) +} + func BenchmarkResizeLargeJpeg(b *testing.B) { options := Options{ Width: 800, diff --git a/vips.go b/vips.go index 06ce8c3..04bf8d6 100644 --- a/vips.go +++ b/vips.go @@ -15,6 +15,11 @@ import ( "unsafe" ) +const ( + maxCacheMem = 100 * 1024 * 1024 + maxCacheSize = 500 +) + var ( m sync.Mutex initialized bool @@ -67,8 +72,9 @@ func Initialize() { panic("unable to start vips!") } - C.vips_cache_set_max_mem(100 * 1024 * 1024) - C.vips_cache_set_max(500) + // Set libvips cache params + C.vips_cache_set_max_mem(maxCacheMem) + C.vips_cache_set_max(maxCacheSize) // Explicit concurrency limit to avoid thread-unsafe issues. // See: https://github.com/jcupitt/libvips/issues/261#issuecomment-92850414 diff --git a/vips.h b/vips.h index 8046e6e..37bcdde 100644 --- a/vips.h +++ b/vips.h @@ -56,7 +56,7 @@ vips_shrink_bridge(VipsImage *in, VipsImage **out, double xshrink, double yshrin }; int -vips_rotate(VipsImage *in, VipsImage **buf, int angle) +vips_rotate(VipsImage *in, VipsImage **out, int angle) { int rotate = VIPS_ANGLE_D0; @@ -68,7 +68,7 @@ vips_rotate(VipsImage *in, VipsImage **buf, int angle) rotate = VIPS_ANGLE_D270; } - return vips_rot(in, buf, rotate, NULL); + return vips_rot(in, out, rotate, NULL); }; int @@ -158,16 +158,16 @@ 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); + code = vips_jpegload_buffer(buf, len, out, "access", VIPS_ACCESS_RANDOM, NULL); } else if (imageType == PNG) { - code = vips_pngload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL); + code = vips_pngload_buffer(buf, len, out, "access", VIPS_ACCESS_RANDOM, NULL); } else if (imageType == WEBP) { - code = vips_webpload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL); + code = vips_webpload_buffer(buf, len, out, "access", VIPS_ACCESS_RANDOM, NULL); } else if (imageType == TIFF) { - code = vips_tiffload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL); + code = vips_tiffload_buffer(buf, len, out, "access", VIPS_ACCESS_RANDOM, NULL); #if (VIPS_MAJOR_VERSION >= 8) } else if (imageType == MAGICK) { - code = vips_magickload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL); + code = vips_magickload_buffer(buf, len, out, "access", VIPS_ACCESS_RANDOM, NULL); #endif } From 13bf08855349a08c8f63951494e6b967dd6ad714 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Aparicio?= Date: Sun, 10 May 2015 22:01:44 +0200 Subject: [PATCH 049/115] refactor(docs): description --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a67125d..e77b5f3 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,14 @@ Small [Go](http://golang.org) library for fast high-level image processing and transformation using [libvips](https://github.com/jcupitt/libvips) via C bindings. It provides a simple, elegant and fluent [programmatic API](#examples). -bimg was designed to be a small and efficient library with a generic and useful features. +bimg was designed to be a small and efficient library providing a generic and common high-level [image operations](#supported-image-operations) such as crop, resize, rotate, zoom, watermark... +It can read JPEG, PNG, WEBP and TIFF formats and output to JPEG, PNG and WEBP, including conversion between them. + It uses internally libvips, a powerful library written in C for binary image processing which requires a [low memory footprint](http://www.vips.ecs.soton.ac.uk/index.php?title=Speed_and_Memory_Use) and it's typically 4x faster than using the quickest ImageMagick and GraphicsMagick settings or Go native `image` package, and in some cases it's even 8x faster processing JPEG images. -It can read JPEG, PNG, WEBP and TIFF formats and output to JPEG, PNG and WEBP, including conversion between them. It supports common [image operations](#supported-image-operations) such as crop, resize, rotate, zoom, watermark... - For getting started, take a look to the [examples](#examples) and [API](https://godoc.org/github.com/h2non/bimg) documentation. -If you're looking for a HTTP-based image processing solution, see [imaginary](https://github.com/h2non/imaginary). +If you're looking for a HTTP based image processing solution, see [imaginary](https://github.com/h2non/imaginary). bimg was heavily inspired in [sharp](https://github.com/lovell/sharp), its homologous package built for node.js by [Lovell Fuller](https://github.com/lovell). ## Prerequisites From fad31e48594b7580c18e20137b7ab5bae56f114a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Aparicio?= Date: Sun, 24 May 2015 12:28:15 +0100 Subject: [PATCH 050/115] refactor(docs): description --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e77b5f3..ed6506f 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # bimg [![Build Status](https://travis-ci.org/h2non/bimg.png)](https://travis-ci.org/h2non/bimg) [![GitHub release](http://img.shields.io/github/tag/h2non/bimg.svg?style=flat-square)](https://github.com/h2non/bimg/releases) [![GoDoc](https://godoc.org/github.com/h2non/bimg?status.svg)](https://godoc.org/github.com/h2non/bimg) [![Coverage Status](https://coveralls.io/repos/h2non/bimg/badge.svg?branch=master)](https://coveralls.io/r/h2non/bimg?branch=master) -Small [Go](http://golang.org) library for fast high-level image processing and transformation using [libvips](https://github.com/jcupitt/libvips) via C bindings. It provides a simple, elegant and fluent [programmatic API](#examples). +Small [Go](http://golang.org) package for fast high-level image processing and transformation using [libvips](https://github.com/jcupitt/libvips) via C bindings. It provides a simple, elegant and fluent [programmatic API](#examples). -bimg was designed to be a small and efficient library providing a generic and common high-level [image operations](#supported-image-operations) such as crop, resize, rotate, zoom, watermark... +bimg was designed to be a small and efficient library providing a generic high-level [image operations](#supported-image-operations) such as crop, resize, rotate, zoom, watermark... It can read JPEG, PNG, WEBP and TIFF formats and output to JPEG, PNG and WEBP, including conversion between them. It uses internally libvips, a powerful library written in C for binary image processing which requires a [low memory footprint](http://www.vips.ecs.soton.ac.uk/index.php?title=Speed_and_Memory_Use) From 3fb0b1a000559351b4387df0139099ddca54fdfc Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sun, 24 May 2015 12:31:21 +0100 Subject: [PATCH 051/115] feat(version): bump --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index 00d6ece..a68f86b 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,3 @@ package bimg -const Version = "0.1.13" +const Version = "0.1.14" From 96e188931d0dac361095fa55c346054e06b3b2ed Mon Sep 17 00:00:00 2001 From: Brant Fitzsimmons Date: Mon, 25 May 2015 11:27:15 -0400 Subject: [PATCH 052/115] Fixed the JPEG watermark benchmark. I believe the wrong image type was being used for the benchmark. It didn't match the pattern of the other tests. --- resize_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resize_test.go b/resize_test.go index 6e8a150..b2109db 100644 --- a/resize_test.go +++ b/resize_test.go @@ -278,7 +278,7 @@ func BenchmarkWatermarkJpeg(b *testing.B) { Background: Color{255, 255, 255}, }, } - runBenchmarkResize("test.webp", options, b) + runBenchmarkResize("test.jpg", options, b) } func BenchmarkWatermarPng(b *testing.B) { From 67028171857d4922b437586a629ecbef8f481226 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Aparicio?= Date: Wed, 3 Jun 2015 13:30:49 +0100 Subject: [PATCH 053/115] refactor(vips): remove debug statement, add comments --- vips.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/vips.go b/vips.go index 04bf8d6..4933b05 100644 --- a/vips.go +++ b/vips.go @@ -76,12 +76,12 @@ func Initialize() { C.vips_cache_set_max_mem(maxCacheMem) C.vips_cache_set_max(maxCacheSize) - // Explicit concurrency limit to avoid thread-unsafe issues. + // Define a custom libvips thread concurrency limit (this may generate thread-unsafe issues) // See: https://github.com/jcupitt/libvips/issues/261#issuecomment-92850414 if os.Getenv("VIPS_CONCURRENCY") == "" { C.vips_concurrency_set(1) } - + // Enable libvips cache tracing if os.Getenv("VIPS_TRACE") != "" { C.vips_enable_cache_set_trace() } @@ -234,7 +234,6 @@ func vipsSave(image *C.struct__VipsImage, o vipsSaveOptions) ([]byte, error) { err = C.vips_webpsave_bridge(image, &ptr, &length, 1, C.int(o.Quality), 0) break default: - debug("Save JPEG options: Q: %s", o.Quality) err = C.vips_jpegsave_bridge(image, &ptr, &length, 1, C.int(o.Quality), 0) break } @@ -306,10 +305,10 @@ func vipsEmbed(input *C.struct__VipsImage, left, top, width, height, extend int) func vipsAffine(input *C.struct__VipsImage, residual float64, i Interpolator) (*C.struct__VipsImage, error) { var image *C.struct__VipsImage - istring := C.CString(i.String()) - interpolator := C.vips_interpolate_new(istring) + cstring := C.CString(i.String()) + interpolator := C.vips_interpolate_new(cstring) - defer C.free(unsafe.Pointer(istring)) + defer C.free(unsafe.Pointer(cstring)) defer C.g_object_unref(C.gpointer(input)) defer C.g_object_unref(C.gpointer(interpolator)) From 6ca9bed9969f19667259c0ece307cdb6e1c42466 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Aparicio?= Date: Wed, 3 Jun 2015 13:37:32 +0100 Subject: [PATCH 054/115] refactor(vips): switch option --- vips.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vips.go b/vips.go index 4933b05..2c89e76 100644 --- a/vips.go +++ b/vips.go @@ -226,11 +226,11 @@ func vipsSave(image *C.struct__VipsImage, o vipsSaveOptions) ([]byte, error) { defer C.g_object_unref(C.gpointer(image)) - switch { - case o.Type == PNG: + switch o.Type { + case PNG: err = C.vips_pngsave_bridge(image, &ptr, &length, 1, C.int(o.Compression), C.int(o.Quality), 0) break - case o.Type == WEBP: + case WEBP: err = C.vips_webpsave_bridge(image, &ptr, &length, 1, C.int(o.Quality), 0) break default: From c6f179d4445f7696b6d6d2b6d3f74a3dba4441a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Aparicio?= Date: Wed, 3 Jun 2015 16:11:06 +0100 Subject: [PATCH 055/115] refactor(docs): feature list --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ed6506f..02fc960 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ The [install script](https://github.com/lovell/sharp/blob/master/preinstall.sh) - Zoom - Thumbnail - Extract area -- Watermark (fully customizable text-based) +- Watermark (text-based) - Format conversion (with additional quality/compression settings) - EXIF metadata (size, alpha channel, profile, orientation...) From cc627a48a68fc2050dad16679478bdf976f9c0cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Aparicio?= Date: Wed, 3 Jun 2015 17:14:15 +0100 Subject: [PATCH 056/115] feat(docs): add openslide how to install. Related with #40 --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 02fc960..c70df2e 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,15 @@ Run the following script as `sudo` (supports OSX, Debian/Ubuntu, Redhat, Fedora, curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | sudo bash - ``` +If you wanne take the advantage of [OpenSlide](http://openslide.org/), simply add `--with-openslide` to enable it: +```bash +curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | sudo bash -s --with-openslide +``` + The [install script](https://github.com/lovell/sharp/blob/master/preinstall.sh) requires `curl` and `pkg-config` +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 - Resize From 9fdbefd33012d729cb28db8704ceef1ca377e7cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Aparicio?= Date: Wed, 3 Jun 2015 18:38:32 +0100 Subject: [PATCH 057/115] fix(docs): minor typo fixes --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c70df2e..ebfb344 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,12 @@ Small [Go](http://golang.org) package for fast high-level image processing and t bimg was designed to be a small and efficient library providing a generic high-level [image operations](#supported-image-operations) such as crop, resize, rotate, zoom, watermark... It can read JPEG, PNG, WEBP and TIFF formats and output to JPEG, PNG and WEBP, including conversion between them. -It uses internally libvips, a powerful library written in C for binary image processing which requires a [low memory footprint](http://www.vips.ecs.soton.ac.uk/index.php?title=Speed_and_Memory_Use) -and it's typically 4x faster than using the quickest ImageMagick and GraphicsMagick settings or Go native `image` package, and in some cases it's even 8x faster processing JPEG images. +bimg uses internally libvips, a powerful library written in C for image processing which requires a [low memory footprint](http://www.vips.ecs.soton.ac.uk/index.php?title=Speed_and_Memory_Use) +and it's typically 4x faster than using the quickest ImageMagick and GraphicsMagick settings or Go native `image` package, and in some cases it's even 8x faster processing JPEG images. For getting started, take a look to the [examples](#examples) and [API](https://godoc.org/github.com/h2non/bimg) documentation. If you're looking for a HTTP based image processing solution, see [imaginary](https://github.com/h2non/imaginary). + bimg was heavily inspired in [sharp](https://github.com/lovell/sharp), its homologous package built for node.js by [Lovell Fuller](https://github.com/lovell). ## Prerequisites @@ -31,7 +32,7 @@ Run the following script as `sudo` (supports OSX, Debian/Ubuntu, Redhat, Fedora, curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | sudo bash - ``` -If you wanne take the advantage of [OpenSlide](http://openslide.org/), simply add `--with-openslide` to enable it: +If you wanna take the advantage of [OpenSlide](http://openslide.org/), simply add `--with-openslide` to enable it: ```bash curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | sudo bash -s --with-openslide ``` From 0019d370e9563188fd5f3d855d078df7776b2094 Mon Sep 17 00:00:00 2001 From: Brant Fitzsimmons Date: Wed, 10 Jun 2015 18:55:58 -0400 Subject: [PATCH 058/115] Added progressive jpeg functionality. --- README.md | 3 +++ options.go | 1 + resize.go | 1 + vips.go | 3 ++- vips_test.go | 2 +- 5 files changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ed6506f..9f060e7 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ The [install script](https://github.com/lovell/sharp/blob/master/preinstall.sh) - Enlarge - Crop - Rotate (with auto-rotate based on EXIF orientation) +- Progressive - Flip (with auto-flip based on EXIF metadata) - Flop - Zoom @@ -159,6 +160,7 @@ options := bimg.Options{ Crop: true, Quality: 95, Rotate: 180, + Interlace: 1, } buffer, err := bimg.Read("image.jpg") @@ -618,6 +620,7 @@ type Options struct { Flip bool Flop bool NoAutoRotate bool + Interlace int Rotate Angle Gravity Gravity Watermark Watermark diff --git a/options.go b/options.go index 4495a55..39b170f 100644 --- a/options.go +++ b/options.go @@ -88,6 +88,7 @@ type Options struct { Flip bool Flop bool NoAutoRotate bool + Interlace int Rotate Angle Gravity Gravity Watermark Watermark diff --git a/resize.go b/resize.go index 36bb10a..a89583c 100644 --- a/resize.go +++ b/resize.go @@ -126,6 +126,7 @@ func Resize(buf []byte, o Options) ([]byte, error) { Quality: o.Quality, Type: o.Type, Compression: o.Compression, + Interlace: o.Interlace, } // Finally save as buffer diff --git a/vips.go b/vips.go index 04bf8d6..b6cc6fe 100644 --- a/vips.go +++ b/vips.go @@ -35,6 +35,7 @@ type vipsSaveOptions struct { Quality int Compression int Type ImageType + Interlace int } type vipsWatermarkOptions struct { @@ -235,7 +236,7 @@ func vipsSave(image *C.struct__VipsImage, o vipsSaveOptions) ([]byte, error) { break default: debug("Save JPEG options: Q: %s", o.Quality) - err = C.vips_jpegsave_bridge(image, &ptr, &length, 1, C.int(o.Quality), 0) + err = C.vips_jpegsave_bridge(image, &ptr, &length, 1, C.int(o.Quality), C.int(o.Interlace)) break } diff --git a/vips_test.go b/vips_test.go index 5eab2c3..38f6fe5 100644 --- a/vips_test.go +++ b/vips_test.go @@ -30,7 +30,7 @@ func TestVipsRead(t *testing.T) { func TestVipsSave(t *testing.T) { image, _, _ := vipsRead(readImage("test.jpg")) - options := vipsSaveOptions{Quality: 95, Type: JPEG} + options := vipsSaveOptions{Quality: 95, Type: JPEG, Interlace: 1} buf, err := vipsSave(image, options) if err != nil { From a292e706eac92864c23b02639a3fb158e018adc3 Mon Sep 17 00:00:00 2001 From: Brant Fitzsimmons Date: Wed, 10 Jun 2015 18:59:14 -0400 Subject: [PATCH 059/115] This should not have been added. --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 9f060e7..a8f7543 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,6 @@ The [install script](https://github.com/lovell/sharp/blob/master/preinstall.sh) - Enlarge - Crop - Rotate (with auto-rotate based on EXIF orientation) -- Progressive - Flip (with auto-flip based on EXIF metadata) - Flop - Zoom From 078915240a3b3d4aca383af0ca6ffcf443b5a33c Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Fri, 12 Jun 2015 10:32:34 +0100 Subject: [PATCH 060/115] feat(docs): update API docs --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9e0c94e..9e3dba5 100644 --- a/README.md +++ b/README.md @@ -167,7 +167,7 @@ options := bimg.Options{ Crop: true, Quality: 95, Rotate: 180, - Interlace: 1, + Interlace: true, } buffer, err := bimg.Read("image.jpg") @@ -247,6 +247,8 @@ Enable libvips traces (note that a lot of data will be written in stdout): VIPS_TRACE=1 ./app ``` +### Programmatic API + #### func DetermineImageTypeName ```go @@ -627,7 +629,7 @@ type Options struct { Flip bool Flop bool NoAutoRotate bool - Interlace int + Interlace bool Rotate Angle Gravity Gravity Watermark Watermark @@ -670,6 +672,7 @@ type Watermark struct { } ``` + ## Special Thanks - [John Cupitt](https://github.com/jcupitt) From 827eb82ea3b75874fc44ae7eb52de1908fb2af09 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Fri, 12 Jun 2015 10:33:30 +0100 Subject: [PATCH 061/115] feat(version): bump --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index a68f86b..21b8f84 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,3 @@ package bimg -const Version = "0.1.14" +const Version = "0.1.15" From 5d3a306adc0b8504e61b5d7f33f22f5e57024f0d Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sat, 13 Jun 2015 17:39:25 +0100 Subject: [PATCH 062/115] fix(#43) --- options.go | 2 ++ resize.go | 47 ++++++++++++++++++++++++++++++++--------------- vips.go | 1 - 3 files changed, 34 insertions(+), 16 deletions(-) diff --git a/options.go b/options.go index 3485de0..d7866b5 100644 --- a/options.go +++ b/options.go @@ -55,6 +55,8 @@ const ( VERTICAL Direction = C.VIPS_DIRECTION_VERTICAL ) +const WATERMARK_FONT = "sans 10" + // Color represents a traditional RGB color scheme type Color struct { R, G, B uint8 diff --git a/resize.go b/resize.go index a89583c..8916eac 100644 --- a/resize.go +++ b/resize.go @@ -72,23 +72,22 @@ func Resize(buf []byte, o Options) ([]byte, error) { residual = float64(shrink) / factor } - debug("Test %s, %s, %s", shrink, residual, factor) - - // Zoom image if necessary + // Zoom image, if necessary image, err = zoomImage(image, o.Zoom) if err != nil { return nil, err } - // Rotate / flip image if necessary + // Rotate / flip image, if necessary image, err = rotateAndFlipImage(image, o) if err != nil { return nil, err } - // Transform image if necessary - shouldTransform := o.Width != inWidth || o.Height != inHeight || o.AreaWidth > 0 || o.AreaHeight > 0 - if shouldTransform { + // 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 { @@ -98,9 +97,12 @@ func Resize(buf []byte, o Options) ([]byte, error) { } } + 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 = vipsAffine(image, residual, o.Interpolator) + image, err = affineImage(image, o, residual) if err != nil { return nil, err } @@ -111,12 +113,9 @@ func Resize(buf []byte, o Options) ([]byte, error) { if err != nil { return nil, err } - - debug("Transform: factor=%v, shrink=%v, residual=%v, interpolator=%v", - factor, shrink, residual, o.Interpolator.String()) } - // Add watermark if necessary + // Add watermark, if necessary image, err = watermakImage(image, o.Watermark) if err != nil { return nil, err @@ -148,6 +147,7 @@ func extractImage(image *C.struct__VipsImage, o Options) (*C.struct__VipsImage, width := int(math.Min(float64(inWidth), float64(o.Width))) height := int(math.Min(float64(inHeight), float64(o.Height))) left, top := calculateCrop(inWidth, inHeight, o.Width, o.Height, o.Gravity) + left, top = int(math.Max(float64(left), 0)), int(math.Max(float64(top), 0)) image, err = vipsExtract(image, left, top, width, height) break case o.Embed: @@ -162,7 +162,7 @@ func extractImage(image *C.struct__VipsImage, o Options) (*C.struct__VipsImage, o.AreaHeight = o.Height } if o.AreaWidth == 0 || o.AreaHeight == 0 { - return nil, errors.New("Extract area width/height is required") + return nil, errors.New("Extract area width/height params are required") } image, err = vipsExtract(image, o.Left, o.Top, o.AreaWidth, o.AreaHeight) break @@ -209,7 +209,7 @@ func watermakImage(image *C.struct__VipsImage, w Watermark) (*C.struct__VipsImag // Defaults if w.Font == "" { - w.Font = "sans 10" + w.Font = WATERMARK_FONT } if w.Width == 0 { w.Width = int(math.Floor(float64(image.Xsize / 6))) @@ -242,6 +242,23 @@ 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 > int(newImage.Xsize) && o.Height > int(newImage.Ysize)) { + C.g_object_unref(C.gpointer(image)) + image = newImage + } else { + 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) @@ -309,8 +326,8 @@ func imageCalculations(o *Options, inWidth, inHeight int) float64 { case o.Height > 0: factor = yfactor o.Width = int(math.Floor(float64(inWidth) / factor)) + // Identity transform default: - // Identity transform o.Width = inWidth o.Height = inHeight break diff --git a/vips.go b/vips.go index d43646c..3535b9e 100644 --- a/vips.go +++ b/vips.go @@ -311,7 +311,6 @@ func vipsAffine(input *C.struct__VipsImage, residual float64, i Interpolator) (* 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) From e4f822b28cb48a498c659067e1df8ef452cb998b Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sat, 13 Jun 2015 17:43:04 +0100 Subject: [PATCH 063/115] feat(version): bump --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index 21b8f84..d7aea8f 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,3 @@ package bimg -const Version = "0.1.15" +const Version = "0.1.16" From 941b15cd9a868590b01e7c909002cda02b7bdcab Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sat, 13 Jun 2015 20:16:28 +0100 Subject: [PATCH 064/115] feat: save a RGB colorspace --- vips.go | 11 ++++++++--- vips.h | 6 ++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/vips.go b/vips.go index 3535b9e..991936d 100644 --- a/vips.go +++ b/vips.go @@ -226,17 +226,22 @@ func vipsSave(image *C.struct__VipsImage, o vipsSaveOptions) ([]byte, error) { err := C.int(0) interlace := C.int(boolToInt(o.Interlace)) + // Force RGB color space + var outImage *C.struct__VipsImage + C.vips_colourspace_bridge(image, &outImage) + defer C.g_object_unref(C.gpointer(image)) + defer C.g_object_unref(C.gpointer(outImage)) switch o.Type { case PNG: - err = C.vips_pngsave_bridge(image, &ptr, &length, 1, C.int(o.Compression), C.int(o.Quality), interlace) + err = C.vips_pngsave_bridge(outImage, &ptr, &length, 1, C.int(o.Compression), C.int(o.Quality), interlace) break case WEBP: - err = C.vips_webpsave_bridge(image, &ptr, &length, 1, C.int(o.Quality)) + err = C.vips_webpsave_bridge(outImage, &ptr, &length, 1, C.int(o.Quality)) break default: - err = C.vips_jpegsave_bridge(image, &ptr, &length, 1, C.int(o.Quality), interlace) + err = C.vips_jpegsave_bridge(outImage, &ptr, &length, 1, C.int(o.Quality), interlace) break } diff --git a/vips.h b/vips.h index f24dadf..9dbecdb 100644 --- a/vips.h +++ b/vips.h @@ -129,6 +129,12 @@ vips_extract_area_bridge(VipsImage *in, VipsImage **out, int left, int top, int return vips_extract_area(in, out, left, top, width, height, NULL); }; +int +vips_colourspace_bridge(VipsImage *in, VipsImage **out) +{ + return vips_colourspace(in, out, VIPS_INTERPRETATION_sRGB, NULL); +}; + gboolean with_interlace(int interlace) { return interlace > 0 ? TRUE : FALSE; From 5d322714eca2bd01facdb201651578e77ba8fd11 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sat, 13 Jun 2015 22:11:54 +0100 Subject: [PATCH 065/115] feat: allow to remove ICC profile metadata --- options.go | 1 + resize.go | 1 + resize_test.go | 15 +++++++++++++++ vips.go | 8 +++++++- vips.h | 7 ++++++- 5 files changed, 30 insertions(+), 2 deletions(-) diff --git a/options.go b/options.go index d7866b5..d29ec3a 100644 --- a/options.go +++ b/options.go @@ -90,6 +90,7 @@ type Options struct { Flip bool Flop bool NoAutoRotate bool + NoProfile bool Interlace bool Rotate Angle Gravity Gravity diff --git a/resize.go b/resize.go index 8916eac..30993b4 100644 --- a/resize.go +++ b/resize.go @@ -126,6 +126,7 @@ func Resize(buf []byte, o Options) ([]byte, error) { Type: o.Type, Compression: o.Compression, Interlace: o.Interlace, + NoProfile: o.NoProfile, } // Finally save as buffer diff --git a/resize_test.go b/resize_test.go index b2109db..1397d6d 100644 --- a/resize_test.go +++ b/resize_test.go @@ -71,6 +71,21 @@ func TestInvalidRotate(t *testing.T) { Write("fixtures/test_invalid_rotate_out.jpg", newImg) } +func TestNoColorProfile(t *testing.T) { + options := Options{Width: 800, Height: 600, NoColorProfile: true} + buf, _ := Read("fixtures/test.jpg") + + newImg, err := Resize(buf, options) + if err != nil { + t.Errorf("Resize(imgData, %#v) error: %#v", options, err) + } + + metadata, err := Metadata(newImg) + if metadata.Profile == true { + t.Fatal("Invalid profile data") + } +} + func TestConvert(t *testing.T) { width, height := 300, 240 formats := [3]ImageType{PNG, WEBP, JPEG} diff --git a/vips.go b/vips.go index 991936d..e190e9c 100644 --- a/vips.go +++ b/vips.go @@ -36,6 +36,7 @@ type vipsSaveOptions struct { Compression int Type ImageType Interlace bool + NoProfile bool } type vipsWatermarkOptions struct { @@ -221,11 +222,15 @@ func vipsRead(buf []byte) (*C.struct__VipsImage, ImageType, error) { } func vipsSave(image *C.struct__VipsImage, o vipsSaveOptions) ([]byte, error) { - var ptr unsafe.Pointer length := C.size_t(0) err := C.int(0) interlace := C.int(boolToInt(o.Interlace)) + // Remove ICC profile metadata + if o.NoProfile { + C.remove_profile(image) + } + // Force RGB color space var outImage *C.struct__VipsImage C.vips_colourspace_bridge(image, &outImage) @@ -233,6 +238,7 @@ func vipsSave(image *C.struct__VipsImage, o vipsSaveOptions) ([]byte, error) { defer C.g_object_unref(C.gpointer(image)) defer C.g_object_unref(C.gpointer(outImage)) + var ptr unsafe.Pointer switch o.Type { case PNG: err = C.vips_pngsave_bridge(outImage, &ptr, &length, 1, C.int(o.Compression), C.int(o.Quality), interlace) diff --git a/vips.h b/vips.h index 9dbecdb..e380b1e 100644 --- a/vips.h +++ b/vips.h @@ -86,7 +86,12 @@ vips_exif_orientation(VipsImage *image) { int has_profile_embed(VipsImage *image) { - return (vips_image_get_typeof(image, VIPS_META_ICC_NAME) > 0) ? 1 : 0; + return vips_image_get_typeof(image, VIPS_META_ICC_NAME); +}; + +void +remove_profile(VipsImage *image) { + vips_image_remove(image, VIPS_META_ICC_NAME); }; int From 099acec8ea0efb158c04f57c3f924d77a8309acb Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sat, 13 Jun 2015 22:12:53 +0100 Subject: [PATCH 066/115] feat(docs): update API --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9e3dba5..2c6290c 100644 --- a/README.md +++ b/README.md @@ -249,6 +249,7 @@ VIPS_TRACE=1 ./app ### Programmatic API + #### func DetermineImageTypeName ```go @@ -629,6 +630,7 @@ type Options struct { Flip bool Flop bool NoAutoRotate bool + NoProfile bool Interlace bool Rotate Angle Gravity Gravity @@ -672,7 +674,6 @@ type Watermark struct { } ``` - ## Special Thanks - [John Cupitt](https://github.com/jcupitt) From 977f04fd7d949d3ce7a46f1ecdb0e7c98bc2364f Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sat, 13 Jun 2015 22:13:10 +0100 Subject: [PATCH 067/115] feat(version): bump --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index d7aea8f..a433764 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,3 @@ package bimg -const Version = "0.1.16" +const Version = "0.1.17" From 4678a066b62eb87d8387c83beacb3f5ca401ce90 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sat, 13 Jun 2015 22:43:32 +0100 Subject: [PATCH 068/115] fix(test): bad option field --- resize_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resize_test.go b/resize_test.go index 1397d6d..7bde1a2 100644 --- a/resize_test.go +++ b/resize_test.go @@ -72,7 +72,7 @@ func TestInvalidRotate(t *testing.T) { } func TestNoColorProfile(t *testing.T) { - options := Options{Width: 800, Height: 600, NoColorProfile: true} + options := Options{Width: 800, Height: 600, NoProfile: true} buf, _ := Read("fixtures/test.jpg") newImg, err := Resize(buf, options) From 5b86fce2072aae249bac3e660beb0f43d3f94097 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s?= Date: Mon, 29 Jun 2015 10:21:26 +0100 Subject: [PATCH 069/115] fix(docs) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2c6290c..0309e33 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # bimg [![Build Status](https://travis-ci.org/h2non/bimg.png)](https://travis-ci.org/h2non/bimg) [![GitHub release](http://img.shields.io/github/tag/h2non/bimg.svg?style=flat-square)](https://github.com/h2non/bimg/releases) [![GoDoc](https://godoc.org/github.com/h2non/bimg?status.svg)](https://godoc.org/github.com/h2non/bimg) [![Coverage Status](https://coveralls.io/repos/h2non/bimg/badge.svg?branch=master)](https://coveralls.io/r/h2non/bimg?branch=master) -Small [Go](http://golang.org) package for fast high-level image processing and transformation using [libvips](https://github.com/jcupitt/libvips) via C bindings. It provides a simple, elegant and fluent [programmatic API](#examples). +Small [Go](http://golang.org) package for fast high-level image processing and transformation using [libvips](https://github.com/jcupitt/libvips) via C bindings, provinding a simple, elegant and fluent [programmatic API](#examples). bimg was designed to be a small and efficient library providing a generic high-level [image operations](#supported-image-operations) such as crop, resize, rotate, zoom, watermark... It can read JPEG, PNG, WEBP and TIFF formats and output to JPEG, PNG and WEBP, including conversion between them. From 16db01647dc60687f026ca8b5802b89cbd479d2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s?= Date: Thu, 2 Jul 2015 22:08:04 +0100 Subject: [PATCH 070/115] refactor(docs): description --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 0309e33..19e5a4e 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,8 @@ It can read JPEG, PNG, WEBP and TIFF formats and output to JPEG, PNG and WEBP, i bimg uses internally libvips, a powerful library written in C for image processing which requires a [low memory footprint](http://www.vips.ecs.soton.ac.uk/index.php?title=Speed_and_Memory_Use) and it's typically 4x faster than using the quickest ImageMagick and GraphicsMagick settings or Go native `image` package, and in some cases it's even 8x faster processing JPEG images. -For getting started, take a look to the [examples](#examples) and [API](https://godoc.org/github.com/h2non/bimg) documentation. +To get started you could take a look to the [examples](#examples) and [API](https://godoc.org/github.com/h2non/bimg) documentation. If you're looking for a HTTP based image processing solution, see [imaginary](https://github.com/h2non/imaginary). - bimg was heavily inspired in [sharp](https://github.com/lovell/sharp), its homologous package built for node.js by [Lovell Fuller](https://github.com/lovell). ## Prerequisites From 9db6f2a0ea6ce5b1ef997b3f2b5d86d022389573 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Thu, 9 Jul 2015 23:30:08 +0100 Subject: [PATCH 071/115] fix(#46): infer resize operation --- resize.go | 52 ++++++++++++++++++++++++++++++++------------------ resize_test.go | 41 +++++++++++++++++++++++++++++++-------- vips.go | 6 ++++-- 3 files changed, 70 insertions(+), 29 deletions(-) diff --git a/resize.go b/resize.go index 30993b4..5769a0e 100644 --- a/resize.go +++ b/resize.go @@ -23,17 +23,6 @@ func Resize(buf []byte, o Options) ([]byte, error) { return nil, err } - // Defaults - if o.Quality == 0 { - o.Quality = QUALITY - } - if o.Compression == 0 { - o.Compression = 6 - } - if o.Type == 0 { - o.Type = imageType - } - if IsTypeSupported(o.Type) == false { return nil, errors.New("Unsupported image output type") } @@ -43,14 +32,20 @@ func Resize(buf []byte, o Options) ([]byte, error) { inWidth := int(image.Xsize) inHeight := int(image.Ysize) + // Define default options + applyDefaults(&o, imageType) + // Infer the required operation based on the in/out image sizes for a coherent transformation + normalizeOperation(&o, inWidth, inHeight) + // image calculations factor := imageCalculations(&o, inWidth, inHeight) shrink := calculateShrink(factor, o.Interpolator) residual := calculateResidual(factor, shrink) - // Do not enlarge the output if the input width *or* height are already less than the required dimensions + // Do not enlarge the output if the input width or height + // are already less than the required dimensions if o.Enlarge == false { - if inWidth < o.Width && inHeight < o.Height { + if inWidth < o.Width || inHeight < o.Height { factor = 1.0 shrink = 1 residual = 0 @@ -88,7 +83,6 @@ func Resize(buf []byte, o Options) ([]byte, error) { 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) @@ -138,6 +132,28 @@ func Resize(buf []byte, o Options) ([]byte, error) { return buf, nil } +func applyDefaults(o *Options, imageType ImageType) { + if o.Quality == 0 { + o.Quality = QUALITY + } + if o.Compression == 0 { + o.Compression = 6 + } + if o.Type == 0 { + o.Type = 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 + } + } +} + func extractImage(image *C.struct__VipsImage, o Options) (*C.struct__VipsImage, error) { var err error = nil inWidth := int(image.Xsize) @@ -239,7 +255,6 @@ func zoomImage(image *C.struct__VipsImage, zoom int) (*C.struct__VipsImage, erro if zoom == 0 { return image, nil } - return vipsZoom(image, zoom+1) } @@ -250,13 +265,12 @@ func affineImage(image *C.struct__VipsImage, o Options, residual float64) (*C.st return nil, err } - if o.Enlarge || (o.Width > int(newImage.Xsize) && o.Height > int(newImage.Ysize)) { + if o.Enlarge || o.Width > 0 || (o.Width > int(newImage.Xsize) && o.Height > int(newImage.Ysize)) { C.g_object_unref(C.gpointer(image)) - image = newImage - } else { - C.g_object_unref(C.gpointer(newImage)) + return newImage, nil } + C.g_object_unref(C.gpointer(newImage)) return image, nil } diff --git a/resize_test.go b/resize_test.go index 7bde1a2..89bb6ae 100644 --- a/resize_test.go +++ b/resize_test.go @@ -20,6 +20,11 @@ func TestResize(t *testing.T) { t.Fatal("Image is not jpeg") } + size, _ := Size(newImg) + if size.Height != options.Height || size.Width != options.Width { + t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height) + } + Write("fixtures/test_out.jpg", newImg) } @@ -36,12 +41,17 @@ func TestRotate(t *testing.T) { t.Fatal("Image is not jpeg") } + size, _ := Size(newImg) + if size.Height != options.Width { + t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height) + } + Write("fixtures/test_rotate_out.jpg", newImg) } -func TestCorruptedImage(t *testing.T) { - options := Options{Width: 800, Height: 600} - buf, _ := Read("fixtures/corrupt.jpg") +func TestInvalidRotate(t *testing.T) { + options := Options{Width: 800, Height: 600, Rotate: 111} + buf, _ := Read("fixtures/test.jpg") newImg, err := Resize(buf, options) if err != nil { @@ -52,12 +62,17 @@ func TestCorruptedImage(t *testing.T) { t.Fatal("Image is not jpeg") } - Write("fixtures/test_corrupt_out.jpg", newImg) + size, _ := Size(newImg) + if size.Height != options.Width { + t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height) + } + + Write("fixtures/test_invalid_rotate_out.jpg", newImg) } -func TestInvalidRotate(t *testing.T) { - options := Options{Width: 800, Height: 600, Rotate: 111} - buf, _ := Read("fixtures/test.jpg") +func TestCorruptedImage(t *testing.T) { + options := Options{Width: 800, Height: 600} + buf, _ := Read("fixtures/corrupt.jpg") newImg, err := Resize(buf, options) if err != nil { @@ -68,7 +83,12 @@ func TestInvalidRotate(t *testing.T) { t.Fatal("Image is not jpeg") } - Write("fixtures/test_invalid_rotate_out.jpg", newImg) + size, _ := Size(newImg) + if size.Height != options.Height || size.Width != options.Width { + t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height) + } + + Write("fixtures/test_corrupt_out.jpg", newImg) } func TestNoColorProfile(t *testing.T) { @@ -84,6 +104,11 @@ func TestNoColorProfile(t *testing.T) { if metadata.Profile == true { t.Fatal("Invalid profile data") } + + size, _ := Size(newImg) + if size.Height != options.Height || size.Width != options.Width { + t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height) + } } func TestConvert(t *testing.T) { diff --git a/vips.go b/vips.go index e190e9c..5a100ce 100644 --- a/vips.go +++ b/vips.go @@ -78,11 +78,12 @@ func Initialize() { C.vips_cache_set_max_mem(maxCacheMem) C.vips_cache_set_max(maxCacheSize) - // Define a custom libvips thread concurrency limit (this may generate thread-unsafe issues) + // Define a custom thread concurrency limit in libvips (this may generate thread-unsafe issues) // See: https://github.com/jcupitt/libvips/issues/261#issuecomment-92850414 if os.Getenv("VIPS_CONCURRENCY") == "" { C.vips_concurrency_set(1) } + // Enable libvips cache tracing if os.Getenv("VIPS_TRACE") != "" { C.vips_enable_cache_set_trace() @@ -91,7 +92,8 @@ func Initialize() { initialized = true } -// Thread-safe function to shutdown libvips. You could call this to drop caches as well. +// Thread-safe function to shutdown libvips. +// You can call this to drop caches as well. // If libvips was already initialized, the function is no-op func Shutdown() { m.Lock() From a45e277f68713731db49942098118ebc68c33c72 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Thu, 9 Jul 2015 23:31:51 +0100 Subject: [PATCH 072/115] refactor(resize) --- resize.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resize.go b/resize.go index 5769a0e..6e14960 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 inWidth < o.Width || inHeight < o.Height { + if inWidth < o.Width && inHeight < o.Height { factor = 1.0 shrink = 1 residual = 0 From 5874efef3ebbc5357d635c2d5a24d7fb12bb5071 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Thu, 9 Jul 2015 23:35:04 +0100 Subject: [PATCH 073/115] fix(resize): default options --- resize.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/resize.go b/resize.go index 6e14960..72c517e 100644 --- a/resize.go +++ b/resize.go @@ -23,6 +23,9 @@ func Resize(buf []byte, o Options) ([]byte, error) { return nil, err } + // Define default options + applyDefaults(&o, imageType) + if IsTypeSupported(o.Type) == false { return nil, errors.New("Unsupported image output type") } @@ -32,8 +35,6 @@ func Resize(buf []byte, o Options) ([]byte, error) { inWidth := int(image.Xsize) inHeight := int(image.Ysize) - // Define default options - applyDefaults(&o, imageType) // Infer the required operation based on the in/out image sizes for a coherent transformation normalizeOperation(&o, inWidth, inHeight) From 8a2b991ce89d6afdfb1807bb842480e520ddfad0 Mon Sep 17 00:00:00 2001 From: Yoan Blanc Date: Fri, 10 Jul 2015 15:56:21 +0200 Subject: [PATCH 074/115] Add support for colourspace (fix #45) --- fixtures/test_image_colourspace_b_w.jpg | Bin 0 -> 59525 bytes image.go | 6 ++++++ image_test.go | 8 ++++++++ options.go | 15 +++++++++++++++ resize.go | 4 ++++ vips.go | 7 ++++++- vips.h | 4 ++-- 7 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 fixtures/test_image_colourspace_b_w.jpg diff --git a/fixtures/test_image_colourspace_b_w.jpg b/fixtures/test_image_colourspace_b_w.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8afd5bc424ed54e7b065abf8c3a8c40167f18250 GIT binary patch literal 59525 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

GD&n0VjA|tpI{|F!& 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 aTuK+l!0=3-j8uiP0(g9ZYl_l;U;Ymm=mEe0 literal 0 HcmV?d00001 diff --git a/image.go b/image.go index 75889fc..df41161 100644 --- a/image.go +++ b/image.go @@ -134,6 +134,12 @@ func (i *Image) Convert(t ImageType) ([]byte, error) { return i.Process(options) } +// Colour space conversion +func (i *Image) Colourspace(c Interpretation) ([]byte, error) { + options := Options{Interpretation: c} + return i.Process(options) +} + // Transform the image by custom options func (i *Image) Process(o Options) ([]byte, error) { image, err := Resize(i.buffer, o) diff --git a/image_test.go b/image_test.go index d097f21..dc56768 100644 --- a/image_test.go +++ b/image_test.go @@ -262,6 +262,14 @@ func TestImageMetadata(t *testing.T) { } } +func TestImageColourspaceBW(t *testing.T) { + buf, err := initImage("test.jpg").Colourspace(B_W) + if err != nil { + t.Errorf("Cannot process the image: %#v", err) + } + Write("fixtures/test_image_colourspace_b_w.jpg", buf) +} + func TestFluentInterface(t *testing.T) { image := initImage("test.jpg") _, err := image.CropByWidth(300) diff --git a/options.go b/options.go index d29ec3a..da7911e 100644 --- a/options.go +++ b/options.go @@ -55,6 +55,20 @@ const ( VERTICAL Direction = C.VIPS_DIRECTION_VERTICAL ) +type Interpretation int + +const ( + ERROR Interpretation = C.VIPS_INTERPRETATION_ERROR + MULTIBAND Interpretation = C.VIPS_INTERPRETATION_MULTIBAND + B_W Interpretation = C.VIPS_INTERPRETATION_B_W + CMYK Interpretation = C.VIPS_INTERPRETATION_CMYK + RGB Interpretation = C.VIPS_INTERPRETATION_RGB + sRGB Interpretation = C.VIPS_INTERPRETATION_sRGB + RGB16 Interpretation = C.VIPS_INTERPRETATION_RGB16 + GREY16 Interpretation = C.VIPS_INTERPRETATION_GREY16 + scRGB Interpretation = C.VIPS_INTERPRETATION_scRGB +) + const WATERMARK_FONT = "sans 10" // Color represents a traditional RGB color scheme @@ -97,4 +111,5 @@ type Options struct { Watermark Watermark Type ImageType Interpolator Interpolator + Interpretation Interpretation } diff --git a/resize.go b/resize.go index 72c517e..c71b312 100644 --- a/resize.go +++ b/resize.go @@ -122,6 +122,7 @@ func Resize(buf []byte, o Options) ([]byte, error) { Compression: o.Compression, Interlace: o.Interlace, NoProfile: o.NoProfile, + Interpretation: o.Interpretation, } // Finally save as buffer @@ -143,6 +144,9 @@ func applyDefaults(o *Options, imageType ImageType) { if o.Type == 0 { o.Type = imageType } + if o.Interpretation == 0 { + o.Interpretation = sRGB + } } func normalizeOperation(o *Options, inWidth, inHeight int) { diff --git a/vips.go b/vips.go index 5a100ce..90c653c 100644 --- a/vips.go +++ b/vips.go @@ -37,6 +37,7 @@ type vipsSaveOptions struct { Type ImageType Interlace bool NoProfile bool + Interpretation Interpretation } type vipsWatermarkOptions struct { @@ -227,6 +228,10 @@ func vipsSave(image *C.struct__VipsImage, o vipsSaveOptions) ([]byte, error) { length := C.size_t(0) err := C.int(0) interlace := C.int(boolToInt(o.Interlace)) + if o.Interpretation == 0 { + o.Interpretation = sRGB + } + interpretation := C.VipsInterpretation(o.Interpretation) // Remove ICC profile metadata if o.NoProfile { @@ -235,7 +240,7 @@ func vipsSave(image *C.struct__VipsImage, o vipsSaveOptions) ([]byte, error) { // Force RGB color space var outImage *C.struct__VipsImage - C.vips_colourspace_bridge(image, &outImage) + C.vips_colourspace_bridge(image, &outImage, interpretation) defer C.g_object_unref(C.gpointer(image)) defer C.g_object_unref(C.gpointer(outImage)) diff --git a/vips.h b/vips.h index e380b1e..476cabf 100644 --- a/vips.h +++ b/vips.h @@ -135,9 +135,9 @@ vips_extract_area_bridge(VipsImage *in, VipsImage **out, int left, int top, int }; int -vips_colourspace_bridge(VipsImage *in, VipsImage **out) +vips_colourspace_bridge(VipsImage *in, VipsImage **out, VipsInterpretation space) { - return vips_colourspace(in, out, VIPS_INTERPRETATION_sRGB, NULL); + return vips_colourspace(in, out, space, NULL); }; gboolean From ed4faadba6169ce92d4ffaa8ed9e7a8a41a85917 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sat, 11 Jul 2015 16:50:25 +0100 Subject: [PATCH 075/115] refactor(#47): minor refactors, code normalization and test coverage --- README.md | 111 ++++++++++++++++++------ fixtures/test_image_colourspace_b_w.jpg | Bin 59525 -> 59110 bytes image.go | 11 +++ image_test.go | 28 +++++- metadata.go | 12 +++ metadata_test.go | 58 ++++++++++++- options.go | 66 +++++++------- resize.go | 14 +-- vips.go | 86 +++++++++++++----- vips.h | 11 +++ 10 files changed, 307 insertions(+), 90 deletions(-) diff --git a/README.md b/README.md index 19e5a4e..f302ebd 100644 --- a/README.md +++ b/README.md @@ -238,7 +238,7 @@ bimg.Write("new.jpg", newImage) Run the process passing the `DEBUG` environment variable ``` -DEBUG=* ./app +DEBUG=bimg ./app ``` Enable libvips traces (note that a lot of data will be written in stdout): @@ -248,6 +248,12 @@ VIPS_TRACE=1 ./app ### Programmatic API +#### func ColourspaceIsSupported + +```go +func ColourspaceIsSupported(buf []byte) (bool, error) +``` +Check in the image colourspace is supported by libvips #### func DetermineImageTypeName @@ -295,7 +301,7 @@ func Resize(buf []byte, o Options) ([]byte, error) ```go func Shutdown() ``` -Thread-safe function to shutdown libvips. You could call this to drop caches as +Thread-safe function to shutdown libvips. You can call this to drop caches as well. If libvips was already initialized, the function is no-op #### func VipsDebugInfo @@ -383,6 +389,20 @@ func NewImage(buf []byte) *Image ``` Creates a new image +#### func (*Image) Colourspace + +```go +func (i *Image) Colourspace(c Interpretation) ([]byte, error) +``` +Colour space conversion + +#### func (*Image) ColourspaceIsSupported + +```go +func (i *Image) ColourspaceIsSupported() (bool, error) +``` +Check if the current image has a valid colourspace + #### func (*Image) Convert ```go @@ -453,6 +473,14 @@ func (i *Image) Image() []byte ``` Get image buffer +#### func (*Image) Interpretation + +```go +func (i *Image) Interpretation() (Interpretation, error) +``` +Get the image interpretation type See: +http://www.vips.ecs.soton.ac.uk/supported/current/doc/html/libvips/VipsImage.html#VipsInterpretation + #### func (*Image) Metadata ```go @@ -533,6 +561,7 @@ type ImageMetadata struct { Profile bool Type string Space string + Colourspace string Size ImageSize } ``` @@ -609,33 +638,65 @@ const ( func (i Interpolator) String() string ``` +#### type Interpretation + +```go +type Interpretation int +``` + +Image interpretation type See: +http://www.vips.ecs.soton.ac.uk/supported/current/doc/html/libvips/VipsImage.html#VipsInterpretation + +```go +const ( + INTERPRETATION_ERROR Interpretation = C.VIPS_INTERPRETATION_ERROR + INTERPRETATION_MULTIBAND Interpretation = C.VIPS_INTERPRETATION_MULTIBAND + INTERPRETATION_B_W Interpretation = C.VIPS_INTERPRETATION_B_W + INTERPRETATION_CMYK Interpretation = C.VIPS_INTERPRETATION_CMYK + INTERPRETATION_RGB Interpretation = C.VIPS_INTERPRETATION_RGB + INTERPRETATION_sRGB Interpretation = C.VIPS_INTERPRETATION_sRGB + INTERPRETATION_RGB16 Interpretation = C.VIPS_INTERPRETATION_RGB16 + INTERPRETATION_GREY16 Interpretation = C.VIPS_INTERPRETATION_GREY16 + INTERPRETATION_scRGB Interpretation = C.VIPS_INTERPRETATION_scRGB +) +``` + +#### func ImageInterpretation + +```go +func ImageInterpretation(buf []byte) (Interpretation, error) +``` +Get the image interpretation type See: +http://www.vips.ecs.soton.ac.uk/supported/current/doc/html/libvips/VipsImage.html#VipsInterpretation + #### type Options ```go type Options struct { - Height int - Width int - AreaHeight int - AreaWidth int - Top int - Left int - Extend int - Quality int - Compression int - Zoom int - Crop bool - Enlarge bool - Embed bool - Flip bool - Flop bool - NoAutoRotate bool - NoProfile bool - Interlace bool - Rotate Angle - Gravity Gravity - Watermark Watermark - Type ImageType - Interpolator Interpolator + Height int + Width int + AreaHeight int + AreaWidth int + Top int + Left int + Extend int + Quality int + Compression int + Zoom int + Crop bool + Enlarge bool + Embed bool + Flip bool + Flop bool + NoAutoRotate bool + NoProfile bool + Interlace bool + Rotate Angle + Gravity Gravity + Watermark Watermark + Type ImageType + Interpolator Interpolator + Interpretation Interpretation } ``` diff --git a/fixtures/test_image_colourspace_b_w.jpg b/fixtures/test_image_colourspace_b_w.jpg index 8afd5bc424ed54e7b065abf8c3a8c40167f18250..9618548ade4b5e262ddf8a958936b5f0dd78fb26 100644 GIT binary patch literal 59110 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 literal 59525 zcmYhi2Q-`S|33aC_G}QF8bOIsyGCh@#9lFLRP9mJR!SSK8GFPkY6YQYZKc{qZ8c)l zs-`H~dY95ctH1R7Ip=@=cTP^8oa7|;^LpL)Yh2fL|6TsO2|$cdMkoLT0ss)@0sLJB z^Z;6F8d@4^T3Q-fIyzcxTpO!U-zwAmmK8}OG12mk<>3Pc&e|2M!OD$1YG(NjLvfB=+t z{~KUhDu4>~@7vVuG;E@@9Eu_ex^z%9Oi72+%FR85o=Z$wFVs5z0k>!Si@z%X{lDM5 z2K;`-mxYwL7F^A)$dux)lyr5>kxQ0($Ux^7Z;9a_WW+e8!Az=C7stpw8)q1knhCok z;$ld=BAk5#9ueb;7}%FJ3OL?6FdcRrEL1Iw{Qm8HE7sxT;vpTLJy^TV1hU zzj!xb%5A3!;c|u}Uosr9`g#xt|6uyrbM-z7o> zf08JMRLY|R| za%&hiU0*5ccmT}r>!K?~R(3z)r5V+Eadl!A)xb(1q7YBKjkrnJkzLW(%K1v9_VVF; z=HZY^t2QHTPs)RblsS`t2Cv#4U1ODZ5%KfLO@)I zYnA}m2;BO=zXxF$#BKt>Wc(OFP{RP*wv6fYkn!1Tbi{rUBL~srdrvGx1KD?R`IG{z35TU-U`0=}FZOY7Z@WBRo zak0vsi5cSYAgDEKnK+X0(^8`H5Qia`IetL7LlnMk- zMANHE$6KOHAJ-nT9vBsP zBLR!&Y_TPEQ_UV*jIN*X~Y-O`#~VVl}wF@JvQ?0Dnd zY0+31oxgq71cx@YC|{lLCwBNHn1#iPK~#g&tKdI9TM>4*zs6;Ka-ANJdFtrBUes^E@jQ2qHv8JCO{}EbFJeS-& zuWj}Vit2Q{kPQ(DJA>5zG*i|ywR+^odUysk@pjK8O@#4Iw;V8c5cwW_)~0?vSZ!qj zt2nN7&R<@tLG!I6e+YFK!m1apxrvW=m6r7!9$x(m+zPqt))p#!QyY8}R*AH10fuU5 z)~>LOtXZzXZF8~@FQ1V5udze`9p#{Q!xF+9}O>d zN#GHJfQXi_nZgjmw!-m>m@@TYP`41fiyEU|%0qUfjb#Oib0}Mw zo|k^@iWm(T0p#clSOTEgP&!E7(^?bv60eu*64u=h1>yy6{&N7pn1coYRhEplpBE5l zn{)%B3w9$OHQL1&!{6kwk!-xd_$=9b&rm*>jskR7Rnz_pqSSzDn8fuA^HaBc&VH&SkNj3+8u+M?GI!v6x`0qP;&xx^{m zNFgfz`W{^5c)#aO37R{(hznWF*aR`a65!{0C4!)ZQsyQciU2TTbdX>q2>Y@c7)>10 zC0P#DbzKXHpHYAS9X+LKmc5hz?I=p_;>UJkn(Xc7efIkAs>!lz7X7aIrLaYm0j&<{ z3;t#-^|FFXX39Lc++Z;Z%2q+1&vZ$B@ij* z8p41KAUTK-I!HFu4Pgn0WrGs`8H>k~vUm}|f@zwhip}G#;L%dXjfB^UU+a{b+%DQ^ zaXs=WvoP@Tu-An6y%S96o3q(*_EQttI?>dC2{72DY-^?&9%=N+wi^+2+vk&N|HfVD_RaN1pa|$R$gLe2%lCUtVBi0$K zEk0VYe<@%l4NH&Jg4$UHgPAM^>Tfc(;=~LjvlD;F0B|C$E)BPFl#S5-7#u(FN5G^2v5VHgXXehSK0cZtbenL>^ zvOy7LGY;}z+C5Qb$$!TFH!6U@I?ORaQuhMcF|BB_h*QhZG3L=1j-2&***_QDs;>Kp zy2srz+7(3g1;ixYbkw_Xg_>U%Q5oldlwd|R;} z?Xpe;j!F_DMqgu{TjJ%{BG)y)3?! zss56g^scU}Qs~{K6kEgWH?E-}0@$BvXkrizy3NWLV=IjNA%g%3xG75C(;=IIvjfx^kOLPZz^UR_ z80ZrXoj6qe^uv|ZT=I!I^tU-u3oc*r&+wE1#(A+BU+ib@$(@vN!TcfoKtR#8n307n4mS6U_EV)!E z%5KLh*`m`0Q4l26#65J2WJv)HC4(t3Obnn0BLTh#xy=ivhPQPvl%Vl{0}8O>-QWEb z4E$uR>!@|@!CfBcqHq?7dT-UaBuiMbEW57mT>zwy0)n1uwgVim;DD6BCk~H7F z{4~`0LS>=tLjPZYAd??6e`fi0Woz_sz@p%UIt^#Rbblul@1Y_Tllx_~7Ab>QV?heq z&m=oz_d6U<%L|b`bA`G+nPi#f)#1!5?7aq#4C2g3oUeCkEZN4(OsC}}qP~IKKSh4g z)^izce-0&%Z+ogEnk9*>fFzfMD27pu5-tEkcG`~k!FRofN`+Q;uy%m99*h>5PY8&B z0^z*FI*A-N^)DOtmavO%Svy_<*SVL5${CUrWC;4@+rHKP^gI}Kg?a^dwp8nSdKfMf zz&c2X2T7S=xG%AlwTIThId(Kt%!iJpiJ?|TVi50IvaBkXjz26ln-wGcGiBdgG|!6r zrvSAO`{b2#nufsNoHJMefB=A$uzWQLK>RZ?NX!PCE)0Nn1FNR%s^&VjOKj{8e_c$o zn+YW6ooD5iNB#vYsstF+)NtlB)*?uEnZ!7A+*83g?b{?R+r*K|Ps%3eZ}9#xCUNNg z|BP4`NOo=a-hVu4GVe_GB7FN2`ro|l8^N&97Hc`DN@p+gwRopuneA3mZ}d#dS-#O^ zlhu!7LKoMuvSD%kiG_~XSH*FBxU02|NT!&_P;IS`B}~LP=HySZPXv*&a1hyYsGNBy2iKdT!`v-;Dhz|ciCj(1mOQucv?ME*hc(4lUshih zDN8h8{z>MgciguUJ(SiFpyLpEsj&P28LreL%$W%oa`swg0%}#gH*_x*-@;-I4-%<1 z9e9#J@$LrCzrtR1f3?}SwG|m2G6lLjmYKfs$a6!8*p`Y;0mMb+6YW>Wu6wS3fl=w0 zlV?s2O-sLOuiMq7e%dGxD%xinkofpR*xQkO=f}BW1!Y|T0D@U^2*EB13`zc<3$g)# zu65pHJ?rk=X;k#gSy1sYcK<(%r0e)3?T>KTTaHWqe}PMM`*d@5bIN{~r9yM{A7@qm zny~5mvZjvfv25D4&hcbZy2Do>!`fM<+Qj^fkQD~OmRTHc;kWkb*Fj;>#GTjcElM=kO{RI#YdI` zY-tjCqE!gMjh5=O(ASg8#>1qROZMMaIMa^%2Q?{4c>0ff->h8U^mp@OY|ir2V4JHK z^_X;aB{#vj*LkC)I}Ap^v6Tw)9@ z`1^3U>@ebotJz?se;iwca-S2rBPYb`KF8FFc5hgV{}Nx~hi&DlJcSpb6<~j)0SJ(k zWdJ2oDhPTWntz25C3geg`J5jq9BUVnwAW_~$@fy)PoLIF;BH^v%xRJAu)}LLItpDo zC7kefu8MbenlChI%?+Mh)A|&;Ei#fbKwFWe=ao1&)bdIu&Cay+)5`qvu8vE__iy~0 zJr|v(>8PxqjUGikJ+F-4@8^70{VF8U;%WH1MfLFi>T-`iuGW4*A@lyIXd6XcyBC!+ zsKa<#CJCDy_<)xheWSvCkw#XBtfyNCUP$n-qtdcOSq#B+PiKs0nrrQ&hm$bee>u&n!kLWYgmb7SA6^MxP1TDPj zagI44O)#lS9R@7lHP7T?){2TfGEQy#$hHrUFTuVb{B*Aaul% zG&F*mFELyUV4!4hO6f`&mmm<}f{Evu&2blBBCK*g%{D-5viry6-a&L3DquJCdN;xU ztZ;-Y{IX%%2fa3LfA8-aU+0!ShrT&^&Y@vwE3?L`a@>E>d?&~|=mU#mcD`&iceNmY z)Pc>XmAZ;K5z`#^s>meUO^u~Y@d@27Q-3p#-%Q;gRfz^`wAgng1y=N~PXp&-Vkp$6 zi5SA(L2O8~yRAEhHvBZDAepzAz$ouAWGUt@WtQ4>MJO=w2Om#6#SOxgnTP0!yke_| zLSyyVvhAsPN~eOevi60lwTr`-^w#54`Ue)hGy4i;_a2L#uUh^@)iK|?ODTl?*G#Pn zUTYY%lA{!aB;4NHDRX|=N4=s4WwFb1gW>%)r?+RdD`sNuI+n!UTabf!4#UR^WTG<% zNHU$;b%TBLvUB`?{9+jvT5UpX^%axEmNqZTWOS)Mu|c<_Ah73ln`LtiqveNcccmr8 zV3^y*1XM=vJ?fj2>uy3HsInx1XTkUgS1aPBPy*b8l7U3& zY3i%7Cy<+Ppb{#^P?dw=!N{!Ow6kX3nf#~GSlg*}W138JCN|!7CC~ELvqNR;XMtOI zeGH{6W~3<&hFSuec1LCW(NUERQpP)FnGqA-mMKo$y;YYz+$|UQ1ev^Ka1Wz7)h|Je zuyv2(Uy|gs%*9`-A~o5b@>!`Bye^&C#fB>{516m}x4q`9H0iQcc5{|!gE+*Q#7t^=2nOTz@E1hPZyb&o!N#S-L@rJF?W)FvE7uzG-AQ?$OP53SQt*)=i zmx^b*`g35fFGaW#39B)r$)tYY5__c0Se9PHvct--qSL6H-#g*LgF;YP^ zp))ODKxc1LX~qS+=H)*_*r*P2EZ@!-&$mVU*DUBqaE`ke`zKfLexVh-ZFzP#EuqUf zwrJ@ct-m^qOIOdoVgM!`em#h6fRpLDK>QG&@mAz*2}_7^z2NB(ht`7CO&SHs}k zzRiUp@ZN_Y!IqKVkEwj>zFBA8je52AVL~;6&B3u5Y1&hW^2_qKUh8{H=v{0#%bz<4 zG``J+3P(i_TR6?cAqJ!g6CtvF7Hf7lTshK6r(YtRy7QRrDR>9&sWSXP>4B6Y)aZ9{ z3ZHI6j<{)yZYpy-=YX*^I>&g;n0^zCVGUOfwEJKb?RepLS{jd+cY&SvFM1&H;Pd(I z9BV99is2RjkO6iL`Q2BZ(hrm_VT6~lVBlCLeUppKfiaoY`=jF;@ z{`aih*z9D&#rWvj=;&5e2~79c*`)WTSH8lgjx|op1pSQGy)>yww^Lf+@m4@uJ#l^# z5`PGfkc@x~_~fp)ryhDJA58zcCgGm$pyONQ$6cPV~aDzdg%UmC`pCj-NhLa8-XI7!aNYtz49b-daX#!!G%z>XR#UqJ;_Lnu`& z#c6;cgtljGp1-6(Ys$`E(a~Bw)?qkW+DyXKZlf)g<56W;4EL73{mR2(96#optI_K4 zW8${+y7u`;MWZNA|4y0uhAFZcx8lA*Jk)U^V=)rfWd3@+gCp{C;%bL0i=*loO^IFP z7`CCm65h|<7|64U+3~xUr$DcSvuX0T5@!^X%pQiN^;)W%q=4P2$ii%GngHvklthf3 z8peT&p6bIZ5jskK1O#ajAf2HYE|h=9fLThlS~0iNx)JT2=h=JCzqpvO*ZB%92e?Jw z_KFmf7}qBEia1;2PnJz_sNhlibHqIG`PmCvL8yVXQoU%ZxakSnd3xW5?5-#k z1baorMW#x5AFr0ClNf@qhZJii=B?={z;24griRSK>W>s1pmjni4c&rqsWWyxlsQCj z*Ty>-px|*tX)uBY040JEpwWN!1S2rYc#{udc;fCK#*bQ{Y57p)U@~?`lUPK-%+3MEJvdI4`>??3E;Be7@dj4+oxn1Up^z+jz<4SNU z+$i4G72VA?-le2PQ=>Oa`-K)Ztj&;;IXwC>faihad(1*=tCC@PjXO<{C@YN<<1WG- zMYX9aEK6r@mb$Q@T>K=ks>GSs^N_ej3NS!2QoLWr*vdN(uB*URmKZaNXwtr71;FYK z0J~%bAkWt%Q`g`4gzakjv&Phdwsw=WU;l%ZK*GX&vnG&j+a}`w;f<^SJ%hE{gYnO+il=V9HMK+3GUJd){+UI#b({bAu+;5DZh=gJ4GjFF@IW#6s$0- zX1p*lm`3#B0(YLqfXv-M|gv&^GRzhMn)%-#d84;$ls4 z{A|Q}oXD=k@NFGuzsAQ(!N-KE%Da_OB;Bjqevtm|Q#$D;FrC6!Y zH`1MSbQsWYFwHiNsWe|}BeX8&aaS&7$Sb28LCZPz$t3{r2<1pOPBpkg7!@LCe0;@7@BBMq))ZWr>F;nO& zK-=pkh2D8cR!|YeuLV=f+7VOvMN5ZesL|v6f%$J!{sM}-^SBF7|FE2<)rH#*{28pK zaq-@1zf@TF^-c>xP=CORwB(3Iu3mSNu{km^o4D)UAFfjMkYMKAfJgIe{g4Lv+rrok z{sOQUmtmDmTmG3fO&!|Gc)zSrl*eR=Knh#aq1pmNqwA%;1%(V~w$sMI~vo--iGzil|-SFc0XLNY5 z9p`V;qlA0j6Mi8Yt+o#H@O-i~%391TB0UkkZP{|b`k>eD>Wjj7=kj3{3#&GPB&#`v z4Fz`j2xdLEi-|??&Ujr-#+|4T25@)CMSMNA{ILkrHEfPma=rC3B$&=hTBrgiGM*5k zAc|BntaX#>OMC4y*uZmtV|T}KkG?{v0{Wo81RY>7p%g3G!+5M@>XT#2vE+e^lYoZ? zdC51n{(dMtO3BQTkU-)Z0%R!wT1J2r0Rd_O0Fo`t0EW;pGYD7>(KBN@xkVlNs63JN zH?WT85)VdamZ-h+(>Qb`4Qq{{b!>yQQt1b1GJ`PgtC*&TfgaxL@>TP8dkqG-`xd&G z#zU#^G=Ek->xOv5pO-%$nG`x+hQPJb}30UdX!$e^TUQh z175ruYvOk6BL%BjlTGUYe?KJ9fLqk2jZDW+hVho-$D_U3C2@jKZLft&m1fq+0Iw;Q z(D^VG-%-USks8zVg|uuH#)iavJ3Ou9P3dNHp5g=U_{SWk(nMbUS|o{#(v1d7h46fZ z6Lkyk>s;XoNeEStintV@zpOyVtV2Q&eJrK(mTPM~9bIz#Sz#D57M8LT1#KcSK&Q}T z6rltO0t|#1D9x`J1z!ATZLu5#u>0BAavogbVU3nG$#tx3xH85l$IHTZ&Dwd7t5Egp zz4e#mk&+Tr!Ta?=9e#e8`&L$%b7Twpb*F-hBWb*s2|X&hQk$3-hWFt&(6edpWZ{dQ z5{xsOzY!Gg74+1;jJ09!FCf_SXz(2)lZ{5%wYk2>R;GKkFXtAbRnOs`OMM>bS#Phw z#P<=uvs7g7+Kj!qM%d43{#3mE-|Z3uB|EeE&?ne-?mh_*)?DmoZCy=vPB%C|?OT8< z{~?A|m=%1XK$I1b63sLOxER%{G4xwxb$70+0;y&iIie@}Gs;5ACR}AG>8Irq<5!7F z+Y|P|VKnE&4O;t3+|tb0rQ*jB!{O|VWaj7sQ2VqqYmLgJgq5iBYg;(3J>SR*7KFmK z5O^LEaN}*VtDq8S8qk& z=?m5-wE8wfngiSJNrWfEoD1HtM8~Gxd>Ez46}ejCWYJ`FCSarg)X(|YF|OowvU$fc zJjqBhPI~Wx!`>h5Zq=APH|8_Nal$9#`Lojdjav5Hmael+ex1)^?f)3O(&(n7ja9uD zDiemkiEnNm1W5OIOT6z5YrI_a5C(XHNl>2(aezLwg0e{fokK`4ghJcppagP^s0B

GD&n0VjA|tpI{|F!& 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 aTuK+l!0=3-j8uiP0(g9ZYl_l;U;Ymm=mEe0 diff --git a/image.go b/image.go index df41161..c0ae94b 100644 --- a/image.go +++ b/image.go @@ -155,6 +155,17 @@ func (i *Image) Metadata() (ImageMetadata, error) { return Metadata(i.buffer) } +// Get the image interpretation type +// See: http://www.vips.ecs.soton.ac.uk/supported/current/doc/html/libvips/VipsImage.html#VipsInterpretation +func (i *Image) Interpretation() (Interpretation, error) { + return ImageInterpretation(i.buffer) +} + +// Check if the current image has a valid colourspace +func (i *Image) ColourspaceIsSupported() (bool, error) { + return ColourspaceIsSupported(i.buffer) +} + // Get image type format (jpeg, png, webp, tiff) func (i *Image) Type() string { return DetermineImageTypeName(i.buffer) diff --git a/image_test.go b/image_test.go index dc56768..b837dc7 100644 --- a/image_test.go +++ b/image_test.go @@ -262,12 +262,36 @@ func TestImageMetadata(t *testing.T) { } } +func TestInterpretation(t *testing.T) { + interpretation, err := initImage("test.jpg").Interpretation() + if err != nil { + t.Errorf("Cannot process the image: %#v", err) + } + if interpretation != INTERPRETATION_sRGB { + t.Errorf("Invalid interpretation: %d", interpretation) + } +} + func TestImageColourspaceBW(t *testing.T) { - buf, err := initImage("test.jpg").Colourspace(B_W) + buf, err := initImage("test.jpg").Colourspace(INTERPRETATION_B_W) + if err != nil { + t.Errorf("Cannot process the image: %#v", err) + } + + interpretation, err := ImageInterpretation(buf) + if interpretation != INTERPRETATION_B_W { + t.Errorf("Invalid colourspace") + } +} + +func TestImageColourspaceIsSupported(t *testing.T) { + supported, err := initImage("test.jpg").ColourspaceIsSupported() if err != nil { t.Errorf("Cannot process the image: %#v", err) } - Write("fixtures/test_image_colourspace_b_w.jpg", buf) + if supported != true { + t.Errorf("Non-supported colourspace") + } } func TestFluentInterface(t *testing.T) { diff --git a/metadata.go b/metadata.go index a7c6d5a..1ec26de 100644 --- a/metadata.go +++ b/metadata.go @@ -18,6 +18,7 @@ type ImageMetadata struct { Profile bool Type string Space string + Colourspace string Size ImageSize } @@ -34,6 +35,17 @@ func Size(buf []byte) (ImageSize, error) { }, nil } +// Check in the image colourspace is supported by libvips +func ColourspaceIsSupported(buf []byte) (bool, error) { + return vipsColourspaceIsSupportedBuffer(buf) +} + +// Get the image interpretation type +// See: http://www.vips.ecs.soton.ac.uk/supported/current/doc/html/libvips/VipsImage.html#VipsInterpretation +func ImageInterpretation(buf []byte) (Interpretation, error) { + return vipsInterpretationBuffer(buf) +} + // Extract the image metadata (size, type, alpha channel, profile, EXIF orientation...) func Metadata(buf []byte) (ImageMetadata, error) { defer C.vips_thread_shutdown() diff --git a/metadata_test.go b/metadata_test.go index c21bf46..90ab09a 100644 --- a/metadata_test.go +++ b/metadata_test.go @@ -38,9 +38,9 @@ func TestMetadata(t *testing.T) { profile bool space string }{ - {"test.jpg", "jpeg", 0, false, false, "bicubic"}, - {"test.png", "png", 0, true, false, "bicubic"}, - {"test.webp", "webp", 0, false, false, "bicubic"}, + {"test.jpg", "jpeg", 0, false, false, "srgb"}, + {"test.png", "png", 0, true, false, "srgb"}, + {"test.webp", "webp", 0, false, false, "srgb"}, } for _, file := range files { @@ -61,6 +61,58 @@ func TestMetadata(t *testing.T) { if metadata.Profile != file.profile { t.Fatalf("Unexpected image profile: %s != %s", metadata.Profile, file.profile) } + if metadata.Space != file.space { + t.Fatalf("Unexpected image profile: %s != %s", metadata.Profile, file.profile) + } + } +} + +func TestImageInterpretation(t *testing.T) { + files := []struct { + name string + interpretation Interpretation + }{ + {"test.jpg", INTERPRETATION_sRGB}, + {"test.png", INTERPRETATION_sRGB}, + {"test.webp", INTERPRETATION_sRGB}, + } + + for _, file := range files { + interpretation, err := ImageInterpretation(readFile(file.name)) + if err != nil { + t.Fatalf("Cannot read the image: %s -> %s", file.name, err) + } + if interpretation != file.interpretation { + t.Fatalf("Unexpected image interpretation") + } + } +} + +func TestColourspaceIsSupported(t *testing.T) { + files := []struct { + name string + }{ + {"test.jpg"}, + {"test.png"}, + {"test.webp"}, + } + + for _, file := range files { + supported, err := ColourspaceIsSupported(readFile(file.name)) + if err != nil { + t.Fatalf("Cannot read the image: %s -> %s", file.name, err) + } + if supported != true { + t.Fatalf("Unsupported image colourspace") + } + } + + supported, err := initImage("test.jpg").ColourspaceIsSupported() + if err != nil { + t.Errorf("Cannot process the image: %#v", err) + } + if supported != true { + t.Errorf("Non-supported colourspace") } } diff --git a/options.go b/options.go index da7911e..1162335 100644 --- a/options.go +++ b/options.go @@ -55,18 +55,20 @@ const ( VERTICAL Direction = C.VIPS_DIRECTION_VERTICAL ) +// Image interpretation type +// See: http://www.vips.ecs.soton.ac.uk/supported/current/doc/html/libvips/VipsImage.html#VipsInterpretation type Interpretation int const ( - ERROR Interpretation = C.VIPS_INTERPRETATION_ERROR - MULTIBAND Interpretation = C.VIPS_INTERPRETATION_MULTIBAND - B_W Interpretation = C.VIPS_INTERPRETATION_B_W - CMYK Interpretation = C.VIPS_INTERPRETATION_CMYK - RGB Interpretation = C.VIPS_INTERPRETATION_RGB - sRGB Interpretation = C.VIPS_INTERPRETATION_sRGB - RGB16 Interpretation = C.VIPS_INTERPRETATION_RGB16 - GREY16 Interpretation = C.VIPS_INTERPRETATION_GREY16 - scRGB Interpretation = C.VIPS_INTERPRETATION_scRGB + INTERPRETATION_ERROR Interpretation = C.VIPS_INTERPRETATION_ERROR + INTERPRETATION_MULTIBAND Interpretation = C.VIPS_INTERPRETATION_MULTIBAND + INTERPRETATION_B_W Interpretation = C.VIPS_INTERPRETATION_B_W + INTERPRETATION_CMYK Interpretation = C.VIPS_INTERPRETATION_CMYK + INTERPRETATION_RGB Interpretation = C.VIPS_INTERPRETATION_RGB + INTERPRETATION_sRGB Interpretation = C.VIPS_INTERPRETATION_sRGB + INTERPRETATION_RGB16 Interpretation = C.VIPS_INTERPRETATION_RGB16 + INTERPRETATION_GREY16 Interpretation = C.VIPS_INTERPRETATION_GREY16 + INTERPRETATION_scRGB Interpretation = C.VIPS_INTERPRETATION_scRGB ) const WATERMARK_FONT = "sans 10" @@ -88,28 +90,28 @@ type Watermark struct { } type Options struct { - Height int - Width int - AreaHeight int - AreaWidth int - Top int - Left int - Extend int - Quality int - Compression int - Zoom int - Crop bool - Enlarge bool - Embed bool - Flip bool - Flop bool - NoAutoRotate bool - NoProfile bool - Interlace bool - Rotate Angle - Gravity Gravity - Watermark Watermark - Type ImageType - Interpolator Interpolator + Height int + Width int + AreaHeight int + AreaWidth int + Top int + Left int + Extend int + Quality int + Compression int + Zoom int + Crop bool + Enlarge bool + Embed bool + Flip bool + Flop bool + NoAutoRotate bool + NoProfile bool + Interlace bool + Rotate Angle + Gravity Gravity + Watermark Watermark + Type ImageType + Interpolator Interpolator Interpretation Interpretation } diff --git a/resize.go b/resize.go index c71b312..e77a2c1 100644 --- a/resize.go +++ b/resize.go @@ -117,15 +117,15 @@ func Resize(buf []byte, o Options) ([]byte, error) { } saveOptions := vipsSaveOptions{ - Quality: o.Quality, - Type: o.Type, - Compression: o.Compression, - Interlace: o.Interlace, - NoProfile: o.NoProfile, + Quality: o.Quality, + Type: o.Type, + Compression: o.Compression, + Interlace: o.Interlace, + NoProfile: o.NoProfile, Interpretation: o.Interpretation, } - // Finally save as buffer + // Finally get the resultant buffer buf, err = vipsSave(image, saveOptions) if err != nil { return nil, err @@ -145,7 +145,7 @@ func applyDefaults(o *Options, imageType ImageType) { o.Type = imageType } if o.Interpretation == 0 { - o.Interpretation = sRGB + o.Interpretation = INTERPRETATION_sRGB } } diff --git a/vips.go b/vips.go index 90c653c..93be07b 100644 --- a/vips.go +++ b/vips.go @@ -32,11 +32,11 @@ type VipsMemoryInfo struct { } type vipsSaveOptions struct { - Quality int - Compression int - Type ImageType - Interlace bool - NoProfile bool + Quality int + Compression int + Type ImageType + Interlace bool + NoProfile bool Interpretation Interpretation } @@ -224,41 +224,85 @@ func vipsRead(buf []byte) (*C.struct__VipsImage, ImageType, error) { return image, imageType, nil } -func vipsSave(image *C.struct__VipsImage, o vipsSaveOptions) ([]byte, error) { - length := C.size_t(0) - err := C.int(0) - interlace := C.int(boolToInt(o.Interlace)) - if o.Interpretation == 0 { - o.Interpretation = sRGB +func vipsColourspaceIsSupportedBuffer(buf []byte) (bool, error) { + image, _, err := vipsRead(buf) + defer C.g_object_unref(C.gpointer(image)) + if err != nil { + return false, err } - interpretation := C.VipsInterpretation(o.Interpretation) + return vipsColourspaceIsSupported(image), nil +} + +func vipsColourspaceIsSupported(image *C.struct__VipsImage) bool { + return int(C.vips_colourspace_issupported_bridge(image)) == 1 +} +func vipsInterpretationBuffer(buf []byte) (Interpretation, error) { + image, _, err := vipsRead(buf) + defer C.g_object_unref(C.gpointer(image)) + if err != nil { + return Interpretation(-1), err + } + return vipsInterpretation(image), nil +} + +func vipsInterpretation(image *C.struct__VipsImage) Interpretation { + return Interpretation(C.vips_image_guess_interpretation_bridge(image)) +} + +func vipsPreSave(image *C.struct__VipsImage, o *vipsSaveOptions) (*C.struct__VipsImage, error) { // Remove ICC profile metadata if o.NoProfile { C.remove_profile(image) } - // Force RGB color space + // Use a default interpretation and cast it to C type + if o.Interpretation == 0 { + o.Interpretation = INTERPRETATION_sRGB + } + interpretation := C.VipsInterpretation(o.Interpretation) + + // Apply the proper colour space var outImage *C.struct__VipsImage - C.vips_colourspace_bridge(image, &outImage, interpretation) + if vipsColourspaceIsSupported(image) { + err := int(C.vips_colourspace_bridge(image, &outImage, interpretation)) + C.g_object_unref(C.gpointer(image)) + if err != 0 { + return nil, catchVipsError() + } + image = outImage + } + + return image, nil +} +func vipsSave(image *C.struct__VipsImage, o vipsSaveOptions) ([]byte, error) { defer C.g_object_unref(C.gpointer(image)) - defer C.g_object_unref(C.gpointer(outImage)) + + image, err := vipsPreSave(image, &o) + if err != nil { + return nil, err + } + + length := C.size_t(0) + saveErr := C.int(0) + interlace := C.int(boolToInt(o.Interlace)) + quality := C.int(o.Quality) var ptr unsafe.Pointer switch o.Type { - case PNG: - err = C.vips_pngsave_bridge(outImage, &ptr, &length, 1, C.int(o.Compression), C.int(o.Quality), interlace) - break case WEBP: - err = C.vips_webpsave_bridge(outImage, &ptr, &length, 1, C.int(o.Quality)) + saveErr = C.vips_webpsave_bridge(image, &ptr, &length, 1, quality) + break + case PNG: + saveErr = C.vips_pngsave_bridge(image, &ptr, &length, 1, C.int(o.Compression), quality, interlace) break default: - err = C.vips_jpegsave_bridge(outImage, &ptr, &length, 1, C.int(o.Quality), interlace) + saveErr = C.vips_jpegsave_bridge(image, &ptr, &length, 1, quality, interlace) break } - if int(err) != 0 { + if int(saveErr) != 0 { return nil, catchVipsError() } diff --git a/vips.h b/vips.h index 476cabf..1948907 100644 --- a/vips.h +++ b/vips.h @@ -134,6 +134,17 @@ vips_extract_area_bridge(VipsImage *in, VipsImage **out, int left, int top, int return vips_extract_area(in, out, left, top, width, height, NULL); }; +int +vips_colourspace_issupported_bridge(VipsImage *in) +{ + return vips_colourspace_issupported(in) ? 1 : 0; +}; + +VipsInterpretation +vips_image_guess_interpretation_bridge(VipsImage *in) { + return vips_image_guess_interpretation(in); +}; + int vips_colourspace_bridge(VipsImage *in, VipsImage **out, VipsInterpretation space) { From 1287087a441eeb739e1260e86badfe0f15495d20 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sat, 11 Jul 2015 16:51:08 +0100 Subject: [PATCH 076/115] feat: remove fixture --- fixtures/test_image_colourspace_b_w.jpg | Bin 59110 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 fixtures/test_image_colourspace_b_w.jpg diff --git a/fixtures/test_image_colourspace_b_w.jpg b/fixtures/test_image_colourspace_b_w.jpg deleted file mode 100644 index 9618548ade4b5e262ddf8a958936b5f0dd78fb26..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59110 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 From 0bff28381fd060fec8080e7fa05ad0066108737e Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sat, 11 Jul 2015 19:45:10 +0100 Subject: [PATCH 077/115] 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") } From fa55264a977f69de995810813b5cb1ba95bc1235 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sat, 11 Jul 2015 19:53:30 +0100 Subject: [PATCH 078/115] feat(docs): add force resize example --- README.md | 19 +++++++++++++++++++ image.go | 10 ++++++++++ 2 files changed, 29 insertions(+) diff --git a/README.md b/README.md index f7f4e1c..c4dc79a 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,25 @@ if bimg.NewImage(newImage).Type() == "png" { } ``` +#### Force resize + +```go +buffer, err := bimg.Read("image.jpg") +if err != nil { + fmt.Fprintln(os.Stderr, err) +} + +newImage, err := bimg.NewImage(buffer).ForceResize(1000, 500) +if err != nil { + fmt.Fprintln(os.Stderr, err) +} + +size := bimg.Size(newImage) +if size.Width != 1000 || size.Height != 500 { + fmt.Fprintln(os.Stderr, "Incorrect image size") +} +``` + #### Custom options See [Options](https://godoc.org/github.com/h2non/bimg#Options) struct to discover all the available fields diff --git a/image.go b/image.go index c0ae94b..fa02dd3 100644 --- a/image.go +++ b/image.go @@ -14,6 +14,16 @@ func (i *Image) Resize(width, height int) ([]byte, error) { return i.Process(options) } +// Force resize with custom size (aspect ratio won't be maintained) +func (i *Image) ForceResize(width, height int) ([]byte, error) { + options := Options{ + Width: width, + Height: height, + Force: true, + } + return i.Process(options) +} + // Resize the image to fixed width and height with additional crop transformation func (i *Image) ResizeAndCrop(width, height int) ([]byte, error) { options := Options{ From 851e65e71ef6b2ac591bf1b24c53cefa9b014957 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sat, 11 Jul 2015 20:16:39 +0100 Subject: [PATCH 079/115] refactor(colourspace) --- README.md | 19 +++++++++++++++++++ image_test.go | 28 +++++++++++++++++++--------- options.go | 2 ++ vips.go | 8 ++++---- 4 files changed, 44 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index c4dc79a..6779cac 100644 --- a/README.md +++ b/README.md @@ -175,6 +175,25 @@ if size.Width != 1000 || size.Height != 500 { } ``` +#### Custom colour space (black & white) + +```go +buffer, err := bimg.Read("image.jpg") +if err != nil { + fmt.Fprintln(os.Stderr, err) +} + +newImage, err := bimg.NewImage(buffer).Colourspace(bimg.INTERPRETATION_B_W) +if err != nil { + fmt.Fprintln(os.Stderr, err) +} + +colourSpace, _ := bimg.ImageInterpretation(newImage) +if colourSpace != bimg.INTERPRETATION_B_W { + fmt.Fprintln(os.Stderr, "Invalid colour space") +} +``` + #### Custom options See [Options](https://godoc.org/github.com/h2non/bimg#Options) struct to discover all the available fields diff --git a/image_test.go b/image_test.go index b837dc7..fb20c62 100644 --- a/image_test.go +++ b/image_test.go @@ -272,15 +272,25 @@ func TestInterpretation(t *testing.T) { } } -func TestImageColourspaceBW(t *testing.T) { - buf, err := initImage("test.jpg").Colourspace(INTERPRETATION_B_W) - if err != nil { - t.Errorf("Cannot process the image: %#v", err) - } - - interpretation, err := ImageInterpretation(buf) - if interpretation != INTERPRETATION_B_W { - t.Errorf("Invalid colourspace") +func TestImageColourspace(t *testing.T) { + tests := []struct { + file string + interpretation Interpretation + }{ + {"test.jpg", INTERPRETATION_sRGB}, + {"test.jpg", INTERPRETATION_B_W}, + } + + for _, test := range tests { + buf, err := initImage(test.file).Colourspace(test.interpretation) + if err != nil { + t.Errorf("Cannot process the image: %#v", err) + } + + interpretation, err := ImageInterpretation(buf) + if interpretation != test.interpretation { + t.Errorf("Invalid colourspace") + } } } diff --git a/options.go b/options.go index 8cf00cf..67af96d 100644 --- a/options.go +++ b/options.go @@ -69,6 +69,8 @@ const ( INTERPRETATION_RGB16 Interpretation = C.VIPS_INTERPRETATION_RGB16 INTERPRETATION_GREY16 Interpretation = C.VIPS_INTERPRETATION_GREY16 INTERPRETATION_scRGB Interpretation = C.VIPS_INTERPRETATION_scRGB + INTERPRETATION_LAB Interpretation = C.VIPS_INTERPRETATION_LAB + INTERPRETATION_XYZ Interpretation = C.VIPS_INTERPRETATION_XYZ ) const WATERMARK_FONT = "sans 10" diff --git a/vips.go b/vips.go index d9504a7..f41e8de 100644 --- a/vips.go +++ b/vips.go @@ -226,10 +226,10 @@ func vipsRead(buf []byte) (*C.struct__VipsImage, ImageType, error) { func vipsColourspaceIsSupportedBuffer(buf []byte) (bool, error) { image, _, err := vipsRead(buf) - defer C.g_object_unref(C.gpointer(image)) if err != nil { return false, err } + C.g_object_unref(C.gpointer(image)) return vipsColourspaceIsSupported(image), nil } @@ -239,10 +239,10 @@ func vipsColourspaceIsSupported(image *C.struct__VipsImage) bool { func vipsInterpretationBuffer(buf []byte) (Interpretation, error) { image, _, err := vipsRead(buf) - defer C.g_object_unref(C.gpointer(image)) if err != nil { - return Interpretation(-1), err + return INTERPRETATION_ERROR, err } + C.g_object_unref(C.gpointer(image)) return vipsInterpretation(image), nil } @@ -266,10 +266,10 @@ func vipsPreSave(image *C.struct__VipsImage, o *vipsSaveOptions) (*C.struct__Vip var outImage *C.struct__VipsImage if vipsColourspaceIsSupported(image) { err := int(C.vips_colourspace_bridge(image, &outImage, interpretation)) - C.g_object_unref(C.gpointer(image)) if err != 0 { return nil, catchVipsError() } + C.g_object_unref(C.gpointer(image)) image = outImage } From f1fc2d662b5909dd1302a4eeea96cc25191fb9ed Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sat, 11 Jul 2015 20:45:29 +0100 Subject: [PATCH 080/115] feat(version): bump --- README.md | 2 +- version.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6779cac..efedc40 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 -## Features +## Supported image operations - Resize - Enlarge diff --git a/version.go b/version.go index a433764..2688700 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,3 @@ package bimg -const Version = "0.1.17" +const Version = "0.1.18" From ebe8d27556add57056fac612a2d38c99baf80dab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s?= Date: Tue, 21 Jul 2015 18:55:25 +0100 Subject: [PATCH 081/115] refactor(docs): description --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index efedc40..1293e84 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,8 @@ # bimg [![Build Status](https://travis-ci.org/h2non/bimg.png)](https://travis-ci.org/h2non/bimg) [![GitHub release](http://img.shields.io/github/tag/h2non/bimg.svg?style=flat-square)](https://github.com/h2non/bimg/releases) [![GoDoc](https://godoc.org/github.com/h2non/bimg?status.svg)](https://godoc.org/github.com/h2non/bimg) [![Coverage Status](https://coveralls.io/repos/h2non/bimg/badge.svg?branch=master)](https://coveralls.io/r/h2non/bimg?branch=master) -Small [Go](http://golang.org) package for fast high-level image processing and transformation using [libvips](https://github.com/jcupitt/libvips) via C bindings, provinding a simple, elegant and fluent [programmatic API](#examples). +Small [Go](http://golang.org) package for fast high-level image processing and transformation using [libvips](https://github.com/jcupitt/libvips) via C bindings. Provides a simple, elegant and fluent [programmatic API](#examples). -bimg was designed to be a small and efficient library providing a generic high-level [image operations](#supported-image-operations) such as crop, resize, rotate, zoom, watermark... -It can read JPEG, PNG, WEBP and TIFF formats and output to JPEG, PNG and WEBP, including conversion between them. +bimg was designed to be a small and efficient library supporting a common set of [image operations](#supported-image-operations) such as crop, resize, rotate, zoom or watermark. It can read JPEG, PNG, WEBP and TIFF formats and output to JPEG, PNG and WEBP, including conversion between them. bimg uses internally libvips, a powerful library written in C for image processing which requires a [low memory footprint](http://www.vips.ecs.soton.ac.uk/index.php?title=Speed_and_Memory_Use) and it's typically 4x faster than using the quickest ImageMagick and GraphicsMagick settings or Go native `image` package, and in some cases it's even 8x faster processing JPEG images. From c1e0c73d50ab4464344fa56acac0154f681550ca Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Tue, 28 Jul 2015 21:29:47 +0100 Subject: [PATCH 082/115] feat(#49) --- vips.go | 53 ++++++++++++++++++++++++++++------------------------- vips.h | 6 ++++++ 2 files changed, 34 insertions(+), 25 deletions(-) diff --git a/vips.go b/vips.go index f41e8de..04f44b4 100644 --- a/vips.go +++ b/vips.go @@ -15,6 +15,8 @@ import ( "unsafe" ) +const HasMagickSupport = int(C.VIPS_MAGICK_SUPPORT) == 1 + const ( maxCacheMem = 100 * 1024 * 1024 maxCacheSize = 500 @@ -384,36 +386,37 @@ func vipsAffine(input *C.struct__VipsImage, residualx, residualy float64, i Inte return image, nil } -func vipsImageType(buf []byte) ImageType { - imageType := UNKNOWN +func vipsImageType(bytes []byte) ImageType { + if len(bytes) == 0 { + return UNKNOWN + } - if len(buf) == 0 { - return imageType + if bytes[0] == 0x89 && bytes[1] == 0x50 && bytes[2] == 0x4E && bytes[3] == 0x47 { + return PNG + } + if bytes[0] == 0xFF && bytes[1] == 0xD8 && bytes[2] == 0xFF { + return JPEG + } + if bytes[8] == 0x57 && bytes[9] == 0x45 && bytes[10] == 0x42 && bytes[11] == 0x50 { + return WEBP } + if (bytes[0] == 0x49 && bytes[1] == 0x49 && bytes[2] == 0x2A && bytes[3] == 0x0) || + (bytes[0] == 0x4D && bytes[1] == 0x4D && bytes[2] == 0x0 && bytes[3] == 0x2A) { + return TIFF + } + if HasMagickSupport && strings.HasSuffix(readImageType(bytes), "MagickBuffer") { + return MAGICK + } + + return UNKNOWN +} +func readImageType(buf []byte) string { length := C.size_t(len(buf)) imageBuf := unsafe.Pointer(&buf[0]) - bufferType := C.GoString(C.vips_foreign_find_load_buffer(imageBuf, length)) - - switch { - case strings.HasSuffix(bufferType, "JpegBuffer"): - imageType = JPEG - break - case strings.HasSuffix(bufferType, "PngBuffer"): - imageType = PNG - break - case strings.HasSuffix(bufferType, "TiffBuffer"): - imageType = TIFF - break - case strings.HasSuffix(bufferType, "WebpBuffer"): - imageType = WEBP - break - case strings.HasSuffix(bufferType, "MagickBuffer"): - imageType = MAGICK - break - } - - return imageType + load := C.vips_foreign_find_load_buffer(imageBuf, length) + defer C.free(imageBuf) + return C.GoString(load) } func catchVipsError() error { diff --git a/vips.h b/vips.h index 1948907..a97fe20 100644 --- a/vips.h +++ b/vips.h @@ -2,6 +2,12 @@ #include #include +#ifdef VIPS_MAGICK_H +#define VIPS_MAGICK_SUPPORT 1 +#else +#define VIPS_MAGICK_SUPPORT 0 +#endif + enum types { UNKNOWN = 0, JPEG, From c07a3fcbc2b262efaaafc602a958933ad8386163 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Tue, 28 Jul 2015 21:30:47 +0100 Subject: [PATCH 083/115] version(bump) --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index 2688700..94d8b32 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,3 @@ package bimg -const Version = "0.1.18" +const Version = "0.1.19" From b78fe09f17d64ce26f58d79a2b2206cd0ee579ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s?= Date: Sun, 2 Aug 2015 13:01:58 +0100 Subject: [PATCH 084/115] fix(docs): remove old badge --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1293e84..8f0437c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# bimg [![Build Status](https://travis-ci.org/h2non/bimg.png)](https://travis-ci.org/h2non/bimg) [![GitHub release](http://img.shields.io/github/tag/h2non/bimg.svg?style=flat-square)](https://github.com/h2non/bimg/releases) [![GoDoc](https://godoc.org/github.com/h2non/bimg?status.svg)](https://godoc.org/github.com/h2non/bimg) [![Coverage Status](https://coveralls.io/repos/h2non/bimg/badge.svg?branch=master)](https://coveralls.io/r/h2non/bimg?branch=master) +# bimg [![Build Status](https://travis-ci.org/h2non/bimg.png)](https://travis-ci.org/h2non/bimg) [![GitHub release](http://img.shields.io/github/tag/h2non/bimg.svg?style=flat-square)](https://github.com/h2non/bimg/releases) [![GoDoc](https://godoc.org/github.com/h2non/bimg?status.svg)](https://godoc.org/github.com/h2non/bimg) -Small [Go](http://golang.org) package for fast high-level image processing and transformation using [libvips](https://github.com/jcupitt/libvips) via C bindings. Provides a simple, elegant and fluent [programmatic API](#examples). +Small [Go](http://golang.org) package for fast high-level image processing using [libvips](https://github.com/jcupitt/libvips) via C bindings. Provides a simple, elegant and fluent [programmatic API](#examples). bimg was designed to be a small and efficient library supporting a common set of [image operations](#supported-image-operations) such as crop, resize, rotate, zoom or watermark. It can read JPEG, PNG, WEBP and TIFF formats and output to JPEG, PNG and WEBP, including conversion between them. From 1c2f37648c1c354737abdff55a5cc51a1e92929e Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Mon, 7 Sep 2015 20:33:21 +0200 Subject: [PATCH 085/115] refactor(vips): use shortcut to VipsImage C type --- resize.go | 18 ++++++++--------- vips.go | 58 +++++++++++++++++++++++++++---------------------------- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/resize.go b/resize.go index 185a997..7b5b2e9 100644 --- a/resize.go +++ b/resize.go @@ -138,7 +138,7 @@ func shouldTransformImage(o Options, inWidth, inHeight int) bool { (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) { +func transformImage(image *C.VipsImage, o Options, shrink int, residual float64) (*C.VipsImage, error) { var err error // Use vips_shrink with the integral reduction @@ -178,7 +178,7 @@ func transformImage(image *C.struct__VipsImage, o Options, shrink int, residual return image, nil } -func extractOrEmbedImage(image *C.struct__VipsImage, o Options) (*C.struct__VipsImage, error) { +func extractOrEmbedImage(image *C.VipsImage, o Options) (*C.VipsImage, error) { var err error = nil inWidth := int(image.Xsize) inHeight := int(image.Ysize) @@ -212,7 +212,7 @@ func extractOrEmbedImage(image *C.struct__VipsImage, o Options) (*C.struct__Vips return image, err } -func rotateAndFlipImage(image *C.struct__VipsImage, o Options) (*C.struct__VipsImage, error) { +func rotateAndFlipImage(image *C.VipsImage, o Options) (*C.VipsImage, error) { var err error var direction Direction = -1 @@ -243,7 +243,7 @@ func rotateAndFlipImage(image *C.struct__VipsImage, o Options) (*C.struct__VipsI return image, err } -func watermakImage(image *C.struct__VipsImage, w Watermark) (*C.struct__VipsImage, error) { +func watermakImage(image *C.VipsImage, w Watermark) (*C.VipsImage, error) { if w.Text == "" { return image, nil } @@ -275,14 +275,14 @@ func watermakImage(image *C.struct__VipsImage, w Watermark) (*C.struct__VipsImag return image, nil } -func zoomImage(image *C.struct__VipsImage, zoom int) (*C.struct__VipsImage, error) { +func zoomImage(image *C.VipsImage, zoom int) (*C.VipsImage, error) { if zoom == 0 { return image, nil } return vipsZoom(image, zoom+1) } -func shrinkImage(image *C.struct__VipsImage, o Options, residual float64, shrink int) (*C.struct__VipsImage, float64, error) { +func shrinkImage(image *C.VipsImage, o Options, residual float64, shrink int) (*C.VipsImage, float64, error) { // Use vips_shrink with the integral reduction image, err := vipsShrink(image, shrink) if err != nil { @@ -302,8 +302,8 @@ func shrinkImage(image *C.struct__VipsImage, o Options, residual float64, shrink return image, residual, nil } -func shrinkJpegImage(buf []byte, input *C.struct__VipsImage, factor float64, shrink int) (*C.struct__VipsImage, float64, error) { - var image *C.struct__VipsImage +func shrinkJpegImage(buf []byte, input *C.VipsImage, factor float64, shrink int) (*C.VipsImage, float64, error) { + var image *C.VipsImage var err error shrinkOnLoad := 1 @@ -381,7 +381,7 @@ func calculateCrop(inWidth, inHeight, outWidth, outHeight int, gravity Gravity) return left, top } -func calculateRotationAndFlip(image *C.struct__VipsImage, angle Angle) (Angle, bool) { +func calculateRotationAndFlip(image *C.VipsImage, angle Angle) (Angle, bool) { rotate := D0 flip := false diff --git a/vips.go b/vips.go index 04f44b4..7734057 100644 --- a/vips.go +++ b/vips.go @@ -122,15 +122,15 @@ func VipsMemory() VipsMemoryInfo { } } -func vipsExifOrientation(image *C.struct__VipsImage) int { +func vipsExifOrientation(image *C.VipsImage) int { return int(C.vips_exif_orientation(image)) } -func vipsHasAlpha(image *C.struct__VipsImage) bool { +func vipsHasAlpha(image *C.VipsImage) bool { return int(C.has_alpha_channel(image)) > 0 } -func vipsHasProfile(image *C.struct__VipsImage) bool { +func vipsHasProfile(image *C.VipsImage) bool { return int(C.has_profile_embed(image)) > 0 } @@ -140,12 +140,12 @@ func vipsWindowSize(name string) float64 { return float64(C.interpolator_window_size(cname)) } -func vipsSpace(image *C.struct__VipsImage) string { +func vipsSpace(image *C.VipsImage) string { return C.GoString(C.vips_enum_nick_bridge(image)) } -func vipsRotate(image *C.struct__VipsImage, angle Angle) (*C.struct__VipsImage, error) { - var out *C.struct__VipsImage +func vipsRotate(image *C.VipsImage, angle Angle) (*C.VipsImage, error) { + var out *C.VipsImage defer C.g_object_unref(C.gpointer(image)) err := C.vips_rotate(image, &out, C.int(angle)) @@ -156,8 +156,8 @@ func vipsRotate(image *C.struct__VipsImage, angle Angle) (*C.struct__VipsImage, return out, nil } -func vipsFlip(image *C.struct__VipsImage, direction Direction) (*C.struct__VipsImage, error) { - var out *C.struct__VipsImage +func vipsFlip(image *C.VipsImage, direction Direction) (*C.VipsImage, error) { + var out *C.VipsImage defer C.g_object_unref(C.gpointer(image)) err := C.vips_flip_bridge(image, &out, C.int(direction)) @@ -168,8 +168,8 @@ func vipsFlip(image *C.struct__VipsImage, direction Direction) (*C.struct__VipsI return out, nil } -func vipsZoom(image *C.struct__VipsImage, zoom int) (*C.struct__VipsImage, error) { - var out *C.struct__VipsImage +func vipsZoom(image *C.VipsImage, zoom int) (*C.VipsImage, error) { + var out *C.VipsImage defer C.g_object_unref(C.gpointer(image)) err := C.vips_zoom_bridge(image, &out, C.int(zoom), C.int(zoom)) @@ -180,8 +180,8 @@ func vipsZoom(image *C.struct__VipsImage, zoom int) (*C.struct__VipsImage, error return out, nil } -func vipsWatermark(image *C.struct__VipsImage, w Watermark) (*C.struct__VipsImage, error) { - var out *C.struct__VipsImage +func vipsWatermark(image *C.VipsImage, w Watermark) (*C.VipsImage, error) { + var out *C.VipsImage // Defaults noReplicate := 0 @@ -207,8 +207,8 @@ func vipsWatermark(image *C.struct__VipsImage, w Watermark) (*C.struct__VipsImag return out, nil } -func vipsRead(buf []byte) (*C.struct__VipsImage, ImageType, error) { - var image *C.struct__VipsImage +func vipsRead(buf []byte) (*C.VipsImage, ImageType, error) { + var image *C.VipsImage imageType := vipsImageType(buf) if imageType == UNKNOWN { @@ -235,7 +235,7 @@ func vipsColourspaceIsSupportedBuffer(buf []byte) (bool, error) { return vipsColourspaceIsSupported(image), nil } -func vipsColourspaceIsSupported(image *C.struct__VipsImage) bool { +func vipsColourspaceIsSupported(image *C.VipsImage) bool { return int(C.vips_colourspace_issupported_bridge(image)) == 1 } @@ -248,11 +248,11 @@ func vipsInterpretationBuffer(buf []byte) (Interpretation, error) { return vipsInterpretation(image), nil } -func vipsInterpretation(image *C.struct__VipsImage) Interpretation { +func vipsInterpretation(image *C.VipsImage) Interpretation { return Interpretation(C.vips_image_guess_interpretation_bridge(image)) } -func vipsPreSave(image *C.struct__VipsImage, o *vipsSaveOptions) (*C.struct__VipsImage, error) { +func vipsPreSave(image *C.VipsImage, o *vipsSaveOptions) (*C.VipsImage, error) { // Remove ICC profile metadata if o.NoProfile { C.remove_profile(image) @@ -265,7 +265,7 @@ func vipsPreSave(image *C.struct__VipsImage, o *vipsSaveOptions) (*C.struct__Vip interpretation := C.VipsInterpretation(o.Interpretation) // Apply the proper colour space - var outImage *C.struct__VipsImage + var outImage *C.VipsImage if vipsColourspaceIsSupported(image) { err := int(C.vips_colourspace_bridge(image, &outImage, interpretation)) if err != 0 { @@ -278,7 +278,7 @@ func vipsPreSave(image *C.struct__VipsImage, o *vipsSaveOptions) (*C.struct__Vip return image, nil } -func vipsSave(image *C.struct__VipsImage, o vipsSaveOptions) ([]byte, error) { +func vipsSave(image *C.VipsImage, o vipsSaveOptions) ([]byte, error) { defer C.g_object_unref(C.gpointer(image)) image, err := vipsPreSave(image, &o) @@ -317,8 +317,8 @@ func vipsSave(image *C.struct__VipsImage, o vipsSaveOptions) ([]byte, error) { return buf, nil } -func vipsExtract(image *C.struct__VipsImage, left, top, width, height int) (*C.struct__VipsImage, error) { - var buf *C.struct__VipsImage +func vipsExtract(image *C.VipsImage, left, top, width, height int) (*C.VipsImage, error) { + var buf *C.VipsImage defer C.g_object_unref(C.gpointer(image)) if width > MAX_SIZE || height > MAX_SIZE { @@ -333,8 +333,8 @@ func vipsExtract(image *C.struct__VipsImage, left, top, width, height int) (*C.s return buf, nil } -func vipsShrinkJpeg(buf []byte, input *C.struct__VipsImage, shrink int) (*C.struct__VipsImage, error) { - var image *C.struct__VipsImage +func vipsShrinkJpeg(buf []byte, input *C.VipsImage, shrink int) (*C.VipsImage, error) { + var image *C.VipsImage defer C.g_object_unref(C.gpointer(input)) err := C.vips_jpegload_buffer_shrink(unsafe.Pointer(&buf[0]), C.size_t(len(buf)), &image, C.int(shrink)) @@ -345,8 +345,8 @@ func vipsShrinkJpeg(buf []byte, input *C.struct__VipsImage, shrink int) (*C.stru return image, nil } -func vipsShrink(input *C.struct__VipsImage, shrink int) (*C.struct__VipsImage, error) { - var image *C.struct__VipsImage +func vipsShrink(input *C.VipsImage, shrink int) (*C.VipsImage, error) { + var image *C.VipsImage defer C.g_object_unref(C.gpointer(input)) err := C.vips_shrink_bridge(input, &image, C.double(float64(shrink)), C.double(float64(shrink))) @@ -357,8 +357,8 @@ func vipsShrink(input *C.struct__VipsImage, shrink int) (*C.struct__VipsImage, e return image, nil } -func vipsEmbed(input *C.struct__VipsImage, left, top, width, height, extend int) (*C.struct__VipsImage, error) { - var image *C.struct__VipsImage +func vipsEmbed(input *C.VipsImage, left, top, width, height, extend int) (*C.VipsImage, error) { + var image *C.VipsImage defer C.g_object_unref(C.gpointer(input)) err := C.vips_embed_bridge(input, &image, C.int(left), C.int(top), C.int(width), C.int(height), C.int(extend)) @@ -369,8 +369,8 @@ func vipsEmbed(input *C.struct__VipsImage, left, top, width, height, extend int) return image, nil } -func vipsAffine(input *C.struct__VipsImage, residualx, residualy float64, i Interpolator) (*C.struct__VipsImage, error) { - var image *C.struct__VipsImage +func vipsAffine(input *C.VipsImage, residualx, residualy float64, i Interpolator) (*C.VipsImage, error) { + var image *C.VipsImage cstring := C.CString(i.String()) interpolator := C.vips_interpolate_new(cstring) From 801615d05f97cf4f6a340a4f9919a2db9f13c21e Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Mon, 7 Sep 2015 20:46:38 +0200 Subject: [PATCH 086/115] refactor(vips): free watermark cache. refactor vips.h --- vips.h | 143 ++++++++++++++++++++++++++------------------------------- 1 file changed, 64 insertions(+), 79 deletions(-) diff --git a/vips.h b/vips.h index a97fe20..69e30e7 100644 --- a/vips.h +++ b/vips.h @@ -31,39 +31,57 @@ typedef struct { double Background[3]; } WatermarkOptions; +static int +has_profile_embed(VipsImage *image) { + return vips_image_get_typeof(image, VIPS_META_ICC_NAME); +} + +static void +remove_profile(VipsImage *image) { + vips_image_remove(image, VIPS_META_ICC_NAME); +} + +static gboolean +with_interlace(int interlace) { + return interlace > 0 ? TRUE : FALSE; +} + +static int +has_alpha_channel(VipsImage *image) { + return ( + (image->Bands == 2 && image->Type == VIPS_INTERPRETATION_B_W) || + (image->Bands == 4 && image->Type != VIPS_INTERPRETATION_CMYK) || + (image->Bands == 5 && image->Type == VIPS_INTERPRETATION_CMYK) + ) ? 1 : 0; +} + void -vips_enable_cache_set_trace() -{ +vips_enable_cache_set_trace() { vips_cache_set_trace(TRUE); -}; +} int -vips_affine_interpolator(VipsImage *in, VipsImage **out, double a, double b, double c, double d, VipsInterpolate *interpolator) -{ +vips_affine_interpolator(VipsImage *in, VipsImage **out, double a, double b, double c, double d, VipsInterpolate *interpolator) { return vips_affine(in, out, a, b, c, d, "interpolate", interpolator, NULL); -}; +} int -vips_jpegload_buffer_shrink(void *buf, size_t len, VipsImage **out, int shrink) -{ +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_flip_bridge(VipsImage *in, VipsImage **out, int direction) -{ +vips_flip_bridge(VipsImage *in, VipsImage **out, int direction) { return vips_flip(in, out, direction, NULL); -}; +} int -vips_shrink_bridge(VipsImage *in, VipsImage **out, double xshrink, double yshrink) -{ +vips_shrink_bridge(VipsImage *in, VipsImage **out, double xshrink, double yshrink) { return vips_shrink(in, out, xshrink, yshrink, NULL); -}; +} int -vips_rotate(VipsImage *in, VipsImage **out, int angle) -{ +vips_rotate(VipsImage *in, VipsImage **out, int angle) { int rotate = VIPS_ANGLE_D0; if (angle == 90) { @@ -75,7 +93,7 @@ vips_rotate(VipsImage *in, VipsImage **out, int angle) } return vips_rot(in, out, rotate, NULL); -}; +} int vips_exif_orientation(VipsImage *image) { @@ -88,26 +106,7 @@ vips_exif_orientation(VipsImage *image) { orientation = atoi(&exif[0]); } return orientation; -}; - -int -has_profile_embed(VipsImage *image) { - return vips_image_get_typeof(image, VIPS_META_ICC_NAME); -}; - -void -remove_profile(VipsImage *image) { - vips_image_remove(image, VIPS_META_ICC_NAME); -}; - -int -has_alpha_channel(VipsImage *image) { - return ( - (image->Bands == 2 && image->Type == VIPS_INTERPRETATION_B_W) || - (image->Bands == 4 && image->Type != VIPS_INTERPRETATION_CMYK) || - (image->Bands == 5 && image->Type == VIPS_INTERPRETATION_CMYK) - ) ? 1 : 0; -}; +} int interpolator_window_size(char const *name) { @@ -115,56 +114,45 @@ interpolator_window_size(char const *name) { int window_size = vips_interpolate_get_window_size(interpolator); g_object_unref(interpolator); return window_size; -}; +} const char * vips_enum_nick_bridge(VipsImage *image) { return vips_enum_nick(VIPS_TYPE_INTERPRETATION, image->Type); -}; +} int -vips_zoom_bridge(VipsImage *in, VipsImage **out, int xfac, int yfac) -{ +vips_zoom_bridge(VipsImage *in, VipsImage **out, int xfac, int yfac) { return vips_zoom(in, out, xfac, yfac, NULL); -}; +} int -vips_embed_bridge(VipsImage *in, VipsImage **out, int left, int top, int width, int height, int extend) -{ +vips_embed_bridge(VipsImage *in, VipsImage **out, int left, int top, int width, int height, int extend) { return vips_embed(in, out, left, top, width, height, "extend", extend, NULL); -}; +} int -vips_extract_area_bridge(VipsImage *in, VipsImage **out, int left, int top, int width, int height) -{ +vips_extract_area_bridge(VipsImage *in, VipsImage **out, int left, int top, int width, int height) { return vips_extract_area(in, out, left, top, width, height, NULL); -}; +} int -vips_colourspace_issupported_bridge(VipsImage *in) -{ +vips_colourspace_issupported_bridge(VipsImage *in) { return vips_colourspace_issupported(in) ? 1 : 0; -}; +} VipsInterpretation vips_image_guess_interpretation_bridge(VipsImage *in) { return vips_image_guess_interpretation(in); -}; +} int -vips_colourspace_bridge(VipsImage *in, VipsImage **out, VipsInterpretation space) -{ - return vips_colourspace(in, out, space, NULL); -}; - -gboolean -with_interlace(int interlace) { - return interlace > 0 ? TRUE : FALSE; -}; +vips_colourspace_bridge(VipsImage *in, VipsImage **out, VipsInterpretation space) { + return vips_colourspace(in, out, space, NULL); +} int -vips_jpegsave_bridge(VipsImage *in, void **buf, size_t *len, int strip, int quality, int interlace) -{ +vips_jpegsave_bridge(VipsImage *in, void **buf, size_t *len, int strip, int quality, int interlace) { return vips_jpegsave_buffer(in, buf, len, "strip", strip, "Q", quality, @@ -172,11 +160,10 @@ vips_jpegsave_bridge(VipsImage *in, void **buf, size_t *len, int strip, int qual "interlace", with_interlace(interlace), NULL ); -}; +} int -vips_pngsave_bridge(VipsImage *in, void **buf, size_t *len, int strip, int compression, int quality, int interlace) -{ +vips_pngsave_bridge(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, @@ -193,20 +180,19 @@ vips_pngsave_bridge(VipsImage *in, void **buf, size_t *len, int strip, int compr NULL ); #endif -}; +} int -vips_webpsave_bridge(VipsImage *in, void **buf, size_t *len, int strip, int quality) -{ +vips_webpsave_bridge(VipsImage *in, void **buf, size_t *len, int strip, int quality) { return vips_webpsave_buffer(in, buf, len, "strip", strip, "Q", quality, NULL ); -}; +} int -vips_init_image(void *buf, size_t len, int imageType, VipsImage **out) { +vips_init_image (void *buf, size_t len, int imageType, VipsImage **out) { int code = 1; if (imageType == JPEG) { @@ -224,11 +210,10 @@ vips_init_image(void *buf, size_t len, int imageType, VipsImage **out) { } return code; -}; +} int -vips_watermark_replicate(VipsImage *orig, VipsImage *in, VipsImage **out) -{ +vips_watermark_replicate (VipsImage *orig, VipsImage *in, VipsImage **out) { VipsImage *cache = vips_image_new(); if ( @@ -241,12 +226,12 @@ vips_watermark_replicate(VipsImage *orig, VipsImage *in, VipsImage **out) return 1; } + g_object_unref(cache); return 0; -}; +} int -vips_watermark(VipsImage *in, VipsImage **out, WatermarkTextOptions *to, WatermarkOptions *o) -{ +vips_watermark(VipsImage *in, VipsImage **out, WatermarkTextOptions *to, WatermarkOptions *o) { double ones[3] = { 1, 1, 1 }; VipsImage *base = vips_image_new(); @@ -300,4 +285,4 @@ vips_watermark(VipsImage *in, VipsImage **out, WatermarkTextOptions *to, Waterma g_object_unref(base); return 0; -}; +} From ae4046b5a5501daa53f2c5889ad0c1482cf9ac8d Mon Sep 17 00:00:00 2001 From: Thomas Meson Date: Tue, 8 Sep 2015 14:31:12 +0200 Subject: [PATCH 087/115] vips.h: fail to build on Debian Jessie Debian Jessie has libvips 7.40.6. Problem is that before 7.41, VIPS_ANGLE_DXX did not exists. So we need to define properly the macro in order to build for libvips < 7.41 --- vips.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/vips.h b/vips.h index a97fe20..9f2e101 100644 --- a/vips.h +++ b/vips.h @@ -64,6 +64,18 @@ vips_shrink_bridge(VipsImage *in, VipsImage **out, double xshrink, double yshrin int vips_rotate(VipsImage *in, VipsImage **out, int angle) { +#if (VIPS_MAJOR_VERSION == 7 && VIPS_MINOR_VERSION < 41) +/* + * Starting libvips 7.41, VIPS_ANGLE_x has been renamed to VIPS_ANGLE_Dx + * "to help python". So we provide the macro to correctly build for versions + * before 7.41.x. + * https://github.com/jcupitt/libvips/blob/master/ChangeLog#L128 + */ +# define VIPS_ANGLE_D0 VIPS_ANGLE_0 +# define VIPS_ANGLE_D90 VIPS_ANGLE_90 +# define VIPS_ANGLE_D180 VIPS_ANGLE_180 +# define VIPS_ANGLE_D270 VIPS_ANGLE_270 +#endif int rotate = VIPS_ANGLE_D0; if (angle == 90) { From 0e5ba538470a47585009c5818f78281fcda24594 Mon Sep 17 00:00:00 2001 From: Thomas Meson Date: Tue, 8 Sep 2015 14:31:54 +0200 Subject: [PATCH 088/115] vips.h: problem with vips_init() For version prior 7.41, vips_init() is a macro to VIPS_INIT(). VIPS_INIT() should be called, but as it is a macro, cgo could not determine the return type, hence making the build fail. So, we undef vips_init(), and define the vips_init() method. --- vips.h | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/vips.h b/vips.h index 9f2e101..c264bc4 100644 --- a/vips.h +++ b/vips.h @@ -31,6 +31,25 @@ typedef struct { double Background[3]; } WatermarkOptions; +/* + * This method is here to handle the weird initialization of the vips lib. + * libvips use a macro VIPS_INIT() that call vips__init() in version < 7.41, + * or calls vips_init() in version >= 7.41. + * Anyway, it's not possible to build bimg on Debian Jessie with libvips 7.40.x, + * as vips_init() is a macro to VIPS_INIT(), which is also a macro, hence, cgo + * is unable to determine the return type of vips_init(), making the build impossible. + * In order to correctly build bimg, for version < 7.41, we should undef vips_init and + * creates a vips_init() method that calls VIPS_INIT(). + */ +#if (VIPS_MAJOR_VERSION == 7 && VIPS_MINOR_VERSION < 41) +# undef vips_init +int +vips_init(const char *argv0) +{ + return VIPS_INIT(argv0); +} +#endif + void vips_enable_cache_set_trace() { From 0f31f20deda09ccc4b882839b172bfc37a3cb7b9 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Tue, 8 Sep 2015 23:09:46 +0200 Subject: [PATCH 089/115] feat(version): bump --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index 94d8b32..243c0d5 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,3 @@ package bimg -const Version = "0.1.19" +const Version = "0.1.20" From 404cbf902fa61e7a19b04be932f5c808b4bc7af7 Mon Sep 17 00:00:00 2001 From: Thomas Meson Date: Mon, 14 Sep 2015 17:12:46 +0200 Subject: [PATCH 090/115] vips: add a vips__gaussblur method handle both < 7.41 and higher. Prior 7.41, vips_gaussblur took only a int param, the radius. The radius was then divided by 2.0 (min_ampl) in vips_gaussblur. After, you can now parameter the min_ampl and radius became sigma (and passed from an integer to a double). --- options.go | 6 ++++++ vips.go | 11 +++++++++++ vips.h | 12 ++++++++++++ 3 files changed, 29 insertions(+) diff --git a/options.go b/options.go index 67af96d..9526914 100644 --- a/options.go +++ b/options.go @@ -91,6 +91,11 @@ type Watermark struct { Background Color } +type GaussianBlur struct { + Sigma float64 + MinAmpl float64 +} + type Options struct { Height int Width int @@ -117,4 +122,5 @@ type Options struct { Type ImageType Interpolator Interpolator Interpretation Interpretation + GaussianBlur GaussianBlur } diff --git a/vips.go b/vips.go index 7734057..fc8bc7c 100644 --- a/vips.go +++ b/vips.go @@ -432,3 +432,14 @@ func boolToInt(b bool) int { } return 0 } + +func vipsGaussianBlur(image *C.VipsImage, o GaussianBlur) (*C.VipsImage, error) { + var out *C.VipsImage + defer C.g_object_unref(C.gpointer(image)) + + err := C.vips__gaussblur(image, &out, C.double(o.Sigma), C.double(o.MinAmpl)) + if err != 0 { + return nil, catchVipsError() + } + return out, nil +} diff --git a/vips.h b/vips.h index 428e994..ee0be0f 100644 --- a/vips.h +++ b/vips.h @@ -16,6 +16,9 @@ */ #if (VIPS_MAJOR_VERSION == 7 && VIPS_MINOR_VERSION < 41) +/* we need math.h for ceil() in vips__gaussblur */ +#include + #define VIPS_ANGLE_D0 VIPS_ANGLE_0 #define VIPS_ANGLE_D90 VIPS_ANGLE_90 #define VIPS_ANGLE_D180 VIPS_ANGLE_180 @@ -321,3 +324,12 @@ vips_watermark(VipsImage *in, VipsImage **out, WatermarkTextOptions *to, Waterma g_object_unref(base); return 0; } + +int +vips__gaussblur(VipsImage *in, VipsImage **out, double sigma, double min_ampl) { +#if (VIPS_MAJOR_VERSION == 7 && VIPS_MINOR_VERSION < 41) + return vips_gaussblur(in, out, ceil(sigma), NULL); +#else + return vips_gaussblur(in, out, sigma, NULL, "min_ampl", min_ampl); +#endif +} From 4cc9e0dde1983acbeae0d83f1233c11c3fc88cc6 Mon Sep 17 00:00:00 2001 From: Thomas Meson Date: Mon, 14 Sep 2015 17:58:24 +0200 Subject: [PATCH 091/115] transformImage: apply gaussian blur if needed --- resize.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/resize.go b/resize.go index 7b5b2e9..f79ce44 100644 --- a/resize.go +++ b/resize.go @@ -135,7 +135,8 @@ func normalizeOperation(o *Options, inWidth, inHeight int) { 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 + (o.Height > 0 && o.Height != inHeight) || o.AreaWidth > 0 || o.AreaHeight > 0 || + o.GaussianBlur.Sigma > 0 || o.GaussianBlur.MinAmpl > 0 } func transformImage(image *C.VipsImage, o Options, shrink int, residual float64) (*C.VipsImage, error) { @@ -172,6 +173,13 @@ func transformImage(image *C.VipsImage, o Options, shrink int, residual float64) return nil, err } + if o.GaussianBlur.Sigma > 0 || o.GaussianBlur.MinAmpl > 0 { + image, err = vipsGaussianBlur(image, o.GaussianBlur) + if err != nil { + return nil, err + } + } + debug("Transform: shrink=%v, residual=%v, interpolator=%v", shrink, residual, o.Interpolator.String()) From 6968a8a4532b5a187a02bdf8e5b3719fb47749b6 Mon Sep 17 00:00:00 2001 From: Thomas Meson Date: Mon, 14 Sep 2015 18:16:03 +0200 Subject: [PATCH 092/115] vips__gaussblur: add the missing sentinel --- vips.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vips.h b/vips.h index ee0be0f..e27066c 100644 --- a/vips.h +++ b/vips.h @@ -330,6 +330,6 @@ vips__gaussblur(VipsImage *in, VipsImage **out, double sigma, double min_ampl) { #if (VIPS_MAJOR_VERSION == 7 && VIPS_MINOR_VERSION < 41) return vips_gaussblur(in, out, ceil(sigma), NULL); #else - return vips_gaussblur(in, out, sigma, NULL, "min_ampl", min_ampl); + return vips_gaussblur(in, out, sigma, NULL, "min_ampl", min_ampl, NULL); #endif } From b74cbd51739289163d17001006e3f8d391dad354 Mon Sep 17 00:00:00 2001 From: Thomas Meson Date: Tue, 15 Sep 2015 11:28:05 +0200 Subject: [PATCH 093/115] resize: move effects to more explicit methods add method shouldApplyEffects and applyEffects --- resize.go | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/resize.go b/resize.go index f79ce44..e234841 100644 --- a/resize.go +++ b/resize.go @@ -88,6 +88,14 @@ func Resize(buf []byte, o Options) ([]byte, error) { } } + // Apply effects, if necessary + if shouldApplyEffects(o) { + image, err = applyEffects(image, o) + if err != nil { + return nil, err + } + } + // Add watermark, if necessary image, err = watermakImage(image, o.Watermark) if err != nil { @@ -135,8 +143,11 @@ func normalizeOperation(o *Options, inWidth, inHeight int) { 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 || - o.GaussianBlur.Sigma > 0 || o.GaussianBlur.MinAmpl > 0 + (o.Height > 0 && o.Height != inHeight) || o.AreaWidth > 0 || o.AreaHeight > 0 +} + +func shouldApplyEffects(o Options) bool { + return o.GaussianBlur.Sigma > 0 || o.GaussianBlur.MinAmpl > 0 } func transformImage(image *C.VipsImage, o Options, shrink int, residual float64) (*C.VipsImage, error) { @@ -173,6 +184,15 @@ func transformImage(image *C.VipsImage, o Options, shrink int, residual float64) return nil, err } + debug("Transform: shrink=%v, residual=%v, interpolator=%v", + shrink, residual, o.Interpolator.String()) + + return image, nil +} + +func applyEffects(image *C.VipsImage, o Options) (*C.VipsImage, error) { + var err error + if o.GaussianBlur.Sigma > 0 || o.GaussianBlur.MinAmpl > 0 { image, err = vipsGaussianBlur(image, o.GaussianBlur) if err != nil { @@ -180,8 +200,8 @@ func transformImage(image *C.VipsImage, o Options, shrink int, residual float64) } } - debug("Transform: shrink=%v, residual=%v, interpolator=%v", - shrink, residual, o.Interpolator.String()) + debug("Effects: gaussSigma=%v, gaussMinAmpl=%v", + o.GaussianBlur.Sigma, o.GaussianBlur.MinAmpl) return image, nil } From 5667ecda25afc7ababa3134ae234931c1c2f8c7f Mon Sep 17 00:00:00 2001 From: Thomas Meson Date: Tue, 15 Sep 2015 12:23:05 +0200 Subject: [PATCH 094/115] vips__gaussblur: renamed to vips_gaussblur_bridge --- vips.go | 2 +- vips.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/vips.go b/vips.go index fc8bc7c..9fcbc5b 100644 --- a/vips.go +++ b/vips.go @@ -437,7 +437,7 @@ func vipsGaussianBlur(image *C.VipsImage, o GaussianBlur) (*C.VipsImage, error) var out *C.VipsImage defer C.g_object_unref(C.gpointer(image)) - err := C.vips__gaussblur(image, &out, C.double(o.Sigma), C.double(o.MinAmpl)) + err := C.vips_gaussblur_bridge(image, &out, C.double(o.Sigma), C.double(o.MinAmpl)) if err != 0 { return nil, catchVipsError() } diff --git a/vips.h b/vips.h index e27066c..84f1680 100644 --- a/vips.h +++ b/vips.h @@ -16,7 +16,7 @@ */ #if (VIPS_MAJOR_VERSION == 7 && VIPS_MINOR_VERSION < 41) -/* we need math.h for ceil() in vips__gaussblur */ +/* we need math.h for ceil() in vips_gaussblur_bridge */ #include #define VIPS_ANGLE_D0 VIPS_ANGLE_0 @@ -326,7 +326,7 @@ vips_watermark(VipsImage *in, VipsImage **out, WatermarkTextOptions *to, Waterma } int -vips__gaussblur(VipsImage *in, VipsImage **out, double sigma, double min_ampl) { +vips_gaussblur_bridge(VipsImage *in, VipsImage **out, double sigma, double min_ampl) { #if (VIPS_MAJOR_VERSION == 7 && VIPS_MINOR_VERSION < 41) return vips_gaussblur(in, out, ceil(sigma), NULL); #else From bfe0c7949fa8dd99921fde7a810a2a6c5ccc8f88 Mon Sep 17 00:00:00 2001 From: Thomas Meson Date: Tue, 15 Sep 2015 12:24:38 +0200 Subject: [PATCH 095/115] vips_gaussblur: remove dependency on libmath remove ceil() on call to vips_gaussblur, simply cast to int --- vips.h | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/vips.h b/vips.h index 84f1680..0a52b39 100644 --- a/vips.h +++ b/vips.h @@ -16,9 +16,6 @@ */ #if (VIPS_MAJOR_VERSION == 7 && VIPS_MINOR_VERSION < 41) -/* we need math.h for ceil() in vips_gaussblur_bridge */ -#include - #define VIPS_ANGLE_D0 VIPS_ANGLE_0 #define VIPS_ANGLE_D90 VIPS_ANGLE_90 #define VIPS_ANGLE_D180 VIPS_ANGLE_180 @@ -328,7 +325,7 @@ vips_watermark(VipsImage *in, VipsImage **out, WatermarkTextOptions *to, Waterma int vips_gaussblur_bridge(VipsImage *in, VipsImage **out, double sigma, double min_ampl) { #if (VIPS_MAJOR_VERSION == 7 && VIPS_MINOR_VERSION < 41) - return vips_gaussblur(in, out, ceil(sigma), NULL); + return vips_gaussblur(in, out, (int) sigma, NULL); #else return vips_gaussblur(in, out, sigma, NULL, "min_ampl", min_ampl, NULL); #endif From 5dfb883f50e0041f70ccf79efb42639368d4114e Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Tue, 15 Sep 2015 21:30:32 +0100 Subject: [PATCH 096/115] feat(#52): add test case --- README.md | 1 + resize_test.go | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/README.md b/README.md index 8f0437c..53931be 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ For platform specific installations, see [Mac OS](https://github.com/lovell/sha - Thumbnail - Extract area - Watermark (text-based) +- Gaussian blur effect - Custom output color space (RGB, grayscale...) - Format conversion (with additional quality/compression settings) - EXIF metadata (size, alpha channel, profile, orientation...) diff --git a/resize_test.go b/resize_test.go index c419b4a..c83ff39 100644 --- a/resize_test.go +++ b/resize_test.go @@ -151,6 +151,23 @@ func TestNoColorProfile(t *testing.T) { } } +func TestGaussianBlur(t *testing.T) { + options := Options{Width: 800, Height: 600, GaussianBlur: GaussianBlur{Sigma: 5}} + buf, _ := Read("fixtures/test.jpg") + + newImg, err := Resize(buf, options) + if err != nil { + t.Errorf("Resize(imgData, %#v) error: %#v", options, err) + } + + size, _ := Size(newImg) + if size.Height != options.Height || size.Width != options.Width { + t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height) + } + + Write("fixtures/test_gaussian.jpg", newImg) +} + func TestConvert(t *testing.T) { width, height := 300, 240 formats := [3]ImageType{PNG, WEBP, JPEG} From 92e14757b0ff756af5e402f6ee6b718d1fd3c567 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Tue, 15 Sep 2015 21:31:19 +0100 Subject: [PATCH 097/115] feat(docs): update API docs --- README.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/README.md b/README.md index 53931be..6384d6c 100644 --- a/README.md +++ b/README.md @@ -287,6 +287,18 @@ VIPS_TRACE=1 ./app ### Programmatic API +```go +const HasMagickSupport = int(C.VIPS_MAGICK_SUPPORT) == 1 +``` + +```go +const Version = "0.1.20" +``` + +```go +const WATERMARK_FONT = "sans 10" +``` + #### func ColourspaceIsSupported ```go @@ -396,6 +408,16 @@ const ( ) ``` +#### type GaussianBlur + +```go +type GaussianBlur struct { + Sigma float64 + MinAmpl float64 +} +``` + + #### type Gravity ```go @@ -505,6 +527,13 @@ func (i *Image) Flop() ([]byte, error) ``` Flop the image about the horizontal X axis +#### func (*Image) ForceResize + +```go +func (i *Image) ForceResize(width, height int) ([]byte, error) +``` +Force resize with custom size (aspect ratio won't be maintained) + #### func (*Image) Image ```go @@ -697,6 +726,8 @@ const ( INTERPRETATION_RGB16 Interpretation = C.VIPS_INTERPRETATION_RGB16 INTERPRETATION_GREY16 Interpretation = C.VIPS_INTERPRETATION_GREY16 INTERPRETATION_scRGB Interpretation = C.VIPS_INTERPRETATION_scRGB + INTERPRETATION_LAB Interpretation = C.VIPS_INTERPRETATION_LAB + INTERPRETATION_XYZ Interpretation = C.VIPS_INTERPRETATION_XYZ ) ``` @@ -737,6 +768,7 @@ type Options struct { Type ImageType Interpolator Interpolator Interpretation Interpretation + GaussianBlur GaussianBlur } ``` From 5f551e53b8016b4cc2c447900619c75a2b32e722 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Tue, 15 Sep 2015 21:44:43 +0100 Subject: [PATCH 098/115] feat(docs): add list of contributors --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6384d6c..f9d9f28 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,9 @@ bimg uses internally libvips, a powerful library written in C for image processi and it's typically 4x faster than using the quickest ImageMagick and GraphicsMagick settings or Go native `image` package, and in some cases it's even 8x faster processing JPEG images. To get started you could take a look to the [examples](#examples) and [API](https://godoc.org/github.com/h2non/bimg) documentation. + If you're looking for a HTTP based image processing solution, see [imaginary](https://github.com/h2non/imaginary). -bimg was heavily inspired in [sharp](https://github.com/lovell/sharp), its homologous package built for node.js by [Lovell Fuller](https://github.com/lovell). +bimg was heavily inspired in [sharp](https://github.com/lovell/sharp), its homologous package built for [node.js](http://nodejs.org). ## Prerequisites @@ -806,6 +807,15 @@ type Watermark struct { } ``` +## Contributors + +Special thanks to people who freely contributed to improve `bimg` in some or other way. + +- [Yoan Blanc](https://github.com/greut) +- [Christophe Eblé](https://github.com/chreble) +- [Brant Fitzsimmons](https://github.com/bfitzsimmons) +- [Thomas Meson](https://github.com/zllak) + ## Special Thanks - [John Cupitt](https://github.com/jcupitt) From ebb3688b44b6563034aa0e4b9438817a004bc1f4 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Mon, 21 Sep 2015 11:01:21 +0100 Subject: [PATCH 099/115] feat(docs): update benchmarks --- README.md | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index f9d9f28..627d169 100644 --- a/README.md +++ b/README.md @@ -67,27 +67,27 @@ Here you can see some performance test comparisons for multiple scenarios: #### Benchmarks -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 -BenchmarkResizeWebP 500 2872295 ns/op -BenchmarkConvertToJpeg 30 41835497 ns/op -BenchmarkConvertToPng 10 153382204 ns/op -BenchmarkConvertToWebp 10000 264542 ns/op -BenchmarkCropJpeg 30 52267699 ns/op -BenchmarkCropPng 30 56477454 ns/op -BenchmarkCropWebP 5000 274302 ns/op -BenchmarkExtractJpeg 50 27827670 ns/op -BenchmarkExtractPng 2000 769761 ns/op -BenchmarkExtractWebp 3000 513954 ns/op -BenchmarkZoomJpeg 10 159272494 ns/op -BenchmarkZoomPng 20 65771476 ns/op -BenchmarkZoomWebp 5000 368327 ns/op -BenchmarkWatermarkJpeg 100 10026033 ns/op -BenchmarkWatermarPng 200 7350821 ns/op -BenchmarkWatermarWebp 200 9014197 ns/op -ok 30.698s +Tested using Go 1.5.1 and libvips-7.42.3 in OSX i7 2.7Ghz +``` +BenchmarkRotateJpeg-8 20 64686945 ns/op +BenchmarkResizeLargeJpeg-8 20 63390416 ns/op +BenchmarkResizePng-8 100 18147294 ns/op +BenchmarkResizeWebP-8 100 20836741 ns/op +BenchmarkConvertToJpeg-8 100 12831812 ns/op +BenchmarkConvertToPng-8 10 128901422 ns/op +BenchmarkConvertToWebp-8 10 204027990 ns/op +BenchmarkCropJpeg-8 30 59068572 ns/op +BenchmarkCropPng-8 10 117303259 ns/op +BenchmarkCropWebP-8 10 107060659 ns/op +BenchmarkExtractJpeg-8 50 30708919 ns/op +BenchmarkExtractPng-8 3000 595546 ns/op +BenchmarkExtractWebp-8 3000 386379 ns/op +BenchmarkZoomJpeg-8 10 160005424 ns/op +BenchmarkZoomPng-8 30 44561047 ns/op +BenchmarkZoomWebp-8 10 126732678 ns/op +BenchmarkWatermarkJpeg-8 20 79006133 ns/op +BenchmarkWatermarPng-8 200 8197291 ns/op +BenchmarkWatermarWebp-8 30 49360369 ns/op ``` ## API From e83c80c93ce7c0ef0b3255af4e621e491ce51f78 Mon Sep 17 00:00:00 2001 From: Clement DAL PALU Date: Mon, 28 Sep 2015 17:33:38 -0700 Subject: [PATCH 100/115] - Adding a Background option when flattening out a transparent PNG --- image_test.go | 13 +++++++++++++ options.go | 1 + resize.go | 9 +++++++++ vips.go | 10 ++++++++++ vips.h | 9 +++++++++ 5 files changed, 42 insertions(+) diff --git a/image_test.go b/image_test.go index fb20c62..e396857 100644 --- a/image_test.go +++ b/image_test.go @@ -246,6 +246,19 @@ func TestImageConvert(t *testing.T) { Write("fixtures/test_image_convert_out.png", buf) } +func TestTransparentImageConvert(t *testing.T) { + image := initImage("transparent.png") + options := Options{ + Type: JPEG, + Background: Color{255, 255, 255}, + } + buf, err := image.Process(options) + if err != nil { + t.Errorf("Cannot process the image: %#v", err) + } + Write("fixtures/test_transparent_image_convert_out.jpg", buf) +} + func TestImageMetadata(t *testing.T) { data, err := initImage("test.png").Metadata() if err != nil { diff --git a/options.go b/options.go index 9526914..0e7d2e4 100644 --- a/options.go +++ b/options.go @@ -123,4 +123,5 @@ type Options struct { Interpolator Interpolator Interpretation Interpretation GaussianBlur GaussianBlur + Background Color } diff --git a/resize.go b/resize.go index e234841..0ca1479 100644 --- a/resize.go +++ b/resize.go @@ -102,6 +102,15 @@ func Resize(buf []byte, o Options) ([]byte, error) { return nil, err } + // Flatten image on a background, if necessary + black := Color{0, 0, 0} + if imageType == PNG && o.Background != black { + image, err = vipsFlatten(image, o.Background) + if err != nil { + return nil, err + } + } + saveOptions := vipsSaveOptions{ Quality: o.Quality, Type: o.Type, diff --git a/vips.go b/vips.go index 9fcbc5b..8454231 100644 --- a/vips.go +++ b/vips.go @@ -252,6 +252,16 @@ func vipsInterpretation(image *C.VipsImage) Interpretation { return Interpretation(C.vips_image_guess_interpretation_bridge(image)) } +func vipsFlatten(image *C.VipsImage, background Color) (*C.VipsImage, error) { + var outImage *C.VipsImage + backgroundC := [3]C.double{C.double(background.R), C.double(background.G), C.double(background.B)} + err := int(C.vips_flatten_image(image, &outImage, (*C.double)(&backgroundC[0]))) + if err != 0 { + return nil, catchVipsError() + } + return outImage, nil +} + func vipsPreSave(image *C.VipsImage, o *vipsSaveOptions) (*C.VipsImage, error) { // Remove ICC profile metadata if o.NoProfile { diff --git a/vips.h b/vips.h index 0a52b39..4f75627 100644 --- a/vips.h +++ b/vips.h @@ -226,6 +226,15 @@ vips_webpsave_bridge(VipsImage *in, void **buf, size_t *len, int strip, int qual ); } +int +vips_flatten_image(VipsImage *in, VipsImage **out, double background[3]) { + VipsArrayDouble *vipsBackground = vips_array_double_new(background, 3); + return vips_flatten(in, out, + "background", vipsBackground, + NULL + ); +} + int vips_init_image (void *buf, size_t len, int imageType, VipsImage **out) { int code = 1; From 174de89a409cad1ddeecf9c0a0cc4790f0235213 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Tue, 29 Sep 2015 08:45:02 +0100 Subject: [PATCH 101/115] refactor(#55): minor changes, use proper declarations, unref image --- image.go | 10 +++++----- options.go | 7 ++++++- resize.go | 18 ++++++++++++------ vips.go | 24 +++++++++++++++++------- vips.h | 2 +- 5 files changed, 41 insertions(+), 20 deletions(-) diff --git a/image.go b/image.go index fa02dd3..2836787 100644 --- a/image.go +++ b/image.go @@ -4,6 +4,11 @@ type Image struct { buffer []byte } +// Creates a new image +func NewImage(buf []byte) *Image { + return &Image{buf} +} + // Resize the image to fixed width and height func (i *Image) Resize(width, height int) ([]byte, error) { options := Options{ @@ -190,8 +195,3 @@ func (i *Image) Size() (ImageSize, error) { func (i *Image) Image() []byte { return i.buffer } - -// Creates a new image -func NewImage(buf []byte) *Image { - return &Image{buf} -} diff --git a/options.go b/options.go index 0e7d2e4..8dcc2a9 100644 --- a/options.go +++ b/options.go @@ -80,6 +80,10 @@ type Color struct { R, G, B uint8 } +// Shortcut to black RGB color representation +var ColorBlack = Color{0, 0, 0} + +// Text-based watermark configuration type Watermark struct { Width int DPI int @@ -96,6 +100,7 @@ type GaussianBlur struct { MinAmpl float64 } +// Supported image transformation options type Options struct { Height int Width int @@ -117,11 +122,11 @@ type Options struct { NoProfile bool Interlace bool Rotate Angle + Background Color Gravity Gravity Watermark Watermark Type ImageType Interpolator Interpolator Interpretation Interpretation GaussianBlur GaussianBlur - Background Color } diff --git a/resize.go b/resize.go index 0ca1479..87600c2 100644 --- a/resize.go +++ b/resize.go @@ -103,12 +103,9 @@ func Resize(buf []byte, o Options) ([]byte, error) { } // Flatten image on a background, if necessary - black := Color{0, 0, 0} - if imageType == PNG && o.Background != black { - image, err = vipsFlatten(image, o.Background) - if err != nil { - return nil, err - } + image, err = imageFlatten(image, imageType, o) + if err != nil { + return nil, err } saveOptions := vipsSaveOptions{ @@ -312,6 +309,15 @@ func watermakImage(image *C.VipsImage, w Watermark) (*C.VipsImage, error) { return image, nil } +func imageFlatten(image *C.VipsImage, imageType ImageType, o Options) (*C.VipsImage, error) { + // Only PNG images are supported for now + if imageType != PNG || o.Background == ColorBlack { + return image, nil + } + + return vipsFlattenBackground(image, o.Background) +} + func zoomImage(image *C.VipsImage, zoom int) (*C.VipsImage, error) { if zoom == 0 { return image, nil diff --git a/vips.go b/vips.go index 8454231..f30eb09 100644 --- a/vips.go +++ b/vips.go @@ -252,14 +252,24 @@ func vipsInterpretation(image *C.VipsImage) Interpretation { return Interpretation(C.vips_image_guess_interpretation_bridge(image)) } -func vipsFlatten(image *C.VipsImage, background Color) (*C.VipsImage, error) { +func vipsFlattenBackground(image *C.VipsImage, background Color) (*C.VipsImage, error) { var outImage *C.VipsImage - backgroundC := [3]C.double{C.double(background.R), C.double(background.G), C.double(background.B)} - err := int(C.vips_flatten_image(image, &outImage, (*C.double)(&backgroundC[0]))) - if err != 0 { + + backgroundC := [3]C.double{ + C.double(background.R), + C.double(background.G), + C.double(background.B), + } + + err := C.vips_flatten_background_brigde(image, &outImage, (*C.double)(&backgroundC[0])) + if int(err) != 0 { return nil, catchVipsError() } - return outImage, nil + + C.g_object_unref(C.gpointer(image)) + image = outImage + + return image, nil } func vipsPreSave(image *C.VipsImage, o *vipsSaveOptions) (*C.VipsImage, error) { @@ -277,8 +287,8 @@ func vipsPreSave(image *C.VipsImage, o *vipsSaveOptions) (*C.VipsImage, error) { // Apply the proper colour space var outImage *C.VipsImage if vipsColourspaceIsSupported(image) { - err := int(C.vips_colourspace_bridge(image, &outImage, interpretation)) - if err != 0 { + err := C.vips_colourspace_bridge(image, &outImage, interpretation) + if int(err) != 0 { return nil, catchVipsError() } C.g_object_unref(C.gpointer(image)) diff --git a/vips.h b/vips.h index 4f75627..8685a1c 100644 --- a/vips.h +++ b/vips.h @@ -227,7 +227,7 @@ vips_webpsave_bridge(VipsImage *in, void **buf, size_t *len, int strip, int qual } int -vips_flatten_image(VipsImage *in, VipsImage **out, double background[3]) { +vips_flatten_background_brigde(VipsImage *in, VipsImage **out, double background[3]) { VipsArrayDouble *vipsBackground = vips_array_double_new(background, 3); return vips_flatten(in, out, "background", vipsBackground, From 8fdf28fe5e0f0674847a6bfd8fe6d3eee2c78320 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Tue, 29 Sep 2015 23:37:42 +0100 Subject: [PATCH 102/115] fix(#56) --- vips.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/vips.go b/vips.go index f30eb09..248b511 100644 --- a/vips.go +++ b/vips.go @@ -15,6 +15,9 @@ import ( "unsafe" ) +// Current libvips version +const VipsVersion = string(C.VIPS_VERSION) + const HasMagickSupport = int(C.VIPS_MAGICK_SUPPORT) == 1 const ( @@ -291,7 +294,6 @@ func vipsPreSave(image *C.VipsImage, o *vipsSaveOptions) (*C.VipsImage, error) { if int(err) != 0 { return nil, catchVipsError() } - C.g_object_unref(C.gpointer(image)) image = outImage } @@ -301,10 +303,11 @@ func vipsPreSave(image *C.VipsImage, o *vipsSaveOptions) (*C.VipsImage, error) { func vipsSave(image *C.VipsImage, o vipsSaveOptions) ([]byte, error) { defer C.g_object_unref(C.gpointer(image)) - image, err := vipsPreSave(image, &o) + tmpImage, err := vipsPreSave(image, &o) if err != nil { return nil, err } + defer C.g_object_unref(C.gpointer(tmpImage)) length := C.size_t(0) saveErr := C.int(0) @@ -314,13 +317,13 @@ func vipsSave(image *C.VipsImage, o vipsSaveOptions) ([]byte, error) { var ptr unsafe.Pointer switch o.Type { case WEBP: - saveErr = C.vips_webpsave_bridge(image, &ptr, &length, 1, quality) + saveErr = C.vips_webpsave_bridge(tmpImage, &ptr, &length, 1, quality) break case PNG: - saveErr = C.vips_pngsave_bridge(image, &ptr, &length, 1, C.int(o.Compression), quality, interlace) + saveErr = C.vips_pngsave_bridge(tmpImage, &ptr, &length, 1, C.int(o.Compression), quality, interlace) break default: - saveErr = C.vips_jpegsave_bridge(image, &ptr, &length, 1, quality, interlace) + saveErr = C.vips_jpegsave_bridge(tmpImage, &ptr, &length, 1, quality, interlace) break } @@ -355,9 +358,10 @@ func vipsExtract(image *C.VipsImage, left, top, width, height int) (*C.VipsImage func vipsShrinkJpeg(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_jpegload_buffer_shrink(unsafe.Pointer(&buf[0]), C.size_t(len(buf)), &image, C.int(shrink)) + err := C.vips_jpegload_buffer_shrink(ptr, C.size_t(len(buf)), &image, C.int(shrink)) if err != 0 { return nil, catchVipsError() } From d06464fca2acd591bcf50edd1676fbb728b45016 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Tue, 29 Sep 2015 23:49:58 +0100 Subject: [PATCH 103/115] feat(version): bump --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index 243c0d5..7372907 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,3 @@ package bimg -const Version = "0.1.20" +const Version = "0.1.21" From 9342d6482ac671319bc3c4e0f00a269c95516938 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Sun, 4 Oct 2015 10:05:05 +0100 Subject: [PATCH 104/115] refactor(type): simplify image type matching --- metadata.go | 2 +- type.go | 48 +++++++++++++++++++++--------------------------- type_test.go | 2 +- 3 files changed, 23 insertions(+), 29 deletions(-) diff --git a/metadata.go b/metadata.go index 1ec26de..c1e311a 100644 --- a/metadata.go +++ b/metadata.go @@ -68,7 +68,7 @@ func Metadata(buf []byte) (ImageMetadata, error) { Alpha: vipsHasAlpha(image), Profile: vipsHasProfile(image), Space: vipsSpace(image), - Type: getImageTypeName(imageType), + Type: ImageTypeName(imageType), } return metadata, nil diff --git a/type.go b/type.go index 3be6dd4..4077231 100644 --- a/type.go +++ b/type.go @@ -11,6 +11,15 @@ const ( MAGICK ) +// Pairs of image type and its name +var ImageTypes = map[ImageType]string{ + JPEG: "jpeg", + PNG: "png", + WEBP: "webp", + TIFF: "tiff", + MAGICK: "magick", +} + // Determines the image type format (jpeg, png, webp or tiff) func DetermineImageType(buf []byte) ImageType { return vipsImageType(buf) @@ -18,43 +27,28 @@ func DetermineImageType(buf []byte) ImageType { // Determines the image type format by name (jpeg, png, webp or tiff) func DetermineImageTypeName(buf []byte) string { - return getImageTypeName(vipsImageType(buf)) + return ImageTypeName(vipsImageType(buf)) } // Check if a given image type is supported func IsTypeSupported(t ImageType) bool { - return t == JPEG || t == PNG || t == WEBP || t == MAGICK + return ImageTypes[t] != "" } // Check if a given image type name is supported func IsTypeNameSupported(t string) bool { - return t == "jpeg" || - t == "jpg" || - t == "png" || - t == "webp" || - t == "magick" + for _, name := range ImageTypes { + if name == t { + return true + } + } + return false } -func getImageTypeName(code ImageType) string { - imageType := "unknown" - - switch { - case code == JPEG: - imageType = "jpeg" - break - case code == WEBP: - imageType = "webp" - break - case code == PNG: - imageType = "png" - break - case code == TIFF: - imageType = "tiff" - break - case code == MAGICK: - imageType = "magick" - break +func ImageTypeName(t ImageType) string { + imageType := ImageTypes[t] + if imageType == "" { + return "unknown" } - return imageType } diff --git a/type_test.go b/type_test.go index 534f738..68e6d21 100644 --- a/type_test.go +++ b/type_test.go @@ -68,7 +68,7 @@ func TestIsTypeNameSupported(t *testing.T) { name string expected bool }{ - {"jpg", true}, + {"jpeg", true}, {"png", true}, {"webp", true}, {"gif", false}, From 7980ee1ae83606477db42b8289add2e0e297faf2 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Tue, 6 Oct 2015 13:37:06 +0100 Subject: [PATCH 105/115] feat(docs): add libvips version compatibility note --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 627d169..0d9b870 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,9 @@ To get started you could take a look to the [examples](#examples) and [API](http If you're looking for a HTTP based image processing solution, see [imaginary](https://github.com/h2non/imaginary). bimg was heavily inspired in [sharp](https://github.com/lovell/sharp), its homologous package built for [node.js](http://nodejs.org). +**libvips compatibility**: `bimg` was not fully tested yet in libvips `+8.x`, so minor things could be broken. +For production-focused environments I recommend you to use libvips `7.42.x`. + ## Prerequisites - [libvips](https://github.com/jcupitt/libvips) v7.40.0+ (7.42.0+ recommended) From 89292e7ead85a8961bf53b92f46cbe7812dc917f Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Tue, 6 Oct 2015 22:28:50 +0100 Subject: [PATCH 106/115] refactor(docs): support with libvips 8.0 is stable for now --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 0d9b870..627d169 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,6 @@ To get started you could take a look to the [examples](#examples) and [API](http If you're looking for a HTTP based image processing solution, see [imaginary](https://github.com/h2non/imaginary). bimg was heavily inspired in [sharp](https://github.com/lovell/sharp), its homologous package built for [node.js](http://nodejs.org). -**libvips compatibility**: `bimg` was not fully tested yet in libvips `+8.x`, so minor things could be broken. -For production-focused environments I recommend you to use libvips `7.42.x`. - ## Prerequisites - [libvips](https://github.com/jcupitt/libvips) v7.40.0+ (7.42.0+ recommended) From b7eaa00f104a8eab49eedf49d75b11308df95f7a Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Mon, 12 Oct 2015 21:36:37 +0100 Subject: [PATCH 107/115] feat(#60): support zero top and left params in extract operation --- image.go | 5 +++++ image_test.go | 14 ++++++++++++++ resize.go | 2 +- vips.go | 6 ++++++ 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/image.go b/image.go index 2836787..6b6e179 100644 --- a/image.go +++ b/image.go @@ -48,6 +48,11 @@ func (i *Image) Extract(top, left, width, height int) ([]byte, error) { AreaWidth: width, AreaHeight: height, } + + if top == 0 && left == 0 { + options.Top = -1 + } + return i.Process(options) } diff --git a/image_test.go b/image_test.go index e396857..b0faff5 100644 --- a/image_test.go +++ b/image_test.go @@ -48,6 +48,20 @@ func TestImageExtract(t *testing.T) { Write("fixtures/test_extract_out.jpg", buf) } +func TestImageExtractZero(t *testing.T) { + buf, err := initImage("test.jpg").Extract(0, 0, 300, 200) + if err != nil { + t.Errorf("Cannot process the image: %s", err) + } + + err = assertSize(buf, 300, 200) + if err != nil { + t.Error(err) + } + + Write("fixtures/test_extract_zero_out.jpg", buf) +} + func TestImageEnlarge(t *testing.T) { buf, err := initImage("test.png").Enlarge(500, 375) if err != nil { diff --git a/resize.go b/resize.go index 87600c2..9c7005e 100644 --- a/resize.go +++ b/resize.go @@ -229,7 +229,7 @@ func extractOrEmbedImage(image *C.VipsImage, o Options) (*C.VipsImage, error) { left, top := (o.Width-inWidth)/2, (o.Height-inHeight)/2 image, err = vipsEmbed(image, left, top, o.Width, o.Height, o.Extend) break - case o.Top > 0 || o.Left > 0: + case o.Top != 0 || o.Left != 0: if o.AreaWidth == 0 { o.AreaHeight = o.Width } diff --git a/vips.go b/vips.go index 248b511..57bca95 100644 --- a/vips.go +++ b/vips.go @@ -8,6 +8,7 @@ import "C" import ( "errors" + "math" "os" "runtime" "strings" @@ -340,6 +341,10 @@ func vipsSave(image *C.VipsImage, o vipsSaveOptions) ([]byte, error) { return buf, nil } +func max(x int) int { + return int(math.Max(float64(x), 0)) +} + func vipsExtract(image *C.VipsImage, left, top, width, height int) (*C.VipsImage, error) { var buf *C.VipsImage defer C.g_object_unref(C.gpointer(image)) @@ -348,6 +353,7 @@ func vipsExtract(image *C.VipsImage, left, top, width, height int) (*C.VipsImage return nil, errors.New("Maximum image size exceeded") } + top, left = max(top), max(left) err := C.vips_extract_area_bridge(image, &buf, C.int(left), C.int(top), C.int(width), C.int(height)) if err != 0 { return nil, catchVipsError() From da839c603ed9ff5fec9d3a759ae2b1014b14c368 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Aparicio?= Date: Tue, 13 Oct 2015 09:00:40 +0100 Subject: [PATCH 108/115] fix(docs): typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 627d169..1d45e32 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ and it's typically 4x faster than using the quickest ImageMagick and GraphicsMag To get started you could take a look to the [examples](#examples) and [API](https://godoc.org/github.com/h2non/bimg) documentation. -If you're looking for a HTTP based image processing solution, see [imaginary](https://github.com/h2non/imaginary). +If you're looking for an HTTP based image processing solution, see [imaginary](https://github.com/h2non/imaginary). bimg was heavily inspired in [sharp](https://github.com/lovell/sharp), its homologous package built for [node.js](http://nodejs.org). ## Prerequisites From c22442428cfe714ff4835edc97a5b8185d4d05af Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Fri, 16 Oct 2015 14:25:32 +0100 Subject: [PATCH 109/115] refactor(vips): define constant --- vips.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/vips.h b/vips.h index 8685a1c..aa97668 100644 --- a/vips.h +++ b/vips.h @@ -22,6 +22,8 @@ #define VIPS_ANGLE_D270 VIPS_ANGLE_270 #endif +#define EXIF_IFD0_ORIENTATION "exif-ifd0-Orientation" + enum types { UNKNOWN = 0, JPEG, @@ -135,8 +137,8 @@ vips_exif_orientation(VipsImage *image) { int orientation = 0; const char *exif; if ( - vips_image_get_typeof(image, "exif-ifd0-Orientation") != 0 && - !vips_image_get_string(image, "exif-ifd0-Orientation", &exif) + vips_image_get_typeof(image, EXIF_IFD0_ORIENTATION) != 0 && + !vips_image_get_string(image, EXIF_IFD0_ORIENTATION, &exif) ) { orientation = atoi(&exif[0]); } From 9b60d9da84e16019cb16ec7589b7d29eaa5a105f Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Fri, 16 Oct 2015 21:57:02 +0100 Subject: [PATCH 110/115] feat(docs): add toc, remove API docs --- README.md | 591 ++++-------------------------------------------------- 1 file changed, 42 insertions(+), 549 deletions(-) diff --git a/README.md b/README.md index 1d45e32..9307137 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,45 @@ # bimg [![Build Status](https://travis-ci.org/h2non/bimg.png)](https://travis-ci.org/h2non/bimg) [![GitHub release](http://img.shields.io/github/tag/h2non/bimg.svg?style=flat-square)](https://github.com/h2non/bimg/releases) [![GoDoc](https://godoc.org/github.com/h2non/bimg?status.svg)](https://godoc.org/github.com/h2non/bimg) -Small [Go](http://golang.org) package for fast high-level image processing using [libvips](https://github.com/jcupitt/libvips) via C bindings. Provides a simple, elegant and fluent [programmatic API](#examples). +Small [Go](http://golang.org) package for fast high-level image processing using [libvips](https://github.com/jcupitt/libvips) via C bindings, providing a simple, elegant and fluent [programmatic API](#examples). bimg was designed to be a small and efficient library supporting a common set of [image operations](#supported-image-operations) such as crop, resize, rotate, zoom or watermark. It can read JPEG, PNG, WEBP and TIFF formats and output to JPEG, PNG and WEBP, including conversion between them. bimg uses internally libvips, a powerful library written in C for image processing which requires a [low memory footprint](http://www.vips.ecs.soton.ac.uk/index.php?title=Speed_and_Memory_Use) and it's typically 4x faster than using the quickest ImageMagick and GraphicsMagick settings or Go native `image` package, and in some cases it's even 8x faster processing JPEG images. -To get started you could take a look to the [examples](#examples) and [API](https://godoc.org/github.com/h2non/bimg) documentation. - If you're looking for an HTTP based image processing solution, see [imaginary](https://github.com/h2non/imaginary). + bimg was heavily inspired in [sharp](https://github.com/lovell/sharp), its homologous package built for [node.js](http://nodejs.org). +## Contents + +- [Supported image operations](#supported-image-operations) +- [Prerequisites](#prerequisites) +- [Installation](#installation) +- [Performance](#performance) +- [Benchmark](#benchmark) +- [Examples](#examples) +- [Debugging](#debugging) +- [API](#api) +- [Credits](#credits) + +## Supported image operations + +- Resize +- Enlarge +- Crop +- Rotate (with auto-rotate based on EXIF orientation) +- Flip (with auto-flip based on EXIF metadata) +- Flop +- Zoom +- Thumbnail +- Extract area +- Watermark (text-based) +- Gaussian blur effect +- Custom output color space (RGB, grayscale...) +- Format conversion (with additional quality/compression settings) +- EXIF metadata (size, alpha channel, profile, orientation...) + ## Prerequisites - [libvips](https://github.com/jcupitt/libvips) v7.40.0+ (7.42.0+ recommended) @@ -40,23 +68,6 @@ 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 - -- Resize -- Enlarge -- Crop -- Rotate (with auto-rotate based on EXIF orientation) -- Flip (with auto-flip based on EXIF metadata) -- Flop -- Zoom -- Thumbnail -- Extract area -- Watermark (text-based) -- Gaussian blur effect -- Custom output color space (RGB, grayscale...) -- Format conversion (with additional quality/compression settings) -- EXIF metadata (size, alpha channel, profile, orientation...) - ## Performance libvips is probably the faster open source solution for image processing. @@ -65,7 +76,7 @@ Here you can see some performance test comparisons for multiple scenarios: - [libvips speed and memory usage](http://www.vips.ecs.soton.ac.uk/index.php?title=Speed_and_Memory_Use) - [sharp performance tests](https://github.com/lovell/sharp#the-task) -#### Benchmarks +## Benchmark Tested using Go 1.5.1 and libvips-7.42.3 in OSX i7 2.7Ghz ``` @@ -90,9 +101,7 @@ BenchmarkWatermarPng-8 200 8197291 ns/op BenchmarkWatermarWebp-8 30 49360369 ns/op ``` -## API - -### Examples +## Examples ```go import ( @@ -159,6 +168,8 @@ if bimg.NewImage(newImage).Type() == "png" { #### Force resize +Force resize operation without perserving the aspect ratio: + ```go buffer, err := bimg.Read("image.jpg") if err != nil { @@ -274,7 +285,7 @@ if err != nil { bimg.Write("new.jpg", newImage) ``` -#### Debugging +## Debugging Run the process passing the `DEBUG` environment variable ``` @@ -286,539 +297,21 @@ Enable libvips traces (note that a lot of data will be written in stdout): VIPS_TRACE=1 ./app ``` -### Programmatic API - -```go -const HasMagickSupport = int(C.VIPS_MAGICK_SUPPORT) == 1 -``` - -```go -const Version = "0.1.20" -``` - -```go -const WATERMARK_FONT = "sans 10" -``` - -#### func ColourspaceIsSupported - -```go -func ColourspaceIsSupported(buf []byte) (bool, error) -``` -Check in the image colourspace is supported by libvips - -#### func DetermineImageTypeName - -```go -func DetermineImageTypeName(buf []byte) string -``` -Determines the image type format by name (jpeg, png, webp or tiff) - -#### func Initialize - -```go -func Initialize() -``` -Explicit thread-safe start of libvips. Only call this function if you've -previously shutdown libvips - -#### func IsTypeNameSupported - -```go -func IsTypeNameSupported(t string) bool -``` -Check if a given image type name is supported - -#### func IsTypeSupported - -```go -func IsTypeSupported(t ImageType) bool -``` -Check if a given image type is supported - -#### func Read - -```go -func Read(path string) ([]byte, error) -``` - -#### func Resize - -```go -func Resize(buf []byte, o Options) ([]byte, error) -``` - -#### func Shutdown - -```go -func Shutdown() -``` -Thread-safe function to shutdown libvips. You can call this to drop caches as -well. If libvips was already initialized, the function is no-op - -#### func VipsDebugInfo - -```go -func VipsDebugInfo() -``` -Output to stdout vips collected data. Useful for debugging - -#### func Write - -```go -func Write(path string, buf []byte) error -``` - -#### type Angle - -```go -type Angle int -``` - - -```go -const ( - D0 Angle = 0 - D90 Angle = 90 - D180 Angle = 180 - D270 Angle = 270 -) -``` - -#### type Color - -```go -type Color struct { - R, G, B uint8 -} -``` - -Color represents a traditional RGB color scheme - -#### type Direction - -```go -type Direction int -``` - - -```go -const ( - HORIZONTAL Direction = C.VIPS_DIRECTION_HORIZONTAL - VERTICAL Direction = C.VIPS_DIRECTION_VERTICAL -) -``` - -#### type GaussianBlur - -```go -type GaussianBlur struct { - Sigma float64 - MinAmpl float64 -} -``` - - -#### type Gravity - -```go -type Gravity int -``` - - -```go -const ( - CENTRE Gravity = iota - NORTH - EAST - SOUTH - WEST -) -``` - -#### type Image - -```go -type Image struct { -} -``` - - -#### func NewImage - -```go -func NewImage(buf []byte) *Image -``` -Creates a new image - -#### func (*Image) Colourspace - -```go -func (i *Image) Colourspace(c Interpretation) ([]byte, error) -``` -Colour space conversion - -#### func (*Image) ColourspaceIsSupported - -```go -func (i *Image) ColourspaceIsSupported() (bool, error) -``` -Check if the current image has a valid colourspace - -#### func (*Image) Convert - -```go -func (i *Image) Convert(t ImageType) ([]byte, error) -``` -Convert image to another format - -#### func (*Image) Crop - -```go -func (i *Image) Crop(width, height int, gravity Gravity) ([]byte, error) -``` -Crop the image to the exact size specified - -#### func (*Image) CropByHeight - -```go -func (i *Image) CropByHeight(height int) ([]byte, error) -``` -Crop an image by height (auto width) - -#### func (*Image) CropByWidth - -```go -func (i *Image) CropByWidth(width int) ([]byte, error) -``` -Crop an image by width (auto height) - -#### func (*Image) Enlarge - -```go -func (i *Image) Enlarge(width, height int) ([]byte, error) -``` -Enlarge the image by width and height. Aspect ratio is maintained - -#### func (*Image) EnlargeAndCrop - -```go -func (i *Image) EnlargeAndCrop(width, height int) ([]byte, error) -``` -Enlarge the image by width and height with additional crop transformation - -#### func (*Image) Extract - -```go -func (i *Image) Extract(top, left, width, height int) ([]byte, error) -``` -Extract area from the by X/Y axis - -#### func (*Image) Flip - -```go -func (i *Image) Flip() ([]byte, error) -``` -Flip the image about the vertical Y axis - -#### func (*Image) Flop - -```go -func (i *Image) Flop() ([]byte, error) -``` -Flop the image about the horizontal X axis - -#### func (*Image) ForceResize - -```go -func (i *Image) ForceResize(width, height int) ([]byte, error) -``` -Force resize with custom size (aspect ratio won't be maintained) - -#### func (*Image) Image - -```go -func (i *Image) Image() []byte -``` -Get image buffer - -#### func (*Image) Interpretation - -```go -func (i *Image) Interpretation() (Interpretation, error) -``` -Get the image interpretation type See: -http://www.vips.ecs.soton.ac.uk/supported/current/doc/html/libvips/VipsImage.html#VipsInterpretation - -#### func (*Image) Metadata - -```go -func (i *Image) Metadata() (ImageMetadata, error) -``` -Get image metadata (size, alpha channel, profile, EXIF rotation) - -#### func (*Image) Process - -```go -func (i *Image) Process(o Options) ([]byte, error) -``` -Transform the image by custom options - -#### func (*Image) Resize - -```go -func (i *Image) Resize(width, height int) ([]byte, error) -``` -Resize the image to fixed width and height - -#### func (*Image) ResizeAndCrop - -```go -func (i *Image) ResizeAndCrop(width, height int) ([]byte, error) -``` -Resize the image to fixed width and height with additional crop transformation - -#### func (*Image) Rotate - -```go -func (i *Image) Rotate(a Angle) ([]byte, error) -``` -Rotate the image by given angle degrees (0, 90, 180 or 270) - -#### func (*Image) Size - -```go -func (i *Image) Size() (ImageSize, error) -``` -Get image size - -#### func (*Image) Thumbnail - -```go -func (i *Image) Thumbnail(pixels int) ([]byte, error) -``` -Thumbnail the image by the a given width by aspect ratio 4:4 - -#### func (*Image) Type - -```go -func (i *Image) Type() string -``` -Get image type format (jpeg, png, webp, tiff) - -#### func (*Image) Watermark - -```go -func (i *Image) Watermark(w Watermark) ([]byte, error) -``` -Add text as watermark on the given image - -#### func (*Image) Zoom - -```go -func (i *Image) Zoom(factor int) ([]byte, error) -``` -Zoom the image by the given factor. You should probably call Extract() before - -#### type ImageMetadata - -```go -type ImageMetadata struct { - Orientation int - Channels int - Alpha bool - Profile bool - Type string - Space string - Colourspace string - Size ImageSize -} -``` - - -#### func Metadata - -```go -func Metadata(buf []byte) (ImageMetadata, error) -``` -Extract the image metadata (size, type, alpha channel, profile, EXIF -orientation...) - -#### type ImageSize - -```go -type ImageSize struct { - Width int - Height int -} -``` - - -#### func Size - -```go -func Size(buf []byte) (ImageSize, error) -``` -Get the image size by width and height pixels - -#### type ImageType - -```go -type ImageType int -``` - - -```go -const ( - UNKNOWN ImageType = iota - JPEG - WEBP - PNG - TIFF - MAGICK -) -``` - -#### func DetermineImageType - -```go -func DetermineImageType(buf []byte) ImageType -``` -Determines the image type format (jpeg, png, webp or tiff) - -#### type Interpolator - -```go -type Interpolator int -``` - +## API -```go -const ( - BICUBIC Interpolator = iota - BILINEAR - NOHALO -) -``` +See [godoc reference](https://godoc.org/github.com/h2non/bimg) for detailed API documentation. -#### func (Interpolator) String +## Credits -```go -func (i Interpolator) String() string -``` - -#### type Interpretation - -```go -type Interpretation int -``` - -Image interpretation type See: -http://www.vips.ecs.soton.ac.uk/supported/current/doc/html/libvips/VipsImage.html#VipsInterpretation - -```go -const ( - INTERPRETATION_ERROR Interpretation = C.VIPS_INTERPRETATION_ERROR - INTERPRETATION_MULTIBAND Interpretation = C.VIPS_INTERPRETATION_MULTIBAND - INTERPRETATION_B_W Interpretation = C.VIPS_INTERPRETATION_B_W - INTERPRETATION_CMYK Interpretation = C.VIPS_INTERPRETATION_CMYK - INTERPRETATION_RGB Interpretation = C.VIPS_INTERPRETATION_RGB - INTERPRETATION_sRGB Interpretation = C.VIPS_INTERPRETATION_sRGB - INTERPRETATION_RGB16 Interpretation = C.VIPS_INTERPRETATION_RGB16 - INTERPRETATION_GREY16 Interpretation = C.VIPS_INTERPRETATION_GREY16 - INTERPRETATION_scRGB Interpretation = C.VIPS_INTERPRETATION_scRGB - INTERPRETATION_LAB Interpretation = C.VIPS_INTERPRETATION_LAB - INTERPRETATION_XYZ Interpretation = C.VIPS_INTERPRETATION_XYZ -) -``` - -#### func ImageInterpretation - -```go -func ImageInterpretation(buf []byte) (Interpretation, error) -``` -Get the image interpretation type See: -http://www.vips.ecs.soton.ac.uk/supported/current/doc/html/libvips/VipsImage.html#VipsInterpretation - -#### type Options - -```go -type Options struct { - Height int - Width int - AreaHeight int - AreaWidth int - Top int - Left int - Extend int - Quality int - Compression int - Zoom int - Crop bool - Enlarge bool - Embed bool - Flip bool - Flop bool - Force bool - NoAutoRotate bool - NoProfile bool - Interlace bool - Rotate Angle - Gravity Gravity - Watermark Watermark - Type ImageType - Interpolator Interpolator - Interpretation Interpretation - GaussianBlur GaussianBlur -} -``` - - -#### type VipsMemoryInfo - -```go -type VipsMemoryInfo struct { - Memory int64 - MemoryHighwater int64 - Allocations int64 -} -``` - - -#### func VipsMemory - -```go -func VipsMemory() VipsMemoryInfo -``` -Get memory info stats from vips (cache size, memory allocs...) - -#### type Watermark - -```go -type Watermark struct { - Width int - DPI int - Margin int - Opacity float32 - NoReplicate bool - Text string - Font string - Background Color -} -``` - -## Contributors - -Special thanks to people who freely contributed to improve `bimg` in some or other way. +People who recurrently contributed to improve `bimg` in some or other way. +- [John Cupitt](https://github.com/jcupitt) - [Yoan Blanc](https://github.com/greut) - [Christophe Eblé](https://github.com/chreble) - [Brant Fitzsimmons](https://github.com/bfitzsimmons) - [Thomas Meson](https://github.com/zllak) -## Special Thanks - -- [John Cupitt](https://github.com/jcupitt) +Thank you! ## License From d4ebbaa875524e53c763fd9864190c3ce06a6aed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Aparicio?= Date: Fri, 16 Oct 2015 22:29:57 +0100 Subject: [PATCH 111/115] fix(docs): typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9307137..5a0f6da 100644 --- a/README.md +++ b/README.md @@ -303,7 +303,7 @@ See [godoc reference](https://godoc.org/github.com/h2non/bimg) for detailed API ## Credits -People who recurrently contributed to improve `bimg` in some or other way. +People who recurrently contributed to improve `bimg` in some way. - [John Cupitt](https://github.com/jcupitt) - [Yoan Blanc](https://github.com/greut) From 6ee9b69627d2f6525487924e328141b45baa17fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Aparicio?= Date: Tue, 3 Nov 2015 07:49:05 +0000 Subject: [PATCH 112/115] refactor(resize): simplify code --- resize.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/resize.go b/resize.go index 9c7005e..d6dce26 100644 --- a/resize.go +++ b/resize.go @@ -118,12 +118,7 @@ func Resize(buf []byte, o Options) ([]byte, error) { } // Finally get the resultant buffer - buf, err = vipsSave(image, saveOptions) - if err != nil { - return nil, err - } - - return buf, nil + return vipsSave(image, saveOptions) } func applyDefaults(o *Options, imageType ImageType) { From e27f8d5c7b0481d684b7c21076ef7e04ee349751 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Tue, 3 Nov 2015 07:49:46 +0000 Subject: [PATCH 113/115] refactor(docs) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9307137..388ee2a 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ bimg was heavily inspired in [sharp](https://github.com/lovell/sharp), its homol - Zoom - Thumbnail - Extract area -- Watermark (text-based) +- Watermark (text only) - Gaussian blur effect - Custom output color space (RGB, grayscale...) - Format conversion (with additional quality/compression settings) From 13ef72dc0350ae98961e2e721c7fb0b8167a04b6 Mon Sep 17 00:00:00 2001 From: Tomas Aparicio Date: Tue, 3 Nov 2015 07:54:40 +0000 Subject: [PATCH 114/115] refactor(resize): clone options by value --- resize.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/resize.go b/resize.go index d6dce26..f0ba8e3 100644 --- a/resize.go +++ b/resize.go @@ -23,8 +23,8 @@ func Resize(buf []byte, o Options) ([]byte, error) { return nil, err } - // Define default options - applyDefaults(&o, imageType) + // Clone and define default options + o = applyDefaults(o, imageType) if IsTypeSupported(o.Type) == false { return nil, errors.New("Unsupported image output type") @@ -121,7 +121,7 @@ func Resize(buf []byte, o Options) ([]byte, error) { return vipsSave(image, saveOptions) } -func applyDefaults(o *Options, imageType ImageType) { +func applyDefaults(o Options, imageType ImageType) Options { if o.Quality == 0 { o.Quality = QUALITY } @@ -134,6 +134,7 @@ func applyDefaults(o *Options, imageType ImageType) { if o.Interpretation == 0 { o.Interpretation = INTERPRETATION_sRGB } + return o } func normalizeOperation(o *Options, inWidth, inHeight int) { From 99abaf0752ebb7b0b8db01618eddefb086f6601e Mon Sep 17 00:00:00 2001 From: Chuck Neerdaels Date: Mon, 16 Nov 2015 15:46:08 -0800 Subject: [PATCH 115/115] Added interface and test for sharpen --- options.go | 10 ++++++++++ resize.go | 13 ++++++++++--- resize_test.go | 17 +++++++++++++++++ vips.go | 11 +++++++++++ vips.h | 9 +++++++++ 5 files changed, 57 insertions(+), 3 deletions(-) diff --git a/options.go b/options.go index 8dcc2a9..fae5fd7 100644 --- a/options.go +++ b/options.go @@ -100,6 +100,15 @@ type GaussianBlur struct { MinAmpl float64 } +type Sharpen struct { + Radius int + X1 float64 + Y2 float64 + Y3 float64 + M1 float64 + M2 float64 +} + // Supported image transformation options type Options struct { Height int @@ -129,4 +138,5 @@ type Options struct { Interpolator Interpolator Interpretation Interpretation GaussianBlur GaussianBlur + Sharpen Sharpen } diff --git a/resize.go b/resize.go index f0ba8e3..563a787 100644 --- a/resize.go +++ b/resize.go @@ -149,7 +149,7 @@ func shouldTransformImage(o Options, inWidth, inHeight int) bool { } func shouldApplyEffects(o Options) bool { - return o.GaussianBlur.Sigma > 0 || o.GaussianBlur.MinAmpl > 0 + return o.GaussianBlur.Sigma > 0 || o.GaussianBlur.MinAmpl > 0 || o.Sharpen.Radius > 0 && o.Sharpen.Y2 > 0 || o.Sharpen.Y3 > 0 } func transformImage(image *C.VipsImage, o Options, shrink int, residual float64) (*C.VipsImage, error) { @@ -202,8 +202,15 @@ func applyEffects(image *C.VipsImage, o Options) (*C.VipsImage, error) { } } - debug("Effects: gaussSigma=%v, gaussMinAmpl=%v", - o.GaussianBlur.Sigma, o.GaussianBlur.MinAmpl) + if o.Sharpen.Radius > 0 && o.Sharpen.Y2 > 0 || o.Sharpen.Y3 > 0 { + image, err = vipsSharpen(image, o.Sharpen) + if err != nil { + return nil, err + } + } + + debug("Effects: gaussSigma=%v, gaussMinAmpl=%v, sharpenRadius=%v", + o.GaussianBlur.Sigma, o.GaussianBlur.MinAmpl, o.Sharpen.Radius) return image, nil } diff --git a/resize_test.go b/resize_test.go index c83ff39..d57852c 100644 --- a/resize_test.go +++ b/resize_test.go @@ -168,6 +168,23 @@ func TestGaussianBlur(t *testing.T) { Write("fixtures/test_gaussian.jpg", newImg) } +func TestSharpen(t *testing.T) { + options := Options{Width: 800, Height: 600, Sharpen: Sharpen{Radius: 1, X1: 1.5, Y2: 20, Y3: 50, M1: 1, M2: 2}} + buf, _ := Read("fixtures/test.jpg") + + newImg, err := Resize(buf, options) + if err != nil { + t.Errorf("Resize(imgData, %#v) error: %#v", options, err) + } + + size, _ := Size(newImg) + if size.Height != options.Height || size.Width != options.Width { + t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height) + } + + Write("fixtures/test_sharpen.jpg", newImg) +} + func TestConvert(t *testing.T) { width, height := 300, 240 formats := [3]ImageType{PNG, WEBP, JPEG} diff --git a/vips.go b/vips.go index 57bca95..a2555d4 100644 --- a/vips.go +++ b/vips.go @@ -473,3 +473,14 @@ func vipsGaussianBlur(image *C.VipsImage, o GaussianBlur) (*C.VipsImage, error) } return out, nil } + +func vipsSharpen(image *C.VipsImage, o Sharpen) (*C.VipsImage, error) { + var out *C.VipsImage + defer C.g_object_unref(C.gpointer(image)) + + err := C.vips_sharpen_bridge(image, &out, C.int(o.Radius), C.double(o.X1), C.double(o.Y2), C.double(o.Y3), C.double(o.M1), C.double(o.M2)) + if err != 0 { + return nil, catchVipsError() + } + return out, nil +} diff --git a/vips.h b/vips.h index aa97668..1eecdc3 100644 --- a/vips.h +++ b/vips.h @@ -341,3 +341,12 @@ vips_gaussblur_bridge(VipsImage *in, VipsImage **out, double sigma, double min_a return vips_gaussblur(in, out, sigma, NULL, "min_ampl", min_ampl, NULL); #endif } + +int +vips_sharpen_bridge(VipsImage *in, VipsImage **out, int radius, double x1, double y2, double y3, double m1, double m2) { +#if (VIPS_MAJOR_VERSION == 7 && VIPS_MINOR_VERSION < 41) + return vips_sharpen(in, out, radius, x1, y2, y3, m1, m2, NULL); +#else + return vips_sharpen(in, out, "radius", radius, "x1", x1, "y2", y2, "y3", y3, "m1", m1, "m2", m2, NULL); +#endif +}