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}
269