tools.go 6.9 KB

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