Skip to content

Commit 165977e

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 165977e

File tree

6 files changed

+149
-35
lines changed

6 files changed

+149
-35
lines changed

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: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,15 @@ func TestImageTrimParameters(t *testing.T) {
520520
Write("testdata/parameter_trim.png", buf)
521521
}
522522

523+
func TestImageComposite(t *testing.T) {
524+
overlay := initImage("transparent.png")
525+
buf, err := initImage("test.jpg").Composite(overlay, BlendModeAdd)
526+
if err != nil {
527+
t.Errorf("Cannot process the image: %#v", err)
528+
}
529+
Write("testdata/test_composite_out.jpg", buf)
530+
}
531+
523532
func TestImageLength(t *testing.T) {
524533
i := initImage("test.jpg")
525534

options.go

Lines changed: 95 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -188,39 +188,99 @@ type Sharpen struct {
188188

189189
// Options represents the supported image transformation options.
190190
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
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+
Composite bool
214+
Extend Extend
215+
Rotate Angle
216+
Background Color
217+
Gravity Gravity
218+
Watermark Watermark
219+
WatermarkImage WatermarkImage
220+
Type ImageType
221+
Interpolator Interpolator
222+
Interpretation Interpretation
223+
GaussianBlur GaussianBlur
224+
Sharpen Sharpen
225+
BlendMode BlendMode
226+
Threshold float64
227+
OutputICC string
228+
CompositeLayers []*Image
226229
}
230+
231+
// BlendMode represents the blend mode used when compositing.
232+
// See: https://jcupitt.github.io/libvips/API/current/libvips-conversion.html#VipsBlendMode
233+
type BlendMode int
234+
235+
const (
236+
// BlendModeClear where the second object is drawn, the first is removed
237+
BlendModeClear BlendMode = C.VIPS_BLEND_MODE_CLEAR
238+
// BlendModeSource the second object is drawn as if nothing were below
239+
BlendModeSource BlendMode = C.VIPS_BLEND_MODE_SOURCE
240+
// BlendModeOver the image shows what you would expect if you held two semi-transparent slides on top of each other
241+
BlendModeOver BlendMode = C.VIPS_BLEND_MODE_OVER
242+
// BlendModeIn the first object is removed completely, the second is only drawn where the first was
243+
BlendModeIn BlendMode = C.VIPS_BLEND_MODE_IN
244+
// BlendModeOut the second is drawn only where the first isn't
245+
BlendModeOut BlendMode = C.VIPS_BLEND_MODE_OUT
246+
// BlendModeAtop this leaves the first object mostly intact, but mixes both objects in the overlapping area
247+
BlendModeAtop BlendMode = C.VIPS_BLEND_MODE_ATOP
248+
// BlendModeDest leaves the first object untouched, the second is discarded completely
249+
BlendModeDest BlendMode = C.VIPS_BLEND_MODE_DEST
250+
// BlendModeDestOver like OVER, but swaps the arguments
251+
BlendModeDestOver BlendMode = C.VIPS_BLEND_MODE_DEST_OVER
252+
// BlendModeDestIn like IN, but swaps the arguments
253+
BlendModeDestIn BlendMode = C.VIPS_BLEND_MODE_DEST_IN
254+
// BlendModeDestOut like OUT, but swaps the arguments
255+
BlendModeDestOut BlendMode = C.VIPS_BLEND_MODE_DEST_OUT
256+
// BlendModeDestAtop like ATOP, but swaps the arguments
257+
BlendModeDestAtop BlendMode = C.VIPS_BLEND_MODE_DEST_ATOP
258+
// BlendModeXOR something like a difference operator
259+
BlendModeXOR BlendMode = C.VIPS_BLEND_MODE_XOR
260+
// BlendModeAdd a bit like adding the two images
261+
BlendModeAdd BlendMode = C.VIPS_BLEND_MODE_ADD
262+
// BlendModeSaturate a bit like the darker of the two
263+
BlendModeSaturate BlendMode = C.VIPS_BLEND_MODE_SATURATE
264+
// BlendModeMultiply at least as dark as the darker of the two inputs
265+
BlendModeMultiply BlendMode = C.VIPS_BLEND_MODE_MULTIPLY
266+
// BlendModeScreen at least as light as the lighter of the inputs
267+
BlendModeScreen BlendMode = C.VIPS_BLEND_MODE_SCREEN
268+
// BlendModeOverlay multiplies or screens colors, depending on the lightness
269+
BlendModeOverlay BlendMode = C.VIPS_BLEND_MODE_OVERLAY
270+
// BlendModeDarken the darker of each component
271+
BlendModeDarken BlendMode = C.VIPS_BLEND_MODE_DARKEN
272+
// BlendModeLighten the lighter of each component
273+
BlendModeLighten BlendMode = C.VIPS_BLEND_MODE_LIGHTEN
274+
// BlendModeColorDodge brighten first by a factor second
275+
BlendModeColorDodge BlendMode = C.VIPS_BLEND_MODE_COLOUR_DODGE
276+
// BlendModeColorBurn darken first by a factor of second
277+
BlendModeColorBurn BlendMode = C.VIPS_BLEND_MODE_COLOUR_BURN
278+
// BlendModeHardLight multiply or screen, depending on lightness
279+
BlendModeHardLight BlendMode = C.VIPS_BLEND_MODE_HARD_LIGHT
280+
// BlendModeSoftLight darken or lighten, depending on lightness
281+
BlendModeSoftLight BlendMode = C.VIPS_BLEND_MODE_SOFT_LIGHT
282+
// BlendModeDifference difference of the two
283+
BlendModeDifference BlendMode = C.VIPS_BLEND_MODE_DIFFERENCE
284+
// BlendModeExclusion somewhat like DIFFERENCE, but lower-contrast
285+
BlendModeExclusion BlendMode = C.VIPS_BLEND_MODE_EXCLUSION
286+
)

resizer.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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)

vips.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,17 @@ func vipsReduce(input *C.VipsImage, xshrink float64, yshrink float64) (*C.VipsIm
569569
return image, nil
570570
}
571571

572+
func vipsComposite(inputs []*C.VipsImage, mode BlendMode) (*C.VipsImage, error) {
573+
var image *C.VipsImage
574+
575+
err := C.vips_composite_bridge(&inputs[0], &image, C.int(len(inputs)), C.int(mode))
576+
if err != 0 {
577+
return nil, catchVipsError()
578+
}
579+
580+
return image, nil
581+
}
582+
572583
func vipsEmbed(input *C.VipsImage, left, top, width, height int, extend Extend, background Color) (*C.VipsImage, error) {
573584
var image *C.VipsImage
574585

vips.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,11 @@ vips_zoom_bridge(VipsImage *in, VipsImage **out, int xfac, int yfac) {
238238
return vips_zoom(in, out, xfac, yfac, NULL);
239239
}
240240

241+
int
242+
vips_composite_bridge(VipsImage **in, VipsImage **out, int n, int mode) {
243+
return vips_composite(in, out, n, &mode, NULL);
244+
}
245+
241246
int
242247
vips_embed_bridge(VipsImage *in, VipsImage **out, int left, int top, int width, int height, int extend, double r, double g, double b) {
243248
if (extend == VIPS_EXTEND_BACKGROUND) {

0 commit comments

Comments
 (0)