tools.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. package imaging
  2. import (
  3. "image"
  4. "math"
  5. )
  6. // Anchor is the anchor point for image alignment.
  7. type Anchor int
  8. // Anchor point positions.
  9. const (
  10. Center Anchor = iota
  11. TopLeft
  12. Top
  13. TopRight
  14. Left
  15. Right
  16. BottomLeft
  17. Bottom
  18. BottomRight
  19. )
  20. func anchorPt(b image.Rectangle, w, h int, anchor Anchor) image.Point {
  21. var x, y int
  22. switch anchor {
  23. case TopLeft:
  24. x = b.Min.X
  25. y = b.Min.Y
  26. case Top:
  27. x = b.Min.X + (b.Dx()-w)/2
  28. y = b.Min.Y
  29. case TopRight:
  30. x = b.Max.X - w
  31. y = b.Min.Y
  32. case Left:
  33. x = b.Min.X
  34. y = b.Min.Y + (b.Dy()-h)/2
  35. case Right:
  36. x = b.Max.X - w
  37. y = b.Min.Y + (b.Dy()-h)/2
  38. case BottomLeft:
  39. x = b.Min.X
  40. y = b.Max.Y - h
  41. case Bottom:
  42. x = b.Min.X + (b.Dx()-w)/2
  43. y = b.Max.Y - h
  44. case BottomRight:
  45. x = b.Max.X - w
  46. y = b.Max.Y - h
  47. default:
  48. x = b.Min.X + (b.Dx()-w)/2
  49. y = b.Min.Y + (b.Dy()-h)/2
  50. }
  51. return image.Pt(x, y)
  52. }
  53. // Crop cuts out a rectangular region with the specified bounds
  54. // from the image and returns the cropped image.
  55. func Crop(img image.Image, rect image.Rectangle) *image.NRGBA {
  56. r := rect.Intersect(img.Bounds()).Sub(img.Bounds().Min)
  57. if r.Empty() {
  58. return &image.NRGBA{}
  59. }
  60. src := newScanner(img)
  61. dst := image.NewNRGBA(image.Rect(0, 0, r.Dx(), r.Dy()))
  62. rowSize := r.Dx() * 4
  63. parallel(r.Min.Y, r.Max.Y, func(ys <-chan int) {
  64. for y := range ys {
  65. i := (y - r.Min.Y) * dst.Stride
  66. src.scan(r.Min.X, y, r.Max.X, y+1, dst.Pix[i:i+rowSize])
  67. }
  68. })
  69. return dst
  70. }
  71. // CropAnchor cuts out a rectangular region with the specified size
  72. // from the image using the specified anchor point and returns the cropped image.
  73. func CropAnchor(img image.Image, width, height int, anchor Anchor) *image.NRGBA {
  74. srcBounds := img.Bounds()
  75. pt := anchorPt(srcBounds, width, height, anchor)
  76. r := image.Rect(0, 0, width, height).Add(pt)
  77. b := srcBounds.Intersect(r)
  78. return Crop(img, b)
  79. }
  80. // CropCenter cuts out a rectangular region with the specified size
  81. // from the center of the image and returns the cropped image.
  82. func CropCenter(img image.Image, width, height int) *image.NRGBA {
  83. return CropAnchor(img, width, height, Center)
  84. }
  85. // Paste pastes the img image to the background image at the specified position and returns the combined image.
  86. func Paste(background, img image.Image, pos image.Point) *image.NRGBA {
  87. dst := Clone(background)
  88. pos = pos.Sub(background.Bounds().Min)
  89. pasteRect := image.Rectangle{Min: pos, Max: pos.Add(img.Bounds().Size())}
  90. interRect := pasteRect.Intersect(dst.Bounds())
  91. if interRect.Empty() {
  92. return dst
  93. }
  94. src := newScanner(img)
  95. parallel(interRect.Min.Y, interRect.Max.Y, func(ys <-chan int) {
  96. for y := range ys {
  97. x1 := interRect.Min.X - pasteRect.Min.X
  98. x2 := interRect.Max.X - pasteRect.Min.X
  99. y1 := y - pasteRect.Min.Y
  100. y2 := y1 + 1
  101. i1 := y*dst.Stride + interRect.Min.X*4
  102. i2 := i1 + interRect.Dx()*4
  103. src.scan(x1, y1, x2, y2, dst.Pix[i1:i2])
  104. }
  105. })
  106. return dst
  107. }
  108. // PasteCenter pastes the img image to the center of the background image and returns the combined image.
  109. func PasteCenter(background, img image.Image) *image.NRGBA {
  110. bgBounds := background.Bounds()
  111. bgW := bgBounds.Dx()
  112. bgH := bgBounds.Dy()
  113. bgMinX := bgBounds.Min.X
  114. bgMinY := bgBounds.Min.Y
  115. centerX := bgMinX + bgW/2
  116. centerY := bgMinY + bgH/2
  117. x0 := centerX - img.Bounds().Dx()/2
  118. y0 := centerY - img.Bounds().Dy()/2
  119. return Paste(background, img, image.Pt(x0, y0))
  120. }
  121. // Overlay draws the img image over the background image at given position
  122. // and returns the combined image. Opacity parameter is the opacity of the img
  123. // image layer, used to compose the images, it must be from 0.0 to 1.0.
  124. //
  125. // Usage examples:
  126. //
  127. // // Draw spriteImage over backgroundImage at the given position (x=50, y=50).
  128. // dstImage := imaging.Overlay(backgroundImage, spriteImage, image.Pt(50, 50), 1.0)
  129. //
  130. // // Blend two opaque images of the same size.
  131. // dstImage := imaging.Overlay(imageOne, imageTwo, image.Pt(0, 0), 0.5)
  132. //
  133. func Overlay(background, img image.Image, pos image.Point, opacity float64) *image.NRGBA {
  134. opacity = math.Min(math.Max(opacity, 0.0), 1.0) // Ensure 0.0 <= opacity <= 1.0.
  135. dst := Clone(background)
  136. pos = pos.Sub(background.Bounds().Min)
  137. pasteRect := image.Rectangle{Min: pos, Max: pos.Add(img.Bounds().Size())}
  138. interRect := pasteRect.Intersect(dst.Bounds())
  139. if interRect.Empty() {
  140. return dst
  141. }
  142. src := newScanner(img)
  143. parallel(interRect.Min.Y, interRect.Max.Y, func(ys <-chan int) {
  144. scanLine := make([]uint8, interRect.Dx()*4)
  145. for y := range ys {
  146. x1 := interRect.Min.X - pasteRect.Min.X
  147. x2 := interRect.Max.X - pasteRect.Min.X
  148. y1 := y - pasteRect.Min.Y
  149. y2 := y1 + 1
  150. src.scan(x1, y1, x2, y2, scanLine)
  151. i := y*dst.Stride + interRect.Min.X*4
  152. j := 0
  153. for x := interRect.Min.X; x < interRect.Max.X; x++ {
  154. r1 := float64(dst.Pix[i+0])
  155. g1 := float64(dst.Pix[i+1])
  156. b1 := float64(dst.Pix[i+2])
  157. a1 := float64(dst.Pix[i+3])
  158. r2 := float64(scanLine[j+0])
  159. g2 := float64(scanLine[j+1])
  160. b2 := float64(scanLine[j+2])
  161. a2 := float64(scanLine[j+3])
  162. coef2 := opacity * a2 / 255
  163. coef1 := (1 - coef2) * a1 / 255
  164. coefSum := coef1 + coef2
  165. coef1 /= coefSum
  166. coef2 /= coefSum
  167. dst.Pix[i+0] = uint8(r1*coef1 + r2*coef2)
  168. dst.Pix[i+1] = uint8(g1*coef1 + g2*coef2)
  169. dst.Pix[i+2] = uint8(b1*coef1 + b2*coef2)
  170. dst.Pix[i+3] = uint8(math.Min(a1+a2*opacity*(255-a1)/255, 255))
  171. i += 4
  172. j += 4
  173. }
  174. }
  175. })
  176. return dst
  177. }
  178. // OverlayCenter overlays the img image to the center of the background image and
  179. // returns the combined image. Opacity parameter is the opacity of the img
  180. // image layer, used to compose the images, it must be from 0.0 to 1.0.
  181. func OverlayCenter(background, img image.Image, opacity float64) *image.NRGBA {
  182. bgBounds := background.Bounds()
  183. bgW := bgBounds.Dx()
  184. bgH := bgBounds.Dy()
  185. bgMinX := bgBounds.Min.X
  186. bgMinY := bgBounds.Min.Y
  187. centerX := bgMinX + bgW/2
  188. centerY := bgMinY + bgH/2
  189. x0 := centerX - img.Bounds().Dx()/2
  190. y0 := centerY - img.Bounds().Dy()/2
  191. return Overlay(background, img, image.Point{x0, y0}, opacity)
  192. }