mirror of
https://github.com/imgproxy/imgproxy.git
synced 2025-09-25 19:24:38 +02:00
Processor instance
This commit is contained in:
13
config.go
13
config.go
@@ -7,6 +7,7 @@ import (
|
||||
processinghandler "github.com/imgproxy/imgproxy/v3/handlers/processing"
|
||||
streamhandler "github.com/imgproxy/imgproxy/v3/handlers/stream"
|
||||
"github.com/imgproxy/imgproxy/v3/options"
|
||||
"github.com/imgproxy/imgproxy/v3/processing"
|
||||
"github.com/imgproxy/imgproxy/v3/security"
|
||||
"github.com/imgproxy/imgproxy/v3/server"
|
||||
"github.com/imgproxy/imgproxy/v3/workers"
|
||||
@@ -27,6 +28,7 @@ type Config struct {
|
||||
Handlers HandlerConfigs
|
||||
Server server.Config
|
||||
Security security.Config
|
||||
Processing processing.Config
|
||||
Options options.Config
|
||||
}
|
||||
|
||||
@@ -41,9 +43,10 @@ func NewDefaultConfig() Config {
|
||||
Processing: processinghandler.NewDefaultConfig(),
|
||||
Stream: streamhandler.NewDefaultConfig(),
|
||||
},
|
||||
Server: server.NewDefaultConfig(),
|
||||
Security: security.NewDefaultConfig(),
|
||||
Options: options.NewDefaultConfig(),
|
||||
Server: server.NewDefaultConfig(),
|
||||
Security: security.NewDefaultConfig(),
|
||||
Processing: processing.NewDefaultConfig(),
|
||||
Options: options.NewDefaultConfig(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,5 +92,9 @@ func LoadConfigFromEnv(c *Config) (*Config, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err = processing.LoadConfigFromEnv(&c.Processing); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/imgproxy/imgproxy/v3/monitoring"
|
||||
"github.com/imgproxy/imgproxy/v3/monitoring/stats"
|
||||
"github.com/imgproxy/imgproxy/v3/options"
|
||||
"github.com/imgproxy/imgproxy/v3/processing"
|
||||
"github.com/imgproxy/imgproxy/v3/security"
|
||||
"github.com/imgproxy/imgproxy/v3/server"
|
||||
"github.com/imgproxy/imgproxy/v3/workers"
|
||||
@@ -27,6 +28,7 @@ type HandlerContext interface {
|
||||
ImageDataFactory() *imagedata.Factory
|
||||
Security() *security.Checker
|
||||
OptionsFactory() *options.Factory
|
||||
Processor() *processing.Processor
|
||||
}
|
||||
|
||||
// Handler handles image processing requests
|
||||
|
@@ -157,7 +157,7 @@ func (r *request) getFallbackImage(
|
||||
// processImage calls actual image processing
|
||||
func (r *request) processImage(ctx context.Context, originData imagedata.ImageData) (*processing.Result, error) {
|
||||
defer monitoring.StartProcessingSegment(ctx, r.monitoringMeta.Filter(monitoring.MetaProcessingOptions))()
|
||||
return processing.ProcessImage(ctx, originData, r.po, r.WatermarkImage())
|
||||
return r.Processor().ProcessImage(ctx, originData, r.po, r.WatermarkImage())
|
||||
}
|
||||
|
||||
// writeDebugHeaders writes debug headers (X-Origin-*, X-Result-*) to the response
|
||||
|
13
imgproxy.go
13
imgproxy.go
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/imgproxy/imgproxy/v3/memory"
|
||||
"github.com/imgproxy/imgproxy/v3/monitoring/prometheus"
|
||||
"github.com/imgproxy/imgproxy/v3/options"
|
||||
"github.com/imgproxy/imgproxy/v3/processing"
|
||||
"github.com/imgproxy/imgproxy/v3/security"
|
||||
"github.com/imgproxy/imgproxy/v3/server"
|
||||
"github.com/imgproxy/imgproxy/v3/workers"
|
||||
@@ -31,6 +32,7 @@ type ImgproxyHandlers struct {
|
||||
Landing *landinghandler.Handler
|
||||
Processing *processinghandler.Handler
|
||||
Stream *streamhandler.Handler
|
||||
Processor *processing.Processor
|
||||
}
|
||||
|
||||
// Imgproxy holds all the components needed for imgproxy to function
|
||||
@@ -43,6 +45,7 @@ type Imgproxy struct {
|
||||
handlers ImgproxyHandlers
|
||||
security *security.Checker
|
||||
optionsFactory *options.Factory
|
||||
processor *processing.Processor
|
||||
config *Config
|
||||
}
|
||||
|
||||
@@ -80,6 +83,11 @@ func New(ctx context.Context, config *Config) (*Imgproxy, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
processor, err := processing.New(&config.Processing, watermarkImage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
imgproxy := &Imgproxy{
|
||||
workers: workers,
|
||||
fallbackImage: fallbackImage,
|
||||
@@ -89,6 +97,7 @@ func New(ctx context.Context, config *Config) (*Imgproxy, error) {
|
||||
config: config,
|
||||
security: security,
|
||||
optionsFactory: processingOptionsFactory,
|
||||
processor: processor,
|
||||
}
|
||||
|
||||
imgproxy.handlers.Health = healthhandler.New()
|
||||
@@ -211,3 +220,7 @@ func (i *Imgproxy) Security() *security.Checker {
|
||||
func (i *Imgproxy) OptionsFactory() *options.Factory {
|
||||
return i.optionsFactory
|
||||
}
|
||||
|
||||
func (i *Imgproxy) Processor() *processing.Processor {
|
||||
return i.processor
|
||||
}
|
||||
|
6
init.go
6
init.go
@@ -10,7 +10,6 @@ import (
|
||||
"github.com/imgproxy/imgproxy/v3/gliblog"
|
||||
"github.com/imgproxy/imgproxy/v3/logger"
|
||||
"github.com/imgproxy/imgproxy/v3/monitoring"
|
||||
"github.com/imgproxy/imgproxy/v3/processing"
|
||||
"github.com/imgproxy/imgproxy/v3/vips"
|
||||
"go.uber.org/automaxprocs/maxprocs"
|
||||
)
|
||||
@@ -47,11 +46,6 @@ func Init() error {
|
||||
|
||||
errorreport.Init()
|
||||
|
||||
if err := processing.ValidatePreferredFormats(); err != nil {
|
||||
vips.Shutdown()
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
package processing
|
||||
|
||||
func applyFilters(c *Context) error {
|
||||
func (p *Processor) applyFilters(c *Context) error {
|
||||
if c.PO.Blur == 0 && c.PO.Sharpen == 0 && c.PO.Pixelate <= 1 {
|
||||
return nil
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
package processing
|
||||
|
||||
func colorspaceToProcessing(c *Context) error {
|
||||
func (p *Processor) colorspaceToProcessing(c *Context) error {
|
||||
if c.Img.ColourProfileImported() {
|
||||
return nil
|
||||
}
|
||||
@@ -9,7 +9,7 @@ func colorspaceToProcessing(c *Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
convertToLinear := c.Config.UseLinearColorspace && (c.WScale != 1 || c.HScale != 1)
|
||||
convertToLinear := p.config.UseLinearColorspace && (c.WScale != 1 || c.HScale != 1)
|
||||
|
||||
if c.Img.IsLinear() {
|
||||
// The image is linear. If we keep its ICC, we'll get wrong colors after
|
||||
|
@@ -1,6 +1,6 @@
|
||||
package processing
|
||||
|
||||
func colorspaceToResult(c *Context) error {
|
||||
func (p *Processor) colorspaceToResult(c *Context) error {
|
||||
keepProfile := !c.PO.StripColorProfile && c.PO.Format.SupportsColourProfile()
|
||||
|
||||
if c.Img.IsLinear() {
|
||||
|
@@ -1,14 +1,18 @@
|
||||
package pipeline
|
||||
package processing
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/imgproxy/imgproxy/v3/config"
|
||||
"github.com/imgproxy/imgproxy/v3/ensure"
|
||||
"github.com/imgproxy/imgproxy/v3/imagetype"
|
||||
"github.com/imgproxy/imgproxy/v3/vips"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Config holds pipeline-related configuration.
|
||||
type Config struct {
|
||||
PreferredFormats []imagetype.Type
|
||||
WatermarkOpacity float64
|
||||
DisableShrinkOnLoad bool
|
||||
UseLinearColorspace bool
|
||||
@@ -18,6 +22,11 @@ type Config struct {
|
||||
func NewDefaultConfig() Config {
|
||||
return Config{
|
||||
WatermarkOpacity: 1,
|
||||
PreferredFormats: []imagetype.Type{
|
||||
imagetype.JPEG,
|
||||
imagetype.PNG,
|
||||
imagetype.GIF,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +37,7 @@ func LoadConfigFromEnv(c *Config) (*Config, error) {
|
||||
c.WatermarkOpacity = config.WatermarkOpacity
|
||||
c.DisableShrinkOnLoad = config.DisableShrinkOnLoad
|
||||
c.UseLinearColorspace = config.UseLinearColorspace
|
||||
c.PreferredFormats = config.PreferredFormats
|
||||
|
||||
return c, nil
|
||||
}
|
||||
@@ -40,5 +50,21 @@ func (c *Config) Validate() error {
|
||||
return errors.New("watermark opacity should be less than or equal to 1")
|
||||
}
|
||||
|
||||
filtered := c.PreferredFormats[:0]
|
||||
|
||||
for _, t := range c.PreferredFormats {
|
||||
if !vips.SupportsSave(t) {
|
||||
log.Warnf("%s can't be a preferred format as it's saving is not supported", t)
|
||||
} else {
|
||||
filtered = append(filtered, t)
|
||||
}
|
||||
}
|
||||
|
||||
if len(filtered) == 0 {
|
||||
return errors.New("no supported preferred formats specified")
|
||||
}
|
||||
|
||||
c.PreferredFormats = filtered
|
||||
|
||||
return nil
|
||||
}
|
@@ -31,7 +31,7 @@ func cropImage(img *vips.Image, cropWidth, cropHeight int, gravity *options.Grav
|
||||
return img.Crop(left, top, cropWidth, cropHeight)
|
||||
}
|
||||
|
||||
func crop(c *Context) error {
|
||||
func (p *Processor) crop(c *Context) error {
|
||||
width, height := c.CropWidth, c.CropHeight
|
||||
|
||||
opts := c.CropGravity
|
||||
@@ -46,6 +46,6 @@ func crop(c *Context) error {
|
||||
return cropImage(c.Img, width, height, &opts, 1.0)
|
||||
}
|
||||
|
||||
func cropToResult(c *Context) error {
|
||||
func (p *Processor) cropToResult(c *Context) error {
|
||||
return cropImage(c.Img, c.ResultCropWidth, c.ResultCropHeight, &c.PO.Gravity, c.DprScale)
|
||||
}
|
||||
|
@@ -24,7 +24,7 @@ func extendImage(img *vips.Image, width, height int, gravity *options.GravityOpt
|
||||
return img.Embed(width, height, offX, offY)
|
||||
}
|
||||
|
||||
func extend(c *Context) error {
|
||||
func (p *Processor) extend(c *Context) error {
|
||||
if !c.PO.Extend.Enabled {
|
||||
return nil
|
||||
}
|
||||
@@ -33,7 +33,7 @@ func extend(c *Context) error {
|
||||
return extendImage(c.Img, width, height, &c.PO.Extend.Gravity, c.DprScale)
|
||||
}
|
||||
|
||||
func extendAspectRatio(c *Context) error {
|
||||
func (p *Processor) extendAspectRatio(c *Context) error {
|
||||
if !c.PO.ExtendAspectRatio.Enabled {
|
||||
return nil
|
||||
}
|
||||
|
@@ -88,7 +88,7 @@ func fixIcoSize(img *vips.Image) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func fixSize(c *Context) error {
|
||||
func (p *Processor) fixSize(c *Context) error {
|
||||
switch c.PO.Format {
|
||||
case imagetype.WEBP:
|
||||
return fixWebpSize(c.Img)
|
||||
|
@@ -1,6 +1,6 @@
|
||||
package processing
|
||||
|
||||
func flatten(c *Context) error {
|
||||
func (p *Processor) flatten(c *Context) error {
|
||||
if !c.PO.Flatten && c.PO.Format.SupportsAlpha() {
|
||||
return nil
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@ import (
|
||||
"github.com/imgproxy/imgproxy/v3/imath"
|
||||
)
|
||||
|
||||
func padding(c *Context) error {
|
||||
func (p *Processor) padding(c *Context) error {
|
||||
if !c.PO.Padding.Enabled {
|
||||
return nil
|
||||
}
|
||||
|
@@ -6,21 +6,14 @@ import (
|
||||
"github.com/imgproxy/imgproxy/v3/auximageprovider"
|
||||
"github.com/imgproxy/imgproxy/v3/imagedata"
|
||||
"github.com/imgproxy/imgproxy/v3/options"
|
||||
"github.com/imgproxy/imgproxy/v3/processing/pipeline"
|
||||
"github.com/imgproxy/imgproxy/v3/server"
|
||||
"github.com/imgproxy/imgproxy/v3/vips"
|
||||
)
|
||||
|
||||
// NOTE: this will be called pipeline.Context in the separate package
|
||||
type Context struct {
|
||||
// The runner that runs this pipeline
|
||||
runner *Runner
|
||||
|
||||
// The context to check for timeouts and cancellations
|
||||
Ctx context.Context
|
||||
|
||||
// Global processing configuration which could be used by individual steps
|
||||
Config *pipeline.Config
|
||||
|
||||
// VIPS image
|
||||
Img *vips.Image
|
||||
|
||||
@@ -80,33 +73,18 @@ type Context struct {
|
||||
ExtendAspectRatioHeight int
|
||||
}
|
||||
|
||||
// NOTE: same, pipeline.Step, pipeline.Pipeline, pipeline.Runner
|
||||
type Step func(ctx *Context) error
|
||||
type Step func(c *Context) error
|
||||
type Pipeline []Step
|
||||
|
||||
// Runner is responsible for running a processing pipeline
|
||||
type Runner struct {
|
||||
config *pipeline.Config
|
||||
watermark auximageprovider.Provider
|
||||
}
|
||||
|
||||
// New creates a new Runner instance with the given configuration and watermark provider
|
||||
func New(config *pipeline.Config, watermark auximageprovider.Provider) *Runner {
|
||||
return &Runner{
|
||||
config: config,
|
||||
watermark: watermark,
|
||||
}
|
||||
}
|
||||
|
||||
// Run runs the given pipeline with the given parameters
|
||||
func (f *Runner) Run(
|
||||
p Pipeline,
|
||||
func (p Pipeline) Run(
|
||||
ctx context.Context,
|
||||
img *vips.Image,
|
||||
po *options.ProcessingOptions,
|
||||
imgdata imagedata.ImageData,
|
||||
) error {
|
||||
pctx := f.newContext(ctx, img, po, imgdata)
|
||||
pctx := p.newContext(ctx, img, po, imgdata)
|
||||
pctx.CalcParams() // calc initial params if not done before
|
||||
|
||||
for _, step := range p {
|
||||
if err := step(&pctx); err != nil {
|
||||
@@ -123,17 +101,14 @@ func (f *Runner) Run(
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Runner) newContext(
|
||||
func (p Pipeline) newContext(
|
||||
ctx context.Context,
|
||||
img *vips.Image,
|
||||
po *options.ProcessingOptions,
|
||||
imgdata imagedata.ImageData,
|
||||
) Context {
|
||||
pctx := Context{
|
||||
runner: r,
|
||||
|
||||
Ctx: ctx,
|
||||
Config: r.config,
|
||||
Img: img,
|
||||
PO: po,
|
||||
ImgData: imgdata,
|
||||
@@ -144,8 +119,7 @@ func (r *Runner) newContext(
|
||||
DprScale: 1.0,
|
||||
VectorBaseScale: 1.0,
|
||||
|
||||
CropGravity: po.Crop.Gravity,
|
||||
WatermarkProvider: r.watermark,
|
||||
CropGravity: po.Crop.Gravity,
|
||||
}
|
||||
|
||||
if pctx.CropGravity.Type == options.GravityUnknown {
|
||||
@@ -154,7 +128,3 @@ func (r *Runner) newContext(
|
||||
|
||||
return pctx
|
||||
}
|
||||
|
||||
func (c *Context) Runner() *Runner {
|
||||
return c.runner
|
||||
}
|
||||
|
@@ -8,29 +8,12 @@ import (
|
||||
"github.com/imgproxy/imgproxy/v3/vips"
|
||||
)
|
||||
|
||||
func extractMeta(img *vips.Image, baseAngle int, useOrientation bool) (int, int, int, bool) {
|
||||
// ExtractGeometry extracts image width, height, orientation angle and flip flag from the image metadata.
|
||||
func (c *Context) ExtractGeometry(img *vips.Image, baseAngle int, autoRotate bool) (int, int, int, bool) {
|
||||
width := img.Width()
|
||||
height := img.Height()
|
||||
|
||||
angle := 0
|
||||
flip := false
|
||||
|
||||
if useOrientation {
|
||||
orientation := img.Orientation()
|
||||
|
||||
if orientation == 3 || orientation == 4 {
|
||||
angle = 180
|
||||
}
|
||||
if orientation == 5 || orientation == 6 {
|
||||
angle = 90
|
||||
}
|
||||
if orientation == 7 || orientation == 8 {
|
||||
angle = 270
|
||||
}
|
||||
if orientation == 2 || orientation == 4 || orientation == 5 || orientation == 7 {
|
||||
flip = true
|
||||
}
|
||||
}
|
||||
angle, flip := c.angleFlip(img, autoRotate)
|
||||
|
||||
if (angle+baseAngle)%180 != 0 {
|
||||
width, height = height, width
|
||||
@@ -39,7 +22,39 @@ func extractMeta(img *vips.Image, baseAngle int, useOrientation bool) (int, int,
|
||||
return width, height, angle, flip
|
||||
}
|
||||
|
||||
func calcCropSize(orig int, crop float64) int {
|
||||
// angleFlip returns the orientation angle and flip flag based on the image metadata
|
||||
// and po.AutoRotate flag.
|
||||
func (c *Context) angleFlip(img *vips.Image, autoRotate bool) (int, bool) {
|
||||
if !autoRotate {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
angle := 0
|
||||
flip := false
|
||||
|
||||
orientation := img.Orientation()
|
||||
|
||||
if orientation == 3 || orientation == 4 {
|
||||
angle = 180
|
||||
}
|
||||
|
||||
if orientation == 5 || orientation == 6 {
|
||||
angle = 90
|
||||
}
|
||||
|
||||
if orientation == 7 || orientation == 8 {
|
||||
angle = 270
|
||||
}
|
||||
|
||||
if orientation == 2 || orientation == 4 || orientation == 5 || orientation == 7 {
|
||||
flip = true
|
||||
}
|
||||
|
||||
return angle, flip
|
||||
}
|
||||
|
||||
// CalcCropSize calculates the crop size based on the original size and crop scale.
|
||||
func (c *Context) CalcCropSize(orig int, crop float64) int {
|
||||
switch {
|
||||
case crop == 0.0:
|
||||
return 0
|
||||
@@ -50,31 +65,28 @@ func calcCropSize(orig int, crop float64) int {
|
||||
}
|
||||
}
|
||||
|
||||
func (pctx *Context) calcScale(width, height int, po *options.ProcessingOptions) {
|
||||
// calcShrink calculates the destination size and shrink factor
|
||||
func calcShrink(value int, src, dst float64) (float64, float64) {
|
||||
if value == 0 {
|
||||
dst = src
|
||||
}
|
||||
|
||||
shrink := 1.0
|
||||
if dst != src {
|
||||
shrink = src / dst
|
||||
}
|
||||
|
||||
return dst, shrink
|
||||
}
|
||||
|
||||
func (c *Context) calcScale(width, height int, po *options.ProcessingOptions) {
|
||||
var wshrink, hshrink float64
|
||||
|
||||
srcW, srcH := float64(width), float64(height)
|
||||
dstW, dstH := float64(po.Width), float64(po.Height)
|
||||
|
||||
if po.Width == 0 {
|
||||
dstW = srcW
|
||||
}
|
||||
|
||||
if dstW == srcW {
|
||||
wshrink = 1
|
||||
} else {
|
||||
wshrink = srcW / dstW
|
||||
}
|
||||
|
||||
if po.Height == 0 {
|
||||
dstH = srcH
|
||||
}
|
||||
|
||||
if dstH == srcH {
|
||||
hshrink = 1
|
||||
} else {
|
||||
hshrink = srcH / dstH
|
||||
}
|
||||
dstW, wshrink = calcShrink(po.Width, srcW, dstW)
|
||||
dstH, hshrink = calcShrink(po.Height, srcH, dstH)
|
||||
|
||||
if wshrink != 1 || hshrink != 1 {
|
||||
rt := po.ResizingType
|
||||
@@ -107,22 +119,22 @@ func (pctx *Context) calcScale(width, height int, po *options.ProcessingOptions)
|
||||
wshrink /= po.ZoomWidth
|
||||
hshrink /= po.ZoomHeight
|
||||
|
||||
pctx.DprScale = po.Dpr
|
||||
c.DprScale = po.Dpr
|
||||
|
||||
if !po.Enlarge && pctx.ImgData != nil && !pctx.ImgData.Format().IsVector() {
|
||||
if !po.Enlarge && c.ImgData != nil && !c.ImgData.Format().IsVector() {
|
||||
minShrink := math.Min(wshrink, hshrink)
|
||||
if minShrink < 1 {
|
||||
wshrink /= minShrink
|
||||
hshrink /= minShrink
|
||||
|
||||
if !po.Extend.Enabled {
|
||||
pctx.DprScale /= minShrink
|
||||
c.DprScale /= minShrink
|
||||
}
|
||||
}
|
||||
|
||||
// The minimum of wshrink and hshrink is the maximum dprScale value
|
||||
// that can be used without enlarging the image.
|
||||
pctx.DprScale = math.Min(pctx.DprScale, math.Min(wshrink, hshrink))
|
||||
c.DprScale = math.Min(c.DprScale, math.Min(wshrink, hshrink))
|
||||
}
|
||||
|
||||
if po.MinWidth > 0 {
|
||||
@@ -139,8 +151,8 @@ func (pctx *Context) calcScale(width, height int, po *options.ProcessingOptions)
|
||||
}
|
||||
}
|
||||
|
||||
wshrink /= pctx.DprScale
|
||||
hshrink /= pctx.DprScale
|
||||
wshrink /= c.DprScale
|
||||
hshrink /= c.DprScale
|
||||
|
||||
if wshrink > srcW {
|
||||
wshrink = srcW
|
||||
@@ -150,110 +162,113 @@ func (pctx *Context) calcScale(width, height int, po *options.ProcessingOptions)
|
||||
hshrink = srcH
|
||||
}
|
||||
|
||||
pctx.WScale = 1.0 / wshrink
|
||||
pctx.HScale = 1.0 / hshrink
|
||||
c.WScale = 1.0 / wshrink
|
||||
c.HScale = 1.0 / hshrink
|
||||
}
|
||||
|
||||
func (pctx *Context) calcSizes(widthToScale, heightToScale int, po *options.ProcessingOptions) {
|
||||
pctx.TargetWidth = imath.Scale(po.Width, pctx.DprScale*po.ZoomWidth)
|
||||
pctx.TargetHeight = imath.Scale(po.Height, pctx.DprScale*po.ZoomHeight)
|
||||
func (c *Context) calcSizes(widthToScale, heightToScale int, po *options.ProcessingOptions) {
|
||||
c.TargetWidth = imath.Scale(po.Width, c.DprScale*po.ZoomWidth)
|
||||
c.TargetHeight = imath.Scale(po.Height, c.DprScale*po.ZoomHeight)
|
||||
|
||||
pctx.ScaledWidth = imath.Scale(widthToScale, pctx.WScale)
|
||||
pctx.ScaledHeight = imath.Scale(heightToScale, pctx.HScale)
|
||||
c.ScaledWidth = imath.Scale(widthToScale, c.WScale)
|
||||
c.ScaledHeight = imath.Scale(heightToScale, c.HScale)
|
||||
|
||||
if po.ResizingType == options.ResizeFillDown && !po.Enlarge {
|
||||
diffW := float64(pctx.TargetWidth) / float64(pctx.ScaledWidth)
|
||||
diffH := float64(pctx.TargetHeight) / float64(pctx.ScaledHeight)
|
||||
diffW := float64(c.TargetWidth) / float64(c.ScaledWidth)
|
||||
diffH := float64(c.TargetHeight) / float64(c.ScaledHeight)
|
||||
|
||||
switch {
|
||||
case diffW > diffH && diffW > 1.0:
|
||||
pctx.ResultCropHeight = imath.Scale(pctx.ScaledWidth, float64(pctx.TargetHeight)/float64(pctx.TargetWidth))
|
||||
pctx.ResultCropWidth = pctx.ScaledWidth
|
||||
c.ResultCropHeight = imath.Scale(c.ScaledWidth, float64(c.TargetHeight)/float64(c.TargetWidth))
|
||||
c.ResultCropWidth = c.ScaledWidth
|
||||
|
||||
case diffH > diffW && diffH > 1.0:
|
||||
pctx.ResultCropWidth = imath.Scale(pctx.ScaledHeight, float64(pctx.TargetWidth)/float64(pctx.TargetHeight))
|
||||
pctx.ResultCropHeight = pctx.ScaledHeight
|
||||
c.ResultCropWidth = imath.Scale(c.ScaledHeight, float64(c.TargetWidth)/float64(c.TargetHeight))
|
||||
c.ResultCropHeight = c.ScaledHeight
|
||||
|
||||
default:
|
||||
pctx.ResultCropWidth = pctx.TargetWidth
|
||||
pctx.ResultCropHeight = pctx.TargetHeight
|
||||
c.ResultCropWidth = c.TargetWidth
|
||||
c.ResultCropHeight = c.TargetHeight
|
||||
}
|
||||
} else {
|
||||
pctx.ResultCropWidth = pctx.TargetWidth
|
||||
pctx.ResultCropHeight = pctx.TargetHeight
|
||||
c.ResultCropWidth = c.TargetWidth
|
||||
c.ResultCropHeight = c.TargetHeight
|
||||
}
|
||||
|
||||
if po.ExtendAspectRatio.Enabled && pctx.TargetWidth > 0 && pctx.TargetHeight > 0 {
|
||||
outWidth := imath.MinNonZero(pctx.ScaledWidth, pctx.ResultCropWidth)
|
||||
outHeight := imath.MinNonZero(pctx.ScaledHeight, pctx.ResultCropHeight)
|
||||
if po.ExtendAspectRatio.Enabled && c.TargetWidth > 0 && c.TargetHeight > 0 {
|
||||
outWidth := imath.MinNonZero(c.ScaledWidth, c.ResultCropWidth)
|
||||
outHeight := imath.MinNonZero(c.ScaledHeight, c.ResultCropHeight)
|
||||
|
||||
diffW := float64(pctx.TargetWidth) / float64(outWidth)
|
||||
diffH := float64(pctx.TargetHeight) / float64(outHeight)
|
||||
diffW := float64(c.TargetWidth) / float64(outWidth)
|
||||
diffH := float64(c.TargetHeight) / float64(outHeight)
|
||||
|
||||
switch {
|
||||
case diffH > diffW:
|
||||
pctx.ExtendAspectRatioHeight = imath.Scale(outWidth, float64(pctx.TargetHeight)/float64(pctx.TargetWidth))
|
||||
pctx.ExtendAspectRatioWidth = outWidth
|
||||
c.ExtendAspectRatioHeight = imath.Scale(outWidth, float64(c.TargetHeight)/float64(c.TargetWidth))
|
||||
c.ExtendAspectRatioWidth = outWidth
|
||||
|
||||
case diffW > diffH:
|
||||
pctx.ExtendAspectRatioWidth = imath.Scale(outHeight, float64(pctx.TargetWidth)/float64(pctx.TargetHeight))
|
||||
pctx.ExtendAspectRatioHeight = outHeight
|
||||
c.ExtendAspectRatioWidth = imath.Scale(outHeight, float64(c.TargetWidth)/float64(c.TargetHeight))
|
||||
c.ExtendAspectRatioHeight = outHeight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (pctx *Context) limitScale(widthToScale, heightToScale int, po *options.ProcessingOptions) {
|
||||
func (c *Context) limitScale(widthToScale, heightToScale int, po *options.ProcessingOptions) {
|
||||
maxresultDim := po.SecurityOptions.MaxResultDimension
|
||||
|
||||
if maxresultDim <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
outWidth := imath.MinNonZero(pctx.ScaledWidth, pctx.ResultCropWidth)
|
||||
outHeight := imath.MinNonZero(pctx.ScaledHeight, pctx.ResultCropHeight)
|
||||
outWidth := imath.MinNonZero(c.ScaledWidth, c.ResultCropWidth)
|
||||
outHeight := imath.MinNonZero(c.ScaledHeight, c.ResultCropHeight)
|
||||
|
||||
if po.Extend.Enabled {
|
||||
outWidth = max(outWidth, pctx.TargetWidth)
|
||||
outHeight = max(outHeight, pctx.TargetHeight)
|
||||
outWidth = max(outWidth, c.TargetWidth)
|
||||
outHeight = max(outHeight, c.TargetHeight)
|
||||
} else if po.ExtendAspectRatio.Enabled {
|
||||
outWidth = max(outWidth, pctx.ExtendAspectRatioWidth)
|
||||
outHeight = max(outHeight, pctx.ExtendAspectRatioHeight)
|
||||
outWidth = max(outWidth, c.ExtendAspectRatioWidth)
|
||||
outHeight = max(outHeight, c.ExtendAspectRatioHeight)
|
||||
}
|
||||
|
||||
if po.Padding.Enabled {
|
||||
outWidth += imath.ScaleToEven(po.Padding.Left, pctx.DprScale) + imath.ScaleToEven(po.Padding.Right, pctx.DprScale)
|
||||
outHeight += imath.ScaleToEven(po.Padding.Top, pctx.DprScale) + imath.ScaleToEven(po.Padding.Bottom, pctx.DprScale)
|
||||
outWidth += imath.ScaleToEven(po.Padding.Left, c.DprScale) + imath.ScaleToEven(po.Padding.Right, c.DprScale)
|
||||
outHeight += imath.ScaleToEven(po.Padding.Top, c.DprScale) + imath.ScaleToEven(po.Padding.Bottom, c.DprScale)
|
||||
}
|
||||
|
||||
if maxresultDim > 0 && (outWidth > maxresultDim || outHeight > maxresultDim) {
|
||||
downScale := float64(maxresultDim) / float64(max(outWidth, outHeight))
|
||||
|
||||
pctx.WScale *= downScale
|
||||
pctx.HScale *= downScale
|
||||
c.WScale *= downScale
|
||||
c.HScale *= downScale
|
||||
|
||||
// Prevent scaling below 1px
|
||||
if minWScale := 1.0 / float64(widthToScale); pctx.WScale < minWScale {
|
||||
pctx.WScale = minWScale
|
||||
if minWScale := 1.0 / float64(widthToScale); c.WScale < minWScale {
|
||||
c.WScale = minWScale
|
||||
}
|
||||
if minHScale := 1.0 / float64(heightToScale); pctx.HScale < minHScale {
|
||||
pctx.HScale = minHScale
|
||||
if minHScale := 1.0 / float64(heightToScale); c.HScale < minHScale {
|
||||
c.HScale = minHScale
|
||||
}
|
||||
|
||||
pctx.DprScale *= downScale
|
||||
c.DprScale *= downScale
|
||||
|
||||
// Recalculate the sizes after changing the scales
|
||||
pctx.calcSizes(widthToScale, heightToScale, po)
|
||||
c.calcSizes(widthToScale, heightToScale, po)
|
||||
}
|
||||
}
|
||||
|
||||
// prepare extracts image metadata and calculates scaling factors and target sizes.
|
||||
// This can't be done in advance because some steps like trimming and rasterization could
|
||||
// happen before this step.
|
||||
func prepare(c *Context) error {
|
||||
c.SrcWidth, c.SrcHeight, c.Angle, c.Flip = extractMeta(c.Img, c.PO.Rotate, c.PO.AutoRotate)
|
||||
// Prepare calculates context image parameters based on the current image size.
|
||||
// Some steps (like trim) must call this function when finished.
|
||||
func (c *Context) CalcParams() {
|
||||
if c.ImgData == nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.CropWidth = calcCropSize(c.SrcWidth, c.PO.Crop.Width)
|
||||
c.CropHeight = calcCropSize(c.SrcHeight, c.PO.Crop.Height)
|
||||
c.SrcWidth, c.SrcHeight, c.Angle, c.Flip = c.ExtractGeometry(c.Img, c.PO.Rotate, c.PO.AutoRotate)
|
||||
|
||||
c.CropWidth = c.CalcCropSize(c.SrcWidth, c.PO.Crop.Width)
|
||||
c.CropHeight = c.CalcCropSize(c.SrcHeight, c.PO.Crop.Height)
|
||||
|
||||
widthToScale := imath.MinNonZero(c.CropWidth, c.SrcWidth)
|
||||
heightToScale := imath.MinNonZero(c.CropHeight, c.SrcHeight)
|
||||
@@ -261,6 +276,4 @@ func prepare(c *Context) error {
|
||||
c.calcScale(widthToScale, heightToScale, c.PO)
|
||||
c.calcSizes(widthToScale, heightToScale, c.PO)
|
||||
c.limitScale(widthToScale, heightToScale, c.PO)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@@ -2,7 +2,6 @@ package processing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"runtime"
|
||||
"slices"
|
||||
|
||||
@@ -13,59 +12,41 @@ import (
|
||||
"github.com/imgproxy/imgproxy/v3/imagedata"
|
||||
"github.com/imgproxy/imgproxy/v3/imagetype"
|
||||
"github.com/imgproxy/imgproxy/v3/options"
|
||||
"github.com/imgproxy/imgproxy/v3/processing/pipeline"
|
||||
"github.com/imgproxy/imgproxy/v3/security"
|
||||
"github.com/imgproxy/imgproxy/v3/server"
|
||||
"github.com/imgproxy/imgproxy/v3/svg"
|
||||
"github.com/imgproxy/imgproxy/v3/vips"
|
||||
)
|
||||
|
||||
// The main processing pipeline (without finalization).
|
||||
// Applied to non-animated images and individual frames of animated images.
|
||||
var mainPipeline = Pipeline{
|
||||
vectorGuardScale,
|
||||
trim,
|
||||
prepare,
|
||||
scaleOnLoad,
|
||||
colorspaceToProcessing,
|
||||
crop,
|
||||
scale,
|
||||
rotateAndFlip,
|
||||
cropToResult,
|
||||
applyFilters,
|
||||
extend,
|
||||
extendAspectRatio,
|
||||
padding,
|
||||
fixSize,
|
||||
flatten,
|
||||
watermark,
|
||||
// mainPipeline constructs the main image processing pipeline.
|
||||
// This pipeline is applied to each image frame.
|
||||
func (p *Processor) mainPipeline() Pipeline {
|
||||
return Pipeline{
|
||||
p.vectorGuardScale,
|
||||
p.trim,
|
||||
p.scaleOnLoad,
|
||||
p.colorspaceToProcessing,
|
||||
p.crop,
|
||||
p.scale,
|
||||
p.rotateAndFlip,
|
||||
p.cropToResult,
|
||||
p.applyFilters,
|
||||
p.extend,
|
||||
p.extendAspectRatio,
|
||||
p.padding,
|
||||
p.fixSize,
|
||||
p.flatten,
|
||||
p.watermark,
|
||||
}
|
||||
}
|
||||
|
||||
// The finalization pipeline.
|
||||
// Applied right before saving the image.
|
||||
var finalizePipeline = Pipeline{
|
||||
colorspaceToResult,
|
||||
stripMetadata,
|
||||
}
|
||||
|
||||
func ValidatePreferredFormats() error {
|
||||
filtered := config.PreferredFormats[:0]
|
||||
|
||||
for _, t := range config.PreferredFormats {
|
||||
if !vips.SupportsSave(t) {
|
||||
log.Warnf("%s can't be a preferred format as it's saving is not supported", t)
|
||||
} else {
|
||||
filtered = append(filtered, t)
|
||||
}
|
||||
// finalizePipeline constructs the finalization pipeline.
|
||||
// This pipeline is applied before saving the image.
|
||||
func (p *Processor) finalizePipeline() Pipeline {
|
||||
return Pipeline{
|
||||
p.colorspaceToResult,
|
||||
p.stripMetadata,
|
||||
}
|
||||
|
||||
if len(filtered) == 0 {
|
||||
return errors.New("no supported preferred formats specified")
|
||||
}
|
||||
|
||||
config.PreferredFormats = filtered
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Result holds the result of image processing.
|
||||
@@ -81,7 +62,7 @@ type Result struct {
|
||||
// and returns a [Result] that includes the processed image data and dimensions.
|
||||
//
|
||||
// The provided processing options may be modified during processing.
|
||||
func ProcessImage(
|
||||
func (p *Processor) ProcessImage(
|
||||
ctx context.Context,
|
||||
imgdata imagedata.ImageData,
|
||||
po *options.ProcessingOptions,
|
||||
@@ -139,19 +120,12 @@ func ProcessImage(
|
||||
}
|
||||
|
||||
// Transform the image (resize, crop, etc)
|
||||
if err = transformImage(ctx, img, po, imgdata, animated, watermarkProvider); err != nil {
|
||||
if err = p.transformImage(ctx, img, po, imgdata, animated); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// NOTE: THIS IS TEMPORARY
|
||||
runner, err := tmpNewRunner(watermarkProvider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// NOTE: END TEMPORARY BLOCK
|
||||
|
||||
// Finalize the image (colorspace conversion, metadata stripping, etc)
|
||||
if err = runner.Run(finalizePipeline, ctx, img, po, imgdata); err != nil {
|
||||
if err = p.finalizePipeline().Run(ctx, img, po, imgdata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -389,41 +363,25 @@ func findPreferredFormat(animated, expectTransparency bool) imagetype.Type {
|
||||
return config.PreferredFormats[0]
|
||||
}
|
||||
|
||||
func transformImage(
|
||||
func (p *Processor) transformImage(
|
||||
ctx context.Context,
|
||||
img *vips.Image,
|
||||
po *options.ProcessingOptions,
|
||||
imgdata imagedata.ImageData,
|
||||
asAnimated bool,
|
||||
watermark auximageprovider.Provider,
|
||||
) error {
|
||||
if asAnimated {
|
||||
return transformAnimated(ctx, img, po, watermark)
|
||||
return p.transformAnimated(ctx, img, po)
|
||||
}
|
||||
|
||||
// NOTE: THIS IS TEMPORARY
|
||||
runner, err := tmpNewRunner(watermark)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// NOTE: END TEMPORARY BLOCK
|
||||
|
||||
return runner.Run(mainPipeline, ctx, img, po, imgdata)
|
||||
return p.mainPipeline().Run(ctx, img, po, imgdata)
|
||||
}
|
||||
|
||||
func transformAnimated(
|
||||
func (p *Processor) transformAnimated(
|
||||
ctx context.Context,
|
||||
img *vips.Image,
|
||||
po *options.ProcessingOptions,
|
||||
watermark auximageprovider.Provider,
|
||||
) error {
|
||||
// NOTE: THIS IS TEMPORARY
|
||||
runner, rerr := tmpNewRunner(watermark)
|
||||
if rerr != nil {
|
||||
return rerr
|
||||
}
|
||||
// NOTE: END TEMPORARY BLOCK
|
||||
|
||||
if po.Trim.Enabled {
|
||||
log.Warning("Trim is not supported for animated images")
|
||||
po.Trim.Enabled = false
|
||||
@@ -477,7 +435,7 @@ func transformAnimated(
|
||||
// Transform the frame using the main pipeline.
|
||||
// We don't provide imgdata here to prevent scale-on-load.
|
||||
// Watermarking is disabled for individual frames (see above)
|
||||
if err = runner.Run(mainPipeline, ctx, frame, po, nil); err != nil {
|
||||
if err = p.mainPipeline().Run(ctx, frame, po, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -501,7 +459,7 @@ func transformAnimated(
|
||||
|
||||
// Apply watermark to all frames at once if it was requested.
|
||||
// This is much more efficient than applying watermark to individual frames.
|
||||
if watermarkEnabled && watermark != nil {
|
||||
if watermarkEnabled && p.watermarkProvider != nil {
|
||||
// Get DPR scale to apply watermark correctly on HiDPI images.
|
||||
// `imgproxy-dpr-scale` is set by the pipeline.
|
||||
dprScale, derr := img.GetDoubleDefault("imgproxy-dpr-scale", 1.0)
|
||||
@@ -509,7 +467,7 @@ func transformAnimated(
|
||||
dprScale = 1.0
|
||||
}
|
||||
|
||||
if err = applyWatermark(ctx, runner, img, watermark, po, dprScale, framesCount); err != nil {
|
||||
if err = p.applyWatermark(ctx, img, po, dprScale, framesCount); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -565,16 +523,3 @@ func saveImage(
|
||||
// Otherwise, just save the image with the specified quality.
|
||||
return img.Save(po.Format, po.GetQuality())
|
||||
}
|
||||
|
||||
func tmpNewRunner(watermarkProvider auximageprovider.Provider) (*Runner, error) {
|
||||
// NOTE: THIS IS TEMPORARY
|
||||
config, err := pipeline.LoadConfigFromEnv(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
runner := New(config, watermarkProvider)
|
||||
|
||||
return runner, nil
|
||||
// NOTE: END TEMPORARY BLOCK
|
||||
}
|
||||
|
@@ -29,6 +29,8 @@ type ProcessingTestSuite struct {
|
||||
security testutil.LazyObj[*security.Checker]
|
||||
poConfig testutil.LazyObj[*options.Config]
|
||||
po testutil.LazyObj[*options.Factory]
|
||||
config testutil.LazyObj[*Config]
|
||||
processor testutil.LazyObj[*Processor]
|
||||
}
|
||||
|
||||
func (s *ProcessingTestSuite) SetupSuite() {
|
||||
@@ -68,6 +70,15 @@ func (s *ProcessingTestSuite) SetupSuite() {
|
||||
s.po, _ = testutil.NewLazySuiteObj(s, func() (*options.Factory, error) {
|
||||
return options.NewFactory(s.poConfig(), s.security())
|
||||
})
|
||||
|
||||
s.config, _ = testutil.NewLazySuiteObj(s, func() (*Config, error) {
|
||||
c := NewDefaultConfig()
|
||||
return &c, nil
|
||||
})
|
||||
|
||||
s.processor, _ = testutil.NewLazySuiteObj(s, func() (*Processor, error) {
|
||||
return New(s.config(), nil)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *ProcessingTestSuite) openFile(name string) imagedata.ImageData {
|
||||
@@ -116,7 +127,7 @@ func (s *ProcessingTestSuite) TestResizeToFit() {
|
||||
po.Width = tc.width
|
||||
po.Height = tc.height
|
||||
|
||||
result, err := ProcessImage(context.Background(), imgdata, po, nil)
|
||||
result, err := s.processor().ProcessImage(context.Background(), imgdata, po, nil)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(result)
|
||||
|
||||
@@ -155,7 +166,7 @@ func (s *ProcessingTestSuite) TestResizeToFitEnlarge() {
|
||||
po.Width = tc.width
|
||||
po.Height = tc.height
|
||||
|
||||
result, err := ProcessImage(context.Background(), imgdata, po, nil)
|
||||
result, err := s.processor().ProcessImage(context.Background(), imgdata, po, nil)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(result)
|
||||
|
||||
@@ -199,7 +210,7 @@ func (s *ProcessingTestSuite) TestResizeToFitExtend() {
|
||||
po.Width = tc.width
|
||||
po.Height = tc.height
|
||||
|
||||
result, err := ProcessImage(context.Background(), imgdata, po, nil)
|
||||
result, err := s.processor().ProcessImage(context.Background(), imgdata, po, nil)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(result)
|
||||
|
||||
@@ -243,7 +254,7 @@ func (s *ProcessingTestSuite) TestResizeToFitExtendAR() {
|
||||
po.Width = tc.width
|
||||
po.Height = tc.height
|
||||
|
||||
result, err := ProcessImage(context.Background(), imgdata, po, nil)
|
||||
result, err := s.processor().ProcessImage(context.Background(), imgdata, po, nil)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(result)
|
||||
|
||||
@@ -287,7 +298,7 @@ func (s *ProcessingTestSuite) TestResizeToFill() {
|
||||
po.Width = tc.width
|
||||
po.Height = tc.height
|
||||
|
||||
result, err := ProcessImage(context.Background(), imgdata, po, nil)
|
||||
result, err := s.processor().ProcessImage(context.Background(), imgdata, po, nil)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(result)
|
||||
|
||||
@@ -326,7 +337,7 @@ func (s *ProcessingTestSuite) TestResizeToFillEnlarge() {
|
||||
po.Width = tc.width
|
||||
po.Height = tc.height
|
||||
|
||||
result, err := ProcessImage(context.Background(), imgdata, po, nil)
|
||||
result, err := s.processor().ProcessImage(context.Background(), imgdata, po, nil)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(result)
|
||||
|
||||
@@ -372,7 +383,7 @@ func (s *ProcessingTestSuite) TestResizeToFillExtend() {
|
||||
po.Width = tc.width
|
||||
po.Height = tc.height
|
||||
|
||||
result, err := ProcessImage(context.Background(), imgdata, po, nil)
|
||||
result, err := s.processor().ProcessImage(context.Background(), imgdata, po, nil)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(result)
|
||||
|
||||
@@ -418,7 +429,7 @@ func (s *ProcessingTestSuite) TestResizeToFillExtendAR() {
|
||||
po.Width = tc.width
|
||||
po.Height = tc.height
|
||||
|
||||
result, err := ProcessImage(context.Background(), imgdata, po, nil)
|
||||
result, err := s.processor().ProcessImage(context.Background(), imgdata, po, nil)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(result)
|
||||
|
||||
@@ -456,7 +467,7 @@ func (s *ProcessingTestSuite) TestResizeToFillDown() {
|
||||
po.Width = tc.width
|
||||
po.Height = tc.height
|
||||
|
||||
result, err := ProcessImage(context.Background(), imgdata, po, nil)
|
||||
result, err := s.processor().ProcessImage(context.Background(), imgdata, po, nil)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(result)
|
||||
|
||||
@@ -495,7 +506,7 @@ func (s *ProcessingTestSuite) TestResizeToFillDownEnlarge() {
|
||||
po.Width = tc.width
|
||||
po.Height = tc.height
|
||||
|
||||
result, err := ProcessImage(context.Background(), imgdata, po, nil)
|
||||
result, err := s.processor().ProcessImage(context.Background(), imgdata, po, nil)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(result)
|
||||
|
||||
@@ -541,7 +552,7 @@ func (s *ProcessingTestSuite) TestResizeToFillDownExtend() {
|
||||
po.Width = tc.width
|
||||
po.Height = tc.height
|
||||
|
||||
result, err := ProcessImage(context.Background(), imgdata, po, nil)
|
||||
result, err := s.processor().ProcessImage(context.Background(), imgdata, po, nil)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(result)
|
||||
|
||||
@@ -585,7 +596,7 @@ func (s *ProcessingTestSuite) TestResizeToFillDownExtendAR() {
|
||||
po.Width = tc.width
|
||||
po.Height = tc.height
|
||||
|
||||
result, err := ProcessImage(context.Background(), imgdata, po, nil)
|
||||
result, err := s.processor().ProcessImage(context.Background(), imgdata, po, nil)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(result)
|
||||
|
||||
@@ -1014,7 +1025,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
|
||||
po.Rotate = tc.rotate
|
||||
po.Padding = tc.padding
|
||||
|
||||
result, err := ProcessImage(context.Background(), imgdata, po, nil)
|
||||
result, err := s.processor().ProcessImage(context.Background(), imgdata, po, nil)
|
||||
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(result)
|
||||
@@ -1029,7 +1040,7 @@ func (s *ProcessingTestSuite) TestImageResolutionTooLarge() {
|
||||
po.SecurityOptions.MaxSrcResolution = 1
|
||||
|
||||
imgdata := s.openFile("test2.jpg")
|
||||
_, err := ProcessImage(context.Background(), imgdata, po, nil)
|
||||
_, err := s.processor().ProcessImage(context.Background(), imgdata, po, nil)
|
||||
|
||||
s.Require().Error(err)
|
||||
s.Require().Equal(422, ierrors.Wrap(err, 0).StatusCode())
|
||||
|
23
processing/processor.go
Normal file
23
processing/processor.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package processing
|
||||
|
||||
import (
|
||||
"github.com/imgproxy/imgproxy/v3/auximageprovider"
|
||||
)
|
||||
|
||||
// Processor is responsible for processing images according to the given configuration.
|
||||
type Processor struct {
|
||||
config *Config
|
||||
watermarkProvider auximageprovider.Provider
|
||||
}
|
||||
|
||||
// New creates a new Processor instance with the given configuration and watermark provider
|
||||
func New(config *Config, watermark auximageprovider.Provider) (*Processor, error) {
|
||||
if err := config.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Processor{
|
||||
config: config,
|
||||
watermarkProvider: watermark,
|
||||
}, nil
|
||||
}
|
@@ -1,23 +1,23 @@
|
||||
package processing
|
||||
|
||||
func rotateAndFlip(ctx *Context) error {
|
||||
if ctx.Angle%360 == 0 && ctx.PO.Rotate%360 == 0 && !ctx.Flip {
|
||||
func (p *Processor) rotateAndFlip(c *Context) error {
|
||||
if c.Angle%360 == 0 && c.PO.Rotate%360 == 0 && !c.Flip {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := ctx.Img.CopyMemory(); err != nil {
|
||||
if err := c.Img.CopyMemory(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ctx.Img.Rotate(ctx.Angle); err != nil {
|
||||
if err := c.Img.Rotate(c.Angle); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ctx.Flip {
|
||||
if err := ctx.Img.Flip(); err != nil {
|
||||
if c.Flip {
|
||||
if err := c.Img.Flip(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return ctx.Img.Rotate(ctx.PO.Rotate)
|
||||
return c.Img.Rotate(c.PO.Rotate)
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
package processing
|
||||
|
||||
func scale(c *Context) error {
|
||||
func (p *Processor) scale(c *Context) error {
|
||||
if c.WScale == 1 && c.HScale == 1 {
|
||||
return nil
|
||||
}
|
||||
|
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/imgproxy/imgproxy/v3/vips"
|
||||
)
|
||||
|
||||
func canScaleOnLoad(c *Context, imgdata imagedata.ImageData, scale float64) bool {
|
||||
func (p *Processor) canScaleOnLoad(c *Context, imgdata imagedata.ImageData, scale float64) bool {
|
||||
if imgdata == nil || scale == 1 {
|
||||
return false
|
||||
}
|
||||
@@ -21,7 +21,7 @@ func canScaleOnLoad(c *Context, imgdata imagedata.ImageData, scale float64) bool
|
||||
return true
|
||||
}
|
||||
|
||||
if c.Config.DisableShrinkOnLoad || scale >= 1 {
|
||||
if p.config.DisableShrinkOnLoad || scale >= 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ func calcJpegShink(shrink float64) int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func scaleOnLoad(c *Context) error {
|
||||
func (p *Processor) scaleOnLoad(c *Context) error {
|
||||
wshrink := float64(c.SrcWidth) / float64(imath.Scale(c.SrcWidth, c.WScale))
|
||||
hshrink := float64(c.SrcHeight) / float64(imath.Scale(c.SrcHeight, c.HScale))
|
||||
preshrink := math.Min(wshrink, hshrink)
|
||||
@@ -55,7 +55,7 @@ func scaleOnLoad(c *Context) error {
|
||||
prescale *= c.VectorBaseScale
|
||||
}
|
||||
|
||||
if !canScaleOnLoad(c, c.ImgData, prescale) {
|
||||
if !p.canScaleOnLoad(c, c.ImgData, prescale) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ func scaleOnLoad(c *Context) error {
|
||||
}
|
||||
|
||||
angle, flip := 0, false
|
||||
newWidth, newHeight, angle, flip = extractMeta(thumbnail, c.PO.Rotate, c.PO.AutoRotate)
|
||||
newWidth, newHeight, angle, flip = c.ExtractGeometry(thumbnail, c.PO.Rotate, c.PO.AutoRotate)
|
||||
|
||||
if newWidth >= c.SrcWidth || float64(newWidth)/float64(c.SrcWidth) < prescale {
|
||||
return nil
|
||||
@@ -91,7 +91,7 @@ func scaleOnLoad(c *Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
newWidth, newHeight, _, _ = extractMeta(c.Img, c.PO.Rotate, c.PO.AutoRotate)
|
||||
newWidth, newHeight, _, _ = c.ExtractGeometry(c.Img, c.PO.Rotate, c.PO.AutoRotate)
|
||||
}
|
||||
|
||||
// Update scales after scale-on-load
|
||||
|
@@ -103,29 +103,29 @@ func stripXMP(img *vips.Image) []byte {
|
||||
return xmpData
|
||||
}
|
||||
|
||||
func stripMetadata(ctx *Context) error {
|
||||
if !ctx.PO.StripMetadata {
|
||||
func (p *Processor) stripMetadata(c *Context) error {
|
||||
if !c.PO.StripMetadata {
|
||||
return nil
|
||||
}
|
||||
|
||||
var ps3Data, xmpData []byte
|
||||
|
||||
if ctx.PO.KeepCopyright {
|
||||
ps3Data = stripPS3(ctx.Img)
|
||||
xmpData = stripXMP(ctx.Img)
|
||||
if c.PO.KeepCopyright {
|
||||
ps3Data = stripPS3(c.Img)
|
||||
xmpData = stripXMP(c.Img)
|
||||
}
|
||||
|
||||
if err := ctx.Img.Strip(ctx.PO.KeepCopyright); err != nil {
|
||||
if err := c.Img.Strip(c.PO.KeepCopyright); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ctx.PO.KeepCopyright {
|
||||
if c.PO.KeepCopyright {
|
||||
if len(ps3Data) > 0 {
|
||||
ctx.Img.SetBlob("iptc-data", ps3Data)
|
||||
c.Img.SetBlob("iptc-data", ps3Data)
|
||||
}
|
||||
|
||||
if len(xmpData) > 0 {
|
||||
ctx.Img.SetBlob("xmp-data", xmpData)
|
||||
c.Img.SetBlob("xmp-data", xmpData)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,12 +1,12 @@
|
||||
package processing
|
||||
|
||||
func trim(c *Context) error {
|
||||
func (p *Processor) trim(c *Context) error {
|
||||
if !c.PO.Trim.Enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
// We need to import color profile before trim
|
||||
if err := colorspaceToProcessing(c); err != nil {
|
||||
if err := p.colorspaceToProcessing(c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ func trim(c *Context) error {
|
||||
}
|
||||
|
||||
c.ImgData = nil
|
||||
c.CalcParams()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
// vectorGuardScale checks if the image is a vector format and downscales it
|
||||
// to the maximum allowed resolution if necessary
|
||||
func vectorGuardScale(c *Context) error {
|
||||
func (p *Processor) vectorGuardScale(c *Context) error {
|
||||
if c.ImgData == nil || !c.ImgData.Format().IsVector() {
|
||||
return nil
|
||||
}
|
||||
@@ -19,6 +19,7 @@ func vectorGuardScale(c *Context) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
c.CalcParams()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"math"
|
||||
|
||||
"github.com/imgproxy/imgproxy/v3/auximageprovider"
|
||||
"github.com/imgproxy/imgproxy/v3/config"
|
||||
"github.com/imgproxy/imgproxy/v3/imagedata"
|
||||
"github.com/imgproxy/imgproxy/v3/imath"
|
||||
@@ -12,25 +11,20 @@ import (
|
||||
"github.com/imgproxy/imgproxy/v3/vips"
|
||||
)
|
||||
|
||||
var watermarkPipeline = Pipeline{
|
||||
vectorGuardScale,
|
||||
prepare,
|
||||
scaleOnLoad,
|
||||
colorspaceToProcessing,
|
||||
scale,
|
||||
rotateAndFlip,
|
||||
padding,
|
||||
// watermarkPipeline constructs the watermark processing pipeline.
|
||||
// This pipeline is applied to the watermark image.
|
||||
func (p *Processor) watermarkPipeline() Pipeline {
|
||||
return Pipeline{
|
||||
p.vectorGuardScale,
|
||||
p.scaleOnLoad,
|
||||
p.colorspaceToProcessing,
|
||||
p.scale,
|
||||
p.rotateAndFlip,
|
||||
p.padding,
|
||||
}
|
||||
}
|
||||
|
||||
func prepareWatermark(
|
||||
ctx context.Context,
|
||||
runner *Runner,
|
||||
wm *vips.Image,
|
||||
wmData imagedata.ImageData,
|
||||
po *options.ProcessingOptions,
|
||||
imgWidth, imgHeight int,
|
||||
offsetScale float64,
|
||||
) error {
|
||||
func (p *Processor) prepareWatermark(wm *vips.Image, wmData imagedata.ImageData, po *options.ProcessingOptions, imgWidth, imgHeight int, offsetScale float64, framesCount int) error {
|
||||
if err := wm.Load(wmData, 1, 1.0, 1); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -70,7 +64,7 @@ func prepareWatermark(
|
||||
wmPo.Padding.Bottom = offY - wmPo.Padding.Top
|
||||
}
|
||||
|
||||
if err := runner.Run(watermarkPipeline, ctx, wm, wmPo, wmData); err != nil {
|
||||
if err := p.watermarkPipeline().Run(context.Background(), wm, wmPo, wmData); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -90,20 +84,18 @@ func prepareWatermark(
|
||||
return wm.StripAll()
|
||||
}
|
||||
|
||||
func applyWatermark(
|
||||
func (p *Processor) applyWatermark(
|
||||
ctx context.Context,
|
||||
runner *Runner,
|
||||
img *vips.Image,
|
||||
watermark auximageprovider.Provider,
|
||||
po *options.ProcessingOptions,
|
||||
offsetScale float64,
|
||||
framesCount int,
|
||||
) error {
|
||||
if watermark == nil {
|
||||
if p.watermarkProvider == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
wmData, _, err := watermark.Get(ctx, po)
|
||||
wmData, _, err := p.watermarkProvider.Get(ctx, po)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -121,9 +113,7 @@ func applyWatermark(
|
||||
height := img.Height()
|
||||
frameHeight := height / framesCount
|
||||
|
||||
if err := prepareWatermark(
|
||||
ctx, runner, wm, wmData, po, width, frameHeight, offsetScale,
|
||||
); err != nil {
|
||||
if err := p.prepareWatermark(wm, wmData, po, width, frameHeight, offsetScale, framesCount); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -197,10 +187,10 @@ func applyWatermark(
|
||||
return nil
|
||||
}
|
||||
|
||||
func watermark(c *Context) error {
|
||||
func (p *Processor) watermark(c *Context) error {
|
||||
if !c.PO.Watermark.Enabled || c.WatermarkProvider == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return applyWatermark(c.Ctx, c.Runner(), c.Img, c.WatermarkProvider, c.PO, c.DprScale, 1)
|
||||
return p.applyWatermark(c.Ctx, c.Img, c.PO, c.DprScale, 1)
|
||||
}
|
||||
|
Reference in New Issue
Block a user