github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/gift/colors.go (about) 1 package gift 2 3 import ( 4 "image" 5 "image/draw" 6 "math" 7 ) 8 9 func prepareLut(lutSize int, fn func(float32) float32) []float32 { 10 lut := make([]float32, lutSize) 11 q := 1 / float32(lutSize-1) 12 for v := 0; v < lutSize; v++ { 13 u := float32(v) * q 14 lut[v] = fn(u) 15 } 16 return lut 17 } 18 19 func getFromLut(lut []float32, u float32) float32 { 20 v := int(u*float32(len(lut)-1) + 0.5) 21 return lut[v] 22 } 23 24 type colorchanFilter struct { 25 fn func(float32) float32 26 lut bool 27 } 28 29 func (p *colorchanFilter) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) { 30 dstBounds = image.Rect(0, 0, srcBounds.Dx(), srcBounds.Dy()) 31 return 32 } 33 34 func (p *colorchanFilter) Draw(dst draw.Image, src image.Image, options *Options) { 35 if options == nil { 36 options = &defaultOptions 37 } 38 39 srcb := src.Bounds() 40 dstb := dst.Bounds() 41 pixGetter := newPixelGetter(src) 42 pixSetter := newPixelSetter(dst) 43 44 var useLut bool 45 var lut []float32 46 47 useLut = false 48 if p.lut { 49 var lutSize int 50 51 it := pixGetter.imgType 52 if it == itNRGBA || it == itRGBA || it == itGray || it == itYCbCr { 53 lutSize = 0xff + 1 54 } else { 55 lutSize = 0xffff + 1 56 } 57 58 numCalculations := srcb.Dx() * srcb.Dy() * 3 59 if numCalculations > lutSize*2 { 60 useLut = true 61 lut = prepareLut(lutSize, p.fn) 62 } 63 } 64 65 parallelize(options.Parallelization, srcb.Min.Y, srcb.Max.Y, func(pmin, pmax int) { 66 for y := pmin; y < pmax; y++ { 67 for x := srcb.Min.X; x < srcb.Max.X; x++ { 68 px := pixGetter.getPixel(x, y) 69 if useLut { 70 px.R = getFromLut(lut, px.R) 71 px.G = getFromLut(lut, px.G) 72 px.B = getFromLut(lut, px.B) 73 } else { 74 px.R = p.fn(px.R) 75 px.G = p.fn(px.G) 76 px.B = p.fn(px.B) 77 } 78 pixSetter.setPixel(dstb.Min.X+x-srcb.Min.X, dstb.Min.Y+y-srcb.Min.Y, px) 79 } 80 } 81 }) 82 } 83 84 // Invert creates a filter that negates the colors of an image. 85 func Invert() Filter { 86 return &colorchanFilter{ 87 fn: func(x float32) float32 { 88 return 1.0 - x 89 }, 90 lut: false, 91 } 92 } 93 94 // ColorspaceSRGBToLinear creates a filter that converts the colors of an image from sRGB to linear RGB. 95 func ColorspaceSRGBToLinear() Filter { 96 return &colorchanFilter{ 97 fn: func(x float32) float32 { 98 if x <= 0.04045 { 99 return x / 12.92 100 } 101 return float32(math.Pow(float64((x+0.055)/1.055), 2.4)) 102 }, 103 lut: true, 104 } 105 } 106 107 // ColorspaceLinearToSRGB creates a filter that converts the colors of an image from linear RGB to sRGB. 108 func ColorspaceLinearToSRGB() Filter { 109 return &colorchanFilter{ 110 fn: func(x float32) float32 { 111 if x <= 0.0031308 { 112 return x * 12.92 113 } 114 return float32(1.055*math.Pow(float64(x), 1.0/2.4) - 0.055) 115 }, 116 lut: true, 117 } 118 } 119 120 // Gamma creates a filter that performs a gamma correction on an image. 121 // The gamma parameter must be positive. Gamma = 1.0 gives the original image. 122 // Gamma less than 1.0 darkens the image and gamma greater than 1.0 lightens it. 123 func Gamma(gamma float32) Filter { 124 e := 1.0 / maxf32(gamma, 1.0e-5) 125 return &colorchanFilter{ 126 fn: func(x float32) float32 { 127 return powf32(x, e) 128 }, 129 lut: true, 130 } 131 } 132 133 func sigmoid(a, b, x float32) float32 { 134 return 1 / (1 + expf32(b*(a-x))) 135 } 136 137 // Sigmoid creates a filter that changes the contrast of an image using a sigmoidal function and returns the adjusted image. 138 // It's a non-linear contrast change useful for photo adjustments as it preserves highlight and shadow detail. 139 // The midpoint parameter is the midpoint of contrast that must be between 0 and 1, typically 0.5. 140 // The factor parameter indicates how much to increase or decrease the contrast, typically in range (-10, 10). 141 // If the factor parameter is positive the image contrast is increased otherwise the contrast is decreased. 142 // 143 // Example: 144 // 145 // g := gift.New( 146 // gift.Sigmoid(0.5, 3.0), 147 // ) 148 // dst := image.NewRGBA(g.Bounds(src.Bounds())) 149 // g.Draw(dst, src) 150 // 151 func Sigmoid(midpoint, factor float32) Filter { 152 a := minf32(maxf32(midpoint, 0.0), 1.0) 153 b := absf32(factor) 154 sig0 := sigmoid(a, b, 0) 155 sig1 := sigmoid(a, b, 1) 156 e := float32(1.0e-5) 157 158 return &colorchanFilter{ 159 fn: func(x float32) float32 { 160 if factor == 0 { 161 return x 162 } else if factor > 0 { 163 sig := sigmoid(a, b, x) 164 return (sig - sig0) / (sig1 - sig0) 165 } else { 166 arg := minf32(maxf32((sig1-sig0)*x+sig0, e), 1.0-e) 167 return a - logf32(1.0/arg-1.0)/b 168 } 169 }, 170 lut: true, 171 } 172 } 173 174 // Contrast creates a filter that changes the contrast of an image. 175 // The percentage parameter must be in range (-100, 100). The percentage = 0 gives the original image. 176 // The percentage = -100 gives solid grey image. The percentage = 100 gives an overcontrasted image. 177 func Contrast(percentage float32) Filter { 178 if percentage == 0 { 179 return ©imageFilter{} 180 } 181 182 p := 1 + minf32(maxf32(percentage, -100.0), 100.0)/100.0 183 184 return &colorchanFilter{ 185 fn: func(x float32) float32 { 186 if 0 <= p && p <= 1 { 187 return 0.5 + (x-0.5)*p 188 } else if 1 < p && p < 2 { 189 return 0.5 + (x-0.5)*(1/(2.0-p)) 190 } else { 191 if x < 0.5 { 192 return 0.0 193 } 194 return 1.0 195 } 196 }, 197 lut: false, 198 } 199 } 200 201 // Brightness creates a filter that changes the brightness of an image. 202 // The percentage parameter must be in range (-100, 100). The percentage = 0 gives the original image. 203 // The percentage = -100 gives solid black image. The percentage = 100 gives solid white image. 204 func Brightness(percentage float32) Filter { 205 if percentage == 0 { 206 return ©imageFilter{} 207 } 208 209 shift := minf32(maxf32(percentage, -100.0), 100.0) / 100.0 210 211 return &colorchanFilter{ 212 fn: func(x float32) float32 { 213 return x + shift 214 }, 215 lut: false, 216 } 217 } 218 219 type colorFilter struct { 220 fn func(pixel) pixel 221 } 222 223 func (p *colorFilter) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) { 224 dstBounds = image.Rect(0, 0, srcBounds.Dx(), srcBounds.Dy()) 225 return 226 } 227 228 func (p *colorFilter) Draw(dst draw.Image, src image.Image, options *Options) { 229 if options == nil { 230 options = &defaultOptions 231 } 232 233 srcb := src.Bounds() 234 dstb := dst.Bounds() 235 pixGetter := newPixelGetter(src) 236 pixSetter := newPixelSetter(dst) 237 238 parallelize(options.Parallelization, srcb.Min.Y, srcb.Max.Y, func(pmin, pmax int) { 239 for y := pmin; y < pmax; y++ { 240 for x := srcb.Min.X; x < srcb.Max.X; x++ { 241 px := pixGetter.getPixel(x, y) 242 pixSetter.setPixel(dstb.Min.X+x-srcb.Min.X, dstb.Min.Y+y-srcb.Min.Y, p.fn(px)) 243 } 244 } 245 }) 246 } 247 248 // Grayscale creates a filter that produces a grayscale version of an image. 249 func Grayscale() Filter { 250 return &colorFilter{ 251 fn: func(px pixel) pixel { 252 y := 0.299*px.R + 0.587*px.G + 0.114*px.B 253 return pixel{y, y, y, px.A} 254 }, 255 } 256 } 257 258 // Sepia creates a filter that produces a sepia-toned version of an image. 259 // The percentage parameter specifies how much the image should be adjusted. It must be in the range (0, 100) 260 // 261 // Example: 262 // 263 // g := gift.New( 264 // gift.Sepia(100), 265 // ) 266 // dst := image.NewRGBA(g.Bounds(src.Bounds())) 267 // g.Draw(dst, src) 268 // 269 func Sepia(percentage float32) Filter { 270 adjustAmount := minf32(maxf32(percentage, 0.0), 100.0) / 100.0 271 rr := 1.0 - 0.607*adjustAmount 272 rg := 0.769 * adjustAmount 273 rb := 0.189 * adjustAmount 274 gr := 0.349 * adjustAmount 275 gg := 1.0 - 0.314*adjustAmount 276 gb := 0.168 * adjustAmount 277 br := 0.272 * adjustAmount 278 bg := 0.534 * adjustAmount 279 bb := 1.0 - 0.869*adjustAmount 280 return &colorFilter{ 281 fn: func(px pixel) pixel { 282 r := px.R*rr + px.G*rg + px.B*rb 283 g := px.R*gr + px.G*gg + px.B*gb 284 b := px.R*br + px.G*bg + px.B*bb 285 return pixel{r, g, b, px.A} 286 }, 287 } 288 } 289 290 func convertHSLToRGB(h, s, l float32) (float32, float32, float32) { 291 if s == 0.0 { 292 return l, l, l 293 } 294 295 _v := func(p, q, t float32) float32 { 296 if t < 0.0 { 297 t += 1.0 298 } 299 if t > 1.0 { 300 t -= 1.0 301 } 302 if t < 1.0/6.0 { 303 return p + (q-p)*6.0*t 304 } 305 if t < 1.0/2.0 { 306 return q 307 } 308 if t < 2.0/3.0 { 309 return p + (q-p)*(2.0/3.0-t)*6.0 310 } 311 return p 312 } 313 314 var p, q float32 315 if l < 0.5 { 316 q = l * (1.0 + s) 317 } else { 318 q = l + s - l*s 319 } 320 p = 2.0*l - q 321 322 r := _v(p, q, h+1.0/3.0) 323 g := _v(p, q, h) 324 b := _v(p, q, h-1.0/3.0) 325 326 return r, g, b 327 } 328 329 func convertRGBToHSL(r, g, b float32) (float32, float32, float32) { 330 max := maxf32(r, maxf32(g, b)) 331 min := minf32(r, minf32(g, b)) 332 333 l := (max + min) / 2.0 334 335 if max == min { 336 return 0.0, 0.0, l 337 } 338 339 var h, s float32 340 d := max - min 341 if l > 0.5 { 342 s = d / (2.0 - max - min) 343 } else { 344 s = d / (max + min) 345 } 346 347 if r == max { 348 h = (g - b) / d 349 if g < b { 350 h += 6.0 351 } 352 } else if g == max { 353 h = (b-r)/d + 2.0 354 } else { 355 h = (r-g)/d + 4.0 356 } 357 h /= 6.0 358 359 return h, s, l 360 } 361 362 func normalizeHue(hue float32) float32 { 363 hue = hue - float32(int(hue)) 364 if hue < 0 { 365 hue++ 366 } 367 return hue 368 } 369 370 // Hue creates a filter that rotates the hue of an image. 371 // The shift parameter is the hue angle shift, typically in range (-180, 180). 372 // The shift = 0 gives the original image. 373 func Hue(shift float32) Filter { 374 p := normalizeHue(shift / 360.0) 375 if p == 0 { 376 return ©imageFilter{} 377 } 378 379 return &colorFilter{ 380 fn: func(px pixel) pixel { 381 h, s, l := convertRGBToHSL(px.R, px.G, px.B) 382 h = normalizeHue(h + p) 383 r, g, b := convertHSLToRGB(h, s, l) 384 return pixel{r, g, b, px.A} 385 }, 386 } 387 } 388 389 // Saturation creates a filter that changes the saturation of an image. 390 // The percentage parameter must be in range (-100, 500). The percentage = 0 gives the original image. 391 func Saturation(percentage float32) Filter { 392 p := 1 + minf32(maxf32(percentage, -100.0), 500.0)/100.0 393 if p == 1 { 394 return ©imageFilter{} 395 } 396 397 return &colorFilter{ 398 fn: func(px pixel) pixel { 399 h, s, l := convertRGBToHSL(px.R, px.G, px.B) 400 s *= p 401 if s > 1 { 402 s = 1 403 } 404 r, g, b := convertHSLToRGB(h, s, l) 405 return pixel{r, g, b, px.A} 406 }, 407 } 408 } 409 410 // Colorize creates a filter that produces a colorized version of an image. 411 // The hue parameter is the angle on the color wheel, typically in range (0, 360). 412 // The saturation parameter must be in range (0, 100). 413 // The percentage parameter specifies the strength of the effect, it must be in range (0, 100). 414 // 415 // Example: 416 // 417 // g := gift.New( 418 // gift.Colorize(240, 50, 100), // blue colorization, 50% saturation 419 // ) 420 // dst := image.NewRGBA(g.Bounds(src.Bounds())) 421 // g.Draw(dst, src) 422 // 423 func Colorize(hue, saturation, percentage float32) Filter { 424 h := normalizeHue(hue / 360) 425 s := minf32(maxf32(saturation, 0), 100) / 100 426 p := minf32(maxf32(percentage, 0), 100) / 100 427 if p == 0 { 428 return ©imageFilter{} 429 } 430 431 return &colorFilter{ 432 fn: func(px pixel) pixel { 433 _, _, l := convertRGBToHSL(px.R, px.G, px.B) 434 r, g, b := convertHSLToRGB(h, s, l) 435 px.R += (r - px.R) * p 436 px.G += (g - px.G) * p 437 px.B += (b - px.B) * p 438 return px 439 }, 440 } 441 } 442 443 // ColorBalance creates a filter that changes the color balance of an image. 444 // The percentage parameters for each color channel (red, green, blue) must be in range (-100, 500). 445 // 446 // Example: 447 // 448 // g := gift.New( 449 // gift.ColorBalance(20, -20, 0), // +20% red, -20% green 450 // ) 451 // dst := image.NewRGBA(g.Bounds(src.Bounds())) 452 // g.Draw(dst, src) 453 // 454 func ColorBalance(percentageRed, percentageGreen, percentageBlue float32) Filter { 455 pr := 1 + minf32(maxf32(percentageRed, -100), 500)/100 456 pg := 1 + minf32(maxf32(percentageGreen, -100), 500)/100 457 pb := 1 + minf32(maxf32(percentageBlue, -100), 500)/100 458 459 return &colorFilter{ 460 fn: func(px pixel) pixel { 461 px.R *= pr 462 px.G *= pg 463 px.B *= pb 464 return px 465 }, 466 } 467 } 468 469 // ColorFunc creates a filter that changes the colors of an image using custom function. 470 // The fn parameter specifies a function that takes red, green, blue and alpha channels of a pixel 471 // as float32 values in range (0, 1) and returns the modified channel values. 472 // 473 // Example: 474 // 475 // g := gift.New( 476 // gift.ColorFunc( 477 // func(r0, g0, b0, a0 float32) (r, g, b, a float32) { 478 // r = 1 - r0 // invert the red channel 479 // g = g0 + 0.1 // shift the green channel by 0.1 480 // b = 0 // set the blue channel to 0 481 // a = a0 // preserve the alpha channel 482 // return 483 // }, 484 // ), 485 // ) 486 // dst := image.NewRGBA(g.Bounds(src.Bounds())) 487 // g.Draw(dst, src) 488 // 489 func ColorFunc(fn func(r0, g0, b0, a0 float32) (r, g, b, a float32)) Filter { 490 return &colorFilter{ 491 fn: func(px pixel) pixel { 492 r, g, b, a := fn(px.R, px.G, px.B, px.A) 493 return pixel{r, g, b, a} 494 }, 495 } 496 }