transform.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. package imaging
  2. import (
  3. "image"
  4. "image/color"
  5. "math"
  6. )
  7. // FlipH flips the image horizontally (from left to right) and returns the transformed image.
  8. func FlipH(img image.Image) *image.NRGBA {
  9. src := toNRGBA(img)
  10. srcW := src.Bounds().Max.X
  11. srcH := src.Bounds().Max.Y
  12. dstW := srcW
  13. dstH := srcH
  14. dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
  15. parallel(dstH, func(partStart, partEnd int) {
  16. for dstY := partStart; dstY < partEnd; dstY++ {
  17. for dstX := 0; dstX < dstW; dstX++ {
  18. srcX := dstW - dstX - 1
  19. srcY := dstY
  20. srcOff := srcY*src.Stride + srcX*4
  21. dstOff := dstY*dst.Stride + dstX*4
  22. copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4])
  23. }
  24. }
  25. })
  26. return dst
  27. }
  28. // FlipV flips the image vertically (from top to bottom) and returns the transformed image.
  29. func FlipV(img image.Image) *image.NRGBA {
  30. src := toNRGBA(img)
  31. srcW := src.Bounds().Max.X
  32. srcH := src.Bounds().Max.Y
  33. dstW := srcW
  34. dstH := srcH
  35. dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
  36. parallel(dstH, func(partStart, partEnd int) {
  37. for dstY := partStart; dstY < partEnd; dstY++ {
  38. for dstX := 0; dstX < dstW; dstX++ {
  39. srcX := dstX
  40. srcY := dstH - dstY - 1
  41. srcOff := srcY*src.Stride + srcX*4
  42. dstOff := dstY*dst.Stride + dstX*4
  43. copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4])
  44. }
  45. }
  46. })
  47. return dst
  48. }
  49. // Transpose flips the image horizontally and rotates 90 degrees counter-clockwise.
  50. func Transpose(img image.Image) *image.NRGBA {
  51. src := toNRGBA(img)
  52. srcW := src.Bounds().Max.X
  53. srcH := src.Bounds().Max.Y
  54. dstW := srcH
  55. dstH := srcW
  56. dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
  57. parallel(dstH, func(partStart, partEnd int) {
  58. for dstY := partStart; dstY < partEnd; dstY++ {
  59. for dstX := 0; dstX < dstW; dstX++ {
  60. srcX := dstY
  61. srcY := dstX
  62. srcOff := srcY*src.Stride + srcX*4
  63. dstOff := dstY*dst.Stride + dstX*4
  64. copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4])
  65. }
  66. }
  67. })
  68. return dst
  69. }
  70. // Transverse flips the image vertically and rotates 90 degrees counter-clockwise.
  71. func Transverse(img image.Image) *image.NRGBA {
  72. src := toNRGBA(img)
  73. srcW := src.Bounds().Max.X
  74. srcH := src.Bounds().Max.Y
  75. dstW := srcH
  76. dstH := srcW
  77. dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
  78. parallel(dstH, func(partStart, partEnd int) {
  79. for dstY := partStart; dstY < partEnd; dstY++ {
  80. for dstX := 0; dstX < dstW; dstX++ {
  81. srcX := dstH - dstY - 1
  82. srcY := dstW - dstX - 1
  83. srcOff := srcY*src.Stride + srcX*4
  84. dstOff := dstY*dst.Stride + dstX*4
  85. copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4])
  86. }
  87. }
  88. })
  89. return dst
  90. }
  91. // Rotate90 rotates the image 90 degrees counterclockwise and returns the transformed image.
  92. func Rotate90(img image.Image) *image.NRGBA {
  93. src := toNRGBA(img)
  94. srcW := src.Bounds().Max.X
  95. srcH := src.Bounds().Max.Y
  96. dstW := srcH
  97. dstH := srcW
  98. dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
  99. parallel(dstH, func(partStart, partEnd int) {
  100. for dstY := partStart; dstY < partEnd; dstY++ {
  101. for dstX := 0; dstX < dstW; dstX++ {
  102. srcX := dstH - dstY - 1
  103. srcY := dstX
  104. srcOff := srcY*src.Stride + srcX*4
  105. dstOff := dstY*dst.Stride + dstX*4
  106. copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4])
  107. }
  108. }
  109. })
  110. return dst
  111. }
  112. // Rotate180 rotates the image 180 degrees counterclockwise and returns the transformed image.
  113. func Rotate180(img image.Image) *image.NRGBA {
  114. src := toNRGBA(img)
  115. srcW := src.Bounds().Max.X
  116. srcH := src.Bounds().Max.Y
  117. dstW := srcW
  118. dstH := srcH
  119. dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
  120. parallel(dstH, func(partStart, partEnd int) {
  121. for dstY := partStart; dstY < partEnd; dstY++ {
  122. for dstX := 0; dstX < dstW; dstX++ {
  123. srcX := dstW - dstX - 1
  124. srcY := dstH - dstY - 1
  125. srcOff := srcY*src.Stride + srcX*4
  126. dstOff := dstY*dst.Stride + dstX*4
  127. copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4])
  128. }
  129. }
  130. })
  131. return dst
  132. }
  133. // Rotate270 rotates the image 270 degrees counterclockwise and returns the transformed image.
  134. func Rotate270(img image.Image) *image.NRGBA {
  135. src := toNRGBA(img)
  136. srcW := src.Bounds().Max.X
  137. srcH := src.Bounds().Max.Y
  138. dstW := srcH
  139. dstH := srcW
  140. dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
  141. parallel(dstH, func(partStart, partEnd int) {
  142. for dstY := partStart; dstY < partEnd; dstY++ {
  143. for dstX := 0; dstX < dstW; dstX++ {
  144. srcX := dstY
  145. srcY := dstW - dstX - 1
  146. srcOff := srcY*src.Stride + srcX*4
  147. dstOff := dstY*dst.Stride + dstX*4
  148. copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4])
  149. }
  150. }
  151. })
  152. return dst
  153. }
  154. // Rotate rotates an image by the given angle counter-clockwise .
  155. // The angle parameter is the rotation angle in degrees.
  156. // The bgColor parameter specifies the color of the uncovered zone after the rotation.
  157. func Rotate(img image.Image, angle float64, bgColor color.Color) *image.NRGBA {
  158. angle = angle - math.Floor(angle/360)*360
  159. switch angle {
  160. case 0:
  161. return Clone(img)
  162. case 90:
  163. return Rotate90(img)
  164. case 180:
  165. return Rotate180(img)
  166. case 270:
  167. return Rotate270(img)
  168. }
  169. src := toNRGBA(img)
  170. srcW := src.Bounds().Max.X
  171. srcH := src.Bounds().Max.Y
  172. dstW, dstH := rotatedSize(srcW, srcH, angle)
  173. dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
  174. if dstW <= 0 || dstH <= 0 {
  175. return dst
  176. }
  177. srcXOff := float64(srcW)/2 - 0.5
  178. srcYOff := float64(srcH)/2 - 0.5
  179. dstXOff := float64(dstW)/2 - 0.5
  180. dstYOff := float64(dstH)/2 - 0.5
  181. bgColorNRGBA := color.NRGBAModel.Convert(bgColor).(color.NRGBA)
  182. sin, cos := math.Sincos(math.Pi * angle / 180)
  183. parallel(dstH, func(partStart, partEnd int) {
  184. for dstY := partStart; dstY < partEnd; dstY++ {
  185. for dstX := 0; dstX < dstW; dstX++ {
  186. xf, yf := rotatePoint(float64(dstX)-dstXOff, float64(dstY)-dstYOff, sin, cos)
  187. xf, yf = xf+srcXOff, yf+srcYOff
  188. interpolatePoint(dst, dstX, dstY, src, xf, yf, bgColorNRGBA)
  189. }
  190. }
  191. })
  192. return dst
  193. }
  194. func rotatePoint(x, y, sin, cos float64) (float64, float64) {
  195. return x*cos - y*sin, x*sin + y*cos
  196. }
  197. func rotatedSize(w, h int, angle float64) (int, int) {
  198. if w <= 0 || h <= 0 {
  199. return 0, 0
  200. }
  201. sin, cos := math.Sincos(math.Pi * angle / 180)
  202. x1, y1 := rotatePoint(float64(w-1), 0, sin, cos)
  203. x2, y2 := rotatePoint(float64(w-1), float64(h-1), sin, cos)
  204. x3, y3 := rotatePoint(0, float64(h-1), sin, cos)
  205. minx := math.Min(x1, math.Min(x2, math.Min(x3, 0)))
  206. maxx := math.Max(x1, math.Max(x2, math.Max(x3, 0)))
  207. miny := math.Min(y1, math.Min(y2, math.Min(y3, 0)))
  208. maxy := math.Max(y1, math.Max(y2, math.Max(y3, 0)))
  209. neww := maxx - minx + 1
  210. if neww-math.Floor(neww) > 0.1 {
  211. neww++
  212. }
  213. newh := maxy - miny + 1
  214. if newh-math.Floor(newh) > 0.1 {
  215. newh++
  216. }
  217. return int(neww), int(newh)
  218. }
  219. func interpolatePoint(dst *image.NRGBA, dstX, dstY int, src *image.NRGBA, xf, yf float64, bgColor color.NRGBA) {
  220. dstIndex := dstY*dst.Stride + dstX*4
  221. x0 := int(math.Floor(xf))
  222. y0 := int(math.Floor(yf))
  223. bounds := src.Bounds()
  224. if !image.Pt(x0, y0).In(image.Rect(bounds.Min.X-1, bounds.Min.Y-1, bounds.Max.X, bounds.Max.Y)) {
  225. dst.Pix[dstIndex+0] = bgColor.R
  226. dst.Pix[dstIndex+1] = bgColor.G
  227. dst.Pix[dstIndex+2] = bgColor.B
  228. dst.Pix[dstIndex+3] = bgColor.A
  229. return
  230. }
  231. xq := xf - float64(x0)
  232. yq := yf - float64(y0)
  233. var pxs [4]color.NRGBA
  234. var cfs [4]float64
  235. for i := 0; i < 2; i++ {
  236. for j := 0; j < 2; j++ {
  237. k := i*2 + j
  238. pt := image.Pt(x0+j, y0+i)
  239. if pt.In(bounds) {
  240. l := pt.Y*src.Stride + pt.X*4
  241. pxs[k].R = src.Pix[l+0]
  242. pxs[k].G = src.Pix[l+1]
  243. pxs[k].B = src.Pix[l+2]
  244. pxs[k].A = src.Pix[l+3]
  245. } else {
  246. pxs[k] = bgColor
  247. }
  248. }
  249. }
  250. cfs[0] = (1 - xq) * (1 - yq)
  251. cfs[1] = xq * (1 - yq)
  252. cfs[2] = (1 - xq) * yq
  253. cfs[3] = xq * yq
  254. var r, g, b, a float64
  255. for i := range pxs {
  256. wa := float64(pxs[i].A) * cfs[i]
  257. r += float64(pxs[i].R) * wa
  258. g += float64(pxs[i].G) * wa
  259. b += float64(pxs[i].B) * wa
  260. a += wa
  261. }
  262. if a != 0 {
  263. r /= a
  264. g /= a
  265. b /= a
  266. }
  267. dst.Pix[dstIndex+0] = clamp(r)
  268. dst.Pix[dstIndex+1] = clamp(g)
  269. dst.Pix[dstIndex+2] = clamp(b)
  270. dst.Pix[dstIndex+3] = clamp(a)
  271. }