github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/gift/convolution.go (about) 1 package gift 2 3 import ( 4 "image" 5 "image/draw" 6 "math" 7 ) 8 9 func prepareConvolutionWeights(kernel []float32, normalize bool) (int, []uvweight) { 10 size := int(math.Sqrt(float64(len(kernel)))) 11 if size%2 == 0 { 12 size-- 13 } 14 if size < 1 { 15 return 0, []uvweight{} 16 } 17 center := size / 2 18 19 weights := []uvweight{} 20 for i := 0; i < size; i++ { 21 for j := 0; j < size; j++ { 22 k := j*size + i 23 w := float32(0.0) 24 if k < len(kernel) { 25 w = kernel[k] 26 } 27 if w != 0.0 { 28 weights = append(weights, uvweight{u: i - center, v: j - center, weight: w}) 29 } 30 } 31 } 32 33 if !normalize { 34 return size, weights 35 } 36 37 var sum, sumpositive float32 38 for _, w := range weights { 39 sum += w.weight 40 if w.weight > 0 { 41 sumpositive += w.weight 42 } 43 } 44 45 var div float32 46 if sum != 0.0 { 47 div = sum 48 } else if sumpositive != 0.0 { 49 div = sumpositive 50 } else { 51 return size, weights 52 } 53 54 for i := 0; i < len(weights); i++ { 55 weights[i].weight /= div 56 } 57 58 return size, weights 59 } 60 61 type convolutionFilter struct { 62 kernel []float32 63 normalize bool 64 alpha bool 65 abs bool 66 delta float32 67 } 68 69 func (p *convolutionFilter) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) { 70 dstBounds = image.Rect(0, 0, srcBounds.Dx(), srcBounds.Dy()) 71 return 72 } 73 74 func (p *convolutionFilter) Draw(dst draw.Image, src image.Image, options *Options) { 75 if options == nil { 76 options = &defaultOptions 77 } 78 79 srcb := src.Bounds() 80 dstb := dst.Bounds() 81 82 if srcb.Dx() <= 0 || srcb.Dy() <= 0 { 83 return 84 } 85 86 ksize, weights := prepareConvolutionWeights(p.kernel, p.normalize) 87 kcenter := ksize / 2 88 89 if ksize < 1 { 90 copyimage(dst, src, options) 91 return 92 } 93 94 pixGetter := newPixelGetter(src) 95 pixSetter := newPixelSetter(dst) 96 97 parallelize(options.Parallelization, srcb.Min.Y, srcb.Max.Y, func(pmin, pmax int) { 98 // init temp rows 99 starty := pmin 100 rows := make([][]pixel, ksize) 101 for i := 0; i < ksize; i++ { 102 rowy := starty + i - kcenter 103 if rowy < srcb.Min.Y { 104 rowy = srcb.Min.Y 105 } else if rowy > srcb.Max.Y-1 { 106 rowy = srcb.Max.Y - 1 107 } 108 row := make([]pixel, srcb.Dx()) 109 pixGetter.getPixelRow(rowy, &row) 110 rows[i] = row 111 } 112 113 for y := pmin; y < pmax; y++ { 114 // calculate dst row 115 for x := srcb.Min.X; x < srcb.Max.X; x++ { 116 var r, g, b, a float32 117 for _, w := range weights { 118 wx := x + w.u 119 if wx < srcb.Min.X { 120 wx = srcb.Min.X 121 } else if wx > srcb.Max.X-1 { 122 wx = srcb.Max.X - 1 123 } 124 rowsx := wx - srcb.Min.X 125 rowsy := kcenter + w.v 126 127 px := rows[rowsy][rowsx] 128 r += px.R * w.weight 129 g += px.G * w.weight 130 b += px.B * w.weight 131 if p.alpha { 132 a += px.A * w.weight 133 } 134 } 135 if p.abs { 136 r = absf32(r) 137 g = absf32(g) 138 b = absf32(b) 139 if p.alpha { 140 a = absf32(a) 141 } 142 } 143 if p.delta != 0.0 { 144 r += p.delta 145 g += p.delta 146 b += p.delta 147 if p.alpha { 148 a += p.delta 149 } 150 } 151 if !p.alpha { 152 a = rows[kcenter][x-srcb.Min.X].A 153 } 154 pixSetter.setPixel(dstb.Min.X+x-srcb.Min.X, dstb.Min.Y+y-srcb.Min.Y, pixel{r, g, b, a}) 155 } 156 157 // rotate temp rows 158 if y < pmax-1 { 159 tmprow := rows[0] 160 for i := 0; i < ksize-1; i++ { 161 rows[i] = rows[i+1] 162 } 163 nextrowy := y + ksize/2 + 1 164 if nextrowy > srcb.Max.Y-1 { 165 nextrowy = srcb.Max.Y - 1 166 } 167 pixGetter.getPixelRow(nextrowy, &tmprow) 168 rows[ksize-1] = tmprow 169 } 170 } 171 }) 172 } 173 174 // Convolution creates a filter that applies a square convolution kernel to an image. 175 // The length of the kernel slice must be the square of an odd kernel size (e.g. 9 for 3x3 kernel, 25 for 5x5 kernel). 176 // Excessive slice members will be ignored. 177 // If normalize parameter is true, the kernel will be normalized before applying the filter. 178 // If alpha parameter is true, the alpha component of color will be filtered too. 179 // If abs parameter is true, absolute values of color components will be taken after doing calculations. 180 // If delta parameter is not zero, this value will be added to the filtered pixels. 181 // 182 // Example: 183 // 184 // // Apply the emboss filter to an image. 185 // g := gift.New( 186 // gift.Convolution( 187 // []float32{ 188 // -1, -1, 0, 189 // -1, 1, 1, 190 // 0, 1, 1, 191 // }, 192 // false, false, false, 0, 193 // ), 194 // ) 195 // dst := image.NewRGBA(g.Bounds(src.Bounds())) 196 // g.Draw(dst, src) 197 // 198 func Convolution(kernel []float32, normalize, alpha, abs bool, delta float32) Filter { 199 return &convolutionFilter{ 200 kernel: kernel, 201 normalize: normalize, 202 alpha: alpha, 203 abs: abs, 204 delta: delta, 205 } 206 } 207 208 // prepare pixel weights using convolution kernel. weights equal to 0 are excluded 209 func prepareConvolutionWeights1d(kernel []float32) (int, []uweight) { 210 size := len(kernel) 211 if size%2 == 0 { 212 size-- 213 } 214 if size < 1 { 215 return 0, []uweight{} 216 } 217 center := size / 2 218 weights := []uweight{} 219 for i := 0; i < size; i++ { 220 w := float32(0.0) 221 if i < len(kernel) { 222 w = kernel[i] 223 } 224 if w != 0 { 225 weights = append(weights, uweight{i - center, w}) 226 } 227 } 228 return size, weights 229 } 230 231 // calculate pixels for one line according to weights 232 func convolveLine(dstBuf []pixel, srcBuf []pixel, weights []uweight) { 233 max := len(srcBuf) - 1 234 if max < 0 { 235 return 236 } 237 for dstu := 0; dstu < len(srcBuf); dstu++ { 238 var r, g, b, a float32 239 for _, w := range weights { 240 k := dstu + w.u 241 if k < 0 { 242 k = 0 243 } else if k > max { 244 k = max 245 } 246 c := srcBuf[k] 247 wa := c.A * w.weight 248 r += c.R * wa 249 g += c.G * wa 250 b += c.B * wa 251 a += wa 252 } 253 if a != 0 { 254 r /= a 255 g /= a 256 b /= a 257 } 258 dstBuf[dstu] = pixel{r, g, b, a} 259 } 260 } 261 262 // fast vertical 1d convolution 263 func convolve1dv(dst draw.Image, src image.Image, kernel []float32, options *Options) { 264 srcb := src.Bounds() 265 dstb := dst.Bounds() 266 if srcb.Dx() <= 0 || srcb.Dy() <= 0 { 267 return 268 } 269 if kernel == nil || len(kernel) < 1 { 270 copyimage(dst, src, options) 271 return 272 } 273 _, weights := prepareConvolutionWeights1d(kernel) 274 pixGetter := newPixelGetter(src) 275 pixSetter := newPixelSetter(dst) 276 parallelize(options.Parallelization, srcb.Min.X, srcb.Max.X, func(pmin, pmax int) { 277 srcBuf := make([]pixel, srcb.Dy()) 278 dstBuf := make([]pixel, srcb.Dy()) 279 for x := pmin; x < pmax; x++ { 280 pixGetter.getPixelColumn(x, &srcBuf) 281 convolveLine(dstBuf, srcBuf, weights) 282 pixSetter.setPixelColumn(dstb.Min.X+x-srcb.Min.X, dstBuf) 283 } 284 }) 285 } 286 287 // fast horizontal 1d convolution 288 func convolve1dh(dst draw.Image, src image.Image, kernel []float32, options *Options) { 289 srcb := src.Bounds() 290 dstb := dst.Bounds() 291 if srcb.Dx() <= 0 || srcb.Dy() <= 0 { 292 return 293 } 294 if kernel == nil || len(kernel) < 1 { 295 copyimage(dst, src, options) 296 return 297 } 298 _, weights := prepareConvolutionWeights1d(kernel) 299 pixGetter := newPixelGetter(src) 300 pixSetter := newPixelSetter(dst) 301 parallelize(options.Parallelization, srcb.Min.Y, srcb.Max.Y, func(pmin, pmax int) { 302 srcBuf := make([]pixel, srcb.Dx()) 303 dstBuf := make([]pixel, srcb.Dx()) 304 for y := pmin; y < pmax; y++ { 305 pixGetter.getPixelRow(y, &srcBuf) 306 convolveLine(dstBuf, srcBuf, weights) 307 pixSetter.setPixelRow(dstb.Min.Y+y-srcb.Min.Y, dstBuf) 308 } 309 }) 310 } 311 312 func gaussianBlurKernel(x, sigma float32) float32 { 313 return float32(math.Exp(-float64(x*x)/float64(2*sigma*sigma)) / (float64(sigma) * math.Sqrt(2*math.Pi))) 314 } 315 316 type gausssianBlurFilter struct { 317 sigma float32 318 } 319 320 func (p *gausssianBlurFilter) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) { 321 dstBounds = image.Rect(0, 0, srcBounds.Dx(), srcBounds.Dy()) 322 return 323 } 324 325 func (p *gausssianBlurFilter) Draw(dst draw.Image, src image.Image, options *Options) { 326 if options == nil { 327 options = &defaultOptions 328 } 329 330 srcb := src.Bounds() 331 if srcb.Dx() <= 0 || srcb.Dy() <= 0 { 332 return 333 } 334 335 if p.sigma <= 0 { 336 copyimage(dst, src, options) 337 return 338 } 339 340 radius := int(math.Ceil(float64(p.sigma * 3.0))) 341 size := 2*radius + 1 342 center := radius 343 kernel := make([]float32, size) 344 345 kernel[center] = gaussianBlurKernel(0.0, p.sigma) 346 sum := kernel[center] 347 348 for i := 1; i <= radius; i++ { 349 f := gaussianBlurKernel(float32(i), p.sigma) 350 kernel[center-i] = f 351 kernel[center+i] = f 352 sum += 2 * f 353 } 354 355 for i := 0; i < len(kernel); i++ { 356 kernel[i] /= sum 357 } 358 359 tmp := createTempImage(srcb) 360 convolve1dh(tmp, src, kernel, options) 361 convolve1dv(dst, tmp, kernel, options) 362 } 363 364 // GaussianBlur creates a filter that applies a gaussian blur to an image. 365 // The sigma parameter must be positive and indicates how much the image will be blurred. 366 // Blur affected radius roughly equals 3 * sigma. 367 // 368 // Example: 369 // 370 // g := gift.New( 371 // gift.GaussianBlur(1.5), 372 // ) 373 // dst := image.NewRGBA(g.Bounds(src.Bounds())) 374 // g.Draw(dst, src) 375 // 376 func GaussianBlur(sigma float32) Filter { 377 return &gausssianBlurFilter{ 378 sigma: sigma, 379 } 380 } 381 382 type unsharpMaskFilter struct { 383 sigma float32 384 amount float32 385 thresold float32 386 } 387 388 func (p *unsharpMaskFilter) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) { 389 dstBounds = image.Rect(0, 0, srcBounds.Dx(), srcBounds.Dy()) 390 return 391 } 392 393 func unsharp(orig, blurred, amount, thresold float32) float32 { 394 dif := (orig - blurred) * amount 395 if absf32(dif) > absf32(thresold) { 396 return orig + dif 397 } 398 return orig 399 } 400 401 func (p *unsharpMaskFilter) Draw(dst draw.Image, src image.Image, options *Options) { 402 if options == nil { 403 options = &defaultOptions 404 } 405 406 srcb := src.Bounds() 407 dstb := dst.Bounds() 408 409 if srcb.Dx() <= 0 || srcb.Dy() <= 0 { 410 return 411 } 412 413 blurred := createTempImage(srcb) 414 blur := GaussianBlur(p.sigma) 415 blur.Draw(blurred, src, options) 416 417 pixGetterOrig := newPixelGetter(src) 418 pixGetterBlur := newPixelGetter(blurred) 419 pixelSetter := newPixelSetter(dst) 420 421 parallelize(options.Parallelization, srcb.Min.Y, srcb.Max.Y, func(pmin, pmax int) { 422 for y := pmin; y < pmax; y++ { 423 for x := srcb.Min.X; x < srcb.Max.X; x++ { 424 pxOrig := pixGetterOrig.getPixel(x, y) 425 pxBlur := pixGetterBlur.getPixel(x, y) 426 427 r := unsharp(pxOrig.R, pxBlur.R, p.amount, p.thresold) 428 g := unsharp(pxOrig.G, pxBlur.G, p.amount, p.thresold) 429 b := unsharp(pxOrig.B, pxBlur.B, p.amount, p.thresold) 430 a := unsharp(pxOrig.A, pxBlur.A, p.amount, p.thresold) 431 432 pixelSetter.setPixel(dstb.Min.X+x-srcb.Min.X, dstb.Min.Y+y-srcb.Min.Y, pixel{r, g, b, a}) 433 } 434 } 435 }) 436 } 437 438 // UnsharpMask creates a filter that sharpens an image. 439 // The sigma parameter is used in a gaussian function and affects the radius of effect. 440 // Sigma must be positive. Sharpen radius roughly equals 3 * sigma. 441 // The amount parameter controls how much darker and how much lighter the edge borders become. Typically between 0.5 and 1.5. 442 // The thresold parameter controls the minimum brightness change that will be sharpened. Typically between 0 and 0.05. 443 // 444 // Example: 445 // 446 // g := gift.New( 447 // gift.UnsharpMask(1.0, 1.0, 0.0), 448 // ) 449 // dst := image.NewRGBA(g.Bounds(src.Bounds())) 450 // g.Draw(dst, src) 451 // 452 func UnsharpMask(sigma, amount, thresold float32) Filter { 453 return &unsharpMaskFilter{ 454 sigma: sigma, 455 amount: amount, 456 thresold: thresold, 457 } 458 } 459 460 type meanFilter struct { 461 ksize int 462 disk bool 463 } 464 465 func (p *meanFilter) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) { 466 dstBounds = image.Rect(0, 0, srcBounds.Dx(), srcBounds.Dy()) 467 return 468 } 469 470 func (p *meanFilter) Draw(dst draw.Image, src image.Image, options *Options) { 471 if options == nil { 472 options = &defaultOptions 473 } 474 475 srcb := src.Bounds() 476 if srcb.Dx() <= 0 || srcb.Dy() <= 0 { 477 return 478 } 479 480 ksize := p.ksize 481 if ksize%2 == 0 { 482 ksize-- 483 } 484 485 if ksize <= 1 { 486 copyimage(dst, src, options) 487 return 488 } 489 490 if p.disk { 491 diskKernel := genDisk(p.ksize) 492 f := Convolution(diskKernel, true, true, false, 0.0) 493 f.Draw(dst, src, options) 494 } else { 495 kernel := make([]float32, ksize*ksize) 496 for i := range kernel { 497 kernel[i] = 1.0 498 } 499 f := Convolution(kernel, true, true, false, 0.0) 500 f.Draw(dst, src, options) 501 } 502 } 503 504 // Mean creates a local mean image filter. 505 // Takes an average across a neighborhood for each pixel. 506 // The ksize parameter is the kernel size. It must be an odd positive integer (for example: 3, 5, 7). 507 // If the disk parameter is true, a disk-shaped neighborhood will be used instead of a square neighborhood. 508 func Mean(ksize int, disk bool) Filter { 509 return &meanFilter{ 510 ksize: ksize, 511 disk: disk, 512 } 513 } 514 515 type hvConvolutionFilter struct { 516 hkernel, vkernel []float32 517 } 518 519 func (p *hvConvolutionFilter) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) { 520 dstBounds = image.Rect(0, 0, srcBounds.Dx(), srcBounds.Dy()) 521 return 522 } 523 524 func (p *hvConvolutionFilter) Draw(dst draw.Image, src image.Image, options *Options) { 525 if options == nil { 526 options = &defaultOptions 527 } 528 529 srcb := src.Bounds() 530 dstb := dst.Bounds() 531 532 if srcb.Dx() <= 0 || srcb.Dy() <= 0 { 533 return 534 } 535 536 tmph := createTempImage(srcb) 537 Convolution(p.hkernel, false, false, true, 0).Draw(tmph, src, options) 538 pixGetterH := newPixelGetter(tmph) 539 540 tmpv := createTempImage(srcb) 541 Convolution(p.vkernel, false, false, true, 0).Draw(tmpv, src, options) 542 pixGetterV := newPixelGetter(tmpv) 543 544 pixSetter := newPixelSetter(dst) 545 546 parallelize(options.Parallelization, srcb.Min.Y, srcb.Max.Y, func(pmin, pmax int) { 547 for y := pmin; y < pmax; y++ { 548 for x := srcb.Min.X; x < srcb.Max.X; x++ { 549 pxh := pixGetterH.getPixel(x, y) 550 pxv := pixGetterV.getPixel(x, y) 551 r := sqrtf32(pxh.R*pxh.R + pxv.R*pxv.R) 552 g := sqrtf32(pxh.G*pxh.G + pxv.G*pxv.G) 553 b := sqrtf32(pxh.B*pxh.B + pxv.B*pxv.B) 554 pixSetter.setPixel(dstb.Min.X+x-srcb.Min.X, dstb.Min.Y+y-srcb.Min.Y, pixel{r, g, b, pxh.A}) 555 } 556 } 557 }) 558 559 } 560 561 // Sobel creates a filter that applies a sobel operator to an image. 562 func Sobel() Filter { 563 return &hvConvolutionFilter{ 564 hkernel: []float32{-1, 0, 1, -2, 0, 2, -1, 0, 1}, 565 vkernel: []float32{-1, -2, -1, 0, 0, 0, 1, 2, 1}, 566 } 567 }