diff --git a/README.md b/README.md index 55efe600..c8d3d1eb 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ If you're using `gopkg.in`, you can still rely in the `v0` without worrying abou - Thumbnail - Extract area - Watermark (using text or image) +- AddText (support Persian) - Gaussian blur effect - Custom output color space (RGB, grayscale...) - Format conversion (with additional quality/compression settings) @@ -265,6 +266,34 @@ if err != nil { bimg.Write("new.jpg", newImage) ``` +#### AddText + +```go +buffer, err := bimg.Read("image.jpg") +if err != nil { + fmt.Fprintln(os.Stderr, err) +} + +addText := bimg.AddText{ + Text: "Hello | سلام", + Top: 50, + Left: 50, + Opacity: 0.25, + Width: 200, + DPI: 100, + Margin: 150, + Font: "sans bold 12", + Background: bimg.Color{255, 255, 255}, +} + +newImage, err := bimg.NewImage(buffer).AddText(addText) +if err != nil { + fmt.Fprintln(os.Stderr, err) +} + +bimg.Write("new.jpg", newImage) +``` + #### Fluent interface ```go diff --git a/image.go b/image.go index 224dafaf..acddee21 100644 --- a/image.go +++ b/image.go @@ -135,6 +135,12 @@ func (i *Image) Watermark(w Watermark) ([]byte, error) { return i.Process(options) } +// adds text on the given image. +func (i *Image) AddText(a AddText) ([]byte, error) { + options := Options{AddText: a} + return i.Process(options) +} + // WatermarkImage adds image as watermark on the given image. func (i *Image) WatermarkImage(w WatermarkImage) ([]byte, error) { options := Options{WatermarkImage: w} diff --git a/image_test.go b/image_test.go index 5af0431d..f1d8f073 100644 --- a/image_test.go +++ b/image_test.go @@ -241,6 +241,38 @@ func TestImageWatermark(t *testing.T) { Write("testdata/test_watermark_text_out.jpg", buf) } +func TestImageAddText(t *testing.T) { + image := initImage("test.jpg") + _, err := image.Crop(800, 600, GravityNorth) + if err != nil { + t.Errorf("Cannot process the image: %#v", err) + } + + buf, err := image.AddText(AddText{ + Width: 800, + Height: 600, + DPI: 100, + Top: 10, + Left: 500, + Text: "Copy me if you can", + Background: Color{255, 255, 255}, + Opacity: 1, + }) + 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("test_add_text_out.jpg", buf) +} func TestImageWatermarkWithImage(t *testing.T) { image := initImage("test.jpg") diff --git a/options.go b/options.go index c5676210..700b3e8e 100644 --- a/options.go +++ b/options.go @@ -162,6 +162,19 @@ type Watermark struct { Background Color } +// AddText represents the add-text options. +type AddText struct { + Width int + Height int + DPI int + Top int + Left int + Text string + Font string + Background Color + Opacity float32 +} + // WatermarkImage represents the image-based watermark supported options. type WatermarkImage struct { Left int @@ -214,6 +227,7 @@ type Options struct { Rotate Angle Background Color Gravity Gravity + AddText AddText Watermark Watermark WatermarkImage WatermarkImage Type ImageType diff --git a/resizer.go b/resizer.go index a8e998c7..f5acfd39 100644 --- a/resizer.go +++ b/resizer.go @@ -113,6 +113,12 @@ func resizer(buf []byte, o Options) ([]byte, error) { return nil, err } + // Add text, if necessary + image, err = AddTextToImage(image, o.AddText) + if err != nil { + return nil, err + } + // Add watermark, if necessary image, err = watermarkImageWithAnotherImage(image, o.WatermarkImage) if err != nil { @@ -355,6 +361,35 @@ func watermarkImageWithText(image *C.VipsImage, w Watermark) (*C.VipsImage, erro return image, nil } +func AddTextToImage(image *C.VipsImage, a AddText) (*C.VipsImage, error) { + if a.Text == "" { + return image, nil + } + + // Defaults + if a.Font == "" { + a.Font = WatermarkFont + } + if a.Width == 0 { + a.Width = int(math.Floor(float64(image.Xsize / 6))) + } + if a.DPI == 0 { + a.DPI = 150 + } + if a.Opacity == 0 { + a.Opacity = 0.25 + } else if a.Opacity > 1 { + a.Opacity = 1 + } + + image, err := vipsAddText(image, a) + if err != nil { + return nil, err + } + + return image, nil +} + func watermarkImageWithAnotherImage(image *C.VipsImage, w WatermarkImage) (*C.VipsImage, error) { if len(w.Buf) == 0 { diff --git a/vips.go b/vips.go index 25b77aed..65543fa7 100644 --- a/vips.go +++ b/vips.go @@ -65,6 +65,16 @@ type vipsWatermarkOptions struct { Background [3]C.double } +type vipsAddTextOptions struct { + Width C.int + Height C.int + DPI C.int + Top C.int + Left C.int + Background [3]C.double + Opacity C.float +} + type vipsWatermarkImageOptions struct { Left C.int Top C.int @@ -292,6 +302,27 @@ func vipsWatermark(image *C.VipsImage, w Watermark) (*C.VipsImage, error) { return out, nil } +func vipsAddText(image *C.VipsImage, w AddText) (*C.VipsImage, error) { + var out *C.VipsImage + + 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 := vipsAddTextOptions{C.int(w.Width), C.int(w.Height), C.int(w.DPI), C.int(w.Top), C.int(w.Left), background, C.float(w.Opacity)} + + defer C.free(unsafe.Pointer(text)) + defer C.free(unsafe.Pointer(font)) + + err := C.vips_add_text(image, &out, (*C.WatermarkTextOptions)(unsafe.Pointer(&textOpts)), (*C.AddTextOptions)(unsafe.Pointer(&opts))) + if err != 0 { + return nil, catchVipsError() + } + + return out, nil +} + func vipsRead(buf []byte) (*C.VipsImage, ImageType, error) { var image *C.VipsImage imageType := vipsImageType(buf) diff --git a/vips.h b/vips.h index e0d623a8..faff956d 100644 --- a/vips.h +++ b/vips.h @@ -49,6 +49,16 @@ typedef struct { double Background[3]; } WatermarkOptions; +typedef struct { + int Width; + int Height; + int DPI; + int Top; + int Left; + double Background[3]; + float Opacity; +} AddTextOptions; + typedef struct { int Left; int Top; @@ -451,6 +461,51 @@ vips_watermark(VipsImage *in, VipsImage **out, WatermarkTextOptions *to, Waterma return 0; } +int +vips_add_text(VipsImage *in, VipsImage **out, WatermarkTextOptions *to, AddTextOptions *o) { + double ones[3] = { 1, 1, 1 }; + + VipsImage *base = vips_image_new(); + VipsImage **t = (VipsImage **) vips_object_local_array(VIPS_OBJECT(base), 10); + 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], o->Left, o->Top, o->Width, o->Height, NULL) + ) { + g_object_unref(base); + return 1; + } + + // Make the constant image to paint the text with. + if ( + 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; + } + + // Blend the mask and text and write to output. + if (vips_ifthenelse(t[4], t[9], t[0], out, "blend", TRUE, NULL)) { + g_object_unref(base); + return 1; + } + + g_object_unref(base); + return 0; +} + int vips_gaussblur_bridge(VipsImage *in, VipsImage **out, double sigma, double min_ampl) { #if (VIPS_MAJOR_VERSION == 7 && VIPS_MINOR_VERSION < 41)