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))) }