transform.go raw

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