convolution.go 13 KB

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