git.sr.ht/~pingoo/stdx@v0.0.0-20240218134121-094174641f6e/imaging/transform.go (about) 1 package imaging 2 3 import ( 4 "image" 5 "image/color" 6 "math" 7 ) 8 9 // FlipH flips the image horizontally (from left to right) and returns the transformed image. 10 func 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. 28 func 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. 45 func 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. 62 func 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. 80 func 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. 97 func 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. 115 func 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. 135 func 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 180 func rotatePoint(x, y, sin, cos float64) (float64, float64) { 181 return x*cos - y*sin, x*sin + y*cos 182 } 183 184 func 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 211 func 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 }