// HardLight multiplies or screens the colours, depending on the blend // colour. The effect is similar to shining a harsh spotlight on the image. func HardLight(a, b image.Image) image.Image { return BlendPixels(a, b, func(c, d color.Color) color.Color { i, j, k, l := utils.NormalisedRGBAf(c) m, n, o, p := utils.NormalisedRGBAf(d) f := func(i, j float64) float64 { if j > 128 { return 255 - ((255-2*(j-128))*(255-i))/256 } return (2 * j * i) / 256 } r := f(i, m) g := f(j, n) b := f(k, o) a := p + l*(1-p) return color.NRGBA{ uint8(utils.Truncatef(r)), uint8(utils.Truncatef(g)), uint8(utils.Truncatef(b)), uint8(utils.Truncatef(a * 255)), } }) }
// cb is backdrop colour, cs is source (blend layer) colour func BlendPixel(cb, cs color.Color, f Blender) color.Color { // Uses methods described in "PDF Reference, Third Edition" from Adobe // see: http://www.adobe.com/devnet/pdf/pdf_reference_archive.html // result colour cr := f(cb, cs) rb, gb, bb, ab := utils.RatioRGBA(cb) rs, gs, bs, as := utils.RatioRGBA(cs) rr, gr, br, _ := utils.RatioRGBA(cr) // Color compositing formula, expanded form. (Section 7.2.5) red := ((1 - as) * ab * rb) + ((1 - ab) * as * rs) + (ab * as * rr) green := ((1 - as) * ab * gb) + ((1 - ab) * as * gs) + (ab * as * gr) blue := ((1 - as) * ab * bb) + ((1 - ab) * as * bs) + (ab * as * br) // Union function. (Section 7.2.6) alpha := ab + as - (ab * as) return color.RGBA{ uint8(utils.Truncatef(red * 255)), uint8(utils.Truncatef(green * 255)), uint8(utils.Truncatef(blue * 255)), uint8(utils.Truncatef(alpha * 255)), } }
func ratioNRGBA(r, g, b, a float64) color.Color { return color.NRGBA{ uint8(utils.Truncatef(r * 255)), uint8(utils.Truncatef(g * 255)), uint8(utils.Truncatef(b * 255)), uint8(utils.Truncatef(a * 255)), } }
func AdjustC(value float64) utils.Composable { return func(c color.Color) color.Color { r, g, b, a := utils.RatioRGBA(c) r = utils.Truncatef(math.Pow(r, 1/value) * 255) g = utils.Truncatef(math.Pow(g, 1/value) * 255) b = utils.Truncatef(math.Pow(b, 1/value) * 255) return color.NRGBA{uint8(r), uint8(g), uint8(b), uint8(a * 255)} } }
func AdjustC(value float64) utils.Composable { return func(c color.Color) color.Color { r, g, b, a := utils.RatioRGBA(c) r = utils.Truncatef((((r - 0.5) * value) + 0.5) * 255) g = utils.Truncatef((((g - 0.5) * value) + 0.5) * 255) b = utils.Truncatef((((b - 0.5) * value) + 0.5) * 255) a = a * 255 return color.NRGBA{uint8(r), uint8(g), uint8(b), uint8(a)} } }
// BUG: Conversion is broken _somewhere_, check against IM results. func (col HSLA) RGBA() (red, green, blue, alpha uint32) { h := col.H s := col.S l := col.L a := col.A h = math.Mod(h, 360) c := (1.0 - math.Abs(2.0*l-1.0)) * s hdash := h / 60.0 x := c * (1 - math.Abs(math.Mod(hdash, 2)-1)) var r, g, b float64 if h == 0 { r = 0 g = 0 b = 0 } else if hdash < 1 { r = c g = x b = 0 } else if hdash < 2 { r = x g = c b = 0 } else if hdash < 3 { r = 0 g = c b = x } else if hdash < 4 { r = 0 g = x b = c } else if hdash < 5 { r = x g = 0 b = c } else if hdash < 6 { r = c g = 0 b = x } m := l - 0.5*c red = uint32(utils.Truncatef((r+m)*a*255)) << 8 green = uint32(utils.Truncatef((g+m)*a*255)) << 8 blue = uint32(utils.Truncatef((b+m)*a*255)) << 8 alpha = uint32(a*255) << 8 return }
func (col HSIA) RGBA() (red, green, blue, alpha uint32) { var r, g, b float64 h := col.H s := col.S i := col.I a := col.A // normalise h h = math.Mod(h, 360) // need h in radians for trig. calculations hrad := h // need hrad to be in interval [0, 2/3*Pi) if h >= 240 { hrad -= 240 } else if h >= 120 { hrad -= 120 } // finally do the conversion to radians hrad *= math.Pi / 180 x := i * (1 - s) y := i * (1 + (s*math.Cos(hrad))/math.Cos(math.Pi/3-hrad)) z := 3*i - (x + y) if h < 120 { b = x r = y g = z } else if h < 240 { r = x g = y b = z } else { g = x b = y r = z } red = uint32(utils.Truncatef(r*a*255)) << 8 green = uint32(utils.Truncatef(g*a*255)) << 8 blue = uint32(utils.Truncatef(b*a*255)) << 8 alpha = uint32(a*255) << 8 return }
// Overlay multiplies or screens the colours, depending on the base colour. func Overlay(a, b image.Image) image.Image { return BlendPixels(a, b, func(c, d color.Color) color.Color { i, j, k, l := utils.NormalisedRGBAf(c) m, n, o, p := utils.NormalisedRGBAf(d) r := (i / 255) * (i + ((2*m)/255)*(255-i)) g := (j / 255) * (j + ((2*n)/255)*(255-j)) b := (k / 255) * (k + ((2*o)/255)*(255-k)) a := p + l*(1-p) return color.NRGBA{ uint8(utils.Truncatef(r)), uint8(utils.Truncatef(g)), uint8(utils.Truncatef(b)), uint8(utils.Truncatef(a * 255)), } }) }
// BlendPixels takes the base and blend images and applies the given Blender to // each of their pixel pairs. func BlendPixels(a, b image.Image, f Blender) image.Image { ba := a.Bounds() bb := b.Bounds() width := int(utils.Min(uint32(ba.Dx()), uint32(bb.Dx()))) height := int(utils.Min(uint32(ba.Dy()), uint32(bb.Dy()))) result := image.NewRGBA(image.Rect(0, 0, width, height)) for y := 0; y < height; y++ { for x := 0; x < width; x++ { // Uses methods described in "PDF Reference, Third Edition" from Adobe // see: http://www.adobe.com/devnet/pdf/pdf_reference_archive.html // backdrop colour cb := a.At(x, y) // source colour cs := b.At(x, y) // result colour cr := f(cb, cs) rb, gb, bb, ab := utils.RatioRGBA(cb) rs, gs, bs, as := utils.RatioRGBA(cs) rr, gr, br, _ := utils.RatioRGBA(cr) // Color compositing formula, expanded form. (Section 7.2.5) red := ((1 - as) * ab * rb) + ((1 - ab) * as * rs) + (ab * as * rr) green := ((1 - as) * ab * gb) + ((1 - ab) * as * gs) + (ab * as * gr) blue := ((1 - as) * ab * bb) + ((1 - ab) * as * bs) + (ab * as * br) // Union function. (Section 7.2.6) alpha := ab + as - (ab * as) result.Set(x, y, color.RGBA{ uint8(utils.Truncatef(red * 255)), uint8(utils.Truncatef(green * 255)), uint8(utils.Truncatef(blue * 255)), uint8(utils.Truncatef(alpha * 255)), }) } } return result }
// UnsharpMask sharpens the given Image using the unsharp mask technique. // Basically the image is blurred, then subtracted from the original for // differences above the threshold value. func UnsharpMask(in image.Image, radius int, sigma, amount, threshold float64) image.Image { blurred := blur.Gaussian(in, radius, sigma, blur.IGNORE) bounds := in.Bounds() out := image.NewRGBA(bounds) // Absolute difference between a and b, returns float64 between 0 and 1. diff := func(a, b float64) float64 { if a > b { return a - b } return b - a } for y := bounds.Min.Y; y < bounds.Max.Y; y++ { for x := bounds.Min.X; x < bounds.Max.X; x++ { ar, ag, ab, aa := utils.RatioRGBA(in.At(x, y)) br, bg, bb, _ := utils.RatioRGBA(blurred.At(x, y)) if diff(ar, br) >= threshold { ar = amount*(ar-br) + ar } if diff(ag, bg) >= threshold { ag = amount*(ag-bg) + ag } if diff(ab, bb) >= threshold { ab = amount*(ab-bb) + ab } out.Set(x, y, color.NRGBA{ uint8(utils.Truncatef(ar * 255)), uint8(utils.Truncatef(ag * 255)), uint8(utils.Truncatef(ab * 255)), uint8(aa * 255), }) } } return out }
func SigmoidalC(factor, midpoint float64) utils.Composable { alpha := midpoint beta := factor f := func(u float64) float64 { return (1.0/(1.0+math.Exp(beta*(alpha-u))) - 1.0/(1.0+math.Exp(beta))) / (1.0/(1.0+math.Exp(beta*(alpha-1))) - 1.0/(1.0+math.Exp(beta*alpha))) } return func(c color.Color) color.Color { r, g, b, a := utils.RatioRGBA(c) r = utils.Truncatef(f(r) * 255) g = utils.Truncatef(f(g) * 255) b = utils.Truncatef(f(b) * 255) a = a * 255 return color.NRGBA{uint8(r), uint8(g), uint8(b), uint8(a)} } }
func setAlpha(c color.Color, v float64) color.Color { r, g, b, _ := utils.NormalisedRGBA(c) v = utils.Truncatef(255 * v) return color.NRGBA{uint8(r), uint8(g), uint8(b), uint8(v)} }
func setBlue(c color.Color, v float64) color.Color { r, g, _, a := utils.NormalisedRGBA(c) v = utils.Truncatef(255 * v) return color.NRGBA{uint8(r), uint8(g), uint8(v), uint8(a)} }
func setGreen(c color.Color, v float64) color.Color { r, _, b, a := utils.NormalisedRGBA(c) v = utils.Truncatef(255 * v) return color.NRGBA{uint8(r), uint8(v), uint8(b), uint8(a)} }
func setRed(c color.Color, v float64) color.Color { _, g, b, a := utils.NormalisedRGBA(c) v = utils.Truncatef(255 * v) return color.NRGBA{uint8(v), uint8(g), uint8(b), uint8(a)} }
func Convolve(in image.Image, weights Kernel, style Style) image.Image { bnds := in.Bounds() mid := weights.Mid() o := image.NewRGBA(bnds) for y := bnds.Min.Y; y < bnds.Max.Y; y++ { for x := bnds.Min.X; x < bnds.Max.X; x++ { var r, g, b, a, offset float64 for oy := 0; oy < weights.Height(); oy++ { for ox := 0; ox < weights.Width(); ox++ { factor := weights[oy][ox] pt := image.Pt(x+ox-mid.X, y+oy-mid.Y) if pt == weights.Mid() { // Ignore! } else if pt.In(bnds) { or, og, ob, oa := in.At(pt.X, pt.Y).RGBA() r += float64(or) * factor g += float64(og) * factor b += float64(ob) * factor a += float64(oa) * factor } else { switch style { case CLAMP: offset += factor case WRAP: if pt.X >= bnds.Max.X { pt.X = pt.X - bnds.Max.X } else if pt.X < bnds.Min.X { pt.X = bnds.Dx() + pt.X } if pt.Y >= bnds.Max.Y { pt.Y = pt.Y - bnds.Max.Y } else if pt.Y < bnds.Min.Y { pt.Y = bnds.Dy() + pt.Y } or, og, ob, oa := in.At(pt.X, pt.Y).RGBA() r += float64(or) * factor g += float64(og) * factor b += float64(ob) * factor a += float64(oa) * factor } } } } if offset != 0 && style == CLAMP { or, og, ob, oa := in.At(x, y).RGBA() r += float64(or) * offset g += float64(og) * offset b += float64(ob) * offset a += float64(oa) * offset } o.Set(x, y, color.RGBA{ uint8(utils.Truncatef(r / 255)), uint8(utils.Truncatef(g / 255)), uint8(utils.Truncatef(b / 255)), uint8(utils.Truncatef(a / 255)), }) } } return o }