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}