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>
180 lines
5.1 KiB
Go
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
|
|
}
|