Skip to content
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ If you're using `gopkg.in`, you can still rely in the `v0` without worrying abou
- Format conversion (with additional quality/compression settings)
- EXIF metadata (size, alpha channel, profile, orientation...)
- Trim (libvips 8.6+)
- Composite (libvips 8.6+)

## Prerequisites

Expand Down
7 changes: 7 additions & 0 deletions image.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,13 @@ func (i *Image) Trim() ([]byte, error) {
return i.Process(options)
}

// Composite blends two images together working from the bottom upwards, with
// the blend mode at each step being set by the corresponding BlendMode
func (i *Image) Composite(l *Image, mode BlendMode) ([]byte, error) {
options := Options{Composite: true, BlendMode: mode, CompositeLayers: []*Image{l}}
return i.Process(options)
}

// Process processes the image based on the given transformation options,
// talking with libvips bindings accordingly and returning the resultant
// image buffer.
Expand Down
13 changes: 13 additions & 0 deletions image_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,19 @@ func TestImageTrimParameters(t *testing.T) {
Write("testdata/parameter_trim.png", buf)
}

func TestImageComposite(t *testing.T) {
if !(VipsMajorVersion >= 8 && VipsMinorVersion >= 6) {
t.Skipf("Skipping this test, libvips doesn't meet version requirement %s >= 8.6", VipsVersion)
}

overlay := initImage("transparent.png")
buf, err := initImage("test.jpg").Composite(overlay, BlendModeAdd)
if err != nil {
t.Errorf("Cannot process the image: %#v", err)
}
Write("testdata/test_composite_out.jpg", buf)
}

