blend.go 10.2 KB
Newer Older
1 2
/*Package blend provides common image blending modes.*/
package blend
A
Anthony N. Simon 已提交
3

A
Anthony N. Simon 已提交
4 5
import (
	"image"
6
	"math"
7 8 9 10 11

	"github.com/anthonynsimon/bild/clone"
	"github.com/anthonynsimon/bild/fcolor"
	"github.com/anthonynsimon/bild/math/f64"
	"github.com/anthonynsimon/bild/parallel"
A
Anthony N. Simon 已提交
12 13
)

14
// Normal combines the foreground and background images by placing the foreground over the
A
Anthony N. Simon 已提交
15
// background using alpha compositing. The resulting image is then returned.
16 17
func Normal(bg image.Image, fg image.Image) *image.RGBA {
	dst := blend(bg, fg, func(c0, c1 fcolor.RGBAF64) fcolor.RGBAF64 {
A
Anthony N. Simon 已提交
18 19 20 21 22 23
		return alphaComp(c0, c1)
	})

	return dst
}

24 25 26
// Add combines the foreground and background images by adding their values and
// returns the resulting image.
func Add(bg image.Image, fg image.Image) *image.RGBA {
27
	dst := blend(bg, fg, func(c0, c1 fcolor.RGBAF64) fcolor.RGBAF64 {
28 29 30
		r := c0.R + c1.R
		g := c0.G + c1.G
		b := c0.B + c1.B
A
Anthony N. Simon 已提交
31

32
		c2 := fcolor.RGBAF64{R: r, G: g, B: b, A: c1.A}
33
		return alphaComp(c0, c2)
A
Anthony N. Simon 已提交
34 35 36 37 38
	})

	return dst
}

39 40 41
// Multiply combines the foreground and background images by multiplying their
// normalized values and returns the resulting image.
func Multiply(bg image.Image, fg image.Image) *image.RGBA {
42
	dst := blend(bg, fg, func(c0, c1 fcolor.RGBAF64) fcolor.RGBAF64 {
43 44 45
		r := c0.R * c1.R
		g := c0.G * c1.G
		b := c0.B * c1.B
A
Anthony N. Simon 已提交
46

47
		c2 := fcolor.RGBAF64{R: r, G: g, B: b, A: c1.A}
48
		return alphaComp(c0, c2)
A
Anthony N. Simon 已提交
49
	})
A
Anthony N. Simon 已提交
50

A
Anthony N. Simon 已提交
51 52
	return dst
}
A
Anthony N. Simon 已提交
53

54 55 56
// Overlay combines the foreground and background images by using Multiply when channel values < 0.5
// or using Screen otherwise and returns the resulting image.
func Overlay(bg image.Image, fg image.Image) *image.RGBA {
57
	dst := blend(bg, fg, func(c0, c1 fcolor.RGBAF64) fcolor.RGBAF64 {
58 59 60
		var r, g, b float64
		if c0.R > 0.5 {
			r = 1 - (1-2*(c0.R-0.5))*(1-c1.R)
61
		} else {
62
			r = 2 * c0.R * c1.R
63
		}
64 65
		if c0.G > 0.5 {
			g = 1 - (1-2*(c0.G-0.5))*(1-c1.G)
66
		} else {
67
			g = 2 * c0.G * c1.G
68
		}
69 70
		if c0.B > 0.5 {
			b = 1 - (1-2*(c0.B-0.5))*(1-c1.B)
71
		} else {
72
			b = 2 * c0.B * c1.B
A
Anthony N. Simon 已提交
73
		}
A
Anthony N. Simon 已提交
74

75
		c2 := fcolor.RGBAF64{R: r, G: g, B: b, A: c1.A}
76
		return alphaComp(c0, c2)
A
Anthony N. Simon 已提交
77 78 79 80 81
	})

	return dst
}

82 83 84
// SoftLight combines the foreground and background images by using Pegtop's Soft Light formula and
// returns the resulting image.
func SoftLight(bg image.Image, fg image.Image) *image.RGBA {
85
	dst := blend(bg, fg, func(c0, c1 fcolor.RGBAF64) fcolor.RGBAF64 {
86 87 88
		r := (1-2*c1.R)*c0.R*c0.R + 2*c0.R*c1.R
		g := (1-2*c1.G)*c0.G*c0.G + 2*c0.G*c1.G
		b := (1-2*c1.B)*c0.B*c0.B + 2*c0.B*c1.B
A
Anthony N. Simon 已提交
89

90
		c2 := fcolor.RGBAF64{R: r, G: g, B: b, A: c1.A}
91
		return alphaComp(c0, c2)
A
Anthony N. Simon 已提交
92 93 94 95
	})
	return dst
}

