mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-30 23:02:25 -05:00 
			
		
		
		
	* use disintegration/imaging instead of nfnt/resize * update tests * use disintegration lib for thumbing (if necessary)
		
			
				
	
	
		
			268 lines
		
	
	
	
		
			6.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			268 lines
		
	
	
	
		
			6.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package imaging
 | |
| 
 | |
| import (
 | |
| 	"image"
 | |
| 	"image/color"
 | |
| 	"math"
 | |
| )
 | |
| 
 | |
| // FlipH flips the image horizontally (from left to right) and returns the transformed image.
 | |
| func FlipH(img image.Image) *image.NRGBA {
 | |
| 	src := newScanner(img)
 | |
| 	dstW := src.w
 | |
| 	dstH := src.h
 | |
| 	rowSize := dstW * 4
 | |
| 	dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
 | |
| 	parallel(0, dstH, func(ys <-chan int) {
 | |
| 		for dstY := range ys {
 | |
| 			i := dstY * dst.Stride
 | |
| 			srcY := dstY
 | |
| 			src.scan(0, srcY, src.w, srcY+1, dst.Pix[i:i+rowSize])
 | |
| 			reverse(dst.Pix[i : i+rowSize])
 | |
| 		}
 | |
| 	})
 | |
| 	return dst
 | |
| }
 | |
| 
 | |
| // FlipV flips the image vertically (from top to bottom) and returns the transformed image.
 | |
| func FlipV(img image.Image) *image.NRGBA {
 | |
| 	src := newScanner(img)
 | |
| 	dstW := src.w
 | |
| 	dstH := src.h
 | |
| 	rowSize := dstW * 4
 | |
| 	dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
 | |
| 	parallel(0, dstH, func(ys <-chan int) {
 | |
| 		for dstY := range ys {
 | |
| 			i := dstY * dst.Stride
 | |
| 			srcY := dstH - dstY - 1
 | |
| 			src.scan(0, srcY, src.w, srcY+1, dst.Pix[i:i+rowSize])
 | |
| 		}
 | |
| 	})
 | |
| 	return dst
 | |
| }
 | |
| 
 | |
| // Transpose flips the image horizontally and rotates 90 degrees counter-clockwise.
 | |
| func Transpose(img image.Image) *image.NRGBA {
 | |
| 	src := newScanner(img)
 | |
| 	dstW := src.h
 | |
| 	dstH := src.w
 | |
| 	rowSize := dstW * 4
 | |
| 	dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
 | |
| 	parallel(0, dstH, func(ys <-chan int) {
 | |
| 		for dstY := range ys {
 | |
| 			i := dstY * dst.Stride
 | |
| 			srcX := dstY
 | |
| 			src.scan(srcX, 0, srcX+1, src.h, dst.Pix[i:i+rowSize])
 | |
| 		}
 | |
| 	})
 | |
| 	return dst
 | |
| }
 | |
| 
 | |
| // Transverse flips the image vertically and rotates 90 degrees counter-clockwise.
 | |
| func Transverse(img image.Image) *image.NRGBA {
 | |
| 	src := newScanner(img)
 | |
| 	dstW := src.h
 | |
| 	dstH := src.w
 | |
| 	rowSize := dstW * 4
 | |
| 	dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
 | |
| 	parallel(0, dstH, func(ys <-chan int) {
 | |
| 		for dstY := range ys {
 | |
| 			i := dstY * dst.Stride
 | |
| 			srcX := dstH - dstY - 1
 | |
| 			src.scan(srcX, 0, srcX+1, src.h, dst.Pix[i:i+rowSize])
 | |
| 			reverse(dst.Pix[i : i+rowSize])
 | |
| 		}
 | |
| 	})
 | |
| 	return dst
 | |
| }
 | |
| 
 | |
| // Rotate90 rotates the image 90 degrees counter-clockwise and returns the transformed image.
 | |
| func Rotate90(img image.Image) *image.NRGBA {
 | |
| 	src := newScanner(img)
 | |
| 	dstW := src.h
 | |
| 	dstH := src.w
 | |
| 	rowSize := dstW * 4
 | |
| 	dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
 | |
| 	parallel(0, dstH, func(ys <-chan int) {
 | |
| 		for dstY := range ys {
 | |
| 			i := dstY * dst.Stride
 | |
| 			srcX := dstH - dstY - 1
 | |
| 			src.scan(srcX, 0, srcX+1, src.h, dst.Pix[i:i+rowSize])
 | |
| 		}
 | |
| 	})
 | |
| 	return dst
 | |
| }
 | |
