103 lines
2.9 KiB
Go
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
|
|
}
|