clone.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. /*Package clone provides image cloning function.*/
  2. package clone
  3. import (
  4. "image"
  5. "image/draw"
  6. "github.com/anthonynsimon/bild/parallel"
  7. )
  8. // PadMethod is the method used to fill padded pixels.
  9. type PadMethod uint8
  10. const (
  11. // NoFill leaves the padded pixels empty.
  12. NoFill = iota
  13. // EdgeExtend extends the closest edge pixel.
  14. EdgeExtend
  15. // EdgeWrap wraps around the pixels of an image.
  16. EdgeWrap
  17. )
  18. // AsRGBA returns an RGBA copy of the supplied image.
  19. func AsRGBA(src image.Image) *image.RGBA {
  20. bounds := src.Bounds()
  21. img := image.NewRGBA(bounds)
  22. draw.Draw(img, bounds, src, bounds.Min, draw.Src)
  23. return img
  24. }
  25. // AsShallowRGBA tries to cast to image.RGBA to get reference. Otherwise makes a copy
  26. func AsShallowRGBA(src image.Image) *image.RGBA {
  27. if rgba, ok := src.(*image.RGBA); ok {
  28. return rgba
  29. }
  30. return AsRGBA(src)
  31. }
  32. // Pad returns an RGBA copy of the src image parameter with its edges padded
  33. // using the supplied PadMethod.
  34. // Parameter padX and padY correspond to the amount of padding to be applied
  35. // on each side.
  36. // Parameter m is the PadMethod to fill the new pixels.
  37. //
  38. // Usage example:
  39. //
  40. // result := Pad(img, 5,5, EdgeExtend)
  41. func Pad(src image.Image, padX, padY int, m PadMethod) *image.RGBA {
  42. var result *image.RGBA
  43. switch m {
  44. case EdgeExtend:
  45. result = extend(src, padX, padY)
  46. case NoFill:
  47. result = noFill(src, padX, padY)
  48. case EdgeWrap:
  49. result = wrap(src, padX, padY)
  50. default:
  51. result = extend(src, padX, padY)
  52. }
  53. return result
  54. }
  55. func noFill(img image.Image, padX, padY int) *image.RGBA {
  56. srcBounds := img.Bounds()
  57. paddedW, paddedH := srcBounds.Dx()+2*padX, srcBounds.Dy()+2*padY
  58. newBounds := image.Rect(0, 0, paddedW, paddedH)
  59. fillBounds := image.Rect(padX, padY, padX+srcBounds.Dx(), padY+srcBounds.Dy())
  60. dst := image.NewRGBA(newBounds)
  61. draw.Draw(dst, fillBounds, img, srcBounds.Min, draw.Src)
  62. return dst
  63. }
  64. func extend(img image.Image, padX, padY int) *image.RGBA {
  65. dst := noFill(img, padX, padY)
  66. paddedW, paddedH := dst.Bounds().Dx(), dst.Bounds().Dy()
  67. parallel.Line(paddedH, func(start, end int) {
  68. for y := start; y < end; y++ {
  69. iy := y
  70. if iy < padY {
  71. iy = padY
  72. } else if iy >= paddedH-padY {
  73. iy = paddedH - padY - 1
  74. }
  75. for x := 0; x < paddedW; x++ {
  76. ix := x
  77. if ix < padX {
  78. ix = padX
  79. } else if x >= paddedW-padX {
  80. ix = paddedW - padX - 1
  81. } else if iy == y {
  82. // This only enters if we are not in a y-padded area or
  83. // x-padded area, so nothing to extend here.
  84. // So simply jump to the next padded-x index.
  85. x = paddedW - padX - 1
  86. continue
  87. }
  88. dstPos := y*dst.Stride + x*4
  89. edgePos := iy*dst.Stride + ix*4
  90. dst.Pix[dstPos+0] = dst.Pix[edgePos+0]
  91. dst.Pix[dstPos+1] = dst.Pix[edgePos+1]
  92. dst.Pix[dstPos+2] = dst.Pix[edgePos+2]
  93. dst.Pix[dstPos+3] = dst.Pix[edgePos+3]
  94. }
  95. }
  96. })
  97. return dst
  98. }
  99. func wrap(img image.Image, padX, padY int) *image.RGBA {
  100. dst := noFill(img, padX, padY)
  101. paddedW, paddedH := dst.Bounds().Dx(), dst.Bounds().Dy()
  102. parallel.Line(paddedH, func(start, end int) {
  103. for y := start; y < end; y++ {
  104. iy := y
  105. if iy < padY {
  106. iy = (paddedH - padY) - ((padY - y) % (paddedH - padY*2))
  107. } else if iy >= paddedH-padY {
  108. iy = padY - ((padY - y) % (paddedH - padY*2))
  109. }
  110. for x := 0; x < paddedW; x++ {
  111. ix := x
  112. if ix < padX {
  113. ix = (paddedW - padX) - ((padX - x) % (paddedW - padX*2))
  114. } else if ix >= paddedW-padX {
  115. ix = padX - ((padX - x) % (paddedW - padX*2))
  116. } else if iy == y {
  117. // This only enters if we are not in a y-padded area or
  118. // x-padded area, so nothing to extend here.
  119. // So simply jump to the next padded-x index.
  120. x = paddedW - padX - 1
  121. continue
  122. }
  123. dstPos := y*dst.Stride + x*4
  124. edgePos := iy*dst.Stride + ix*4
  125. dst.Pix[dstPos+0] = dst.Pix[edgePos+0]
  126. dst.Pix[dstPos+1] = dst.Pix[edgePos+1]
  127. dst.Pix[dstPos+2] = dst.Pix[edgePos+2]
  128. dst.Pix[dstPos+3] = dst.Pix[edgePos+3]
  129. }
  130. }
  131. })
  132. return dst
  133. }