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