Files
RenewCMS/internal/infrastructure/imaging/variantRenderer.go
T
2026-10-08 02:33:32 +02:00

103 lines
2.9 KiB
Go

package imaging
import (
domain "RenewCMS/internal/domain/image"
"bytes"
"image"
"image/color"
"io"
"slices"
xdraw "golang.org/x/image/draw"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
_ "golang.org/x/image/webp"
)
type VariantRenderer struct {
encoders map[domain.VariantFormat]encoder
}
var _ domain.VariantRenderer = &VariantRenderer{}
func NewVariantRenderer() *VariantRenderer {
return NewVariantRendererEncoding(domain.FormatOriginal, domain.FormatJPEG, domain.FormatPNG, domain.FormatWebP)
}
func NewVariantRendererEncoding(formats ...domain.VariantFormat) *VariantRenderer {
encoders := map[domain.VariantFormat]encoder{}
for format, candidate := range knownEncoders {
if slices.Contains(formats, format) {
encoders[format] = candidate
}
}
return &VariantRenderer{encoders}
}
func (r *VariantRenderer) EncodableFormats() []domain.VariantFormat {
formats := []domain.VariantFormat{domain.FormatOriginal}
for _, format := range domain.VariantFormats() {
if _, encodable := r.encoders[format]; encodable {
formats = append(formats, format)
}
}
return formats
}
func (r *VariantRenderer) DeclaredSize(source io.Reader) (int, int, error) {
header, _, err := image.DecodeConfig(source)
if err != nil {
return 0, 0, err
}
return header.Width, header.Height, nil
}
func (r *VariantRenderer) Render(source io.Reader, settings domain.VariantSettings) (domain.RenderedVariant, error) {
decoded, sourceFormat, err := image.Decode(source)
if err != nil {
return domain.RenderedVariant{}, err
}
chosen := r.encoderFor(settings.Format, sourceFormat)
arranged := arrange(decoded, layoutFor(decoded.Bounds().Size(), settings), chosen.flattensTransparency())
var output bytes.Buffer
if err := chosen.encode(&output, arranged, qualityOf(settings)); err != nil {
return domain.RenderedVariant{}, err
}
return domain.RenderedVariant{Content: output.Bytes(), ContentType: chosen.contentType()}, nil
}
func (r *VariantRenderer) encoderFor(format domain.VariantFormat, sourceFormat string) encoder {
if format == domain.FormatOriginal {
format = sourceFormats[sourceFormat]
}
if chosen, encodable := r.encoders[format]; encodable {
return chosen
}
return pngEncoder{}
}
func qualityOf(settings domain.VariantSettings) int {
if settings.Quality == nil {
return domain.DefaultVariantQuality
}
return *settings.Quality
}
func arrange(decoded image.Image, plan layout, onWhite bool) image.Image {
canvas := image.NewRGBA(image.Rectangle{Max: plan.canvas})
if onWhite {
xdraw.Draw(canvas, canvas.Bounds(), image.NewUniform(color.White), image.Point{}, xdraw.Src)
}
sourceRegion := plan.source.Add(decoded.Bounds().Min)
if plan.source.Size() == plan.content.Size() {
xdraw.Draw(canvas, plan.content, decoded, sourceRegion.Min, xdraw.Over)
return canvas
}
xdraw.CatmullRom.Scale(canvas, plan.content, decoded, sourceRegion, xdraw.Over, nil)
return canvas
}