Skip to content

Commit 001c94d

Browse files
committed
Implement Image.Composite and blend modes
Via [vips_composite][https://jcupitt.github.io/libvips/API/current/libvips-conversion.html#vips-composite]
1 parent 4eb8362 commit 001c94d

File tree

9 files changed

+720
-452
lines changed

9 files changed

+720
-452
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ If you're using `gopkg.in`, you can still rely in the `v0` without worrying abou
4646
- Format conversion (with additional quality/compression settings)
4747
- EXIF metadata (size, alpha channel, profile, orientation...)
4848
- Trim (libvips 8.6+)
49+
- Composite (libvips 8.6+)
4950

5051
## Prerequisites
5152

image.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,13 @@ func (i *Image) Trim() ([]byte, error) {
185185
return i.Process(options)
186186
}
187187

188+
// Composite blends two images together working from the bottom upwards, with
189+
// the blend mode at each step being set by the corresponding BlendMode
190+
func (i *Image) Composite(l *Image, mode BlendMode) ([]byte, error) {
191+
options := Options{Composite: true, BlendMode: mode, CompositeLayers: []*Image{l}}
192+
return i.Process(options)
193+
}
194+
188195
// Process processes the image based on the given transformation options,
189196
// talking with libvips bindings accordingly and returning the resultant
190197
// image buffer.

image_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,19 @@ func TestImageTrimParameters(t *testing.T) {
520520
Write("testdata/parameter_trim.png", buf)
521521
}
522522

523+
func TestImageComposite(t *testing.T) {
524+
if !(VipsMajorVersion >= 8 && VipsMinorVersion >= 6) {
525+
t.Skipf("Skipping this test, libvips doesn't meet version requirement %s >= 8.6", VipsVersion)
526+
}
527+
528+
overlay := initImage("transparent.png")
529+
buf, err := initImage("test.jpg").Composite(overlay, BlendModeAdd)
530+
if err != nil {
531+
t.Errorf("Cannot process the image: %#v", err)
532+
}
533+
Write("testdata/test_composite_out.jpg", buf)
534+
}
535+
523536
func TestImageLength(t *testing.T) {
524537
i := initImage("test.jpg")
525538

metadata.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package bimg
22

33
/*
44
#cgo pkg-config: vips
5-
#include "vips/vips.h"
5+
#include "vips.h"
66
*/
77
import "C"
88

options.go

Lines changed: 96 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package bimg
22

33
/*
44
#cgo pkg-config: vips
5-
#include "vips/vips.h"
5+
#include "vips.h"
66
*/
77
import "C"
88

@@ -139,6 +139,63 @@ const (
139139
ExtendLast Extend = C.VIPS_EXTEND_LAST
140140
)
141141

142+
// BlendMode represents the blend mode used when compositing.
143+
// See: https://jcupitt.github.io/libvips/API/current/libvips-conversion.html#VipsBlendMode
144+
type BlendMode int
145+
146+
const (
147+
// BlendModeClear where the second object is drawn, the first is removed
148+
BlendModeClear BlendMode = C.VIPS_BLEND_MODE_CLEAR
149+
// BlendModeSource the second object is drawn as if nothing were below
150+
BlendModeSource BlendMode = C.VIPS_BLEND_MODE_SOURCE
151+
// BlendModeOver the image shows what you would expect if you held two semi-transparent slides on top of each other
152+
BlendModeOver BlendMode = C.VIPS_BLEND_MODE_OVER
153+
// BlendModeIn the first object is removed completely, the second is only drawn where the first was
154+
BlendModeIn BlendMode = C.VIPS_BLEND_MODE_IN
155+
// BlendModeOut the second is drawn only where the first isn't
156+
BlendModeOut BlendMode = C.VIPS_BLEND_MODE_OUT
157+
// BlendModeAtop this leaves the first object mostly intact, but mixes both objects in the overlapping area
158+
BlendModeAtop BlendMode = C.VIPS_BLEND_MODE_ATOP
159+
// BlendModeDest leaves the first object untouched, the second is discarded completely
160+
BlendModeDest BlendMode = C.VIPS_BLEND_MODE_DEST
161+
// BlendModeDestOver like OVER, but swaps the arguments
162+
BlendModeDestOver BlendMode = C.VIPS_BLEND_MODE_DEST_OVER
163+
// BlendModeDestIn like IN, but swaps the arguments
164+
BlendModeDestIn BlendMode = C.VIPS_BLEND_MODE_DEST_IN
165+
// BlendModeDestOut like OUT, but swaps the arguments
166+
BlendModeDestOut BlendMode = C.VIPS_BLEND_MODE_DEST_OUT
167+
// BlendModeDestAtop like ATOP, but swaps the arguments
168+
BlendModeDestAtop BlendMode = C.VIPS_BLEND_MODE_DEST_ATOP
169+
// BlendModeXOR something like a difference operator
170+
BlendModeXOR BlendMode = C.VIPS_BLEND_MODE_XOR
171+
// BlendModeAdd a bit like adding the two images
172+
BlendModeAdd BlendMode = C.VIPS_BLEND_MODE_ADD
173+
// BlendModeSaturate a bit like the darker of the two
174+
BlendModeSaturate BlendMode = C.VIPS_BLEND_MODE_SATURATE
175+
// BlendModeMultiply at least as dark as the darker of the two inputs
176+
BlendModeMultiply BlendMode = C.VIPS_BLEND_MODE_MULTIPLY
177+
// BlendModeScreen at least as light as the lighter of the inputs
178+
BlendModeScreen BlendMode = C.VIPS_BLEND_MODE_SCREEN
179+
// BlendModeOverlay multiplies or screens colors, depending on the lightness
180+
BlendModeOverlay BlendMode = C.VIPS_BLEND_MODE_OVERLAY
181+
// BlendModeDarken the darker of each component
182+
BlendModeDarken BlendMode = C.VIPS_BLEND_MODE_DARKEN
183+
// BlendModeLighten the lighter of each component
184+
BlendModeLighten BlendMode = C.VIPS_BLEND_MODE_LIGHTEN
185+
// BlendModeColorDodge brighten first by a factor second
186+
BlendModeColorDodge BlendMode = C.VIPS_BLEND_MODE_COLOUR_DODGE
187+
// BlendModeColorBurn darken first by a factor of second
188+
BlendModeColorBurn BlendMode = C.VIPS_BLEND_MODE_COLOUR_BURN
189+
// BlendModeHardLight multiply or screen, depending on lightness
190+
BlendModeHardLight BlendMode = C.VIPS_BLEND_MODE_HARD_LIGHT
191+
// BlendModeSoftLight darken or lighten, depending on lightness
192+
BlendModeSoftLight BlendMode = C.VIPS_BLEND_MODE_SOFT_LIGHT
193+
// BlendModeDifference difference of the two
194+
BlendModeDifference BlendMode = C.VIPS_BLEND_MODE_DIFFERENCE
195+
// BlendModeExclusion somewhat like DIFFERENCE, but lower-contrast
196+
BlendModeExclusion BlendMode = C.VIPS_BLEND_MODE_EXCLUSION
197+
)
198+
142199
// WatermarkFont defines the default watermark font to be used.
143200
var WatermarkFont = "sans 10"
144201

@@ -188,39 +245,42 @@ type Sharpen struct {
188245

189246
// Options represents the supported image transformation options.
190247
type Options struct {
191-
Height int
192-
Width int
193-
AreaHeight int
194-
AreaWidth int
195-
Top int
196-
Left int
197-
Quality int
198-
Compression int
199-
Zoom int
200-
Crop bool
201-
SmartCrop bool // Deprecated, use: bimg.Options.Gravity = bimg.GravitySmart
202-
Enlarge bool
203-
Embed bool
204-
Flip bool
205-
Flop bool
206-
Force bool
207-
NoAutoRotate bool
208-
NoProfile bool
209-
Interlace bool
210-
StripMetadata bool
211-
Trim bool
212-
Lossless bool
213-
Extend Extend
214-
Rotate Angle
215-
Background Color
216-
Gravity Gravity
217-
Watermark Watermark
218-
WatermarkImage WatermarkImage
219-
Type ImageType
220-
Interpolator Interpolator
221-
Interpretation Interpretation
222-
GaussianBlur GaussianBlur
223-
Sharpen Sharpen
224-
Threshold float64
225-
OutputICC string
248+
Height int
249+
Width int
250+
AreaHeight int
251+
AreaWidth int
252+
Top int
253+
Left int
254+
Quality int
255+
Compression int
256+
Zoom int
257+
Crop bool
258+
SmartCrop bool // Deprecated, use: bimg.Options.Gravity = bimg.GravitySmart
259+
Enlarge bool
260+
Embed bool
261+
Flip bool
262+
Flop bool
263+
Force bool
264+
NoAutoRotate bool
265+
NoProfile bool
266+
Interlace bool
267+
StripMetadata bool
268+
Trim bool
269+
Lossless bool
270+
Composite bool
271+
Extend Extend
272+
Rotate Angle
273+
Background Color
274+
Gravity Gravity
275+
Watermark Watermark
276+
WatermarkImage WatermarkImage
277+
Type ImageType
278+
Interpolator Interpolator
279+
Interpretation Interpretation
280+
GaussianBlur GaussianBlur
281+
Sharpen Sharpen
282+
BlendMode BlendMode
283+
Threshold float64
284+
OutputICC string
285+
CompositeLayers []*Image
226286
}

resizer.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package bimg
22

33
/*
44
#cgo pkg-config: vips
5-
#include "vips/vips.h"
5+
#include "vips.h"
66
*/
77
import "C"
88

@@ -87,6 +87,11 @@ func resizer(buf []byte, o Options) ([]byte, error) {
8787
return nil, err
8888
}
8989

90+
image, err = compositeImage(image, o.CompositeLayers, o.BlendMode)
91+
if err != nil {
92+
return nil, err
93+
}
94+
9095
// Transform image, if necessary
9196
if shouldTransformImage(o, inWidth, inHeight) {
9297
image, err = transformImage(image, o, shrink, residual)
@@ -385,6 +390,23 @@ func zoomImage(image *C.VipsImage, zoom int) (*C.VipsImage, error) {
385390
return vipsZoom(image, zoom+1)
386391
}
387392

393+
func compositeImage(image *C.VipsImage, layers []*Image, mode BlendMode) (*C.VipsImage, error) {
394+
if mode == 0 {
395+
return image, nil
396+
}
397+
398+
inputs := make([]*C.VipsImage, 0, len(layers)+1)
399+
inputs = append(inputs, image)
400+
for _, l := range layers {
401+
layer, _, err := loadImage(l.buffer)
402+
if err != nil {
403+
return nil, err
404+
}
405+
inputs = append(inputs, layer)
406+
}
407+
return vipsComposite(inputs, mode)
408+
}
409+
388410
func shrinkImage(image *C.VipsImage, o Options, residual float64, shrink int) (*C.VipsImage, float64, error) {
389411
// Use vips_shrink with the integral reduction
390412
image, err := vipsShrink(image, shrink)

0 commit comments

Comments
 (0)