90 lines
2.9 KiB
Go
90 lines
2.9 KiB
Go
package imaging
|
|
|
|
import (
|
|
domain "RenewCMS/internal/domain/image"
|
|
"image"
|
|
"math"
|
|
)
|
|
|
|
type layout struct {
|
|
canvas image.Point
|
|
source image.Rectangle
|
|
content image.Rectangle
|
|
}
|
|
|
|
func layoutFor(source image.Point, settings domain.VariantSettings) layout {
|
|
if settings.Width == nil || settings.Height == nil {
|
|
return proportionalLayout(source, settings)
|
|
}
|
|
box := image.Pt(*settings.Width, *settings.Height)
|
|
switch settings.Fit {
|
|
case domain.FitContain:
|
|
return containLayout(source, box)
|
|
case domain.FitInside:
|
|
return insideLayout(source, box)
|
|
default:
|
|
return coverLayout(source, box)
|
|
}
|
|
}
|
|
|
|
func proportionalLayout(source image.Point, settings domain.VariantSettings) layout {
|
|
scale := 1.0
|
|
if settings.Width != nil {
|
|
scale = math.Min(scale, ratio(*settings.Width, source.X))
|
|
}
|
|
if settings.Height != nil {
|
|
scale = math.Min(scale, ratio(*settings.Height, source.Y))
|
|
}
|
|
return wholeSourceLayout(source, scaled(source, scale))
|
|
}
|
|
|
|
func insideLayout(source image.Point, box image.Point) layout {
|
|
scale := math.Min(1, math.Min(ratio(box.X, source.X), ratio(box.Y, source.Y)))
|
|
return wholeSourceLayout(source, scaled(source, scale))
|
|
}
|
|
|
|
func wholeSourceLayout(source image.Point, output image.Point) layout {
|
|
return layout{canvas: output, source: image.Rectangle{Max: source}, content: image.Rectangle{Max: output}}
|
|
}
|
|
|
|
func coverLayout(source image.Point, box image.Point) layout {
|
|
shrink := math.Min(1, math.Min(ratio(source.X, box.X), ratio(source.Y, box.Y)))
|
|
output := scaled(box, shrink)
|
|
return layout{canvas: output, source: centeredCrop(source, output), content: image.Rectangle{Max: output}}
|
|
}
|
|
|
|
func containLayout(source image.Point, box image.Point) layout {
|
|
fit := math.Min(1, math.Min(ratio(box.X, source.X), ratio(box.Y, source.Y)))
|
|
content := scaled(source, fit)
|
|
content = image.Pt(min(content.X, box.X), min(content.Y, box.Y))
|
|
origin := image.Pt((box.X-content.X)/2, (box.Y-content.Y)/2)
|
|
return layout{canvas: box, source: image.Rectangle{Max: source}, content: image.Rectangle{Min: origin, Max: origin.Add(content)}}
|
|
}
|
|
|
|
func centeredCrop(source image.Point, output image.Point) image.Rectangle {
|
|
crop := source
|
|
if source.X*output.Y >= source.Y*output.X {
|
|
crop.X = min(source.X, roundedDivision(source.Y*output.X, output.Y))
|
|
} else {
|
|
crop.Y = min(source.Y, roundedDivision(source.X*output.Y, output.X))
|
|
}
|
|
origin := image.Pt((source.X-crop.X)/2, (source.Y-crop.Y)/2)
|
|
return image.Rectangle{Min: origin, Max: origin.Add(crop)}
|
|
}
|
|
|
|
func ratio(numerator int, denominator int) float64 {
|
|
return float64(numerator) / float64(denominator)
|
|
}
|
|
|
|
func roundedDivision(numerator int, denominator int) int {
|
|
return int(math.Round(ratio(numerator, denominator)))
|
|
}
|
|
|
|
func scaled(size image.Point, factor float64) image.Point {
|
|
return image.Pt(scaledLength(size.X, factor), scaledLength(size.Y, factor))
|
|
}
|
|
|
|
func scaledLength(length int, factor float64) int {
|
|
return max(1, int(math.Round(float64(length)*factor)))
|
|
}
|