func LinearC(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)} } }
func SigmoidalC(factor, midpoint float64) utils.Composable { sigmoidal := func(x float64) float64 { return 1.0 / (1.0 + math.Exp(factor*(midpoint-x))) } // Pre-compute useful terms sig0 := sigmoidal(0.0) sig1 := sigmoidal(1.0) var scaledSigmoidal func(float64) float64 if factor == 0 { scaledSigmoidal = func(x float64) float64 { return x } } else if factor > 0 { scaledSigmoidal = func(x float64) float64 { return (sigmoidal(x) - sig0) / (sig1 - sig0) } } else { scaledSigmoidal = func(x float64) float64 { argument := (sig1-sig0)*x + sig0 var clamped float64 if argument < Epsilon { clamped = Epsilon } else { if argument > 1-Epsilon { clamped = 1 - Epsilon } else { clamped = argument } } return midpoint - math.Log(1.0/clamped-1.0)/factor } } return func(c color.Color) color.Color { r, g, b, a := utils.RatioRGBA(c) r = utils.Truncatef(scaledSigmoidal(r) * 255) g = utils.Truncatef(scaledSigmoidal(g) * 255) b = utils.Truncatef(scaledSigmoidal(b) * 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 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 }
// 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 }
// AdjustC returns a Composable function that increases the saturation and // decreases the lightness of unsaturated colours. // // Uses the same method as Darktable: // https://github.com/darktable-org/darktable/blob/24c4a087fd020df587b5260f438bfaf494203cec/src/iop/vibrance.c func AdjustC(amount float64) utils.Composable { return func(c color.Color) color.Color { r, g, b, a := utils.RatioRGBA(c) ll, la, lb := colorful.Color{r, g, b}.Lab() // saturation weight [0, 1] sw := math.Sqrt(la*la+lb*lb) / 2 ls := 1.0 - amount*sw*0.25 ss := 1.0 + amount*sw ll *= ls la *= ss lb *= ss f := colorful.Lab(ll, la, lb) fr := utils.Truncatef(f.R * 255) fg := utils.Truncatef(f.G * 255) fb := utils.Truncatef(f.B * 255) return color.NRGBA{uint8(fr), uint8(fg), uint8(fb), uint8(a * 255)} } }
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 }
func (_ blueCh) Set(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 (_ redCh) Set(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 (_ alphaCh) Set(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)} }