Skip to content

Commit 4031199

Browse files
committed
feat: Implement AddText function, test and example
1 parent 5a9d7f2 commit 4031199

File tree

7 files changed

+202
-0
lines changed

7 files changed

+202
-0
lines changed

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ If you're using `gopkg.in`, you can still rely in the `v0` without worrying abou
4141
- Thumbnail
4242
- Extract area
4343
- Watermark (using text or image)
44+
- AddText (support Persian)
4445
- Gaussian blur effect
4546
- Custom output color space (RGB, grayscale...)
4647
- Format conversion (with additional quality/compression settings)
@@ -265,6 +266,34 @@ if err != nil {
265266
bimg.Write("new.jpg", newImage)
266267
```
267268

269+
#### AddText
270+
271+
```go
272+
buffer, err := bimg.Read("image.jpg")
273+
if err != nil {
274+
fmt.Fprintln(os.Stderr, err)
275+
}
276+
277+
addText := bimg.AddText{
278+
Text: "Hello | سلام",
279+
Top: 50,
280+
Left: 50,
281+
Opacity: 0.25,
282+
Width: 200,
283+
DPI: 100,
284+
Margin: 150,
285+
Font: "sans bold 12",
286+
Background: bimg.Color{255, 255, 255},
287+
}
288+
289+
newImage, err := bimg.NewImage(buffer).AddText(addText)
290+
if err != nil {
291+
fmt.Fprintln(os.Stderr, err)
292+
}
293+
294+
bimg.Write("new.jpg", newImage)
295+
```
296+
268297
#### Fluent interface
269298

270299
```go

image.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,12 @@ func (i *Image) Watermark(w Watermark) ([]byte, error) {
135135
return i.Process(options)
136136
}
137137

138+
// adds text on the given image.
139+
func (i *Image) AddText(a AddText) ([]byte, error) {
140+
options := Options{AddText: a}
141+
return i.Process(options)
142+
}
143+
138144
// WatermarkImage adds image as watermark on the given image.
139145
func (i *Image) WatermarkImage(w WatermarkImage) ([]byte, error) {
140146
options := Options{WatermarkImage: w}

image_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,38 @@ func TestImageWatermark(t *testing.T) {
241241

242242
Write("testdata/test_watermark_text_out.jpg", buf)
243243
}
244+
func TestImageAddText(t *testing.T) {
245+
image := initImage("test.jpg")
246+
_, err := image.Crop(800, 600, GravityNorth)
247+
if err != nil {
248+
t.Errorf("Cannot process the image: %#v", err)
249+
}
250+
251+
buf, err := image.AddText(AddText{
252+
Width: 800,
253+
Height: 600,
254+
DPI: 100,
255+
Top: 10,
256+
Left: 500,
257+
Text: "Copy me if you can",
258+
Background: Color{255, 255, 255},
259+
Opacity: 1,
260+
})
261+
if err != nil {
262+
t.Error(err)
263+
}
264+
265+
err = assertSize(buf, 800, 600)
266+
if err != nil {
267+
t.Error(err)
268+
}
269+
270+
if DetermineImageType(buf) != JPEG {
271+
t.Fatal("Image is not jpeg")
272+
}
273+
274+
Write("test_add_text_out.jpg", buf)
275+
}
244276

245277
func TestImageWatermarkWithImage(t *testing.T) {
246278
image := initImage("test.jpg")

options.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,19 @@ type Watermark struct {
162162
Background Color
163163
}
164164

165+
// AddText represents the add-text options.
166+
type AddText struct {
167+
Width int
168+
Height int
169+
DPI int
170+
Top int
171+
Left int
172+
Text string
173+
Font string
174+
Background Color
175+
Opacity float32
176+
}
177+
165178
// WatermarkImage represents the image-based watermark supported options.
166179
type WatermarkImage struct {
167180
Left int
@@ -214,6 +227,7 @@ type Options struct {
214227
Rotate Angle
215228
Background Color
216229
Gravity Gravity
230+
AddText AddText
217231
Watermark Watermark
218232
WatermarkImage WatermarkImage
219233
Type ImageType

resizer.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,12 @@ func resizer(buf []byte, o Options) ([]byte, error) {
113113
return nil, err
114114
}
115115

116+
// Add text, if necessary
117+
image, err = AddTextToImage(image, o.AddText)
118+
if err != nil {
119+
return nil, err
120+
}
121+
116122
// Add watermark, if necessary
117123
image, err = watermarkImageWithAnotherImage(image, o.WatermarkImage)
118124
if err != nil {
@@ -355,6 +361,35 @@ func watermarkImageWithText(image *C.VipsImage, w Watermark) (*C.VipsImage, erro
355361
return image, nil
356362
}
357363

364+
func AddTextToImage(image *C.VipsImage, a AddText) (*C.VipsImage, error) {
365+
if a.Text == "" {
366+
return image, nil
367+
}
368+
369+
// Defaults
370+
if a.Font == "" {
371+
a.Font = WatermarkFont
372+
}
373+
if a.Width == 0 {
374+
a.Width = int(math.Floor(float64(image.Xsize / 6)))
375+
}
376+
if a.DPI == 0 {
377+
a.DPI = 150
378+
}
379+
if a.Opacity == 0 {
380+
a.Opacity = 0.25
381+
} else if a.Opacity > 1 {
382+
a.Opacity = 1
383+
}
384+
385+
image, err := vipsAddText(image, a)
386+
if err != nil {
387+
return nil, err
388+
}
389+
390+
return image, nil
391+
}
392+
358393
func watermarkImageWithAnotherImage(image *C.VipsImage, w WatermarkImage) (*C.VipsImage, error) {
359394

360395
if len(w.Buf) == 0 {

vips.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,16 @@ type vipsWatermarkOptions struct {
6565
Background [3]C.double
6666
}
6767

68+
type vipsAddTextOptions struct {
69+
Width C.int
70+
Height C.int
71+
DPI C.int
72+
Top C.int
73+
Left C.int
74+
Background [3]C.double
75+
Opacity C.float
76+
}
77+
6878
type vipsWatermarkImageOptions struct {
6979
Left C.int
7080
Top C.int
@@ -292,6 +302,27 @@ func vipsWatermark(image *C.VipsImage, w Watermark) (*C.VipsImage, error) {
292302
return out, nil
293303
}
294304

305+
func vipsAddText(image *C.VipsImage, w AddText) (*C.VipsImage, error) {
306+
var out *C.VipsImage
307+
308+
text := C.CString(w.Text)
309+
font := C.CString(w.Font)
310+
background := [3]C.double{C.double(w.Background.R), C.double(w.Background.G), C.double(w.Background.B)}
311+
312+
textOpts := vipsWatermarkTextOptions{text, font}
313+
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)}
314+
315+
defer C.free(unsafe.Pointer(text))
316+
defer C.free(unsafe.Pointer(font))
317+
318+
err := C.vips_add_text(image, &out, (*C.WatermarkTextOptions)(unsafe.Pointer(&textOpts)), (*C.AddTextOptions)(unsafe.Pointer(&opts)))
319+
if err != 0 {
320+
return nil, catchVipsError()
321+
}
322+
323+
return out, nil
324+
}
325+
295326
func vipsRead(buf []byte) (*C.VipsImage, ImageType, error) {
296327
var image *C.VipsImage
297328
imageType := vipsImageType(buf)

vips.h

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,16 @@ typedef struct {
4949
double Background[3];
5050
} WatermarkOptions;
5151

52+
typedef struct {
53+
int Width;
54+
int Height;
55+
int DPI;
56+
int Top;
57+
int Left;
58+
double Background[3];
59+
float Opacity;
60+
} AddTextOptions;
61+
5262
typedef struct {
5363
int Left;
5464
int Top;
@@ -451,6 +461,51 @@ vips_watermark(VipsImage *in, VipsImage **out, WatermarkTextOptions *to, Waterma
451461
return 0;
452462
}
453463

464+
int
465+
vips_add_text(VipsImage *in, VipsImage **out, WatermarkTextOptions *to, AddTextOptions *o) {
466+
double ones[3] = { 1, 1, 1 };
467+
468+
VipsImage *base = vips_image_new();
469+
VipsImage **t = (VipsImage **) vips_object_local_array(VIPS_OBJECT(base), 10);
470+
t[0] = in;
471+
472+
// Make the mask.
473+
if (
474+
vips_text(&t[1], to->Text,
475+
"width", o->Width,
476+
"dpi", o->DPI,
477+
"font", to->Font,
478+
NULL) ||
479+
vips_linear1(t[1], &t[2], o->Opacity, 0.0, NULL) ||
480+
vips_cast(t[2], &t[3], VIPS_FORMAT_UCHAR, NULL) ||
481+
vips_embed(t[3], &t[4], o->Left, o->Top, o->Width, o->Height, NULL)
482+
) {
483+
g_object_unref(base);
484+
return 1;
485+
}
486+
487+
// Make the constant image to paint the text with.
488+
if (
489+
vips_black(&t[5], 1, 1, NULL) ||
490+
vips_linear(t[5], &t[6], ones, o->Background, 3, NULL) ||
491+
vips_cast(t[6], &t[7], VIPS_FORMAT_UCHAR, NULL) ||
492+
vips_copy(t[7], &t[8], "interpretation", t[0]->Type, NULL) ||
493+
vips_embed(t[8], &t[9], 0, 0, t[0]->Xsize, t[0]->Ysize, "extend", VIPS_EXTEND_COPY, NULL)
494+
) {
495+
g_object_unref(base);
496+
return 1;
497+
}
498+
499+
// Blend the mask and text and write to output.
500+
if (vips_ifthenelse(t[4], t[9], t[0], out, "blend", TRUE, NULL)) {
501+
g_object_unref(base);
502+
return 1;
503+
}
504+
505+
g_object_unref(base);
506+
return 0;
507+
}
508+
454509
int
455510
vips_gaussblur_bridge(VipsImage *in, VipsImage **out, double sigma, double min_ampl) {
456511
#if (VIPS_MAJOR_VERSION == 7 && VIPS_MINOR_VERSION < 41)

0 commit comments

Comments
 (0)