feat(#27, #25): new features

master
Tomas Aparicio 11 years ago
parent 58b2be80a5
commit cc2290cb45

@ -1,12 +1,12 @@
# 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.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)
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](#examples) 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) in pure Go.
bimg is designed to be a small and efficient library with a generic and useful set of features. 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) 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. 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... and conversion between multiple formats. 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.
For getting started, take a look to the [examples](#examples) and [programmatic API](https://godoc.org/github.com/h2non/bimg) documentation. For getting started, take a look to the [examples](#examples) and [programmatic API](https://godoc.org/github.com/h2non/bimg) documentation.
@ -41,13 +41,14 @@ The [install script](https://github.com/lovell/sharp/blob/master/preinstall.sh)
- Resize - Resize
- Enlarge - Enlarge
- Crop - Crop
- Rotate (and auto-rotate based on EXIF orientation) - Rotate (with auto-rotate based on EXIF orientation)
- Flip (and auto-flip based on EXIF metadata) - Flip (with auto-flip based on EXIF metadata)
- Flop - Flop
- Zoom - Zoom
- Thumbnail - Thumbnail
- Extract area - Extract area
- Format conversion - Watermark (fully customizable text-based)
- Format conversion (with additional quality/compression settings)
- EXIF metadata (size, alpha channel, profile, orientation...) - EXIF metadata (size, alpha channel, profile, orientation...)
## Performance ## Performance
@ -165,6 +166,34 @@ if err != nil {
bimg.Write("new.jpg", newImage) bimg.Write("new.jpg", newImage)
``` ```
#### Watermark
```go
buffer, err := bimg.Read("image.jpg")
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
options := bimg.Watermark{
Watermark{
Text: "Chuck Norris - Copyright (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()
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
bimg.Write("new.jpg", newImage)
```
#### Fluent interface #### Fluent interface
```go ```go
@ -491,7 +520,6 @@ Determines the image type format (jpeg, png, webp or tiff)
type Interpolator int type Interpolator int
``` ```
```go ```go
const ( const (
BICUBIC Interpolator = iota BICUBIC Interpolator = iota
@ -531,6 +559,10 @@ type Options struct {
} }
``` ```
## Special Thanks
- [John Cupitt](https://github.com/jcupitt)
## License ## License
MIT - Tomas Aparicio MIT - Tomas Aparicio

@ -75,20 +75,9 @@ func (i *Image) Thumbnail(pixels int) ([]byte, error) {
return i.Process(options) return i.Process(options)
} }
// Insert an image. Alias to Watermark()
func (i *Image) Insert(image []byte, left, top int) ([]byte, error) {
return i.Watermark(image, left, top)
}
// Insert an image to the existent one as watermark // Insert an image to the existent one as watermark
func (i *Image) Watermark(image []byte, left, top int) ([]byte, error) { func (i *Image) Watermark(w Watermark) ([]byte, error) {
options := Options{ options := Options{Watermark: w}
Insert: Insert{
Buffer: image,
Top: top,
Left: left,
},
}
return i.Process(options) return i.Process(options)
} }

@ -111,8 +111,13 @@ func TestImageWatermark(t *testing.T) {
t.Errorf("Cannot process the image: %#v", err) t.Errorf("Cannot process the image: %#v", err)
} }
insert, _ := Read("fixtures/watermark.png") buf, err := image.Watermark(Watermark{
buf, err := image.Watermark(insert, 10, 10) Text: "Copy me if you can",
Opacity: 0.5,
Width: 200,
DPI: 100,
Background: Color{255, 255, 255},
})
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }

@ -55,10 +55,20 @@ const (
VERTICAL Direction = C.VIPS_DIRECTION_VERTICAL VERTICAL Direction = C.VIPS_DIRECTION_VERTICAL
) )
type Insert struct { // Color represents a traditional RGB color scheme
Top int type Color struct {
Left int R, G, B uint8
Buffer []byte }
type Watermark struct {
Width int
DPI int
Margin int
Opacity float32
NoReplicate bool
Text string
Font string
Background Color
} }
type Options struct { type Options struct {
@ -78,9 +88,10 @@ type Options struct {
Flip bool Flip bool
Flop bool Flop bool
NoAutoRotate bool NoAutoRotate bool
Colorspace bool
Rotate Angle Rotate Angle
Insert Insert
Gravity Gravity Gravity Gravity
Watermark Watermark
Type ImageType Type ImageType
Interpolator Interpolator Interpolator Interpolator
} }

@ -124,8 +124,8 @@ func Resize(buf []byte, o Options) ([]byte, error) {
Compression: o.Compression, Compression: o.Compression,
} }
// Insert an image if necessary // watermark
image, err = insertImage(image, imageType, o.Insert, saveOptions) image, err = watermakImage(image, o.Watermark)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -198,42 +198,44 @@ func rotateImage(image *C.struct__VipsImage, o Options) (*C.struct__VipsImage, e
return image, err return image, err
} }
// WIP func watermakImage(image *C.struct__VipsImage, w Watermark) (*C.struct__VipsImage, error) {
func insertImage(image *C.struct__VipsImage, t ImageType, o Insert, save vipsSaveOptions) (*C.struct__VipsImage, error) { if len(w.Text) == 0 {
if len(o.Buffer) == 0 {
return image, nil return image, nil
} }
insert, imageType, err := vipsRead(o.Buffer) // Defaults
if err != nil { if len(w.Font) == 0 {
return nil, err w.Font = "sans 10"
} }
if w.Width == 0 {
if imageType != t { w.Width = int(math.Floor(float64(image.Xsize / 8)))
save.Type = t }
buf, err := vipsSave(insert, save) if w.DPI == 0 {
if err != nil { w.DPI = 150
return nil, err }
} if w.Margin == 0 {
w.Margin = w.Width
insert, imageType, err = vipsRead(buf) }
if err != nil { if w.Opacity == 0 {
return nil, err w.Opacity = 0.25
} } else if w.Opacity > 1 {
w.Opacity = 1
} }
debug("Insert image: %#v", insert) image, err := vipsWatermark(image, w)
if err != nil {
return nil, err
}
return vipsInsert(image, insert, o.Left, o.Top) return image, nil
} }
func zoomImage(image *C.struct__VipsImage, zoom int) (*C.struct__VipsImage, error) { func zoomImage(image *C.struct__VipsImage, zoom int) (*C.struct__VipsImage, error) {
if zoom == 0 { if zoom == 0 {
return image, nil return image, nil
} }
zoom += 1
return vipsZoom(image, zoom) 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.struct__VipsImage, o Options, residual float64, shrink int) (*C.struct__VipsImage, float64, error) {

@ -45,6 +45,44 @@ 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")
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_corrupt_out.jpg", newImg)
if err != nil {
t.Fatal("Cannot save the image")
}
}
func TestInvalidRotate(t *testing.T) { func TestInvalidRotate(t *testing.T) {
options := Options{Width: 800, Height: 600, Rotate: 111} options := Options{Width: 800, Height: 600, Rotate: 111}
buf, _ := Read("fixtures/test.jpg") buf, _ := Read("fixtures/test.jpg")

@ -3,6 +3,7 @@ package bimg
/* /*
#cgo pkg-config: vips #cgo pkg-config: vips
#include "vips.h" #include "vips.h"
#include "stdlib.h"
*/ */
import "C" import "C"
@ -19,16 +20,30 @@ var (
initialized bool = false initialized bool = false
) )
type VipsMemoryInfo struct {
Memory int64
MemoryHighwater int64
Allocations int64
}
type vipsSaveOptions struct { type vipsSaveOptions struct {
Quality int Quality int
Compression int Compression int
Type ImageType Type ImageType
} }
type VipsMemoryInfo struct { type vipsWatermarkOptions struct {
Memory int64 Width C.int
MemoryHighwater int64 DPI C.int
Allocations int64 Margin C.int
NoReplicate C.int
Opacity C.float
Background [3]C.double
}
type vipsWatermarkTextOptions struct {
Text *C.char
Font *C.char
} }
func init() { func init() {
@ -71,12 +86,12 @@ func Shutdown() {
} }
} }
// Output to stdout collected data for debugging purposes // Output to stdout vips collected data. Useful for debugging
func VipsDebug() { func VipsDebug() {
C.im__print_all() C.im__print_all()
} }
// Get memory info stats from vips // Get memory info stats from vips (cache size, memory allocs...)
func VipsMemory() VipsMemoryInfo { func VipsMemory() VipsMemoryInfo {
return VipsMemoryInfo{ return VipsMemoryInfo{
Memory: int64(C.vips_tracked_get_mem()), Memory: int64(C.vips_tracked_get_mem()),
@ -85,6 +100,26 @@ func VipsMemory() VipsMemoryInfo {
} }
} }
func vipsExifOrientation(image *C.struct__VipsImage) int {
return int(C.vips_exif_orientation(image))
}
func vipsHasAlpha(image *C.struct__VipsImage) bool {
return int(C.has_alpha_channel(image)) > 0
}
func vipsHasProfile(image *C.struct__VipsImage) bool {
return int(C.has_profile_embed(image)) > 0
}
func vipsWindowSize(name string) float64 {
return float64(C.interpolator_window_size(C.CString(name)))
}
func vipsSpace(image *C.struct__VipsImage) string {
return C.GoString(C.vips_enum_nick_bridge(image))
}
func vipsRotate(image *C.struct__VipsImage, angle Angle) (*C.struct__VipsImage, error) { func vipsRotate(image *C.struct__VipsImage, angle Angle) (*C.struct__VipsImage, error) {
var out *C.struct__VipsImage var out *C.struct__VipsImage
defer C.g_object_unref(C.gpointer(image)) defer C.g_object_unref(C.gpointer(image))
@ -121,13 +156,54 @@ func vipsZoom(image *C.struct__VipsImage, zoom int) (*C.struct__VipsImage, error
return out, nil return out, nil
} }
func vipsInsert(image *C.struct__VipsImage, sub *C.struct__VipsImage, left, top int) (*C.struct__VipsImage, error) { func vipsColorSpace(image *C.struct__VipsImage) (*C.struct__VipsImage, error) {
var out *C.struct__VipsImage 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)) defer C.g_object_unref(C.gpointer(image))
defer C.g_object_unref(C.gpointer(sub))
err := C.vips_insert_bridge(image, sub, &out, C.int(left), C.int(top)) 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
// Defaults
noReplicate := 0
if w.NoReplicate {
noReplicate = 1
}
text := C.CString(w.Text)
font := C.CString(w.Font)
background := [3]C.double{C.double(w.Background.R), C.double(w.Background.G), C.double(w.Background.B)}
textOpts := vipsWatermarkTextOptions{text, font}
opts := vipsWatermarkOptions{C.int(w.Width), C.int(w.DPI), C.int(w.Margin), C.int(noReplicate), C.float(w.Opacity), background}
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)))
if err != 0 { if err != 0 {
return nil, catchVipsError() return nil, catchVipsError()
} }
@ -247,7 +323,6 @@ func vipsAffine(input *C.struct__VipsImage, residual float64, i Interpolator) (*
defer C.g_object_unref(C.gpointer(input)) defer C.g_object_unref(C.gpointer(input))
defer C.g_object_unref(C.gpointer(interpolator)) defer C.g_object_unref(C.gpointer(interpolator))
// Perform affine transformation
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(residual), 0, 0, C.double(residual), interpolator)
if err != 0 { if err != 0 {
return nil, catchVipsError() return nil, catchVipsError()
@ -288,26 +363,6 @@ func vipsImageType(buf []byte) ImageType {
return imageType return imageType
} }
func vipsExifOrientation(image *C.struct__VipsImage) int {
return int(C.vips_exif_orientation(image))
}
func vipsHasAlpha(image *C.struct__VipsImage) bool {
return int(C.has_alpha_channel(image)) > 0
}
func vipsHasProfile(image *C.struct__VipsImage) bool {
return int(C.has_profile_embed(image)) > 0
}
func vipsWindowSize(name string) float64 {
return float64(C.interpolator_window_size(C.CString(name)))
}
func vipsSpace(image *C.struct__VipsImage) string {
return C.GoString(C.vips_enum_nick_bridge(image))
}
func catchVipsError() error { func catchVipsError() error {
s := C.GoString(C.vips_error_buffer()) s := C.GoString(C.vips_error_buffer())
C.vips_error_clear() C.vips_error_clear()

@ -11,6 +11,20 @@ enum types {
MAGICK MAGICK
}; };
typedef struct {
char *Text;
char *Font;
} watermarkTextOptions;
typedef struct {
int Width;
int DPI;
int Margin;
int NoReplicate;
float Opacity;
double Background[3];
} watermarkOptions;
int 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)
{ {
@ -98,21 +112,28 @@ vips_zoom_bridge(VipsImage *in, VipsImage **out, int xfac, int yfac)
}; };
int int
vips_insert_bridge(VipsImage *in, VipsImage *sub, VipsImage **out, int left, int top) vips_colorspace_bridge(VipsImage *in, VipsImage **out)
{ {
return vips_insert(in, sub, out, left, top, NULL); return vips_colourspace(in, out, VIPS_INTERPRETATION_LAB, NULL);
}; };
int int
vips_embed_bridge(VipsImage *in, VipsImage **out, int left, int top, int width, int height, int extend) vips_hist_find_ndim_bridge(VipsImage *in, VipsImage **out)
{ {
return vips_embed(in, out, left, top, width, height, "extend", extend, NULL); 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 int
vips_colourspace_bridge(VipsImage *in, VipsImage **out, VipsInterpretation space) vips_embed_bridge(VipsImage *in, VipsImage **out, int left, int top, int width, int height, int extend)
{ {
return vips_colourspace(in, out, space, NULL); return vips_embed(in, out, left, top, width, height, "extend", extend, NULL);
}; };
int int
@ -165,3 +186,67 @@ vips_init_image(void *buf, size_t len, int imageType, VipsImage **out) {
return code; return code;
}; };
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);
};

Loading…
Cancel
Save