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  }