96 97 98
// Screen combines the foreground and background images by inverting, multiplying and inverting the output.
// The result is a brighter image which is then returned.
func Screen(bg image.Image, fg image.Image) *image.RGBA {
99
	dst := blend(bg, fg, func(c0, c1 fcolor.RGBAF64) fcolor.RGBAF64 {
100 101 102
		r := 1 - (1-c0.R)*(1-c1.R)
		g := 1 - (1-c0.G)*(1-c1.G)
		b := 1 - (1-c0.B)*(1-c1.B)
A
Anthony N. Simon 已提交
103

104
		c2 := fcolor.RGBAF64{R: r, G: g, B: b, A: c1.A}
105
		return alphaComp(c0, c2)
A
Anthony N. Simon 已提交
106 107 108 109 110
	})

	return dst
}

111 112 113
// Difference calculates the absolute difference between the foreground and background images and
// returns the resulting image.
func Difference(bg image.Image, fg image.Image) *image.RGBA {
114
	dst := blend(bg, fg, func(c0, c1 fcolor.RGBAF64) fcolor.RGBAF64 {
115 116 117
		r := math.Abs(c0.R - c1.R)
		g := math.Abs(c0.G - c1.G)
		b := math.Abs(c0.B - c1.B)
118

119
		c2 := fcolor.RGBAF64{R: r, G: g, B: b, A: c1.A}
120
		return alphaComp(c0, c2)
121 122 123 124 125
	})

	return dst
}

126 127 128
// Divide combines the foreground and background images by diving the values from the background
// by the foreground and returns the resulting image.
func Divide(bg image.Image, fg image.Image) *image.RGBA {
129
	dst := blend(bg, fg, func(c0, c1 fcolor.RGBAF64) fcolor.RGBAF64 {
A
Anthony N. Simon 已提交
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
		var r, g, b float64
		if c1.R == 0 {
			r = 1
		} else {
			r = c0.R / c1.R
		}
		if c1.G == 0 {
			g = 1
		} else {
			g = c0.G / c1.G
		}
		if c1.B == 0 {
			b = 1
		} else {
			b = c0.B / c1.B
		}
146

147
		c2 := fcolor.RGBAF64{R: r, G: g, B: b, A: c1.A}
148
		return alphaComp(c0, c2)
149 150 151 152 153
	})

	return dst
}

154 155 156
// ColorBurn combines the foreground and background images by dividing the inverted
// background by the foreground image and then inverting the result which is then returned.
func ColorBurn(bg image.Image, fg image.Image) *image.RGBA {
157
	dst := blend(bg, fg, func(c0, c1 fcolor.RGBAF64) fcolor.RGBAF64 {
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
		var r, g, b float64
		if c1.R == 0 {
			r = 0
		} else {
			r = 1 - (1-c0.R)/c1.R
		}
		if c1.G == 0 {
			g = 0
		} else {
			g = 1 - (1-c0.G)/c1.G
		}
		if c1.B == 0 {
			b = 0
		} else {
			b = 1 - (1-c0.B)/c1.B
		}
174

175
		c2 := fcolor.RGBAF64{R: r, G: g, B: b, A: c1.A}
176
		return alphaComp(c0, c2)
177 178 179 180 181
	})

	return dst
}

182 183 184
// Exclusion combines the foreground and background images applying the Exclusion blend mode and
// returns the resulting image.
func Exclusion(bg image.Image, fg image.Image) *image.RGBA {
185
	dst := blend(bg, fg, func(c0, c1 fcolor.RGBAF64) fcolor.RGBAF64 {
186 187 188
		r := 0.5 - 2*(c0.R-0.5)*(c1.R-0.5)
		g := 0.5 - 2*(c0.G-0.5)*(c1.G-0.5)
		b := 0.5 - 2*(c0.B-0.5)*(c1.B-0.5)
189

190
		c2 := fcolor.RGBAF64{R: r, G: g, B: b, A: c1.A}
191
		return alphaComp(c0, c2)
192 193 194
	})

	return dst
195

196 197
}

