mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-11-03 19:42:25 -06: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)
 | 
						|
	}
 | 
						|
}
 |