func TestImageLength(t *testing.T) {
i := initImage("test.jpg")

Expand Down
2 changes: 1 addition & 1 deletion metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package bimg

/*
#cgo pkg-config: vips
#include "vips/vips.h"
#include "vips.h"
*/
import "C"

Expand Down
132 changes: 96 additions & 36 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package bimg

/*
#cgo pkg-config: vips
#include "vips/vips.h"
#include "vips.h"
*/
import "C"

Expand Down Expand Up @@ -139,6 +139,63 @@ const (
ExtendLast Extend = C.VIPS_EXTEND_LAST
)

// BlendMode represents the blend mode used when compositing.
// See: https://jcupitt.github.io/libvips/API/current/libvips-conversion.html#VipsBlendMode
type BlendMode int

const (
// BlendModeClear where the second object is drawn, the first is removed
BlendModeClear BlendMode = C.VIPS_BLEND_MODE_CLEAR
// BlendModeSource the second object is drawn as if nothing were below
BlendModeSource BlendMode = C.VIPS_BLEND_MODE_SOURCE
// BlendModeOver the image shows what you would expect if you held two semi-transparent slides on top of each other
BlendModeOver BlendMode = C.VIPS_BLEND_MODE_OVER
// BlendModeIn the first object is removed completely, the second is only drawn where the first was
BlendModeIn BlendMode = C.VIPS_BLEND_MODE_IN
// BlendModeOut the second is drawn only where the first isn't
BlendModeOut BlendMode = C.VIPS_BLEND_MODE_OUT
// BlendModeAtop this leaves the first object mostly intact, but mixes both objects in the overlapping area
BlendModeAtop BlendMode = C.VIPS_BLEND_MODE_ATOP
// BlendModeDest leaves the first object untouched, the second is discarded completely
BlendModeDest BlendMode = C.VIPS_BLEND_MODE_DEST
// BlendModeDestOver like OVER, but swaps the arguments
BlendModeDestOver BlendMode = C.VIPS_BLEND_MODE_DEST_OVER
// BlendModeDestIn like IN, but swaps the arguments
BlendModeDestIn BlendMode = C.VIPS_BLEND_MODE_DEST_IN
// BlendModeDestOut like OUT, but swaps the arguments
BlendModeDestOut BlendMode = C.VIPS_BLEND_MODE_DEST_OUT
// BlendModeDestAtop like ATOP, but swaps the arguments
BlendModeDestAtop BlendMode = C.VIPS_BLEND_MODE_DEST_ATOP
// BlendModeXOR something like a difference operator
BlendModeXOR BlendMode = C.VIPS_BLEND_MODE_XOR
// BlendModeAdd a bit like adding the two images
BlendModeAdd BlendMode = C.VIPS_BLEND_MODE_ADD
// BlendModeSaturate a bit like the darker of the two
BlendModeSaturate BlendMode = C.VIPS_BLEND_MODE_SATURATE
// BlendModeMultiply at least as dark as the darker of the two inputs
BlendModeMultiply BlendMode = C.VIPS_BLEND_MODE_MULTIPLY
// BlendModeScreen at least as light as the lighter of the inputs
BlendModeScreen BlendMode = C.VIPS_BLEND_MODE_SCREEN
// BlendModeOverlay multiplies or screens colors, depending on the lightness
BlendModeOverlay BlendMode = C.VIPS_BLEND_MODE_OVERLAY
// BlendModeDarken the darker of each component
BlendModeDarken BlendMode = C.VIPS_BLEND_MODE_DARKEN
// BlendModeLighten the lighter of each component
BlendModeLighten BlendMode = C.VIPS_BLEND_MODE_LIGHTEN
// BlendModeColorDodge brighten first by a factor second
BlendModeColorDodge BlendMode = C.VIPS_BLEND_MODE_COLOUR_DODGE
// BlendModeColorBurn darken first by a factor of second
BlendModeColorBurn BlendMode = C.VIPS_BLEND_MODE_COLOUR_BURN
// BlendModeHardLight multiply or screen, depending on lightness
BlendModeHardLight BlendMode = C.VIPS_BLEND_MODE_HARD_LIGHT
// BlendModeSoftLight darken or lighten, depending on lightness
BlendModeSoftLight BlendMode = C.VIPS_BLEND_MODE_SOFT_LIGHT
// BlendModeDifference difference of the two
BlendModeDifference BlendMode = C.VIPS_BLEND_MODE_DIFFERENCE
// BlendModeExclusion somewhat like DIFFERENCE, but lower-contrast
BlendModeExclusion BlendMode = C.VIPS_BLEND_MODE_EXCLUSION
)

// WatermarkFont defines the default watermark font to be used.
var WatermarkFont = "sans 10"

Expand Down Expand Up @@ -188,39 +245,42 @@ type Sharpen struct {

// Options represents the supported image transformation options.
type Options struct {
Height int
Width int
AreaHeight int
AreaWidth int
Top int
Left int
Quality int
Compression int
Zoom int
Crop bool
SmartCrop bool // Deprecated, use: bimg.Options.Gravity = bimg.GravitySmart
Enlarge bool
Embed bool
Flip bool
Flop bool
Force bool
NoAutoRotate bool
NoProfile bool
Interlace bool
StripMetadata bool
Trim bool
Lossless bool
Extend Extend
Rotate Angle
Background Color
Gravity Gravity
Watermark Watermark
WatermarkImage WatermarkImage
Type ImageType
Interpolator Interpolator
Interpretation Interpretation
GaussianBlur GaussianBlur
Sharpen Sharpen
Threshold float64
OutputICC string
Height int
Width int
AreaHeight int
AreaWidth int
Top int
Left int
Quality int
Compression int
Zoom int
Crop bool
SmartCrop bool // Deprecated, use: bimg.Options.Gravity = bimg.GravitySmart
Enlarge bool
Embed bool
Flip bool
Flop bool
Force bool
NoAutoRotate bool
NoProfile bool
Interlace bool
StripMetadata bool
Trim bool
Lossless bool
Composite bool
Extend Extend
Rotate Angle
Background Color
Gravity Gravity
Watermark Watermark
WatermarkImage WatermarkImage
Type ImageType
Interpolator Interpolator
Interpretation Interpretation
GaussianBlur GaussianBlur
Sharpen Sharpen
BlendMode BlendMode
Threshold float64
OutputICC string
CompositeLayers []*Image
}
24 changes: 23 additions & 1 deletion resizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package bimg

/*
#cgo pkg-config: vips
#include "vips/vips.h"
#include "vips.h"
*/
import "C"

Expand Down Expand Up @@ -87,6 +87,11 @@ func resizer(buf []byte, o Options) ([]byte, error) {
return nil, err
}

image, err = compositeImage(image, o.CompositeLayers, o.BlendMode)
if err != nil {
return nil, err
}

// Transform image, if necessary
if shouldTransformImage(o, inWidth, inHeight) {
image, err = transformImage(image, o, shrink, residual)
Expand Down Expand Up @@ -385,6 +390,23 @@ func zoomImage(image *C.VipsImage, zoom int) (*C.VipsImage, error) {
return vipsZoom(image, zoom+1)
}

func compositeImage(image *C.VipsImage, layers []*Image, mode BlendMode) (*C.VipsImage, error) {
if mode == 0 {
return image, nil
}

inputs := make([]*C.VipsImage, 0, len(layers)+1)
inputs = append(inputs, image)
for _, l := range layers {
layer, _, err := loadImage(l.buffer)
if err != nil {
return nil, err
}
inputs = append(inputs, layer)
}
return vipsComposite(inputs, mode)
}

func shrinkImage(image *C.VipsImage, o Options, residual float64, shrink int) (*C.VipsImage, float64, error) {
// Use vips_shrink with the integral reduction
image, err := vipsShrink(image, shrink)
Expand Down
Loading