tools.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. package imaging
  2. import (
  3. "image"
  4. "math"
  5. )
  6. // Anchor is the anchor point for image alignment.
  7. type Anchor int
  8. const (
  9. Center Anchor = iota
  10. TopLeft
  11. Top
  12. TopRight
  13. Left
  14. Right
  15. BottomLeft
  16. Bottom
  17. BottomRight
  18. )
  19. func anchorPt(b image.Rectangle, w, h int, anchor Anchor) image.Point {
  20. var x, y int
  21. switch anchor {
  22. case TopLeft:
  23. x = b.Min.X
  24. y = b.Min.Y
  25. case Top:
  26. x = b.Min.X + (b.Dx()-w)/2
  27. y = b.Min.Y
  28. case TopRight:
  29. x = b.Max.X - w
  30. y = b.Min.Y
  31. case Left:
  32. x = b.Min.X
  33. y = b.Min.Y + (b.Dy()-h)/2
  34. case Right:
  35. x = b.Max.X - w
  36. y = b.Min.Y + (b.Dy()-h)/2
  37. case BottomLeft:
  38. x = b.Min.X
  39. y = b.Max.Y - h
  40. case Bottom:
  41. x = b.Min.X + (b.Dx()-w)/2
  42. y = b.Max.Y - h
  43. case BottomRight:
  44. x = b.Max.X - w
  45. y = b.Max.Y - h
  46. default:
  47. x = b.Min.X + (b.Dx()-w)/2
  48. y = b.Min.Y + (b.Dy()-h)/2
  49. }
  50. return image.Pt(x, y)
  51. }
  52. // Crop cuts out a rectangular region with the specified bounds
  53. // from the image and returns the cropped image.
  54. func Crop(img image.Image, rect image.Rectangle) *image.NRGBA {
  55. src := toNRGBA(img)
  56. srcRect := rect.Sub(img.Bounds().Min)
  57. sub := src.SubImage(srcRect)
  58. return Clone(sub) // New image Bounds().Min point will be (0, 0)
  59. }
  60. // CropAnchor cuts out a rectangular region with the specified size
  61. // from the image using the specified anchor point and returns the cropped image.
  62. func CropAnchor(img image.Image, width, height int, anchor Anchor) *image.NRGBA {
  63. srcBounds := img.Bounds()
  64. pt := anchorPt(srcBounds, width, height, anchor)
  65. r := image.Rect(0, 0, width, height).Add(pt)
  66. b := srcBounds.Intersect(r)
  67. return Crop(img, b)
  68. }
  69. // CropCenter cuts out a rectangular region with the specified size
  70. // from the center of the image and returns the cropped image.
  71. func CropCenter(img image.Image, width, height int) *image.NRGBA {
  72. return CropAnchor(img, width, height, Center)
  73. }
  74. // Paste pastes the img image to the background image at the specified position and returns the combined image.
  75. func Paste(background, img image.Image, pos image.Point) *image.NRGBA {
  76. src := toNRGBA(img)
  77. dst := Clone(background) // cloned image bounds start at (0, 0)
  78. startPt := pos.Sub(background.Bounds().Min) // so we should translate start point
  79. endPt := startPt.Add(src.Bounds().Size())
  80. pasteBounds := image.Rectangle{startPt, endPt}
  81. if dst.Bounds().Overlaps(pasteBounds) {
  82. intersectBounds := dst.Bounds().Intersect(pasteBounds)
  83. rowSize := intersectBounds.Dx() * 4
  84. numRows := intersectBounds.Dy()
  85. srcStartX := intersectBounds.Min.X - pasteBounds.Min.X
  86. srcStartY := intersectBounds.Min.Y - pasteBounds.Min.Y
  87. i0 := dst.PixOffset(intersectBounds.Min.X, intersectBounds.Min.Y)
  88. j0 := src.PixOffset(srcStartX, srcStartY)
  89. di := dst.Stride
  90. dj := src.Stride
  91. for row := 0; row < numRows; row++ {
  92. copy(dst.Pix[i0:i0+rowSize], src.Pix[j0:j0+rowSize])
  93. i0 += di
  94. j0 += dj
  95. }
  96. }
  97. return dst
  98. }
  99. // PasteCenter pastes the img image to the center of the background image and returns the combined image.
  100. func PasteCenter(background, img image.Image) *image.NRGBA {
  101. bgBounds := background.Bounds()
  102. bgW := bgBounds.Dx()
  103. bgH := bgBounds.Dy()
  104. bgMinX := bgBounds.Min.X
  105. bgMinY := bgBounds.Min.Y
  106. centerX := bgMinX + bgW/2
  107. centerY := bgMinY + bgH/2
  108. x0 := centerX - img.Bounds().Dx()/2
  109. y0 := centerY - img.Bounds().Dy()/2
  110. return Paste(background, img, image.Pt(x0, y0))
  111. }
  112. // Overlay draws the img image over the background image at given position
  113. // and returns the combined image. Opacity parameter is the opacity of the img
  114. // image layer, used to compose the images, it must be from 0.0 to 1.0.
  115. //
  116. // Usage examples:
  117. //
  118. // // draw the sprite over the background at position (50, 50)
  119. // dstImage := imaging.Overlay(backgroundImage, spriteImage, image.Pt(50, 50), 1.0)
  120. //
  121. // // blend two opaque images of the same size
  122. // dstImage := imaging.Overlay(imageOne, imageTwo, image.Pt(0, 0), 0.5)
  123. //
  124. func Overlay(background, img image.Image, pos image.Point, opacity float64) *image.NRGBA {
  125. opacity = math.Min(math.Max(opacity, 0.0), 1.0) // check: 0.0 <= opacity <= 1.0
  126. src := toNRGBA(img)
  127. dst := Clone(background) // cloned image bounds start at (0, 0)
  128. startPt := pos.Sub(background.Bounds().Min) // so we should translate start point
  129. endPt := startPt.Add(src.Bounds().Size())
  130. pasteBounds := image.Rectangle{startPt, endPt}
  131. if dst.Bounds().Overlaps(pasteBounds) {
  132. intersectBounds := dst.Bounds().Intersect(pasteBounds)
  133. for y := intersectBounds.Min.Y; y < intersectBounds.Max.Y; y++ {
  134. for x := intersectBounds.Min.X; x < intersectBounds.Max.X; x++ {
  135. i := y*dst.Stride + x*4
  136. srcX := x - pasteBounds.Min.X
  137. srcY := y - pasteBounds.Min.Y
  138. j := srcY*src.Stride + srcX*4
  139. a1 := float64(dst.Pix[i+3])
  140. a2 := float64(src.Pix[j+3])
  141. coef2 := opacity * a2 / 255.0
  142. coef1 := (1 - coef2) * a1 / 255.0
  143. coefSum := coef1 + coef2
  144. coef1 /= coefSum
  145. coef2 /= coefSum
  146. dst.Pix[i+0] = uint8(float64(dst.Pix[i+0])*coef1 + float64(src.Pix[j+0])*coef2)
  147. dst.Pix[i+1] = uint8(float64(dst.Pix[i+1])*coef1 + float64(src.Pix[j+1])*coef2)
  148. dst.Pix[i+2] = uint8(float64(dst.Pix[i+2])*coef1 + float64(src.Pix[j+2])*coef2)
  149. dst.Pix[i+3] = uint8(math.Min(a1+a2*opacity*(255.0-a1)/255.0, 255.0))
  150. }
  151. }
  152. }
  153. return dst
  154. }
  155. // OverlayCenter overlays the img image to the center of the background image and
  156. // returns the combined image. Opacity parameter is the opacity of the img
  157. // image layer, used to compose the images, it must be from 0.0 to 1.0.
  158. func OverlayCenter(background, img image.Image, opacity float64) *image.NRGBA {
  159. bgBounds := background.Bounds()
  160. bgW := bgBounds.Dx()
  161. bgH := bgBounds.Dy()
  162. bgMinX := bgBounds.Min.X
  163. bgMinY := bgBounds.Min.Y
  164. centerX := bgMinX + bgW/2
  165. centerY := bgMinY + bgH/2
  166. x0 := centerX - img.Bounds().Dx()/2
  167. y0 := centerY - img.Bounds().Dy()/2
  168. return Overlay(background, img, image.Point{x0, y0}, opacity)
  169. }