198 199 200
// ColorDodge combines the foreground and background images by dividing background by the
// inverted foreground image and returns the result.
func ColorDodge(bg image.Image, fg image.Image) *image.RGBA {
201
	dst := blend(bg, fg, func(c0, c1 fcolor.RGBAF64) fcolor.RGBAF64 {
A
Anthony N. Simon 已提交
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
		var r, g, b float64
		if c1.R == 1 {
			r = 1
		} else {
			r = c0.R / (1 - c1.R)
		}
		if c1.G == 1 {
			g = 1
		} else {
			g = c0.G / (1 - c1.G)
		}
		if c1.B == 1 {
			b = 1
		} else {
			b = c0.B / (1 - c1.B)
		}
218

219
		c2 := fcolor.RGBAF64{R: r, G: g, B: b, A: c1.A}
220
		return alphaComp(c0, c2)
221 222 223 224 225
	})

	return dst
}

226
// LinearBurn combines the foreground and background images by adding them and
A
Anthony N. Simon 已提交
227
// then subtracting 255 (1.0 in normalized scale). The resulting image is then returned.
228
func LinearBurn(bg image.Image, fg image.Image) *image.RGBA {
229
	dst := blend(bg, fg, func(c0, c1 fcolor.RGBAF64) fcolor.RGBAF64 {
230 231 232
		r := c0.R + c1.R - 1
		g := c0.G + c1.G - 1
		b := c0.B + c1.B - 1
233

234
		c2 := fcolor.RGBAF64{R: r, G: g, B: b, A: c1.A}
235
		return alphaComp(c0, c2)
236 237 238 239 240
	})

	return dst
}

241 242 243
// LinearLight combines the foreground and background images by a mix of a Linear Dodge and
// Linear Burn operation. The resulting image is then returned.
func LinearLight(bg image.Image, fg image.Image) *image.RGBA {
244
	dst := blend(bg, fg, func(c0, c1 fcolor.RGBAF64) fcolor.RGBAF64 {
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
		var r, g, b float64
		if c1.R > 0.5 {
			r = c0.R + 2*c1.R - 0.5
		} else {
			r = c0.R + 2*c1.R - 1
		}
		if c1.G > 0.5 {
			g = c0.G + 2*c1.G - 0.5
		} else {
			g = c0.G + 2*c1.G - 1
		}
		if c1.B > 0.5 {
			b = c0.B + 2*c1.B - 0.5
		} else {
			b = c0.B + 2*c1.B - 1
		}
261

262
		c2 := fcolor.RGBAF64{R: r, G: g, B: b, A: c1.A}
263
		return alphaComp(c0, c2)
264 265 266 267 268
	})

	return dst
}

A
Anthony N. Simon 已提交
269
// Subtract combines the foreground and background images by Subtracting the background from the
270
// foreground. The result is then returned.
A
Anthony N. Simon 已提交
271
func Subtract(bg image.Image, fg image.Image) *image.RGBA {
272
	dst := blend(bg, fg, func(c0, c1 fcolor.RGBAF64) fcolor.RGBAF64 {
273 274 275
		r := c1.R - c0.R
		g := c1.G - c0.G
		b := c1.B - c0.B
276

277
		c2 := fcolor.RGBAF64{R: r, G: g, B: b, A: c1.A}
278
		return alphaComp(c0, c2)
279 280 281 282 283
	})

	return dst
}

284 285
// Opacity returns an image which blends the two input images by the percentage provided.
// Percent must be of range 0 <= percent <= 1.0
286
func Opacity(bg image.Image, fg image.Image, percent float64) *image.RGBA {
287
	percent = f64.Clamp(percent, 0, 1.0)
288

289
	dst := blend(bg, fg, func(c0, c1 fcolor.RGBAF64) fcolor.RGBAF64 {
290 291 292
		r := c1.R*percent + (1-percent)*c0.R
		g := c1.G*percent + (1-percent)*c0.G
		b := c1.B*percent + (1-percent)*c0.B
293

294
		c2 := fcolor.RGBAF64{R: r, G: g, B: b, A: c1.A}
295
		return alphaComp(c0, c2)
296 297 298 299 300
	})

	return dst
}