| 
 | |
| // Rotate180 rotates the image 180 degrees counter-clockwise and returns the transformed image.
 | |
| func Rotate180(img image.Image) *image.NRGBA {
 | |
| 	src := newScanner(img)
 | |
| 	dstW := src.w
 | |
| 	dstH := src.h
 | |
| 	rowSize := dstW * 4
 | |
| 	dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
 | |
| 	parallel(0, dstH, func(ys <-chan int) {
 | |
| 		for dstY := range ys {
 | |
| 			i := dstY * dst.Stride
 | |
| 			srcY := dstH - dstY - 1
 | |
| 			src.scan(0, srcY, src.w, srcY+1, dst.Pix[i:i+rowSize])
 | |
| 			reverse(dst.Pix[i : i+rowSize])
 | |
| 		}
 | |
| 	})
 | |
| 	return dst
 | |
| }
 | |
| 
 | |
| // Rotate270 rotates the image 270 degrees counter-clockwise and returns the transformed image.
 | |
| func Rotate270(img image.Image) *image.NRGBA {
 | |
| 	src := newScanner(img)
 | |
| 	dstW := src.h
 | |
| 	dstH := src.w
 | |
| 	rowSize := dstW * 4
 | |
| 	dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
 | |
| 	parallel(0, dstH, func(ys <-chan int) {
 | |
| 		for dstY := range ys {
 | |
| 			i := dstY * dst.Stride
 | |
| 			srcX := dstY
 | |
| 			src.scan(srcX, 0, srcX+1, src.h, dst.Pix[i:i+rowSize])
 | |
| 			reverse(dst.Pix[i : i+rowSize])
 | |
| 		}
 | |
| 	})
 | |
| 	return dst
 | |
| }
 | |
| 
 | |
| // Rotate rotates an image by the given angle counter-clockwise .
 | |
| // The angle parameter is the rotation angle in degrees.
 | |
| // The bgColor parameter specifies the color of the uncovered zone after the rotation.
 | |
| func Rotate(img image.Image, angle float64, bgColor color.Color) *image.NRGBA {
 | |
| 	angle = angle - math.Floor(angle/360)*360
 | |
| 
 | |
| 	switch angle {
 | |
| 	case 0:
 | |
| 		return Clone(img)
 | |
| 	case 90:
 | |
| 		return Rotate90(img)
 | |
| 	case 180:
 | |
| 		return Rotate180(img)
 | |
| 	case 270:
 | |
| 		return Rotate270(img)
 | |
| 	}
 | |
| 
 | |
| 	src := toNRGBA(img)
 | |
| 	srcW := src.Bounds().Max.X
 | |
| 	srcH := src.Bounds().Max.Y
 | |
| 	dstW, dstH := rotatedSize(srcW, srcH, angle)
 | |
| 	dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
 | |
| 
 | |
| 	if dstW <= 0 || dstH <= 0 {
 | |
| 		return dst
 | |
| 	}
 | |
| 
 | |
| 	srcXOff := float64(srcW)/2 - 0.5
 | |
| 	srcYOff := float64(srcH)/2 - 0.5
 | |
| 	dstXOff := float64(dstW)/2 - 0.5
 | |
| 	dstYOff := float64(dstH)/2 - 0.5
 | |
| 
 | |
| 	bgColorNRGBA := color.NRGBAModel.Convert(bgColor).(color.NRGBA)
 | |
| 	sin, cos := math.Sincos(math.Pi * angle / 180)
 | |
| 
 | |
| 	parallel(0, dstH, func(ys <-chan int) {
 | |
| 		for dstY := range ys {
 | |
| 			for dstX := 0; dstX < dstW; dstX++ {
 | |
| 				xf, yf := rotatePoint(float64(dstX)-dstXOff, float64(dstY)-dstYOff, sin, cos)
 | |
| 				xf, yf = xf+srcXOff, yf+srcYOff
 | |
| 				interpolatePoint(dst, dstX, dstY, src, xf, yf, bgColorNRGBA)
 | |
| 			}
 | |
| 		}
 | |
| 	})
 | |
| 
 | |
| 	return dst
 | |
| }
 | |
| 
 | |
