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 }