feat: image variants engine
This commit is contained in:
1 parent
f0845de643
commit
f1d56ef50e
80 files changed
+7312
-84
No files matched your search
@@ -0,0 +1,102 @@
|
||||
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
|
||||
}
|
||||
Reference in new issue
Block a user