tools.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  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. src := toNRGBA(img)
  57. srcRect := rect.Sub(img.Bounds().Min)
  58. sub := src.SubImage(srcRect)
  59. return Clone(sub) // New image Bounds().Min point will be (0, 0)
  60. }
  61. // CropAnchor cuts out a rectangular region with the specified size
  62. // from the image using the specified anchor point and returns the cropped image.
  63. func CropAnchor(img image.Image, width, height int, anchor Anchor) *image.NRGBA {
  64. srcBounds := img.Bounds()
  65. pt := anchorPt(srcBounds, width, height, anchor)
  66. r := image.Rect(0, 0, width, height).Add(pt)
  67. b := srcBounds.Intersect(r)
  68. return Crop(img, b)
  69. }
  70. // CropCenter cuts out a rectangular region with the specified size
  71. // from the center of the image and returns the cropped image.
  72. func CropCenter(img image.Image, width, height int) *image.NRGBA {
  73. return CropAnchor(img, width, height, Center)
  74. }
  75. // Paste pastes the img image to the background image at the specified position and returns the combined image.
  76. func Paste(background, img image.Image, pos image.Point) *image.NRGBA {
  77. src := toNRGBA(img)
  78. dst := Clone(background) // cloned image bounds start at (0, 0)
  79. startPt := pos.Sub(background.Bounds().Min) // so we should translate start point
  80. endPt := startPt.Add(src.Bounds().Size())
  81. pasteBounds := image.Rectangle{startPt, endPt}
  82. if dst.Bounds().Overlaps(pasteBounds) {
  83. intersectBounds := dst.Bounds().Intersect(pasteBounds)
  84. rowSize := intersectBounds.Dx() * 4
  85. numRows := intersectBounds.Dy()
  86. srcStartX := intersectBounds.Min.X - pasteBounds.Min.X
  87. srcStartY := intersectBounds.Min.Y - pasteBounds.Min.Y
  88. i0 := dst.PixOffset(intersectBounds.Min.X, intersectBounds.Min.Y)
  89. j0 := src.PixOffset(srcStartX, srcStartY)
  90. di := dst.Stride
  91. dj := src.Stride
  92. for row := 0; row < numRows; row++ {
  93. copy(dst.Pix[i0:i0+rowSize], src.Pix[j0:j0+rowSize])
  94. i0 += di
  95. j0 += dj
  96. }
  97. }
  98. return dst
  99. }
  100. // PasteCenter pastes the img image to the center of the background image and returns the combined image.
  101. func PasteCenter(background, img image.Image) *image.NRGBA {
  102. bgBounds := background.Bounds()
  103. bgW := bgBounds.Dx()
  104. bgH := bgBounds.Dy()
  105. bgMinX := bgBounds.Min.X
  106. bgMinY := bgBounds.Min.Y
  107. centerX := bgMinX + bgW/2
  108. centerY := bgMinY + bgH/2
  109. x0 := centerX - img.Bounds().Dx()/2
  110. y0 := centerY - img.Bounds().Dy()/2
  111. return Paste(background, img, image.Pt(x0, y0))
  112. }
  113. // Overlay draws the img image over the background image at given position
  114. // and returns the combined image. Opacity parameter is the opacity of the img
  115. // image layer, used to compose the images, it must be from 0.0 to 1.0.
  116. //
  117. // Usage examples:
  118. //
  119. // // draw the sprite over the background at position (50, 50)
  120. // dstImage := imaging.Overlay(backgroundImage, spriteImage, image.Pt(50, 50), 1.0)
  121. //
  122. // // blend two opaque images of the same size
  123. // dstImage := imaging.Overlay(imageOne, imageTwo, image.Pt(0, 0), 0.5)
  124. //
  125. func Overlay(background, img image.Image, pos image.Point, opacity float64) *image.NRGBA {
  126. opacity = math.Min(math.Max(opacity, 0.0), 1.0) // check: 0.0 <= opacity <= 1.0
  127. src := toNRGBA(img)
  128. dst := Clone(background) // cloned image bounds start at (0, 0)
  129. startPt := pos.Sub(background.Bounds().Min) // so we should translate start point
  130. endPt := startPt.Add(src.Bounds().Size())
  131. pasteBounds := image.Rectangle{startPt, endPt}
  132. if dst.Bounds().Overlaps(pasteBounds) {
  133. intersectBounds := dst.Bounds().Intersect(pasteBounds)
  134. for y := intersectBounds.Min.Y; y < intersectBounds.Max.Y; y++ {
  135. for x := intersectBounds.Min.X; x < intersectBounds.Max.X; x++ {
  136. i := y*dst.Stride + x*4
  137. srcX := x - pasteBounds.Min.X
  138. srcY := y - pasteBounds.Min.Y
  139. j := srcY*src.Stride + srcX*4
  140. a1 := float64(dst.Pix[i+3])
  141. a2 := float64(src.Pix[j+3])
  142. coef2 := opacity * a2 / 255.0
  143. coef1 := (1 - coef2) * a1 / 255.0
  144. coefSum := coef1 + coef2
  145. coef1 /= coefSum
  146. coef2 /= coefSum
  147. dst.Pix[i+0] = uint8(float64(dst.Pix[i+0])*coef1 + float64(src.Pix[j+0])*coef2)
  148. dst.Pix[i+1] = uint8(float64(dst.Pix[i+1])*coef1 + float64(src.Pix[j+1])*coef2)
  149. dst.Pix[i+2] = uint8(float64(dst.Pix[i+2])*coef1 + float64(src.Pix[j+2])*coef2)
  150. dst.Pix[i+3] = uint8(math.Min(a1+a2*opacity*(255.0-a1)/255.0, 255.0))
  151. }
  152. }
  153. }
  154. return dst
  155. }
  156. // OverlayCenter overlays the img image to the center of the background image and
  157. // returns the combined image. Opacity parameter is the opacity of the img
  158. // image layer, used to compose the images, it must be from 0.0 to 1.0.
  159. func OverlayCenter(background, img image.Image, opacity float64) *image.NRGBA {
  160. bgBounds := background.Bounds()
  161. bgW := bgBounds.Dx()
  162. bgH := bgBounds.Dy()
  163. bgMinX := bgBounds.Min.X
  164. bgMinY := bgBounds.Min.Y
  165. centerX := bgMinX + bgW/2
  166. centerY := bgMinY + bgH/2
  167. x0 := centerX - img.Bounds().Dx()/2
  168. y0 := centerY - img.Bounds().Dy()/2
  169. return Overlay(background, img, image.Point{x0, y0}, opacity)
  170. }