mattermost-community-enterp.../vendor/github.com/anthonynsimon/bild/transform/rotate.go
Claude ec1f89217a Merge: Complete Mattermost Server with Community Enterprise
Full Mattermost server source with integrated Community Enterprise features.
Includes vendor directory for offline/air-gapped builds.

Structure:
- enterprise-impl/: Enterprise feature implementations
- enterprise-community/: Init files that register implementations
- enterprise/: Bridge imports (community_imports.go)
- vendor/: All dependencies for offline builds

Build (online):
  go build ./cmd/mattermost

Build (offline/air-gapped):
  go build -mod=vendor ./cmd/mattermost

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-17 23:59:07 +09:00

180 lines
5.1 KiB
Go

package transform
import (
"image"
"image/color"
"math"
"github.com/anthonynsimon/bild/clone"
"github.com/anthonynsimon/bild/parallel"
)
// RotationOptions are the rotation parameters
// ResizeBounds set to false will keep the original image bounds, cutting any
// pixels that go past it when rotating.
// Pivot is the point of anchor for the rotation. Default of center is used if a nil is passed.
// If ResizeBounds is set to true, a center pivot will always be used.
type RotationOptions struct {
ResizeBounds bool
Pivot *image.Point
}
// Rotate returns a rotated image by the provided angle using the pivot as an anchor.
// Parameters angle is in degrees and it's applied clockwise.
// Default parameters are used if a nil *RotationOptions is passed.
//
// Usage example:
//
// // Rotate 90.0 degrees clockwise, preserving the image size and the pivot point at the top left corner
// result := transform.Rotate(img, 90.0, &transform.RotationOptions{ResizeBounds: true, Pivot: &image.Point{0, 0}})
func Rotate(img image.Image, angle float64, options *RotationOptions) *image.RGBA {
src := clone.AsShallowRGBA(img)
srcW, srcH := src.Bounds().Dx(), src.Bounds().Dy()
supersample := false
absAngle := int(math.Abs(angle) + 0.5)
if absAngle%360 == 0 {
// Return early if nothing to do
return src
} else if absAngle%90 != 0 {
// Supersampling is required for non-special angles
// Special angles = 90, 180, 270...
supersample = true
}
// Config defaults
resizeBounds := false
// Default pivot position is center of image
pivotX, pivotY := float64(srcW/2), float64(srcH/2)
// Get options if provided
if options != nil {
resizeBounds = options.ResizeBounds
if options.Pivot != nil {
pivotX, pivotY = float64(options.Pivot.X), float64(options.Pivot.Y)
}
}
if supersample {
// Supersample, currently hard set to 2x
srcW, srcH = srcW*2, srcH*2
src = Resize(src, srcW, srcH, NearestNeighbor)
pivotX, pivotY = pivotX*2, pivotY*2
}
// Convert to radians, positive degree maps to clockwise rotation
angleRadians := -angle * (math.Pi / 180)
var dstW, dstH int
var sin, cos = math.Sincos(angleRadians)
if resizeBounds {
// Reserve larger size in destination image for full image bounds rotation
// If not preserving size, always take image center as pivot
pivotX, pivotY = float64(srcW)/2, float64(srcH)/2
a := math.Abs(float64(srcW) * sin)
b := math.Abs(float64(srcW) * cos)
c := math.Abs(float64(srcH) * sin)
d := math.Abs(float64(srcH) * cos)
dstW, dstH = int(c+b+0.5), int(a+d+0.5)
} else {
dstW, dstH = srcW, srcH
}
dst := image.NewRGBA(image.Rect(0, 0, dstW, dstH))
// Calculate offsets in case entire image is being displayed
// Otherwise areas clipped by rotation won't be available
offsetX := (dstW - srcW) / 2
offsetY := (dstH - srcH) / 2
parallel.Line(srcH, func(start, end int) {
// Correct range to include the pixels visible in new bounds
// Note that cannot be done in parallelize function input height, otherwise ranges would overlap
yStart := int((float64(start)/float64(srcH))*float64(dstH)) - offsetY
yEnd := int((float64(end)/float64(srcH))*float64(dstH)) - offsetY
xStart := -offsetX
xEnd := srcW + offsetX
for y := yStart; y < yEnd; y++ {
dy := float64(y) - pivotY + 0.5
for x := xStart; x < xEnd; x++ {
dx := float64(x) - pivotX + 0.5
ix := int((cos*dx - sin*dy + pivotX))
iy := int((sin*dx + cos*dy + pivotY))
if ix < 0 || ix >= srcW || iy < 0 || iy >= srcH {
continue
}
red, green, blue, alpha := src.At(ix, iy).RGBA()
dst.Set(x+offsetX, y+offsetY, color.RGBA64{
R: uint16(red),
G: uint16(green),
B: uint16(blue),
A: uint16(alpha),
})
}
}
})
if supersample {
// Downsample to original bounds as part of the Supersampling
dst = Resize(dst, dstW/2, dstH/2, Linear)
}
return dst
}
// FlipH returns a horizontally flipped version of the image.
func FlipH(img image.Image) *image.RGBA {
bounds := img.Bounds()
src := clone.AsShallowRGBA(img)
dst := image.NewRGBA(bounds)
w, h := dst.Bounds().Dx(), dst.Bounds().Dy()
parallel.Line(h, func(start, end int) {
for y := start; y < end; y++ {
for x := 0; x < w; x++ {
iy := y * dst.Stride
pos := iy + (x * 4)
flippedX := w - x - 1
flippedPos := iy + (flippedX * 4)
dst.Pix[pos+0] = src.Pix[flippedPos+0]
dst.Pix[pos+1] = src.Pix[flippedPos+1]
dst.Pix[pos+2] = src.Pix[flippedPos+2]
dst.Pix[pos+3] = src.Pix[flippedPos+3]
}
}
})
return dst
}
// FlipV returns a vertically flipped version of the image.
func FlipV(img image.Image) *image.RGBA {
bounds := img.Bounds()
src := clone.AsShallowRGBA(img)
dst := image.NewRGBA(bounds)
w, h := dst.Bounds().Dx(), dst.Bounds().Dy()
parallel.Line(h, func(start, end int) {
for y := start; y < end; y++ {
for x := 0; x < w; x++ {
pos := y*dst.Stride + (x * 4)
flippedY := h - y - 1
flippedPos := flippedY*dst.Stride + (x * 4)
dst.Pix[pos+0] = src.Pix[flippedPos+0]
dst.Pix[pos+1] = src.Pix[flippedPos+1]
dst.Pix[pos+2] = src.Pix[flippedPos+2]
dst.Pix[pos+3] = src.Pix[flippedPos+3]
}
}
})
return dst
}