mirror of
https://github.com/esimov/caire.git
synced 2026-04-22 15:58:14 +08:00
feat: extended imop package with new features, test cases
This commit is contained in:
+155
-21
@@ -11,40 +11,174 @@
|
||||
package imop
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"github.com/esimov/caire/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
Darken = "darken"
|
||||
Lighten = "lighten"
|
||||
Multiply = "multiply"
|
||||
Screen = "screen"
|
||||
Overlay = "overlay"
|
||||
Normal = "normal"
|
||||
Darken = "darken"
|
||||
Lighten = "lighten"
|
||||
Multiply = "multiply"
|
||||
Screen = "screen"
|
||||
Overlay = "overlay"
|
||||
SoftLight = "soft_light"
|
||||
HardLight = "hard_light"
|
||||
ColorDodge = "color_dodge"
|
||||
ColorBurn = "color_burn"
|
||||
Difference = "difference"
|
||||
Exclusion = "exclusion"
|
||||
|
||||
// Non-separable blend modes
|
||||
Hue = "hue"
|
||||
Saturation = "saturation"
|
||||
ColorMode = "color"
|
||||
Luminosity = "luminosity"
|
||||
)
|
||||
|
||||
// Blend holds the currently active blend mode.
|
||||
// Blend struct contains the currently active blend mode and all the supported blend modes.
|
||||
type Blend struct {
|
||||
OpType string
|
||||
Current string
|
||||
Modes []string
|
||||
}
|
||||
|
||||
// Color represents the RGB channel of a specific color.
|
||||
type Color struct {
|
||||
R, G, B float64
|
||||
}
|
||||
|
||||
// NewBlend initializes a new Blend.
|
||||
func NewBlend() *Blend {
|
||||
return &Blend{}
|
||||
}
|
||||
|
||||
// Set activate one of the supported blend mode.
|
||||
func (o *Blend) Set(opType string) {
|
||||
bModes := []string{Darken, Lighten, Multiply, Screen, Overlay}
|
||||
|
||||
if utils.Contains(bModes, opType) {
|
||||
o.OpType = opType
|
||||
return &Blend{
|
||||
Modes: []string{
|
||||
Normal,
|
||||
Darken,
|
||||
Lighten,
|
||||
Multiply,
|
||||
Screen,
|
||||
Overlay,
|
||||
SoftLight,
|
||||
HardLight,
|
||||
ColorDodge,
|
||||
ColorBurn,
|
||||
Difference,
|
||||
Exclusion,
|
||||
Hue,
|
||||
Saturation,
|
||||
ColorMode,
|
||||
Luminosity,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the currently active blend mode.
|
||||
func (o *Blend) Get() string {
|
||||
if len(o.OpType) > 0 {
|
||||
return o.OpType
|
||||
// Set activate one of the supported blend modes.
|
||||
func (bl *Blend) Set(blendType string) error {
|
||||
if utils.Contains(bl.Modes, blendType) {
|
||||
bl.Current = blendType
|
||||
return nil
|
||||
}
|
||||
return ""
|
||||
return fmt.Errorf("unsupported blend mode")
|
||||
}
|
||||
|
||||
// Get returns the active blend mode.
|
||||
func (bl *Blend) Get() string {
|
||||
return bl.Current
|
||||
}
|
||||
|
||||
// Lum gets the luminosity of a color.
|
||||
func (bl *Blend) Lum(rgb Color) float64 {
|
||||
return 0.3*rgb.R + 0.59*rgb.G + 0.11*rgb.B
|
||||
}
|
||||
|
||||
// SetLum set the luminosity on a color.
|
||||
func (bl *Blend) SetLum(rgb Color, l float64) Color {
|
||||
delta := l - bl.Lum(rgb)
|
||||
return bl.clip(Color{
|
||||
rgb.R + delta,
|
||||
rgb.G + delta,
|
||||
rgb.B + delta,
|
||||
})
|
||||
}
|
||||
|
||||
// clip clips the channels of a color between certain min and max values.
|
||||
func (bl *Blend) clip(rgb Color) Color {
|
||||
r, g, b := rgb.R, rgb.G, rgb.B
|
||||
|
||||
l := bl.Lum(rgb)
|
||||
min := utils.Min(r, g, b)
|
||||
max := utils.Max(r, g, b)
|
||||
|
||||
if min < 0 {
|
||||
r = l + (((r - l) * l) / (l - min))
|
||||
g = l + (((g - l) * l) / (l - min))
|
||||
b = l + (((b - l) * l) / (l - min))
|
||||
}
|
||||
if max > 1 {
|
||||
r = l + (((r - l) * (1 - l)) / (max - l))
|
||||
g = l + (((g - l) * (1 - l)) / (max - l))
|
||||
b = l + (((b - l) * (1 - l)) / (max - l))
|
||||
}
|
||||
|
||||
return Color{R: r, G: g, B: b}
|
||||
}
|
||||
|
||||
// Sat gets the saturation of a color.
|
||||
func (bl *Blend) Sat(rgb Color) float64 {
|
||||
return utils.Max(rgb.R, rgb.G, rgb.B) - utils.Min(rgb.R, rgb.G, rgb.B)
|
||||
}
|
||||
|
||||
// channel is a key/value struct pair used for sorting the color channels
|
||||
// based on the color components having the minimum, middle, and maximum
|
||||
// values upon entry to the function.
|
||||
// The key component holds the channel name and val is the value it has.
|
||||
type channel struct {
|
||||
key string
|
||||
val float64
|
||||
}
|
||||
|
||||
func (bl *Blend) SetSat(rgb Color, s float64) Color {
|
||||
color := map[string]float64{
|
||||
"R": rgb.R,
|
||||
"G": rgb.G,
|
||||
"B": rgb.B,
|
||||
}
|
||||
channels := make([]channel, 0, 3)
|
||||
for k, v := range color {
|
||||
channels = append(channels, channel{k, v})
|
||||
}
|
||||
// Sort the color channels based on their values.
|
||||
sort.Slice(channels, func(i, j int) bool { return channels[i].val < channels[j].val })
|
||||
minChan, midChan, maxChan := channels[0].key, channels[1].key, channels[2].key
|
||||
|
||||
if color[maxChan] > color[minChan] {
|
||||
color[midChan] = (((color[midChan] - color[minChan]) * s) / (color[maxChan] - color[minChan]))
|
||||
color[maxChan] = s
|
||||
} else {
|
||||
color[midChan], color[maxChan] = 0, 0
|
||||
}
|
||||
color[minChan] = 0
|
||||
|
||||
return Color{
|
||||
R: color["R"],
|
||||
G: color["G"],
|
||||
B: color["B"],
|
||||
}
|
||||
}
|
||||
|
||||
// Applies the alpha blending formula for a blend operation.
|
||||
// See: https://www.w3.org/TR/compositing-1/#blending
|
||||
func (bl *Blend) AlphaCompose(
|
||||
backdropAlpha,
|
||||
sourceAlpha,
|
||||
compositeAlpha,
|
||||
backdropColor,
|
||||
sourceColor,
|
||||
compositeColor float64,
|
||||
) float64 {
|
||||
return ((1 - sourceAlpha/compositeAlpha) * backdropColor) +
|
||||
(sourceAlpha / compositeAlpha *
|
||||
math.Round((1-backdropAlpha)*sourceColor+backdropAlpha*compositeColor))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
package imop
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestBlend_Basic(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
op := NewBlend()
|
||||
assert.Empty(op.Get())
|
||||
err := op.Set("blend_mode_not_supported")
|
||||
assert.Error(err)
|
||||
op.Set(Darken)
|
||||
assert.Equal(Darken, op.Get())
|
||||
op.Set(Lighten)
|
||||
assert.Equal(Lighten, op.Get())
|
||||
|
||||
rgb := Color{R: 0xff, G: 0xff, B: 0xff}
|
||||
lum := op.Lum(rgb)
|
||||
assert.Equal(255.0, lum)
|
||||
|
||||
rgb = Color{R: 0, G: 0, B: 0}
|
||||
lum = op.Lum(rgb)
|
||||
assert.Equal(0.0, lum)
|
||||
|
||||
rgb = Color{R: 127, G: 127, B: 127}
|
||||
lum = op.Lum(rgb)
|
||||
assert.Equal(127.0, lum)
|
||||
|
||||
foreground := Color{R: 0xff, G: 0xff, B: 0xff}
|
||||
background := Color{R: 0, G: 0, B: 0}
|
||||
|
||||
assert.Equal(0.0, op.Sat(foreground))
|
||||
sat := op.SetSat(background, op.Sat(foreground))
|
||||
assert.Equal(Color{R: 0, G: 0, B: 0}, sat)
|
||||
}
|
||||
|
||||
func TestBlend_Modes(t *testing.T) {
|
||||
// Note: all the expected values are taken by using as reference the results
|
||||
// obtained in Photoshop by overlapping two layers and applying the blend mode.
|
||||
assert := assert.New(t)
|
||||
|
||||
op := InitOp()
|
||||
blend := NewBlend()
|
||||
|
||||
pinkFront := color.RGBA{R: 214, G: 20, B: 65, A: 255}
|
||||
orangeBack := color.RGBA{R: 250, G: 121, B: 17, A: 255}
|
||||
|
||||
rect := image.Rect(0, 0, 1, 1)
|
||||
bmp := NewBitmap(rect)
|
||||
source := image.NewNRGBA(rect)
|
||||
backdrop := image.NewNRGBA(rect)
|
||||
|
||||
op.Set(SrcOver)
|
||||
|
||||
// Darken
|
||||
blend.Set(Darken)
|
||||
draw.Draw(source, rect, &image.Uniform{pinkFront}, image.Point{}, draw.Src)
|
||||
draw.Draw(backdrop, rect, &image.Uniform{orangeBack}, image.Point{}, draw.Src)
|
||||
op.Draw(bmp, source, backdrop, blend)
|
||||
|
||||
expected := []uint8{214, 20, 17, 255}
|
||||
assert.EqualValues(expected, bmp.Img.Pix)
|
||||
|
||||
// Multiply
|
||||
blend.Set(Multiply)
|
||||
draw.Draw(source, rect, &image.Uniform{pinkFront}, image.Point{}, draw.Src)
|
||||
draw.Draw(backdrop, rect, &image.Uniform{orangeBack}, image.Point{}, draw.Src)
|
||||
op.Draw(bmp, source, backdrop, blend)
|
||||
|
||||
expected = []uint8{209, 9, 4, 255}
|
||||
assert.EqualValues(expected, bmp.Img.Pix)
|
||||
|
||||
// Screen
|
||||
blend.Set(Screen)
|
||||
draw.Draw(source, rect, &image.Uniform{pinkFront}, image.Point{}, draw.Src)
|
||||
draw.Draw(backdrop, rect, &image.Uniform{orangeBack}, image.Point{}, draw.Src)
|
||||
op.Draw(bmp, source, backdrop, blend)
|
||||
|
||||
expected = []uint8{254, 131, 77, 255}
|
||||
assert.EqualValues(expected, bmp.Img.Pix)
|
||||
|
||||
// Overlay
|
||||
blend.Set(Overlay)
|
||||
draw.Draw(source, rect, &image.Uniform{pinkFront}, image.Point{}, draw.Src)
|
||||
draw.Draw(backdrop, rect, &image.Uniform{orangeBack}, image.Point{}, draw.Src)
|
||||
op.Draw(bmp, source, backdrop, blend)
|
||||
|
||||
expected = []uint8{253, 18, 8, 255}
|
||||
assert.EqualValues(expected, bmp.Img.Pix)
|
||||
|
||||
// SoftLight
|
||||
blend.Set(SoftLight)
|
||||
draw.Draw(source, rect, &image.Uniform{pinkFront}, image.Point{}, draw.Src)
|
||||
draw.Draw(backdrop, rect, &image.Uniform{orangeBack}, image.Point{}, draw.Src)
|
||||
op.Draw(bmp, source, backdrop, blend)
|
||||
|
||||
expected = []uint8{232, 19, 23, 255}
|
||||
assert.EqualValues(expected, bmp.Img.Pix)
|
||||
|
||||
// HardLight
|
||||
blend.Set(HardLight)
|
||||
draw.Draw(source, rect, &image.Uniform{pinkFront}, image.Point{}, draw.Src)
|
||||
draw.Draw(backdrop, rect, &image.Uniform{orangeBack}, image.Point{}, draw.Src)
|
||||
op.Draw(bmp, source, backdrop, blend)
|
||||
|
||||
expected = []uint8{251, 67, 9, 255}
|
||||
assert.EqualValues(expected, bmp.Img.Pix)
|
||||
|
||||
// ColorDodge
|
||||
blend.Set(ColorDodge)
|
||||
draw.Draw(source, rect, &image.Uniform{pinkFront}, image.Point{}, draw.Src)
|
||||
draw.Draw(backdrop, rect, &image.Uniform{orangeBack}, image.Point{}, draw.Src)
|
||||
op.Draw(bmp, source, backdrop, blend)
|
||||
|
||||
expected = []uint8{255, 131, 22, 255}
|
||||
assert.EqualValues(expected, bmp.Img.Pix)
|
||||
|
||||
// ColorBurn
|
||||
blend.Set(ColorBurn)
|
||||
draw.Draw(source, rect, &image.Uniform{pinkFront}, image.Point{}, draw.Src)
|
||||
draw.Draw(backdrop, rect, &image.Uniform{orangeBack}, image.Point{}, draw.Src)
|
||||
op.Draw(bmp, source, backdrop, blend)
|
||||
|
||||
expected = []uint8{249, 0, 0, 255}
|
||||
assert.EqualValues(expected, bmp.Img.Pix)
|
||||
|
||||
// Difference
|
||||
blend.Set(Difference)
|
||||
draw.Draw(source, rect, &image.Uniform{pinkFront}, image.Point{}, draw.Src)
|
||||
draw.Draw(backdrop, rect, &image.Uniform{orangeBack}, image.Point{}, draw.Src)
|
||||
op.Draw(bmp, source, backdrop, blend)
|
||||
|
||||
expected = []uint8{35, 101, 48, 255}
|
||||
assert.EqualValues(expected, bmp.Img.Pix)
|
||||
|
||||
// Exclusion
|
||||
blend.Set(Exclusion)
|
||||
draw.Draw(source, rect, &image.Uniform{pinkFront}, image.Point{}, draw.Src)
|
||||
draw.Draw(backdrop, rect, &image.Uniform{orangeBack}, image.Point{}, draw.Src)
|
||||
op.Draw(bmp, source, backdrop, blend)
|
||||
|
||||
expected = []uint8{44, 122, 73, 255}
|
||||
assert.EqualValues(expected, bmp.Img.Pix)
|
||||
}
|
||||
|
||||
func TestBlend_NonSeparableModes(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
op := InitOp()
|
||||
blend := NewBlend()
|
||||
|
||||
frontColor := color.RGBA{R: 250, G: 121, B: 17, A: 255}
|
||||
backColor := color.RGBA{R: 214, G: 20, B: 65, A: 255}
|
||||
|
||||
rect := image.Rect(0, 0, 1, 1)
|
||||
bmp := NewBitmap(rect)
|
||||
source := image.NewNRGBA(rect)
|
||||
backdrop := image.NewNRGBA(rect)
|
||||
|
||||
op.Set(SrcOver)
|
||||
|
||||
// Hue
|
||||
blend.Set(Hue)
|
||||
draw.Draw(source, rect, &image.Uniform{frontColor}, image.Point{}, draw.Src)
|
||||
draw.Draw(backdrop, rect, &image.Uniform{backColor}, image.Point{}, draw.Src)
|
||||
op.Draw(bmp, source, backdrop, blend)
|
||||
|
||||
expected := []uint8{255, 97, 133, 255}
|
||||
assert.EqualValues(expected, bmp.Img.Pix)
|
||||
|
||||
// Saturation
|
||||
blend.Set(Saturation)
|
||||
draw.Draw(source, rect, &image.Uniform{frontColor}, image.Point{}, draw.Src)
|
||||
draw.Draw(backdrop, rect, &image.Uniform{backColor}, image.Point{}, draw.Src)
|
||||
op.Draw(bmp, source, backdrop, blend)
|
||||
|
||||
expected = []uint8{233, 126, 39, 255}
|
||||
assert.EqualValues(expected, bmp.Img.Pix)
|
||||
|
||||
// Color
|
||||
blend.Set(ColorMode)
|
||||
draw.Draw(source, rect, &image.Uniform{frontColor}, image.Point{}, draw.Src)
|
||||
draw.Draw(backdrop, rect, &image.Uniform{backColor}, image.Point{}, draw.Src)
|
||||
op.Draw(bmp, source, backdrop, blend)
|
||||
|
||||
expected = []uint8{255, 97, 133, 255}
|
||||
assert.EqualValues(expected, bmp.Img.Pix)
|
||||
|
||||
// Luminosity
|
||||
blend.Set(Luminosity)
|
||||
draw.Draw(source, rect, &image.Uniform{frontColor}, image.Point{}, draw.Src)
|
||||
draw.Draw(backdrop, rect, &image.Uniform{backColor}, image.Point{}, draw.Src)
|
||||
op.Draw(bmp, source, backdrop, blend)
|
||||
|
||||
expected = []uint8{148, 66, 0, 255}
|
||||
assert.EqualValues(expected, bmp.Img.Pix)
|
||||
}
|
||||
+387
-179
@@ -3,16 +3,13 @@
|
||||
// Porter and Duff presented in their paper 12 different composition operation, but the
|
||||
// core image/draw core package implements only the source-over-destination and source.
|
||||
// This package implements all of the 12 composite operation together with some blending modes.
|
||||
|
||||
// This package is used mainly to debug the seam carving operation
|
||||
// when the face detection option and the image mask is enabled.
|
||||
// When the GUI mode and the debugging option is activated it will show
|
||||
// the image mask and the detected faces in a distinct color.
|
||||
package imop
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"math"
|
||||
|
||||
"github.com/esimov/caire/utils"
|
||||
)
|
||||
@@ -20,7 +17,7 @@ import (
|
||||
const (
|
||||
Clear = "clear"
|
||||
Copy = "copy"
|
||||
Dest = "dst"
|
||||
Dst = "dst"
|
||||
SrcOver = "src_over"
|
||||
DstOver = "dst_over"
|
||||
SrcIn = "src_in"
|
||||
@@ -38,10 +35,10 @@ type Bitmap struct {
|
||||
Img *image.NRGBA
|
||||
}
|
||||
|
||||
// Composite defines a struct with the active and all the supported composition operations.
|
||||
// Composite struct contains the currently active composition operation and all the supported operations.
|
||||
type Composite struct {
|
||||
currentOp string
|
||||
ops []string
|
||||
CurrentOp string
|
||||
Ops []string
|
||||
}
|
||||
|
||||
// NewBitmap initializes a new Bitmap.
|
||||
@@ -54,9 +51,11 @@ func NewBitmap(rect image.Rectangle) *Bitmap {
|
||||
// InitOp initializes a new composition operation.
|
||||
func InitOp() *Composite {
|
||||
return &Composite{
|
||||
currentOp: Copy,
|
||||
ops: []string{
|
||||
CurrentOp: SrcOver,
|
||||
Ops: []string{
|
||||
Clear,
|
||||
Copy,
|
||||
Dst,
|
||||
SrcOver,
|
||||
DstOver,
|
||||
SrcIn,
|
||||
@@ -71,191 +70,400 @@ func InitOp() *Composite {
|
||||
}
|
||||
|
||||
// Set changes the current composition operation.
|
||||
func (op *Composite) Set(cop string) {
|
||||
op.currentOp = cop
|
||||
func (op *Composite) Set(cop string) error {
|
||||
if utils.Contains(op.Ops, cop) {
|
||||
op.CurrentOp = cop
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unsupported composition operation")
|
||||
}
|
||||
|
||||
// Set changes the current composition operation.
|
||||
func (op *Composite) Get() string {
|
||||
return op.CurrentOp
|
||||
}
|
||||
|
||||
// Draw applies the currently active Ported-Duff composition operation formula,
|
||||
// taking as parameter the source and the destination image and draws the result into the bitmap.
|
||||
// If a blend mode is activated it will plug in the alpha blending formula also into the equation.
|
||||
func (op *Composite) Draw(bitmap *Bitmap, src, dst *image.NRGBA, blend *Blend) {
|
||||
func (op *Composite) Draw(bitmap *Bitmap, src, dst *image.NRGBA, bl *Blend) {
|
||||
dx, dy := src.Bounds().Dx(), src.Bounds().Dy()
|
||||
if bitmap == nil {
|
||||
bitmap = NewBitmap(src.Bounds())
|
||||
}
|
||||
|
||||
var (
|
||||
r, g, b, a uint32
|
||||
rn, gn, bn, an float64
|
||||
)
|
||||
|
||||
if utils.Contains(op.ops, op.currentOp) {
|
||||
for x := 0; x < dx; x++ {
|
||||
for y := 0; y < dy; y++ {
|
||||
r1, g1, b1, a1 := src.At(x, y).RGBA()
|
||||
r2, g2, b2, a2 := dst.At(x, y).RGBA()
|
||||
for x := 0; x < dx; x++ {
|
||||
for y := 0; y < dy; y++ {
|
||||
r1, g1, b1, a1 := src.At(x, y).RGBA()
|
||||
r2, g2, b2, a2 := dst.At(x, y).RGBA()
|
||||
|
||||
rs, gs, bs, as := r1>>8, g1>>8, b1>>8, a1>>8
|
||||
rb, gb, bb, ab := r2>>8, g2>>8, b2>>8, a2>>8
|
||||
rs, gs, bs, as := r1>>8, g1>>8, b1>>8, a1>>8
|
||||
rb, gb, bb, ab := r2>>8, g2>>8, b2>>8, a2>>8
|
||||
|
||||
// normalize the values.
|
||||
rsn := float64(rs) / 255
|
||||
gsn := float64(gs) / 255
|
||||
bsn := float64(bs) / 255
|
||||
asn := float64(as) / 255
|
||||
// normalize the values.
|
||||
rsn := float64(rs) / 255
|
||||
gsn := float64(gs) / 255
|
||||
bsn := float64(bs) / 255
|
||||
asn := float64(as) / 255
|
||||
|
||||
rbn := float64(rb) / 255
|
||||
gbn := float64(gb) / 255
|
||||
bbn := float64(bb) / 255
|
||||
abn := float64(ab) / 255
|
||||
rbn := float64(rb) / 255
|
||||
gbn := float64(gb) / 255
|
||||
bbn := float64(bb) / 255
|
||||
abn := float64(ab) / 255
|
||||
|
||||
// applying the alpha composition formula
|
||||
switch op.currentOp {
|
||||
case Clear:
|
||||
rn, gn, bn, an = 0, 0, 0, 0
|
||||
case Copy:
|
||||
rn = asn * rsn
|
||||
gn = asn * gsn
|
||||
bn = asn * bsn
|
||||
an = asn * asn
|
||||
case Dest:
|
||||
rn = abn * rbn
|
||||
gn = abn * gbn
|
||||
bn = abn * bbn
|
||||
an = abn * abn
|
||||
case SrcOver:
|
||||
rn = asn*rsn + abn*rbn*(1-asn)
|
||||
gn = asn*gsn + abn*gbn*(1-asn)
|
||||
bn = asn*bsn + abn*bbn*(1-asn)
|
||||
an = asn + abn*(1-asn)
|
||||
case DstOver:
|
||||
rn = asn*rsn*(1-abn) + abn*rbn
|
||||
gn = asn*gsn*(1-abn) + abn*gbn
|
||||
bn = asn*bsn*(1-abn) + abn*bbn
|
||||
an = asn*(1-abn) + abn
|
||||
case SrcIn:
|
||||
rn = asn * rsn * abn
|
||||
gn = asn * gsn * abn
|
||||
bn = asn * bsn * abn
|
||||
an = asn * abn
|
||||
case DstIn:
|
||||
rn = abn * rbn * asn
|
||||
gn = abn * gbn * asn
|
||||
bn = abn * bbn * asn
|
||||
an = abn * asn
|
||||
case SrcOut:
|
||||
rn = asn * rsn * (1 - abn)
|
||||
gn = asn * gsn * (1 - abn)
|
||||
bn = asn * bsn * (1 - abn)
|
||||
an = asn * (1 - abn)
|
||||
case DstOut:
|
||||
rn = abn * rbn * (1 - asn)
|
||||
gn = abn * gbn * (1 - asn)
|
||||
bn = abn * bbn * (1 - asn)
|
||||
an = abn * (1 - asn)
|
||||
case SrcAtop:
|
||||
rn = asn*rsn*abn + (1-asn)*abn*rbn
|
||||
gn = asn*gsn*abn + (1-asn)*abn*gbn
|
||||
bn = asn*bsn*abn + (1-asn)*abn*bbn
|
||||
an = asn*abn + abn*(1-asn)
|
||||
case DstAtop:
|
||||
rn = asn*rsn*(1-abn) + abn*rbn*asn
|
||||
gn = asn*gsn*(1-abn) + abn*gbn*asn
|
||||
bn = asn*bsn*(1-abn) + abn*bbn*asn
|
||||
an = asn*(1-abn) + abn*asn
|
||||
case Xor:
|
||||
rn = asn*rsn*(1-abn) + abn*rbn*(1-asn)
|
||||
gn = asn*gsn*(1-abn) + abn*gbn*(1-asn)
|
||||
bn = asn*bsn*(1-abn) + abn*bbn*(1-asn)
|
||||
an = asn*(1-abn) + abn*(1-asn)
|
||||
}
|
||||
|
||||
r = uint32(rn * 255)
|
||||
g = uint32(gn * 255)
|
||||
b = uint32(bn * 255)
|
||||
a = uint32(an * 255)
|
||||
|
||||
bitmap.Img.Set(x, y, color.NRGBA{
|
||||
R: uint8(r),
|
||||
G: uint8(g),
|
||||
B: uint8(b),
|
||||
A: uint8(a),
|
||||
})
|
||||
|
||||
// applying the blending mode
|
||||
if blend != nil {
|
||||
r1, g1, b1, a1 = bitmap.Img.At(x, y).RGBA()
|
||||
r2, g2, b2, a2 = src.At(x, y).RGBA()
|
||||
|
||||
rs, gs, bs, as = r1>>8, g1>>8, b1>>8, a1>>8
|
||||
rb, gb, bb, ab = r2>>8, g2>>8, b2>>8, a2>>8
|
||||
|
||||
rsn = float64(rs) / 255
|
||||
gsn = float64(gs) / 255
|
||||
bsn = float64(bs) / 255
|
||||
asn = float64(as) / 255
|
||||
rbn = float64(rb) / 255
|
||||
gbn = float64(gb) / 255
|
||||
bbn = float64(bb) / 255
|
||||
abn = float64(ab) / 255
|
||||
|
||||
switch blend.OpType {
|
||||
case Darken:
|
||||
rn = utils.Min(rsn, rbn)
|
||||
gn = utils.Min(gsn, gbn)
|
||||
bn = utils.Min(bsn, bbn)
|
||||
an = utils.Min(asn, abn)
|
||||
case Lighten:
|
||||
rn = utils.Max(rsn, rbn)
|
||||
gn = utils.Max(gsn, gbn)
|
||||
bn = utils.Max(bsn, bbn)
|
||||
an = utils.Max(asn, abn)
|
||||
case Screen:
|
||||
rn = 1 - (1-rsn)*(1-rbn)
|
||||
gn = 1 - (1-gsn)*(1-gbn)
|
||||
bn = 1 - (1-bsn)*(1-bbn)
|
||||
an = 1 - (1-asn)*(1-abn)
|
||||
case Multiply:
|
||||
rn = rsn * rbn
|
||||
gn = gsn * gbn
|
||||
bn = bsn * bbn
|
||||
an = asn * abn
|
||||
case Overlay:
|
||||
if rsn <= 0.5 {
|
||||
rn = 2 * rsn * rbn
|
||||
} else {
|
||||
rn = 1 - 2*(1-rsn)*(1-rbn)
|
||||
}
|
||||
if gsn <= 0.5 {
|
||||
gn = 2 * gsn * gbn
|
||||
} else {
|
||||
gn = 1 - 2*(1-gsn)*(1-gbn)
|
||||
}
|
||||
if bsn <= 0.5 {
|
||||
bn = 2 * bsn * bbn
|
||||
} else {
|
||||
bn = 1 - 2*(1-bsn)*(1-bbn)
|
||||
}
|
||||
if asn <= 0.5 {
|
||||
an = 2 * asn * abn
|
||||
} else {
|
||||
an = 1 - 2*(1-asn)*(1-abn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
r = uint32(rn * 255)
|
||||
g = uint32(gn * 255)
|
||||
b = uint32(bn * 255)
|
||||
a = uint32(an * 255)
|
||||
|
||||
bitmap.Img.Set(x, y, color.NRGBA{
|
||||
R: uint8(r),
|
||||
G: uint8(g),
|
||||
B: uint8(b),
|
||||
A: uint8(a),
|
||||
})
|
||||
// applying the alpha composition formula
|
||||
switch op.CurrentOp {
|
||||
case Clear:
|
||||
rn, gn, bn, an = 0, 0, 0, 0
|
||||
case Copy:
|
||||
rn = asn * rsn
|
||||
gn = asn * gsn
|
||||
bn = asn * bsn
|
||||
an = asn * asn
|
||||
case Dst:
|
||||
rn = abn * rbn
|
||||
gn = abn * gbn
|
||||
bn = abn * bbn
|
||||
an = abn * abn
|
||||
case SrcOver:
|
||||
rn = asn*rsn + abn*rbn*(1-asn)
|
||||
gn = asn*gsn + abn*gbn*(1-asn)
|
||||
bn = asn*bsn + abn*bbn*(1-asn)
|
||||
an = asn + abn*(1-asn)
|
||||
case DstOver:
|
||||
rn = asn*rsn*(1-abn) + abn*rbn
|
||||
gn = asn*gsn*(1-abn) + abn*gbn
|
||||
bn = asn*bsn*(1-abn) + abn*bbn
|
||||
an = asn*(1-abn) + abn
|
||||
case SrcIn:
|
||||
rn = asn * rsn * abn
|
||||
gn = asn * gsn * abn
|
||||
bn = asn * bsn * abn
|
||||
an = asn * abn
|
||||
case DstIn:
|
||||
rn = abn * rbn * asn
|
||||
gn = abn * gbn * asn
|
||||
bn = abn * bbn * asn
|
||||
an = abn * asn
|
||||
case SrcOut:
|
||||
rn = asn * rsn * (1 - abn)
|
||||
gn = asn * gsn * (1 - abn)
|
||||
bn = asn * bsn * (1 - abn)
|
||||
an = asn * (1 - abn)
|
||||
case DstOut:
|
||||
rn = abn * rbn * (1 - asn)
|
||||
gn = abn * gbn * (1 - asn)
|
||||
bn = abn * bbn * (1 - asn)
|
||||
an = abn * (1 - asn)
|
||||
case SrcAtop:
|
||||
rn = asn*rsn*abn + (1-asn)*abn*rbn
|
||||
gn = asn*gsn*abn + (1-asn)*abn*gbn
|
||||
bn = asn*bsn*abn + (1-asn)*abn*bbn
|
||||
an = asn*abn + abn*(1-asn)
|
||||
case DstAtop:
|
||||
rn = asn*rsn*(1-abn) + abn*rbn*asn
|
||||
gn = asn*gsn*(1-abn) + abn*gbn*asn
|
||||
bn = asn*bsn*(1-abn) + abn*bbn*asn
|
||||
an = asn*(1-abn) + abn*asn
|
||||
case Xor:
|
||||
rn = asn*rsn*(1-abn) + abn*rbn*(1-asn)
|
||||
gn = asn*gsn*(1-abn) + abn*gbn*(1-asn)
|
||||
bn = asn*bsn*(1-abn) + abn*bbn*(1-asn)
|
||||
an = asn*(1-abn) + abn*(1-asn)
|
||||
}
|
||||
|
||||
r = uint32(rn * 255)
|
||||
g = uint32(gn * 255)
|
||||
b = uint32(bn * 255)
|
||||
a = uint32(an * 255)
|
||||
|
||||
bitmap.Img.Set(x, y, color.NRGBA{
|
||||
R: uint8(r),
|
||||
G: uint8(g),
|
||||
B: uint8(b),
|
||||
A: uint8(a),
|
||||
})
|
||||
|
||||
// applying the blending mode
|
||||
if bl != nil {
|
||||
rn, gn, bn, an = 0, 0, 0, 0 // reset the colors
|
||||
r1, g1, b1, a1 = src.At(x, y).RGBA()
|
||||
r2, g2, b2, a2 = dst.At(x, y).RGBA()
|
||||
|
||||
rs, gs, bs, as = r1>>8, g1>>8, b1>>8, a1>>8
|
||||
rb, gb, bb, ab = r2>>8, g2>>8, b2>>8, a2>>8
|
||||
|
||||
rsn = float64(rs) / 255
|
||||
gsn = float64(gs) / 255
|
||||
bsn = float64(bs) / 255
|
||||
asn = float64(as) / 255
|
||||
|
||||
rbn = float64(rb) / 255
|
||||
gbn = float64(gb) / 255
|
||||
bbn = float64(bb) / 255
|
||||
abn = float64(ab) / 255
|
||||
|
||||
foreground := Color{R: rsn, G: gsn, B: bsn}
|
||||
background := Color{R: rbn, G: gbn, B: bbn}
|
||||
|
||||
switch bl.Current {
|
||||
case Normal:
|
||||
rn, gn, bn, an = rsn, gsn, bsn, asn
|
||||
case Darken:
|
||||
rn = utils.Min(rsn, rbn)
|
||||
gn = utils.Min(gsn, gbn)
|
||||
bn = utils.Min(bsn, bbn)
|
||||
an = utils.Min(asn, abn)
|
||||
case Lighten:
|
||||
rn = utils.Max(rsn, rbn)
|
||||
gn = utils.Max(gsn, gbn)
|
||||
bn = utils.Max(bsn, bbn)
|
||||
an = utils.Max(asn, abn)
|
||||
case Screen:
|
||||
rn = 1 - (1-rsn)*(1-rbn)
|
||||
gn = 1 - (1-gsn)*(1-gbn)
|
||||
bn = 1 - (1-bsn)*(1-bbn)
|
||||
an = 1 - (1-asn)*(1-abn)
|
||||
case Multiply:
|
||||
rn = rsn * rbn
|
||||
gn = gsn * gbn
|
||||
bn = bsn * bbn
|
||||
an = asn * abn
|
||||
case Overlay:
|
||||
if rsn <= 0.5 {
|
||||
rn = 2 * rsn * rbn
|
||||
} else {
|
||||
rn = 1 - 2*(1-rsn)*(1-rbn)
|
||||
}
|
||||
|
||||
if gsn <= 0.5 {
|
||||
gn = 2 * gsn * gbn
|
||||
} else {
|
||||
gn = 1 - 2*(1-gsn)*(1-gbn)
|
||||
}
|
||||
|
||||
if bsn <= 0.5 {
|
||||
bn = 2 * bsn * bbn
|
||||
} else {
|
||||
bn = 1 - 2*(1-bsn)*(1-bbn)
|
||||
}
|
||||
|
||||
if asn <= 0.5 {
|
||||
an = 2 * asn * abn
|
||||
} else {
|
||||
an = 1 - 2*(1-asn)*(1-abn)
|
||||
}
|
||||
case SoftLight:
|
||||
if rbn < 0.5 {
|
||||
rn = rsn - (1-2*rbn)*rsn*(1-rsn)
|
||||
} else {
|
||||
var w3r float64
|
||||
if rsn < 0.25 {
|
||||
w3r = ((16*rsn-12)*rsn + 4) * rsn
|
||||
} else {
|
||||
w3r = math.Sqrt(rsn)
|
||||
}
|
||||
rn = rsn + (2*rbn-1)*(w3r-rsn)
|
||||
}
|
||||
|
||||
if gbn < 0.5 {
|
||||
gn = gsn - (1-2*gbn)*gsn*(1-gsn)
|
||||
} else {
|
||||
var w3g float64
|
||||
if gsn < 0.25 {
|
||||
w3g = ((16*gsn-12)*gsn + 4) * gsn
|
||||
} else {
|
||||
w3g = math.Sqrt(gsn)
|
||||
}
|
||||
gn = gsn + (2*gbn-1)*(w3g-gsn)
|
||||
}
|
||||
|
||||
if bbn < 0.5 {
|
||||
bn = bsn - (1-2*bbn)*bsn*(1-bsn)
|
||||
} else {
|
||||
var w3b float64
|
||||
if bsn < 0.25 {
|
||||
w3b = ((16*bsn-12)*bsn + 4) * bsn
|
||||
} else {
|
||||
w3b = math.Sqrt(bsn)
|
||||
}
|
||||
bn = bsn + (2*bbn-1)*(w3b-bsn)
|
||||
}
|
||||
|
||||
if abn < 0.5 {
|
||||
an = asn - (1-2*abn)*asn*(1-asn)
|
||||
} else {
|
||||
var w3a float64
|
||||
if asn < 0.25 {
|
||||
w3a = ((16*asn-12)*asn + 4) * asn
|
||||
} else {
|
||||
w3a = math.Sqrt(asn)
|
||||
}
|
||||
an = asn + (2*abn-1)*(w3a-asn)
|
||||
}
|
||||
case HardLight:
|
||||
if rbn < 0.5 {
|
||||
rn = rbn - (1-2*rsn)*rbn*(1-rbn)
|
||||
} else {
|
||||
var w3r float64
|
||||
if rbn < 0.25 {
|
||||
w3r = ((16*rbn-12)*rbn + 4) * rbn
|
||||
} else {
|
||||
w3r = math.Sqrt(rbn)
|
||||
}
|
||||
rn = rbn + (2*rsn-1)*(w3r-rbn)
|
||||
}
|
||||
|
||||
if gbn < 0.5 {
|
||||
gn = gbn - (1-2*gsn)*gbn*(1-gbn)
|
||||
} else {
|
||||
var w3g float64
|
||||
if gbn < 0.25 {
|
||||
w3g = ((16*gbn-12)*gbn + 4) * gbn
|
||||
} else {
|
||||
w3g = math.Sqrt(gbn)
|
||||
}
|
||||
gn = gbn + (2*gsn-1)*(w3g-gbn)
|
||||
}
|
||||
|
||||
if bbn < 0.5 {
|
||||
bn = bbn - (1-2*bsn)*bbn*(1-bbn)
|
||||
} else {
|
||||
var w3b float64
|
||||
if bbn < 0.25 {
|
||||
w3b = ((16*bbn-12)*bbn + 4) * bbn
|
||||
} else {
|
||||
w3b = math.Sqrt(bbn)
|
||||
}
|
||||
bn = bbn + (2*bsn-1)*(w3b-bbn)
|
||||
}
|
||||
|
||||
if abn < 0.5 {
|
||||
an = abn - (1-2*asn)*abn*(1-abn)
|
||||
} else {
|
||||
var w3a float64
|
||||
if abn < 0.25 {
|
||||
w3a = ((16*abn-12)*abn + 4) * abn
|
||||
} else {
|
||||
w3a = math.Sqrt(abn)
|
||||
}
|
||||
an = abn + (2*asn-1)*(w3a-abn)
|
||||
}
|
||||
case ColorDodge:
|
||||
if rsn < 1 {
|
||||
rn = utils.Min(1, rbn/(1-rsn))
|
||||
} else if rsn == 1 {
|
||||
rn = 1
|
||||
}
|
||||
|
||||
if gsn < 1 {
|
||||
gn = utils.Min(1, gbn/(1-gsn))
|
||||
} else if gsn == 1 {
|
||||
gn = 1
|
||||
}
|
||||
|
||||
if bsn < 1 {
|
||||
bn = utils.Min(1, bbn/(1-bsn))
|
||||
} else if bsn == 1 {
|
||||
bn = 1
|
||||
}
|
||||
|
||||
if asn < 1 {
|
||||
an = utils.Min(1, abn/(1-asn))
|
||||
} else if asn == 1 {
|
||||
an = 1
|
||||
}
|
||||
case ColorBurn:
|
||||
if rsn > 0 {
|
||||
rn = 1 - utils.Min(1, (1-rbn)/rsn)
|
||||
} else if rsn == 0 {
|
||||
rn = 0
|
||||
}
|
||||
|
||||
if gsn > 0 {
|
||||
gn = 1 - utils.Min(1, (1-gbn)/gsn)
|
||||
} else if gsn == 0 {
|
||||
gn = 0
|
||||
}
|
||||
|
||||
if bsn > 0 {
|
||||
bn = 1 - utils.Min(1, (1-bbn)/bsn)
|
||||
} else if bsn == 0 {
|
||||
bn = 0
|
||||
}
|
||||
|
||||
if asn > 0 {
|
||||
an = 1 - utils.Min(1, (1-abn)/asn)
|
||||
} else if asn == 0 {
|
||||
an = 0
|
||||
}
|
||||
case Difference:
|
||||
rn = utils.Abs(rbn - rsn)
|
||||
gn = utils.Abs(gbn - gsn)
|
||||
bn = utils.Abs(bbn - bsn)
|
||||
an = 1
|
||||
case Exclusion:
|
||||
rn = rsn + rbn - 2*rsn*rbn
|
||||
gn = gsn + gbn - 2*gsn*gbn
|
||||
bn = bsn + bbn - 2*bsn*bbn
|
||||
an = 1
|
||||
|
||||
// Non-separable blend modes
|
||||
// https://www.w3.org/TR/compositing-1/#blendingnonseparable
|
||||
case Hue:
|
||||
sat := bl.SetSat(background, bl.Sat(foreground))
|
||||
rgb := bl.SetLum(sat, bl.Lum(foreground))
|
||||
|
||||
a := asn + abn - asn*abn
|
||||
rn = bl.AlphaCompose(abn, asn, a, rbn*255, rsn*255, rgb.R*255)
|
||||
gn = bl.AlphaCompose(abn, asn, a, gbn*255, gsn*255, rgb.G*255)
|
||||
bn = bl.AlphaCompose(abn, asn, a, bbn*255, bsn*255, rgb.B*255)
|
||||
rn, gn, bn = rn/255, gn/255, bn/255
|
||||
an = a
|
||||
case Saturation:
|
||||
sat := bl.SetSat(foreground, bl.Sat(background))
|
||||
rgb := bl.SetLum(sat, bl.Lum(foreground))
|
||||
|
||||
a := asn + abn - asn*abn
|
||||
rn = bl.AlphaCompose(abn, asn, a, rbn*255, rsn*255, rgb.R*255)
|
||||
gn = bl.AlphaCompose(abn, asn, a, gbn*255, gsn*255, rgb.G*255)
|
||||
bn = bl.AlphaCompose(abn, asn, a, bbn*255, bsn*255, rgb.B*255)
|
||||
rn, gn, bn = rn/255, gn/255, bn/255
|
||||
an = a
|
||||
case ColorMode:
|
||||
rgb := bl.SetLum(background, bl.Lum(foreground))
|
||||
|
||||
a := asn + abn - asn*abn
|
||||
rn = bl.AlphaCompose(abn, asn, a, rbn*255, rsn*255, rgb.R*255)
|
||||
gn = bl.AlphaCompose(abn, asn, a, gbn*255, gsn*255, rgb.G*255)
|
||||
bn = bl.AlphaCompose(abn, asn, a, bbn*255, bsn*255, rgb.B*255)
|
||||
rn, gn, bn = rn/255, gn/255, bn/255
|
||||
an = a
|
||||
case Luminosity:
|
||||
rgb := bl.SetLum(foreground, bl.Lum(background))
|
||||
|
||||
a := asn + abn - asn*abn
|
||||
rn = bl.AlphaCompose(abn, asn, a, rbn*255, rsn*255, rgb.R*255)
|
||||
gn = bl.AlphaCompose(abn, asn, a, gbn*255, gsn*255, rgb.G*255)
|
||||
bn = bl.AlphaCompose(abn, asn, a, bbn*255, bsn*255, rgb.B*255)
|
||||
rn, gn, bn = rn/255, gn/255, bn/255
|
||||
an = a
|
||||
}
|
||||
}
|
||||
|
||||
r = uint32(rn * 255)
|
||||
g = uint32(gn * 255)
|
||||
b = uint32(bn * 255)
|
||||
a = uint32(an * 255)
|
||||
|
||||
bitmap.Img.Set(x, y, color.NRGBA{
|
||||
R: uint8(r),
|
||||
G: uint8(g),
|
||||
B: uint8(b),
|
||||
A: uint8(a),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
package imop
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestComp_Basic(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
op := InitOp()
|
||||
err := op.Set("unsupported_composite_operation")
|
||||
assert.Error(err)
|
||||
|
||||
op.Set(Clear)
|
||||
assert.Equal(Clear, op.Get())
|
||||
assert.NotEqual("unsupported_composite_operation", op.Get())
|
||||
|
||||
op.Set(Dst)
|
||||
assert.Equal(Dst, op.Get())
|
||||
}
|
||||
|
||||
func TestComp_Ops(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
op := InitOp()
|
||||
|
||||
transparent := color.NRGBA{R: 0, G: 0, B: 0, A: 0}
|
||||
cyan := color.NRGBA{R: 33, G: 150, B: 243, A: 255}
|
||||
magenta := color.NRGBA{R: 233, G: 30, B: 99, A: 255}
|
||||
|
||||
rect := image.Rect(0, 0, 10, 10)
|
||||
bmp := NewBitmap(rect)
|
||||
source := image.NewNRGBA(rect)
|
||||
backdrop := image.NewNRGBA(rect)
|
||||
|
||||
// No composition operation applied. The SrcOver is the default one.
|
||||
draw.Draw(source, image.Rect(0, 4, 6, 10), &image.Uniform{cyan}, image.Point{}, draw.Src)
|
||||
draw.Draw(backdrop, image.Rect(4, 0, 10, 6), &image.Uniform{magenta}, image.Point{}, draw.Src)
|
||||
op.Draw(bmp, source, backdrop, nil)
|
||||
|
||||
// Pick three representative points/pixels from the generated image output.
|
||||
// Depending on the applied composition operation the colors of the
|
||||
// selected pixels should be the source color, the destination color or transparent.
|
||||
topRight := bmp.Img.At(9, 0)
|
||||
bottomLeft := bmp.Img.At(0, 9)
|
||||
center := bmp.Img.At(5, 5)
|
||||
|
||||
assert.EqualValues(topRight, magenta)
|
||||
assert.EqualValues(bottomLeft, cyan)
|
||||
assert.EqualValues(center, cyan)
|
||||
|
||||
// Clear
|
||||
op.Set(Clear)
|
||||
op.Draw(bmp, source, backdrop, nil)
|
||||
|
||||
topRight = bmp.Img.At(9, 0)
|
||||
bottomLeft = bmp.Img.At(0, 9)
|
||||
center = bmp.Img.At(5, 5)
|
||||
|
||||
assert.EqualValues(topRight, transparent)
|
||||
assert.EqualValues(bottomLeft, transparent)
|
||||
assert.EqualValues(center, transparent)
|
||||
|
||||
// Copy
|
||||
op.Set(Copy)
|
||||
op.Draw(bmp, source, backdrop, nil)
|
||||
|
||||
topRight = bmp.Img.At(9, 0)
|
||||
bottomLeft = bmp.Img.At(0, 9)
|
||||
center = bmp.Img.At(5, 5)
|
||||
|
||||
assert.EqualValues(topRight, transparent)
|
||||
assert.EqualValues(bottomLeft, cyan)
|
||||
assert.EqualValues(center, cyan)
|
||||
|
||||
// Dst
|
||||
op.Set(Dst)
|
||||
op.Draw(bmp, source, backdrop, nil)
|
||||
|
||||
topRight = bmp.Img.At(9, 0)
|
||||
bottomLeft = bmp.Img.At(0, 9)
|
||||
center = bmp.Img.At(5, 5)
|
||||
|
||||
assert.EqualValues(topRight, magenta)
|
||||
assert.EqualValues(bottomLeft, transparent)
|
||||
assert.EqualValues(center, magenta)
|
||||
|
||||
// SrcOver
|
||||
op.Set(SrcOver)
|
||||
op.Draw(bmp, source, backdrop, nil)
|
||||
|
||||
topRight = bmp.Img.At(9, 0)
|
||||
bottomLeft = bmp.Img.At(0, 9)
|
||||
center = bmp.Img.At(5, 5)
|
||||
|
||||
assert.EqualValues(topRight, magenta)
|
||||
assert.EqualValues(bottomLeft, cyan)
|
||||
assert.EqualValues(center, cyan)
|
||||
|
||||
// DstOver
|
||||
op.Set(DstOver)
|
||||
op.Draw(bmp, source, backdrop, nil)
|
||||
|
||||
topRight = bmp.Img.At(9, 0)
|
||||
bottomLeft = bmp.Img.At(0, 9)
|
||||
center = bmp.Img.At(5, 5)
|
||||
|
||||
assert.EqualValues(topRight, magenta)
|
||||
assert.EqualValues(bottomLeft, cyan)
|
||||
assert.EqualValues(center, magenta)
|
||||
|
||||
// SrcIn
|
||||
op.Set(SrcIn)
|
||||
op.Draw(bmp, source, backdrop, nil)
|
||||
|
||||
topRight = bmp.Img.At(9, 0)
|
||||
bottomLeft = bmp.Img.At(0, 9)
|
||||
center = bmp.Img.At(5, 5)
|
||||
|
||||
assert.EqualValues(topRight, transparent)
|
||||
assert.EqualValues(bottomLeft, transparent)
|
||||
assert.EqualValues(center, cyan)
|
||||
|
||||
// DstIn
|
||||
op.Set(DstIn)
|
||||
op.Draw(bmp, source, backdrop, nil)
|
||||
|
||||
topRight = bmp.Img.At(9, 0)
|
||||
bottomLeft = bmp.Img.At(0, 9)
|
||||
center = bmp.Img.At(5, 5)
|
||||
|
||||
assert.EqualValues(topRight, transparent)
|
||||
assert.EqualValues(bottomLeft, transparent)
|
||||
assert.EqualValues(center, magenta)
|
||||
|
||||
// SrcOut
|
||||
op.Set(SrcOut)
|
||||
op.Draw(bmp, source, backdrop, nil)
|
||||
|
||||
topRight = bmp.Img.At(9, 0)
|
||||
bottomLeft = bmp.Img.At(0, 9)
|
||||
center = bmp.Img.At(5, 5)
|
||||
|
||||
assert.EqualValues(topRight, transparent)
|
||||
assert.EqualValues(bottomLeft, cyan)
|
||||
assert.EqualValues(center, transparent)
|
||||
|
||||
// DstOut
|
||||
op.Set(DstOut)
|
||||
op.Draw(bmp, source, backdrop, nil)
|
||||
|
||||
topRight = bmp.Img.At(9, 0)
|
||||
bottomLeft = bmp.Img.At(0, 9)
|
||||
center = bmp.Img.At(5, 5)
|
||||
|
||||
assert.EqualValues(topRight, magenta)
|
||||
assert.EqualValues(bottomLeft, transparent)
|
||||
assert.EqualValues(center, transparent)
|
||||
|
||||
// SrcAtop
|
||||
op.Set(SrcAtop)
|
||||
op.Draw(bmp, source, backdrop, nil)
|
||||
|
||||
topRight = bmp.Img.At(9, 0)
|
||||
bottomLeft = bmp.Img.At(0, 9)
|
||||
center = bmp.Img.At(5, 5)
|
||||
|
||||
assert.EqualValues(topRight, magenta)
|
||||
assert.EqualValues(bottomLeft, transparent)
|
||||
assert.EqualValues(center, cyan)
|
||||
|
||||
// DstAtop
|
||||
op.Set(DstAtop)
|
||||
op.Draw(bmp, source, backdrop, nil)
|
||||
|
||||
topRight = bmp.Img.At(9, 0)
|
||||
bottomLeft = bmp.Img.At(0, 9)
|
||||
center = bmp.Img.At(5, 5)
|
||||
|
||||
assert.EqualValues(topRight, transparent)
|
||||
assert.EqualValues(bottomLeft, cyan)
|
||||
assert.EqualValues(center, magenta)
|
||||
|
||||
// Xor
|
||||
op.Set(Xor)
|
||||
op.Draw(bmp, source, backdrop, nil)
|
||||
|
||||
topRight = bmp.Img.At(9, 0)
|
||||
bottomLeft = bmp.Img.At(0, 9)
|
||||
center = bmp.Img.At(5, 5)
|
||||
|
||||
assert.EqualValues(topRight, magenta)
|
||||
assert.EqualValues(bottomLeft, cyan)
|
||||
assert.EqualValues(center, transparent)
|
||||
// DstAtop
|
||||
op.Set(DstAtop)
|
||||
op.Draw(bmp, source, backdrop, nil)
|
||||
|
||||
topRight = bmp.Img.At(9, 0)
|
||||
bottomLeft = bmp.Img.At(0, 9)
|
||||
center = bmp.Img.At(5, 5)
|
||||
|
||||
assert.EqualValues(topRight, transparent)
|
||||
assert.EqualValues(bottomLeft, cyan)
|
||||
assert.EqualValues(center, magenta)
|
||||
}
|
||||
Reference in New Issue
Block a user