Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement AddText function, test and example #305

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions image.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
32 changes: 32 additions & 0 deletions image_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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("testdata/test_add_text_out.jpg", buf)
}

func TestImageWatermarkWithImage(t *testing.T) {
image := initImage("test.jpg")
Expand Down
14 changes: 14 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -214,6 +227,7 @@ type Options struct {
Rotate Angle
Background Color
Gravity Gravity
AddText AddText
Watermark Watermark
WatermarkImage WatermarkImage
Type ImageType
Expand Down
35 changes: 35 additions & 0 deletions resizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
31 changes: 31 additions & 0 deletions vips.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
55 changes: 55 additions & 0 deletions vips.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down