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)
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.
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... 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.
@ -41,13 +41,14 @@ The [install script](https://github.com/lovell/sharp/blob/master/preinstall.sh)
- Resize
- Enlarge
- Crop
- Rotate (and auto-rotate based on EXIF orientation)
- Flip (and auto-flip based on EXIF metadata)
- Rotate (with auto-rotate based on EXIF orientation)
- Flip (with auto-flip based on EXIF metadata)
- Flop
- Zoom
- Thumbnail
- Extract area
- Format conversion
- Watermark (fully customizable text-based)
- Format conversion (with additional quality/compression settings)
- EXIF metadata (size, alpha channel, profile, orientation...)
## Performance
@ -165,6 +166,34 @@ if err != nil {
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
```go
@ -491,7 +520,6 @@ Determines the image type format (jpeg, png, webp or tiff)
type Interpolator int
```
```go
const (
BICUBIC Interpolator = iota
@ -531,6 +559,10 @@ type Options struct {
}
```
## Special Thanks
- [John Cupitt](https://github.com/jcupitt)
## License
MIT - Tomas Aparicio

@ -75,20 +75,9 @@ func (i *Image) Thumbnail(pixels int) ([]byte, error) {
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
func (i *Image) Watermark(image []byte, left, top int) ([]byte, error) {
options := Options{
Insert: Insert{
Buffer: image,
Top: top,
Left: left,
},
}
func (i *Image) Watermark(w Watermark) ([]byte, error) {
options := Options{Watermark: w}
return i.Process(options)
}

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

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

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

@ -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) {
options := Options{Width: 800, Height: 600, Rotate: 111}
buf, _ := Read("fixtures/test.jpg")

@ -3,6 +3,7 @@ package bimg
/*
#cgo pkg-config: vips
#include "vips.h"
#include "stdlib.h"
*/
import "C"
@ -19,16 +20,30 @@ var (
initialized bool = false
)
type VipsMemoryInfo struct {
Memory int64
MemoryHighwater int64
Allocations int64
}
type vipsSaveOptions struct {
Quality int
Compression int
Type ImageType
}
type VipsMemoryInfo struct {
Memory int64
MemoryHighwater int64
Allocations int64
type vipsWatermarkOptions struct {
Width C.int
DPI C.int
Margin C.int
NoReplicate C.int
Opacity C.float
Background [3]C.double
}
type vipsWatermarkTextOptions struct {
Text *C.char
Font *C.char
}
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() {
C.im__print_all()
}
// Get memory info stats from vips
// Get memory info stats from vips (cache size, memory allocs...)
func VipsMemory() VipsMemoryInfo {
return VipsMemoryInfo{
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) {
var out *C.struct__VipsImage
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
}
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 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(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 {
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(interpolator))
// Perform affine transformation
err := C.vips_affine_interpolator(input, &image, C.double(residual), 0, 0, C.double(residual), interpolator)
if err != 0 {
return nil, catchVipsError()
@ -288,26 +363,6 @@ func vipsImageType(buf []byte) 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 {
s := C.GoString(C.vips_error_buffer())
C.vips_error_clear()

@ -11,6 +11,20 @@ enum types {
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
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
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
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
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
@ -165,3 +186,67 @@ vips_init_image(void *buf, size_t len, int imageType, VipsImage **out) {
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