| func rotatePoint(x, y, sin, cos float64) (float64, float64) {
 | |
| 	return x*cos - y*sin, x*sin + y*cos
 | |
| }
 | |
| 
 | |
| func rotatedSize(w, h int, angle float64) (int, int) {
 | |
| 	if w <= 0 || h <= 0 {
 | |
| 		return 0, 0
 | |
| 	}
 | |
| 
 | |
| 	sin, cos := math.Sincos(math.Pi * angle / 180)
 | |
| 	x1, y1 := rotatePoint(float64(w-1), 0, sin, cos)
 | |
| 	x2, y2 := rotatePoint(float64(w-1), float64(h-1), sin, cos)
 | |
| 	x3, y3 := rotatePoint(0, float64(h-1), sin, cos)
 | |
| 
 | |
| 	minx := math.Min(x1, math.Min(x2, math.Min(x3, 0)))
 | |
| 	maxx := math.Max(x1, math.Max(x2, math.Max(x3, 0)))
 | |
| 	miny := math.Min(y1, math.Min(y2, math.Min(y3, 0)))
 | |
| 	maxy := math.Max(y1, math.Max(y2, math.Max(y3, 0)))
 | |
| 
 | |
| 	neww := maxx - minx + 1
 | |
| 	if neww-math.Floor(neww) > 0.1 {
 | |
| 		neww++
 | |
| 	}
 | |
| 	newh := maxy - miny + 1
 | |
| 	if newh-math.Floor(newh) > 0.1 {
 | |
| 		newh++
 | |
| 	}
 | |
| 
 | |
| 	return int(neww), int(newh)
 | |
| }
 | |
| 
 | |
| func interpolatePoint(dst *image.NRGBA, dstX, dstY int, src *image.NRGBA, xf, yf float64, bgColor color.NRGBA) {
 | |
| 	j := dstY*dst.Stride + dstX*4
 | |
| 	d := dst.Pix[j : j+4 : j+4]
 | |
| 
 | |
| 	x0 := int(math.Floor(xf))
 | |
| 	y0 := int(math.Floor(yf))
 | |
| 	bounds := src.Bounds()
 | |
| 	if !image.Pt(x0, y0).In(image.Rect(bounds.Min.X-1, bounds.Min.Y-1, bounds.Max.X, bounds.Max.Y)) {
 | |
| 		d[0] = bgColor.R
 | |
| 		d[1] = bgColor.G
 | |
| 		d[2] = bgColor.B
 | |
| 		d[3] = bgColor.A
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	xq := xf - float64(x0)
 | |
| 	yq := yf - float64(y0)
 | |
| 	points := [4]image.Point{
 | |
| 		{x0, y0},
 | |
| 		{x0 + 1, y0},
 | |
| 		{x0, y0 + 1},
 | |
| 		{x0 + 1, y0 + 1},
 | |
| 	}
 | |
| 	weights := [4]float64{
 | |
| 		(1 - xq) * (1 - yq),
 | |
| 		xq * (1 - yq),
 | |
| 		(1 - xq) * yq,
 | |
| 		xq * yq,
 | |
| 	}
 | |
| 
 | |
| 	var r, g, b, a float64
 | |
| 	for i := 0; i < 4; i++ {
 | |
| 		p := points[i]
 | |
| 		w := weights[i]
 | |
| 		if p.In(bounds) {
 | |
| 			i := p.Y*src.Stride + p.X*4
 | |
| 			s := src.Pix[i : i+4 : i+4]
 | |
| 			wa := float64(s[3]) * w
 | |
| 			r += float64(s[0]) * wa
 | |
| 			g += float64(s[1]) * wa
 | |
| 			b += float64(s[2]) * wa
 | |
| 			a += wa
 | |
| 		} else {
 | |
| 			wa := float64(bgColor.A) * w
 | |
| 			r += float64(bgColor.R) * wa
 | |
| 			g += float64(bgColor.G) * wa
 | |
| 			b += float64(bgColor.B) * wa
 | |
| 			a += wa
 | |
| 		}
 | |
| 	}
 | |
| 	if a != 0 {
 | |
| 		aInv := 1 / a
 | |
| 		d[0] = clamp(r * aInv)
 | |
| 		d[1] = clamp(g * aInv)
 | |
| 		d[2] = clamp(b * aInv)
 | |
| 		d[3] = clamp(a)
 | |
| 	}
 | |
| }
 |