// 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 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)} } }
func hsiaModel(c color.Color) color.Color { if _, ok := c.(HSIA); ok { return c } r, g, b, a := utils.RatioRGBA(c) maxi := utils.Maxf(r, g, b) mini := utils.Minf(r, g, b) chroma := maxi - mini // Work out hue hdash := 0.0 if chroma == 0 { hdash = 0 } else if maxi == r { hdash = math.Mod((g-b)/chroma, 6) } else if maxi == g { hdash = (b-r)/chroma + 2.0 } else if maxi == b { hdash = (r-g)/chroma + 4.0 } hue := hdash * 60 if chroma == 0 { hue = 0 } // Work out intensity intensity := (r + g + b) / 3 // Work out saturation saturation := 0.0 if chroma != 0 { saturation = 1 - mini/intensity } // prefer positive hues if hue < 0 { hue += 360 } return HSIA{hue, saturation, intensity, a} }
func hslaModel(c color.Color) color.Color { if _, ok := c.(HSLA); ok { return c } r, g, b, a := utils.RatioRGBA(c) maxi := utils.Maxf(r, g, b) mini := utils.Minf(r, g, b) chroma := maxi - mini // Work out hue hdash := 0.0 if chroma == 0 { hdash = 0 } else if maxi == r { hdash = math.Mod((g-b)/chroma, 6) } else if maxi == g { hdash = (b-r)/chroma + 2.0 } else if maxi == b { hdash = (r-g)/chroma + 4.0 } hue := hdash * 60 if chroma == 0 { hue = 0 } // Work out lightness lightness := 0.5 * (maxi + mini) // Work out saturation saturation := 0.0 if chroma != 0 { saturation = chroma / (1 - math.Abs(2*lightness-1)) } return HSLA{hue, saturation, lightness, a} }
func hsvaModel(c color.Color) color.Color { if _, ok := c.(HSVA); ok { return c } r, g, b, a := utils.RatioRGBA(c) maxi := utils.Maxf(r, g, b) mini := utils.Minf(r, g, b) chroma := maxi - mini // Work out hue hdash := 0.0 if chroma == 0 { hdash = 0 } else if maxi == r { hdash = math.Mod((g-b)/chroma, 6) } else if maxi == g { hdash = (b-r)/chroma + 2.0 } else if maxi == b { hdash = (r-g)/chroma + 4.0 } hue := hdash * 60 if chroma == 0 { hue = 0 } // Work out value value := maxi // Work out saturation saturation := 0.0 if chroma != 0 { saturation = chroma / value } return HSVA{hue, saturation, value, a} }
// 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 (_ alphaCh) Get(c color.Color) float64 { _, _, _, a := utils.RatioRGBA(c) return a }
func (_ blueCh) Get(c color.Color) float64 { _, _, b, _ := utils.RatioRGBA(c) return b }
func (_ greenCh) Get(c color.Color) float64 { _, g, _, _ := utils.RatioRGBA(c) return g }
func (_ redCh) Get(c color.Color) float64 { r, _, _, _ := utils.RatioRGBA(c) return r }