mirror of
https://github.com/superseriousbusiness/gotosocial
synced 2024-12-26 20:53:10 +00:00
91c8d5d20d
* 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)
|
|
}
|
|
}
|