301 302 303
// Darken combines the foreground and background images by picking the darkest value per channel
// for each pixel. The result is then returned.
func Darken(bg image.Image, fg image.Image) *image.RGBA {
304
	dst := blend(bg, fg, func(c0, c1 fcolor.RGBAF64) fcolor.RGBAF64 {
305 306 307 308
		r := math.Min(c0.R, c1.R)
		g := math.Min(c0.G, c1.G)
		b := math.Min(c0.B, c1.B)

309
		c2 := fcolor.RGBAF64{R: r, G: g, B: b, A: c1.A}
310
		return alphaComp(c0, c2)
311 312 313 314 315
	})

	return dst
}

316 317 318
// Lighten combines the foreground and background images by picking the brightest value per channel
// for each pixel. The result is then returned.
func Lighten(bg image.Image, fg image.Image) *image.RGBA {
319
	dst := blend(bg, fg, func(c0, c1 fcolor.RGBAF64) fcolor.RGBAF64 {
320 321 322 323
		r := math.Max(c0.R, c1.R)
		g := math.Max(c0.G, c1.G)
		b := math.Max(c0.B, c1.B)

324
		c2 := fcolor.RGBAF64{R: r, G: g, B: b, A: c1.A}
325
		return alphaComp(c0, c2)
326 327 328 329 330
	})

	return dst
}

331 332 333
// Blend two images together by applying the provided function for each pixel.
// If images differ in size, the minimum width and height will be picked from each one
// when creating the resulting image.
334
func blend(bg image.Image, fg image.Image, fn func(fcolor.RGBAF64, fcolor.RGBAF64) fcolor.RGBAF64) *image.RGBA {
335 336 337 338 339 340 341 342 343 344 345 346 347
	bgBounds := bg.Bounds()
	fgBounds := fg.Bounds()

	var w, h int
	if bgBounds.Dx() < fgBounds.Dx() {
		w = bgBounds.Dx()
	} else {
		w = fgBounds.Dx()
	}
	if bgBounds.Dy() < fgBounds.Dy() {
		h = bgBounds.Dy()
	} else {
		h = fgBounds.Dy()
A
Anthony N. Simon 已提交
348 349
	}

350 351
	bgSrc := clone.AsRGBA(bg)
	fgSrc := clone.AsRGBA(fg)
352
	dst := image.NewRGBA(image.Rect(0, 0, w, h))
A
Anthony N. Simon 已提交
353

354
	parallel.Line(h, func(start, end int) {
355 356 357 358
		for y := start; y < end; y++ {
			for x := 0; x < w; x++ {
				bgPos := y*bgSrc.Stride + x*4
				fgPos := y*fgSrc.Stride + x*4
359
				result := fn(
360 361
					fcolor.NewRGBAF64(bgSrc.Pix[bgPos+0], bgSrc.Pix[bgPos+1], bgSrc.Pix[bgPos+2], bgSrc.Pix[bgPos+3]),
					fcolor.NewRGBAF64(fgSrc.Pix[fgPos+0], fgSrc.Pix[fgPos+1], fgSrc.Pix[fgPos+2], fgSrc.Pix[fgPos+3]))
362 363

				result.Clamp()
364 365 366 367 368
				dstPos := y*dst.Stride + x*4
				dst.Pix[dstPos+0] = uint8(result.R * 255)
				dst.Pix[dstPos+1] = uint8(result.G * 255)
				dst.Pix[dstPos+2] = uint8(result.B * 255)
				dst.Pix[dstPos+3] = uint8(result.A * 255)
A
Anthony N. Simon 已提交
369
			}
A
Anthony N. Simon 已提交
370

A
Anthony N. Simon 已提交
371 372 373 374 375
		}
	})

	return dst
}
376 377 378 379 380 381 382 383 384 385 386 387 388 389

// alphaComp returns a new color after compositing the two colors
// based on the foreground's alpha channel.
func alphaComp(bg, fg fcolor.RGBAF64) fcolor.RGBAF64 {
	fg.Clamp()
	fga := fg.A

	r := (fg.R * fga / 1) + ((1 - fga) * bg.R / 1)
	g := (fg.G * fga / 1) + ((1 - fga) * bg.G / 1)
	b := (fg.B * fga / 1) + ((1 - fga) * bg.B / 1)
	a := bg.A + fga

	return fcolor.RGBAF64{R: r, G: g, B: b, A: a}
}