浏览代码

Decouple optopns parser from options

DarthSim 1 周之前
父节点
当前提交
45229596e0

+ 1 - 1
auximageprovider/static_provider.go

@@ -16,7 +16,7 @@ type staticProvider struct {
 }
 
 // Get returns the static image data and headers stored in the provider.
-func (s *staticProvider) Get(_ context.Context, po *options.Options) (imagedata.ImageData, http.Header, error) {
+func (s *staticProvider) Get(_ context.Context, _ *options.Options) (imagedata.ImageData, http.Header, error) {
 	return s.data, s.headers.Clone(), nil
 }
 

+ 4 - 4
config.go

@@ -6,7 +6,7 @@ import (
 	"github.com/imgproxy/imgproxy/v3/fetcher"
 	processinghandler "github.com/imgproxy/imgproxy/v3/handlers/processing"
 	streamhandler "github.com/imgproxy/imgproxy/v3/handlers/stream"
-	"github.com/imgproxy/imgproxy/v3/options"
+	optionsparser "github.com/imgproxy/imgproxy/v3/options/parser"
 	"github.com/imgproxy/imgproxy/v3/processing"
 	"github.com/imgproxy/imgproxy/v3/security"
 	"github.com/imgproxy/imgproxy/v3/server"
@@ -29,7 +29,7 @@ type Config struct {
 	Server         server.Config
 	Security       security.Config
 	Processing     processing.Config
-	OptionsParser  options.Config
+	OptionsParser  optionsparser.Config
 }
 
 // NewDefaultConfig creates a new default configuration
@@ -46,7 +46,7 @@ func NewDefaultConfig() Config {
 		Server:        server.NewDefaultConfig(),
 		Security:      security.NewDefaultConfig(),
 		Processing:    processing.NewDefaultConfig(),
-		OptionsParser: options.NewDefaultConfig(),
+		OptionsParser: optionsparser.NewDefaultConfig(),
 	}
 }
 
@@ -88,7 +88,7 @@ func LoadConfigFromEnv(c *Config) (*Config, error) {
 		return nil, err
 	}
 
-	if _, err = options.LoadConfigFromEnv(&c.OptionsParser); err != nil {
+	if _, err = optionsparser.LoadConfigFromEnv(&c.OptionsParser); err != nil {
 		return nil, err
 	}
 

+ 11 - 10
handlers/processing/handler.go

@@ -15,6 +15,7 @@ import (
 	"github.com/imgproxy/imgproxy/v3/monitoring/stats"
 	"github.com/imgproxy/imgproxy/v3/options"
 	"github.com/imgproxy/imgproxy/v3/options/keys"
+	optionsparser "github.com/imgproxy/imgproxy/v3/options/parser"
 	"github.com/imgproxy/imgproxy/v3/processing"
 	"github.com/imgproxy/imgproxy/v3/security"
 	"github.com/imgproxy/imgproxy/v3/server"
@@ -27,7 +28,7 @@ type HandlerContext interface {
 	FallbackImage() auximageprovider.Provider
 	ImageDataFactory() *imagedata.Factory
 	Security() *security.Checker
-	OptionsParser() *options.Parser
+	OptionsParser() *optionsparser.Parser
 	Processor() *processing.Processor
 }
 
@@ -69,14 +70,14 @@ func (h *Handler) Execute(
 	ctx := req.Context()
 
 	// Verify URL signature and extract image url and processing options
-	imageURL, po, mm, err := h.newRequest(ctx, req)
+	imageURL, o, mm, err := h.newRequest(ctx, req)
 	if err != nil {
 		return err
 	}
 
 	// if processing options indicate raw image streaming, stream it and return
-	if po.GetBool(keys.Raw, false) {
-		return h.stream.Execute(ctx, req, imageURL, reqID, po, rw)
+	if o.GetBool(keys.Raw, false) {
+		return h.stream.Execute(ctx, req, imageURL, reqID, o, rw)
 	}
 
 	hReq := &request{
@@ -86,8 +87,8 @@ func (h *Handler) Execute(
 		req:            req,
 		rw:             rw,
 		config:         h.config,
-		po:             po,
-		secops:         h.Security().NewOptions(po),
+		opts:           o,
+		secops:         h.Security().NewOptions(o),
 		imageURL:       imageURL,
 		monitoringMeta: mm,
 	}
@@ -112,7 +113,7 @@ func (h *Handler) newRequest(
 	}
 
 	// parse image url and processing options
-	po, imageURL, err := h.OptionsParser().ParsePath(path, req.Header)
+	o, imageURL, err := h.OptionsParser().ParsePath(path, req.Header)
 	if err != nil {
 		return "", nil, nil, ierrors.Wrap(err, 0, ierrors.WithCategory(handlers.CategoryPathParsing))
 	}
@@ -123,13 +124,13 @@ func (h *Handler) newRequest(
 	mm := monitoring.Meta{
 		monitoring.MetaSourceImageURL:    imageURL,
 		monitoring.MetaSourceImageOrigin: imageOrigin,
-		monitoring.MetaOptions:           po.Map(),
+		monitoring.MetaOptions:           o.Map(),
 	}
 
 	// set error reporting and monitoring context
 	errorreport.SetMetadata(req, "Source Image URL", imageURL)
 	errorreport.SetMetadata(req, "Source Image Origin", imageOrigin)
-	errorreport.SetMetadata(req, "Options", po.NestedMap())
+	errorreport.SetMetadata(req, "Options", o.NestedMap())
 
 	monitoring.SetMetadata(ctx, mm)
 
@@ -139,7 +140,7 @@ func (h *Handler) newRequest(
 		return "", options.New(), mm, ierrors.Wrap(err, 0, ierrors.WithCategory(handlers.CategorySecurity))
 	}
 
-	return imageURL, po, mm, nil
+	return imageURL, o, mm, nil
 }
 
 // imageOrigin extracts image origin from URL

+ 2 - 2
handlers/processing/request.go

@@ -26,7 +26,7 @@ type request struct {
 	req            *http.Request
 	rw             server.ResponseWriter
 	config         *Config
-	po             *options.Options
+	opts           *options.Options
 	secops         security.Options
 	imageURL       string
 	monitoringMeta monitoring.Meta
@@ -34,7 +34,7 @@ type request struct {
 
 // execute handles the actual processing logic
 func (r *request) execute(ctx context.Context) error {
-	outFormat := options.Get(r.po, keys.Format, imagetype.Unknown)
+	outFormat := options.Get(r.opts, keys.Format, imagetype.Unknown)
 
 	// Check if we can save the resulting image
 	canSave := vips.SupportsSave(outFormat) ||

+ 10 - 14
handlers/processing/request_methods.go

@@ -14,7 +14,6 @@ import (
 	"github.com/imgproxy/imgproxy/v3/ierrors"
 	"github.com/imgproxy/imgproxy/v3/imagedata"
 	"github.com/imgproxy/imgproxy/v3/monitoring"
-	"github.com/imgproxy/imgproxy/v3/options"
 	"github.com/imgproxy/imgproxy/v3/options/keys"
 	"github.com/imgproxy/imgproxy/v3/processing"
 	"github.com/imgproxy/imgproxy/v3/server"
@@ -94,7 +93,7 @@ func (r *request) handleDownloadError(
 	err := r.wrapDownloadingErr(originalErr)
 
 	// If there is no fallback image configured, just return the error
-	data, headers := r.getFallbackImage(ctx, r.po)
+	data, headers := r.getFallbackImage(ctx)
 	if data == nil {
 		return nil, 0, err
 	}
@@ -134,17 +133,14 @@ func (r *request) handleDownloadError(
 }
 
 // getFallbackImage returns fallback image if any
-func (r *request) getFallbackImage(
-	ctx context.Context,
-	po *options.Options,
-) (imagedata.ImageData, http.Header) {
+func (r *request) getFallbackImage(ctx context.Context) (imagedata.ImageData, http.Header) {
 	fbi := r.FallbackImage()
 
 	if fbi == nil {
 		return nil, nil
 	}
 
-	data, h, err := fbi.Get(ctx, po)
+	data, h, err := fbi.Get(ctx, r.opts)
 	if err != nil {
 		slog.Warn(err.Error())
 
@@ -161,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.MetaOptions))()
-	return r.Processor().ProcessImage(ctx, originData, r.po, r.secops)
+	return r.Processor().ProcessImage(ctx, originData, r.opts, r.secops)
 }
 
 // writeDebugHeaders writes debug headers (X-Origin-*, X-Result-*) to the response
@@ -190,7 +186,7 @@ func (r *request) writeDebugHeaders(result *processing.Result, originData imaged
 
 // respondWithNotModified writes not-modified response
 func (r *request) respondWithNotModified() error {
-	r.rw.SetExpires(r.po.GetTime(keys.Expires))
+	r.rw.SetExpires(r.opts.GetTime(keys.Expires))
 	r.rw.SetVary()
 
 	if r.config.LastModifiedEnabled {
@@ -206,7 +202,7 @@ func (r *request) respondWithNotModified() error {
 	server.LogResponse(
 		r.reqID, r.req, http.StatusNotModified, nil,
 		slog.String("image_url", r.imageURL),
-		slog.Any("processing_options", r.po),
+		slog.Any("processing_options", r.opts),
 	)
 
 	return nil
@@ -225,12 +221,12 @@ func (r *request) respondWithImage(statusCode int, resultData imagedata.ImageDat
 	r.rw.SetContentLength(resultSize)
 	r.rw.SetContentDisposition(
 		r.imageURL,
-		r.po.GetString(keys.Filename, ""),
+		r.opts.GetString(keys.Filename, ""),
 		resultData.Format().Ext(),
 		"",
-		r.po.GetBool(keys.ReturnAttachment, false),
+		r.opts.GetBool(keys.ReturnAttachment, false),
 	)
-	r.rw.SetExpires(r.po.GetTime(keys.Expires))
+	r.rw.SetExpires(r.opts.GetTime(keys.Expires))
 	r.rw.SetVary()
 	r.rw.SetCanonical(r.imageURL)
 
@@ -258,7 +254,7 @@ func (r *request) respondWithImage(statusCode int, resultData imagedata.ImageDat
 	server.LogResponse(
 		r.reqID, r.req, statusCode, ierr,
 		slog.String("image_url", r.imageURL),
-		slog.Any("processing_options", r.po),
+		slog.Any("processing_options", r.opts),
 	)
 
 	return nil

+ 7 - 7
handlers/stream/handler.go

@@ -45,7 +45,7 @@ type request struct {
 	imageRequest *http.Request
 	imageURL     string
 	reqID        string
-	po           *options.Options
+	opts         *options.Options
 	rw           server.ResponseWriter
 }
 
@@ -67,7 +67,7 @@ func (s *Handler) Execute(
 	userRequest *http.Request,
 	imageURL string,
 	reqID string,
-	po *options.Options,
+	o *options.Options,
 	rw server.ResponseWriter,
 ) error {
 	stream := &request{
@@ -75,7 +75,7 @@ func (s *Handler) Execute(
 		imageRequest: userRequest,
 		imageURL:     imageURL,
 		reqID:        reqID,
-		po:           po,
+		opts:         o,
 		rw:           rw,
 	}
 
@@ -118,7 +118,7 @@ func (s *request) execute(ctx context.Context) error {
 	s.rw.Passthrough(s.handler.config.PassthroughResponseHeaders...) // NOTE: priority? This is lowest as it was
 	s.rw.SetContentLength(int(res.ContentLength))
 	s.rw.SetCanonical(s.imageURL)
-	s.rw.SetExpires(s.po.GetTime(keys.Expires))
+	s.rw.SetExpires(s.opts.GetTime(keys.Expires))
 
 	// Set the Content-Disposition header
 	s.setContentDisposition(r.URL().Path, res)
@@ -161,10 +161,10 @@ func (s *request) setContentDisposition(imagePath string, serverResponse *http.R
 
 	s.rw.SetContentDisposition(
 		imagePath,
-		s.po.GetString(keys.Filename, ""),
+		s.opts.GetString(keys.Filename, ""),
 		"",
 		ct,
-		s.po.GetBool(keys.ReturnAttachment, false),
+		s.opts.GetBool(keys.ReturnAttachment, false),
 	)
 }
 
@@ -178,7 +178,7 @@ func (s *request) streamData(res *http.Response) {
 	server.LogResponse(
 		s.reqID, s.imageRequest, res.StatusCode, nil,
 		slog.String("image_url", s.imageURL),
-		slog.Any("processing_options", s.po),
+		slog.Any("processing_options", s.opts),
 	)
 
 	// We've got to skip logging here

+ 10 - 10
handlers/stream/handler_test.go

@@ -94,7 +94,7 @@ func (s *HandlerTestSuite) SetupSubTest() {
 func (s *HandlerTestSuite) execute(
 	imageURL string,
 	header http.Header,
-	po *options.Options,
+	o *options.Options,
 ) *http.Response {
 	imageURL = s.testServer().URL() + imageURL
 	req := httptest.NewRequest("GET", "/", nil)
@@ -104,7 +104,7 @@ func (s *HandlerTestSuite) execute(
 	rw := httptest.NewRecorder()
 	rww := s.rwFactory().NewWriter(rw)
 
-	err := s.handler().Execute(ctx, req, imageURL, "test-req-id", po, rww)
+	err := s.handler().Execute(ctx, req, imageURL, "test-req-id", o, rww)
 	s.Require().NoError(err)
 
 	return rw.Result()
@@ -184,12 +184,12 @@ func (s *HandlerTestSuite) TestHandlerContentDisposition() {
 
 	s.testServer().SetHeaders(httpheaders.ContentType, "image/png").SetBody(data)
 
-	po := options.New()
-	po.Set(keys.Filename, "custom_name")
-	po.Set(keys.ReturnAttachment, true)
+	o := options.New()
+	o.Set(keys.Filename, "custom_name")
+	o.Set(keys.ReturnAttachment, true)
 
 	// Use a URL with a .png extension to help content disposition logic
-	res := s.execute("/test.png", nil, po)
+	res := s.execute("/test.png", nil, o)
 
 	s.Require().Equal(200, res.StatusCode)
 	s.Require().Contains(res.Header.Get(httpheaders.ContentDisposition), "custom_name.png")
@@ -284,7 +284,7 @@ func (s *HandlerTestSuite) TestHandlerCacheControl() {
 				s.Require().InDelta(thirtyMinutes, s.maxAgeValue(res), oneMinuteDelta)
 			},
 		},
-		// When expires is not set in po, but both expires and cc are present in response,
+		// When expires is not set in o, but both expires and cc are present in response,
 		// and passthrough is enabled
 		{
 			name:                    "BothHeadersPassthroughEnabled",
@@ -344,13 +344,13 @@ func (s *HandlerTestSuite) TestHandlerCacheControl() {
 			s.rwConf().CacheControlPassthrough = tc.cacheControlPassthrough
 			s.rwConf().DefaultTTL = 4242
 
-			po := options.New()
+			o := options.New()
 
 			if tc.timestampOffset != nil {
-				po.Set(keys.Expires, time.Now().Add(*tc.timestampOffset))
+				o.Set(keys.Expires, time.Now().Add(*tc.timestampOffset))
 			}
 
-			res := s.execute("", nil, po)
+			res := s.execute("", nil, o)
 			s.Require().Equal(tc.expectedStatusCode, res.StatusCode)
 			tc.validate(s.T(), res)
 		})

+ 4 - 4
imgproxy.go

@@ -14,7 +14,7 @@ import (
 	"github.com/imgproxy/imgproxy/v3/imagedata"
 	"github.com/imgproxy/imgproxy/v3/memory"
 	"github.com/imgproxy/imgproxy/v3/monitoring/prometheus"
-	"github.com/imgproxy/imgproxy/v3/options"
+	optionsparser "github.com/imgproxy/imgproxy/v3/options/parser"
 	"github.com/imgproxy/imgproxy/v3/processing"
 	"github.com/imgproxy/imgproxy/v3/security"
 	"github.com/imgproxy/imgproxy/v3/server"
@@ -43,7 +43,7 @@ type Imgproxy struct {
 	imageDataFactory *imagedata.Factory
 	handlers         ImgproxyHandlers
 	security         *security.Checker
-	optionsParser    *options.Parser
+	optionsParser    *optionsparser.Parser
 	processor        *processing.Processor
 	config           *Config
 }
@@ -77,7 +77,7 @@ func New(ctx context.Context, config *Config) (*Imgproxy, error) {
 		return nil, err
 	}
 
-	optionsParser, err := options.NewParser(&config.OptionsParser)
+	optionsParser, err := optionsparser.New(&config.OptionsParser)
 	if err != nil {
 		return nil, err
 	}
@@ -216,7 +216,7 @@ func (i *Imgproxy) Security() *security.Checker {
 	return i.security
 }
 
-func (i *Imgproxy) OptionsParser() *options.Parser {
+func (i *Imgproxy) OptionsParser() *optionsparser.Parser {
 	return i.optionsParser
 }
 

+ 0 - 80
options/errors.go

@@ -2,19 +2,12 @@ package options
 
 import (
 	"fmt"
-	"net/http"
-	"strings"
 
 	"github.com/imgproxy/imgproxy/v3/ierrors"
 )
 
 type (
 	TypeMismatchError struct{ error }
-
-	InvalidURLError      string
-	UnknownOptionError   string
-	OptionArgumentError  string
-	SecurityOptionsError struct{}
 )
 
 func newTypeMismatchError(key string, exp, got any) error {
@@ -23,76 +16,3 @@ func newTypeMismatchError(key string, exp, got any) error {
 		1,
 	)
 }
-
-func newInvalidURLError(format string, args ...interface{}) error {
-	return ierrors.Wrap(
-		InvalidURLError(fmt.Sprintf(format, args...)),
-		1,
-		ierrors.WithStatusCode(http.StatusNotFound),
-		ierrors.WithPublicMessage("Invalid URL"),
-		ierrors.WithShouldReport(false),
-	)
-}
-
-func (e InvalidURLError) Error() string { return string(e) }
-
-func newUnknownOptionError(kind, opt string) error {
-	return ierrors.Wrap(
-		UnknownOptionError(fmt.Sprintf("Unknown %s option %s", kind, opt)),
-		1,
-		ierrors.WithStatusCode(http.StatusNotFound),
-		ierrors.WithPublicMessage("Invalid URL"),
-		ierrors.WithShouldReport(false),
-	)
-}
-
-func newForbiddenOptionError(kind, opt string) error {
-	return ierrors.Wrap(
-		UnknownOptionError(fmt.Sprintf("Forbidden %s option %s", kind, opt)),
-		1,
-		ierrors.WithStatusCode(http.StatusNotFound),
-		ierrors.WithPublicMessage("Invalid URL"),
-		ierrors.WithShouldReport(false),
-	)
-}
-
-func (e UnknownOptionError) Error() string { return string(e) }
-
-func newOptionArgumentError(format string, args ...interface{}) error {
-	return ierrors.Wrap(
-		OptionArgumentError(fmt.Sprintf(format, args...)),
-		1,
-		ierrors.WithStatusCode(http.StatusNotFound),
-		ierrors.WithPublicMessage("Invalid URL"),
-		ierrors.WithShouldReport(false),
-	)
-}
-
-func (e OptionArgumentError) Error() string { return string(e) }
-
-func newSecurityOptionsError() error {
-	return ierrors.Wrap(
-		SecurityOptionsError{},
-		1,
-		ierrors.WithStatusCode(http.StatusForbidden),
-		ierrors.WithPublicMessage("Invalid URL"),
-		ierrors.WithShouldReport(false),
-	)
-}
-
-func (e SecurityOptionsError) Error() string { return "Security processing options are not allowed" }
-
-// newInvalidArgsError creates a standardized error for invalid arguments
-func newInvalidArgsError(name string, args []string) error {
-	return newOptionArgumentError("Invalid %s arguments: %s", name, args)
-}
-
-// newInvalidArgumentError creates a standardized error for an invalid single argument
-func newInvalidArgumentError(key, arg string, expected ...string) error {
-	msg := "Invalid %s: %s"
-	if len(expected) > 0 {
-		msg += " (expected " + strings.Join(expected, ", ") + ")"
-	}
-
-	return newOptionArgumentError(msg, key, arg)
-}

+ 53 - 51
options/apply.go → options/parser/apply.go

@@ -1,4 +1,4 @@
-package options
+package optionsparser
 
 import (
 	"fmt"
@@ -8,39 +8,41 @@ import (
 	"time"
 
 	"github.com/imgproxy/imgproxy/v3/imagetype"
+	"github.com/imgproxy/imgproxy/v3/options"
 	"github.com/imgproxy/imgproxy/v3/options/keys"
+	"github.com/imgproxy/imgproxy/v3/processing"
 	"github.com/imgproxy/imgproxy/v3/vips/color"
 )
 
-func applyWidthOption(o *Options, args []string) error {
+func applyWidthOption(o *options.Options, args []string) error {
 	return parsePositiveInt(o, keys.Width, args...)
 }
 
-func applyHeightOption(o *Options, args []string) error {
+func applyHeightOption(o *options.Options, args []string) error {
 	return parsePositiveInt(o, keys.Height, args...)
 }
 
-func applyMinWidthOption(o *Options, args []string) error {
+func applyMinWidthOption(o *options.Options, args []string) error {
 	return parsePositiveInt(o, keys.MinWidth, args...)
 }
 
-func applyMinHeightOption(o *Options, args []string) error {
+func applyMinHeightOption(o *options.Options, args []string) error {
 	return parsePositiveInt(o, keys.MinHeight, args...)
 }
 
-func applyEnlargeOption(o *Options, args []string) error {
+func applyEnlargeOption(o *options.Options, args []string) error {
 	return parseBool(o, keys.Enlarge, args...)
 }
 
-func applyExtendOption(o *Options, args []string) error {
+func applyExtendOption(o *options.Options, args []string) error {
 	return parseExtend(o, keys.PrefixExtend, args)
 }
 
-func applyExtendAspectRatioOption(o *Options, args []string) error {
+func applyExtendAspectRatioOption(o *options.Options, args []string) error {
 	return parseExtend(o, keys.PrefixExtendAspectRatio, args)
 }
 
-func applySizeOption(o *Options, args []string) (err error) {
+func applySizeOption(o *options.Options, args []string) (err error) {
 	if err = ensureMaxArgs("size", args, 7); err != nil {
 		return
 	}
@@ -72,11 +74,11 @@ func applySizeOption(o *Options, args []string) (err error) {
 	return nil
 }
 
-func applyResizingTypeOption(o *Options, args []string) error {
-	return parseFromMap(o, keys.ResizingType, resizeTypes, args...)
+func applyResizingTypeOption(o *options.Options, args []string) error {
+	return parseFromMap(o, keys.ResizingType, processing.ResizeTypes, args...)
 }
 
-func applyResizeOption(o *Options, args []string) error {
+func applyResizeOption(o *options.Options, args []string) error {
 	if err := ensureMaxArgs("resize", args, 8); err != nil {
 		return err
 	}
@@ -96,7 +98,7 @@ func applyResizeOption(o *Options, args []string) error {
 	return nil
 }
 
-func applyZoomOption(o *Options, args []string) error {
+func applyZoomOption(o *options.Options, args []string) error {
 	if err := ensureMaxArgs("zoom", args, 2); err != nil {
 		return err
 	}
@@ -117,15 +119,15 @@ func applyZoomOption(o *Options, args []string) error {
 	return nil
 }
 
-func applyDprOption(o *Options, args []string) error {
+func applyDprOption(o *options.Options, args []string) error {
 	return parsePositiveNonZeroFloat(o, keys.Dpr, args...)
 }
 
-func applyGravityOption(o *Options, args []string) error {
-	return parseGravity(o, keys.Gravity, cropGravityTypes, args...)
+func applyGravityOption(o *options.Options, args []string) error {
+	return parseGravity(o, keys.Gravity, processing.CropGravityTypes, args...)
 }
 
-func applyCropOption(o *Options, args []string) error {
+func applyCropOption(o *options.Options, args []string) error {
 	if err := parsePositiveFloat(o, keys.CropWidth, args[0]); err != nil {
 		return err
 	}
@@ -137,13 +139,13 @@ func applyCropOption(o *Options, args []string) error {
 	}
 
 	if len(args) > 2 {
-		return parseGravity(o, keys.CropGravity, cropGravityTypes, args[2:]...)
+		return parseGravity(o, keys.CropGravity, processing.CropGravityTypes, args[2:]...)
 	}
 
 	return nil
 }
 
-func applyPaddingOption(o *Options, args []string) error {
+func applyPaddingOption(o *options.Options, args []string) error {
 	if err := ensureMaxArgs("padding", args, 4); err != nil {
 		return err
 	}
@@ -181,7 +183,7 @@ func applyPaddingOption(o *Options, args []string) error {
 	return nil
 }
 
-func applyTrimOption(o *Options, args []string) error {
+func applyTrimOption(o *options.Options, args []string) error {
 	if err := ensureMaxArgs("trim", args, 4); err != nil {
 		return err
 	}
@@ -219,23 +221,23 @@ func applyTrimOption(o *Options, args []string) error {
 	return nil
 }
 
-func applyRotateOption(o *Options, args []string) error {
+func applyRotateOption(o *options.Options, args []string) error {
 	if err := parseInt(o, keys.Rotate, args...); err != nil {
 		return err
 	}
 
-	if Get(o, keys.Rotate, 0)%90 != 0 {
+	if options.Get(o, keys.Rotate, 0)%90 != 0 {
 		return newOptionArgumentError("Rotation angle must be a multiple of 90")
 	}
 
 	return nil
 }
 
-func applyQualityOption(o *Options, args []string) error {
+func applyQualityOption(o *options.Options, args []string) error {
 	return parseQualityInt(o, keys.Quality, args...)
 }
 
-func applyFormatQualityOption(o *Options, args []string) error {
+func applyFormatQualityOption(o *options.Options, args []string) error {
 	argsLen := len(args)
 	if len(args)%2 != 0 {
 		return newOptionArgumentError("Missing %s for: %s", keys.PrefixFormatQuality, args[argsLen-1])
@@ -255,11 +257,11 @@ func applyFormatQualityOption(o *Options, args []string) error {
 	return nil
 }
 
-func applyMaxBytesOption(o *Options, args []string) error {
+func applyMaxBytesOption(o *options.Options, args []string) error {
 	return parsePositiveInt(o, keys.MaxBytes, args...)
 }
 
-func applyBackgroundOption(o *Options, args []string) error {
+func applyBackgroundOption(o *options.Options, args []string) error {
 	switch len(args) {
 	case 1:
 		if len(args[0]) == 0 {
@@ -301,19 +303,19 @@ func applyBackgroundOption(o *Options, args []string) error {
 	return nil
 }
 
-func applyBlurOption(o *Options, args []string) error {
+func applyBlurOption(o *options.Options, args []string) error {
 	return parsePositiveNonZeroFloat(o, keys.Blur, args...)
 }
 
-func applySharpenOption(o *Options, args []string) error {
+func applySharpenOption(o *options.Options, args []string) error {
 	return parsePositiveNonZeroFloat(o, keys.Sharpen, args...)
 }
 
-func applyPixelateOption(o *Options, args []string) error {
+func applyPixelateOption(o *options.Options, args []string) error {
 	return parsePositiveInt(o, keys.Pixelate, args...)
 }
 
-func applyWatermarkOption(o *Options, args []string) error {
+func applyWatermarkOption(o *options.Options, args []string) error {
 	if err := ensureMaxArgs("watermark", args, 7); err != nil {
 		return err
 	}
@@ -324,7 +326,7 @@ func applyWatermarkOption(o *Options, args []string) error {
 
 	if len(args) > 1 && len(args[1]) > 0 {
 		if _, err := parseGravityType(
-			o, keys.WatermarkPosition, watermarkGravityTypes, args[1],
+			o, keys.WatermarkPosition, processing.WatermarkGravityTypes, args[1],
 		); err != nil {
 			return err
 		}
@@ -351,7 +353,7 @@ func applyWatermarkOption(o *Options, args []string) error {
 	return nil
 }
 
-func applyFormatOption(o *Options, args []string) error {
+func applyFormatOption(o *options.Options, args []string) error {
 	if err := ensureMaxArgs(keys.Format, args, 1); err != nil {
 		return err
 	}
@@ -365,7 +367,7 @@ func applyFormatOption(o *Options, args []string) error {
 	return nil
 }
 
-func applyCacheBusterOption(o *Options, args []string) error {
+func applyCacheBusterOption(o *options.Options, args []string) error {
 	if err := ensureMaxArgs(keys.CacheBuster, args, 1); err != nil {
 		return err
 	}
@@ -375,10 +377,10 @@ func applyCacheBusterOption(o *Options, args []string) error {
 	return nil
 }
 
-func applySkipProcessingFormatsOption(o *Options, args []string) error {
+func applySkipProcessingFormatsOption(o *options.Options, args []string) error {
 	for _, format := range args {
 		if f, ok := imagetype.GetTypeByName(format); ok {
-			AppendToSlice(o, keys.SkipProcessing, f)
+			options.AppendToSlice(o, keys.SkipProcessing, f)
 		} else {
 			return newOptionArgumentError("Invalid image format in %s: %s", keys.SkipProcessing, format)
 		}
@@ -387,11 +389,11 @@ func applySkipProcessingFormatsOption(o *Options, args []string) error {
 	return nil
 }
 
-func applyRawOption(o *Options, args []string) error {
+func applyRawOption(o *options.Options, args []string) error {
 	return parseBool(o, keys.Raw, args...)
 }
 
-func applyFilenameOption(o *Options, args []string) error {
+func applyFilenameOption(o *options.Options, args []string) error {
 	if err := ensureMaxArgs(keys.Filename, args, 2); err != nil {
 		return err
 	}
@@ -407,7 +409,7 @@ func applyFilenameOption(o *Options, args []string) error {
 	return nil
 }
 
-func applyExpiresOption(o *Options, args []string) error {
+func applyExpiresOption(o *options.Options, args []string) error {
 	if err := ensureMaxArgs(keys.Expires, args, 1); err != nil {
 		return err
 	}
@@ -426,31 +428,31 @@ func applyExpiresOption(o *Options, args []string) error {
 	return nil
 }
 
-func applyStripMetadataOption(o *Options, args []string) error {
+func applyStripMetadataOption(o *options.Options, args []string) error {
 	return parseBool(o, keys.StripMetadata, args...)
 }
 
-func applyKeepCopyrightOption(o *Options, args []string) error {
+func applyKeepCopyrightOption(o *options.Options, args []string) error {
 	return parseBool(o, keys.KeepCopyright, args...)
 }
 
-func applyStripColorProfileOption(o *Options, args []string) error {
+func applyStripColorProfileOption(o *options.Options, args []string) error {
 	return parseBool(o, keys.StripColorProfile, args...)
 }
 
-func applyAutoRotateOption(o *Options, args []string) error {
+func applyAutoRotateOption(o *options.Options, args []string) error {
 	return parseBool(o, keys.AutoRotate, args...)
 }
 
-func applyEnforceThumbnailOption(o *Options, args []string) error {
+func applyEnforceThumbnailOption(o *options.Options, args []string) error {
 	return parseBool(o, keys.EnforceThumbnail, args...)
 }
 
-func applyReturnAttachmentOption(o *Options, args []string) error {
+func applyReturnAttachmentOption(o *options.Options, args []string) error {
 	return parseBool(o, keys.ReturnAttachment, args...)
 }
 
-func applyMaxSrcResolutionOption(p *Parser, o *Options, args []string) error {
+func applyMaxSrcResolutionOption(p *Parser, o *options.Options, args []string) error {
 	if err := p.IsSecurityOptionsAllowed(); err != nil {
 		return err
 	}
@@ -458,7 +460,7 @@ func applyMaxSrcResolutionOption(p *Parser, o *Options, args []string) error {
 	return parseResolution(o, keys.MaxSrcResolution, args...)
 }
 
-func applyMaxSrcFileSizeOption(p *Parser, o *Options, args []string) error {
+func applyMaxSrcFileSizeOption(p *Parser, o *options.Options, args []string) error {
 	if err := p.IsSecurityOptionsAllowed(); err != nil {
 		return err
 	}
@@ -466,7 +468,7 @@ func applyMaxSrcFileSizeOption(p *Parser, o *Options, args []string) error {
 	return parseInt(o, keys.MaxSrcFileSize, args...)
 }
 
-func applyMaxAnimationFramesOption(p *Parser, o *Options, args []string) error {
+func applyMaxAnimationFramesOption(p *Parser, o *options.Options, args []string) error {
 	if err := p.IsSecurityOptionsAllowed(); err != nil {
 		return err
 	}
@@ -474,7 +476,7 @@ func applyMaxAnimationFramesOption(p *Parser, o *Options, args []string) error {
 	return parsePositiveNonZeroInt(o, keys.MaxAnimationFrames, args...)
 }
 
-func applyMaxAnimationFrameResolutionOption(p *Parser, o *Options, args []string) error {
+func applyMaxAnimationFrameResolutionOption(p *Parser, o *options.Options, args []string) error {
 	if err := p.IsSecurityOptionsAllowed(); err != nil {
 		return err
 	}
@@ -482,7 +484,7 @@ func applyMaxAnimationFrameResolutionOption(p *Parser, o *Options, args []string
 	return parseResolution(o, keys.MaxAnimationFrameResolution, args...)
 }
 
-func applyMaxResultDimensionOption(p *Parser, o *Options, args []string) error {
+func applyMaxResultDimensionOption(p *Parser, o *options.Options, args []string) error {
 	if err := p.IsSecurityOptionsAllowed(); err != nil {
 		return err
 	}
@@ -490,7 +492,7 @@ func applyMaxResultDimensionOption(p *Parser, o *Options, args []string) error {
 	return parseInt(o, keys.MaxResultDimension, args...)
 }
 
-func applyPresetOption(p *Parser, o *Options, args []string, usedPresets ...string) error {
+func applyPresetOption(p *Parser, o *options.Options, args []string, usedPresets ...string) error {
 	for _, preset := range args {
 		if pr, ok := p.presets[preset]; ok {
 			if slices.Contains(usedPresets, preset) {
@@ -498,7 +500,7 @@ func applyPresetOption(p *Parser, o *Options, args []string, usedPresets ...stri
 				continue
 			}
 
-			AppendToSlice(o, keys.UsedPresets, preset)
+			options.AppendToSlice(o, keys.UsedPresets, preset)
 
 			if err := p.applyURLOptions(o, pr, true, append(usedPresets, preset)...); err != nil {
 				return err

+ 1 - 1
options/config.go → options/parser/config.go

@@ -1,4 +1,4 @@
-package options
+package optionsparser
 
 import (
 	"errors"

+ 89 - 0
options/parser/errors.go

@@ -0,0 +1,89 @@
+package optionsparser
+
+import (
+	"fmt"
+	"net/http"
+	"strings"
+
+	"github.com/imgproxy/imgproxy/v3/ierrors"
+)
+
+type (
+	InvalidURLError      string
+	UnknownOptionError   string
+	OptionArgumentError  string
+	SecurityOptionsError struct{}
+)
+
+func newInvalidURLError(format string, args ...interface{}) error {
+	return ierrors.Wrap(
+		InvalidURLError(fmt.Sprintf(format, args...)),
+		1,
+		ierrors.WithStatusCode(http.StatusNotFound),
+		ierrors.WithPublicMessage("Invalid URL"),
+		ierrors.WithShouldReport(false),
+	)
+}
+
+func (e InvalidURLError) Error() string { return string(e) }
+
+func newUnknownOptionError(kind, opt string) error {
+	return ierrors.Wrap(
+		UnknownOptionError(fmt.Sprintf("Unknown %s option %s", kind, opt)),
+		1,
+		ierrors.WithStatusCode(http.StatusNotFound),
+		ierrors.WithPublicMessage("Invalid URL"),
+		ierrors.WithShouldReport(false),
+	)
+}
+
+func newForbiddenOptionError(kind, opt string) error {
+	return ierrors.Wrap(
+		UnknownOptionError(fmt.Sprintf("Forbidden %s option %s", kind, opt)),
+		1,
+		ierrors.WithStatusCode(http.StatusNotFound),
+		ierrors.WithPublicMessage("Invalid URL"),
+		ierrors.WithShouldReport(false),
+	)
+}
+
+func (e UnknownOptionError) Error() string { return string(e) }
+
+func newOptionArgumentError(format string, args ...interface{}) error {
+	return ierrors.Wrap(
+		OptionArgumentError(fmt.Sprintf(format, args...)),
+		1,
+		ierrors.WithStatusCode(http.StatusNotFound),
+		ierrors.WithPublicMessage("Invalid URL"),
+		ierrors.WithShouldReport(false),
+	)
+}
+
+func (e OptionArgumentError) Error() string { return string(e) }
+
+func newSecurityOptionsError() error {
+	return ierrors.Wrap(
+		SecurityOptionsError{},
+		1,
+		ierrors.WithStatusCode(http.StatusForbidden),
+		ierrors.WithPublicMessage("Invalid URL"),
+		ierrors.WithShouldReport(false),
+	)
+}
+
+func (e SecurityOptionsError) Error() string { return "Security processing options are not allowed" }
+
+// newInvalidArgsError creates a standardized error for invalid arguments
+func newInvalidArgsError(name string, args []string) error {
+	return newOptionArgumentError("Invalid %s arguments: %s", name, args)
+}
+
+// newInvalidArgumentError creates a standardized error for an invalid single argument
+func newInvalidArgumentError(key, arg string, expected ...string) error {
+	msg := "Invalid %s: %s"
+	if len(expected) > 0 {
+		msg += " (expected " + strings.Join(expected, ", ") + ")"
+	}
+
+	return newOptionArgumentError(msg, key, arg)
+}

+ 30 - 28
options/parse.go → options/parser/parse.go

@@ -1,4 +1,4 @@
-package options
+package optionsparser
 
 import (
 	"encoding/base64"
@@ -9,7 +9,9 @@ import (
 	"strconv"
 	"strings"
 
+	"github.com/imgproxy/imgproxy/v3/options"
 	"github.com/imgproxy/imgproxy/v3/options/keys"
+	"github.com/imgproxy/imgproxy/v3/processing"
 	"github.com/imgproxy/imgproxy/v3/vips/color"
 )
 
@@ -22,7 +24,7 @@ func ensureMaxArgs(name string, args []string, max int) error {
 }
 
 // parseBool parses a boolean option value and warns if the value is invalid
-func parseBool(o *Options, key string, args ...string) error {
+func parseBool(o *options.Options, key string, args ...string) error {
 	if err := ensureMaxArgs(key, args, 1); err != nil {
 		return err
 	}
@@ -39,7 +41,7 @@ func parseBool(o *Options, key string, args ...string) error {
 }
 
 // parseFloat parses a float64 option value
-func parseFloat(o *Options, key string, args ...string) error {
+func parseFloat(o *options.Options, key string, args ...string) error {
 	if err := ensureMaxArgs(key, args, 1); err != nil {
 		return err
 	}
@@ -55,7 +57,7 @@ func parseFloat(o *Options, key string, args ...string) error {
 }
 
 // parsePositiveFloat parses a positive float64 option value
-func parsePositiveFloat(o *Options, key string, args ...string) error {
+func parsePositiveFloat(o *options.Options, key string, args ...string) error {
 	if err := ensureMaxArgs(key, args, 1); err != nil {
 		return err
 	}
@@ -71,7 +73,7 @@ func parsePositiveFloat(o *Options, key string, args ...string) error {
 }
 
 // parsePositiveNonZeroFloat parses a positive non-zero float64 option value
-func parsePositiveNonZeroFloat(o *Options, key string, args ...string) error {
+func parsePositiveNonZeroFloat(o *options.Options, key string, args ...string) error {
 	if err := ensureMaxArgs(key, args, 1); err != nil {
 		return err
 	}
@@ -87,7 +89,7 @@ func parsePositiveNonZeroFloat(o *Options, key string, args ...string) error {
 }
 
 // parseInt parses a positive integer option value
-func parseInt(o *Options, key string, args ...string) error {
+func parseInt(o *options.Options, key string, args ...string) error {
 	if err := ensureMaxArgs(key, args, 1); err != nil {
 		return err
 	}
@@ -103,7 +105,7 @@ func parseInt(o *Options, key string, args ...string) error {
 }
 
 // parsePositiveNonZeroInt parses a positive non-zero integer option value
-func parsePositiveNonZeroInt(o *Options, key string, args ...string) error {
+func parsePositiveNonZeroInt(o *options.Options, key string, args ...string) error {
 	if err := ensureMaxArgs(key, args, 1); err != nil {
 		return err
 	}
@@ -119,7 +121,7 @@ func parsePositiveNonZeroInt(o *Options, key string, args ...string) error {
 }
 
 // parsePositiveInt parses a positive integer option value
-func parsePositiveInt(o *Options, key string, args ...string) error {
+func parsePositiveInt(o *options.Options, key string, args ...string) error {
 	if err := ensureMaxArgs(key, args, 1); err != nil {
 		return err
 	}
@@ -135,7 +137,7 @@ func parsePositiveInt(o *Options, key string, args ...string) error {
 }
 
 // parseQualityInt parses a quality integer option value (1-100)
-func parseQualityInt(o *Options, key string, args ...string) error {
+func parseQualityInt(o *options.Options, key string, args ...string) error {
 	if err := ensureMaxArgs(key, args, 1); err != nil {
 		return err
 	}
@@ -151,7 +153,7 @@ func parseQualityInt(o *Options, key string, args ...string) error {
 }
 
 // parseOpacityFloat parses an opacity float option value (0-1)
-func parseOpacityFloat(o *Options, key string, args ...string) error {
+func parseOpacityFloat(o *options.Options, key string, args ...string) error {
 	if err := ensureMaxArgs(key, args, 1); err != nil {
 		return err
 	}
@@ -167,7 +169,7 @@ func parseOpacityFloat(o *Options, key string, args ...string) error {
 }
 
 // parseResolution parses a resolution option value in megapixels and stores it as pixels
-func parseResolution(o *Options, key string, args ...string) error {
+func parseResolution(o *options.Options, key string, args ...string) error {
 	if err := ensureMaxArgs(key, args, 1); err != nil {
 		return err
 	}
@@ -184,7 +186,7 @@ func parseResolution(o *Options, key string, args ...string) error {
 }
 
 // parseBase64String parses a base64-encoded string option value
-func parseBase64String(o *Options, key string, args ...string) error {
+func parseBase64String(o *options.Options, key string, args ...string) error {
 	if err := ensureMaxArgs(key, args, 1); err != nil {
 		return err
 	}
@@ -200,7 +202,7 @@ func parseBase64String(o *Options, key string, args ...string) error {
 }
 
 // parseHexRGBColor parses a hex-encoded RGB color option value
-func parseHexRGBColor(o *Options, key string, args ...string) error {
+func parseHexRGBColor(o *options.Options, key string, args ...string) error {
 	if err := ensureMaxArgs(key, args, 1); err != nil {
 		return err
 	}
@@ -216,7 +218,7 @@ func parseHexRGBColor(o *Options, key string, args ...string) error {
 }
 
 // parseFromMap parses an option value from a map of allowed values
-func parseFromMap[T comparable](o *Options, key string, m map[string]T, args ...string) error {
+func parseFromMap[T comparable](o *options.Options, key string, m map[string]T, args ...string) error {
 	if err := ensureMaxArgs(key, args, 1); err != nil {
 		return err
 	}
@@ -232,22 +234,22 @@ func parseFromMap[T comparable](o *Options, key string, m map[string]T, args ...
 }
 
 func parseGravityType(
-	o *Options,
+	o *options.Options,
 	key string,
-	allowedTypes []GravityType,
+	allowedTypes []processing.GravityType,
 	args ...string,
-) (GravityType, error) {
+) (processing.GravityType, error) {
 	if err := ensureMaxArgs(key, args, 1); err != nil {
-		return GravityUnknown, err
+		return processing.GravityUnknown, err
 	}
 
-	gType, ok := gravityTypes[args[0]]
+	gType, ok := processing.GravityTypes[args[0]]
 	if !ok || !slices.Contains(allowedTypes, gType) {
 		types := make([]string, len(allowedTypes))
 		for i, at := range allowedTypes {
 			types[i] = at.String()
 		}
-		return GravityUnknown, newInvalidArgumentError(key, args[0], types...)
+		return processing.GravityUnknown, newInvalidArgumentError(key, args[0], types...)
 	}
 
 	o.Set(key, gType)
@@ -255,14 +257,14 @@ func parseGravityType(
 	return gType, nil
 }
 
-func isGravityOffsetValid(gravity GravityType, offset float64) bool {
-	return gravity != GravityFocusPoint || (offset >= 0 && offset <= 1)
+func isGravityOffsetValid(gravity processing.GravityType, offset float64) bool {
+	return gravity != processing.GravityFocusPoint || (offset >= 0 && offset <= 1)
 }
 
 func parseGravity(
-	o *Options,
+	o *options.Options,
 	key string,
-	allowedTypes []GravityType,
+	allowedTypes []processing.GravityType,
 	args ...string,
 ) error {
 	nArgs := len(args)
@@ -277,14 +279,14 @@ func parseGravity(
 	}
 
 	switch gType {
-	case GravitySmart:
+	case processing.GravitySmart:
 		if nArgs > 1 {
 			return newInvalidArgsError(key, args)
 		}
 		o.Delete(keyXOffset)
 		o.Delete(keyYOffset)
 
-	case GravityFocusPoint:
+	case processing.GravityFocusPoint:
 		if nArgs != 3 {
 			return newInvalidArgsError(key, args)
 		}
@@ -315,7 +317,7 @@ func parseGravity(
 	return nil
 }
 
-func parseExtend(o *Options, key string, args []string) error {
+func parseExtend(o *options.Options, key string, args []string) error {
 	if err := ensureMaxArgs(key, args, 4); err != nil {
 		return err
 	}
@@ -325,7 +327,7 @@ func parseExtend(o *Options, key string, args []string) error {
 	}
 
 	if len(args) > 1 {
-		return parseGravity(o, key+keys.SuffixGravity, extendGravityTypes, args[1:]...)
+		return parseGravity(o, key+keys.SuffixGravity, processing.ExtendGravityTypes, args[1:]...)
 	}
 
 	return nil

+ 3 - 3
options/parser.go → options/parser/parser.go

@@ -1,4 +1,4 @@
-package options
+package optionsparser
 
 // Presets is a map of preset names to their corresponding urlOptions
 type Presets = map[string]urlOptions
@@ -9,8 +9,8 @@ type Parser struct {
 	presets Presets // Parsed presets
 }
 
-// NewParser creates new Parser instance
-func NewParser(config *Config) (*Parser, error) {
+// New creates new Parser instance
+func New(config *Config) (*Parser, error) {
 	if err := config.Validate(); err != nil {
 		return nil, err
 	}

+ 5 - 3
options/presets.go → options/parser/presets.go

@@ -1,8 +1,10 @@
-package options
+package optionsparser
 
 import (
 	"fmt"
 	"strings"
+
+	"github.com/imgproxy/imgproxy/v3/options"
 )
 
 // parsePresets parses presets from the config and fills the presets map
@@ -60,8 +62,8 @@ func (p *Parser) parsePreset(presetStr string) error {
 // validatePresets validates all presets by applying them to a new Options instance
 func (p *Parser) validatePresets() error {
 	for name, opts := range p.presets {
-		po := New()
-		if err := p.applyURLOptions(po, opts, true, name); err != nil {
+		o := options.New()
+		if err := p.applyURLOptions(o, opts, true, name); err != nil {
 			return fmt.Errorf("Error in preset `%s`: %s", name, err)
 		}
 	}

+ 2 - 2
options/presets_test.go → options/parser/presets_test.go

@@ -1,4 +1,4 @@
-package options
+package optionsparser
 
 import (
 	"testing"
@@ -13,7 +13,7 @@ type PresetsTestSuite struct {
 func (s *PresetsTestSuite) newParser(presets ...string) (*Parser, error) {
 	c := NewDefaultConfig()
 	c.Presets = presets
-	return NewParser(&c)
+	return New(&c)
 }
 
 func (s *PresetsTestSuite) TestParsePreset() {

+ 93 - 78
options/processing_options.go → options/parser/processing_options.go

@@ -1,4 +1,4 @@
-package options
+package optionsparser
 
 import (
 	"net/http"
@@ -8,109 +8,121 @@ import (
 
 	"github.com/imgproxy/imgproxy/v3/ierrors"
 	"github.com/imgproxy/imgproxy/v3/imath"
+	"github.com/imgproxy/imgproxy/v3/options"
 	"github.com/imgproxy/imgproxy/v3/options/keys"
+	"github.com/imgproxy/imgproxy/v3/processing"
 )
 
 const maxClientHintDPR = 8
 
-func (p *Parser) applyURLOption(po *Options, name string, args []string, usedPresets ...string) error {
+func (p *Parser) applyURLOption(
+	o *options.Options,
+	name string,
+	args []string,
+	usedPresets ...string,
+) error {
 	switch name {
 	case "resize", "rs":
-		return applyResizeOption(po, args)
+		return applyResizeOption(o, args)
 	case "size", "s":
-		return applySizeOption(po, args)
+		return applySizeOption(o, args)
 	case "resizing_type", "rt":
-		return applyResizingTypeOption(po, args)
+		return applyResizingTypeOption(o, args)
 	case "width", "w":
-		return applyWidthOption(po, args)
+		return applyWidthOption(o, args)
 	case "height", "h":
-		return applyHeightOption(po, args)
+		return applyHeightOption(o, args)
 	case "min-width", "mw":
-		return applyMinWidthOption(po, args)
+		return applyMinWidthOption(o, args)
 	case "min-height", "mh":
-		return applyMinHeightOption(po, args)
+		return applyMinHeightOption(o, args)
 	case "zoom", "z":
-		return applyZoomOption(po, args)
+		return applyZoomOption(o, args)
 	case "dpr":
-		return applyDprOption(po, args)
+		return applyDprOption(o, args)
 	case "enlarge", "el":
-		return applyEnlargeOption(po, args)
+		return applyEnlargeOption(o, args)
 	case "extend", "ex":
-		return applyExtendOption(po, args)
+		return applyExtendOption(o, args)
 	case "extend_aspect_ratio", "extend_ar", "exar":
-		return applyExtendAspectRatioOption(po, args)
+		return applyExtendAspectRatioOption(o, args)
 	case "gravity", "g":
-		return applyGravityOption(po, args)
+		return applyGravityOption(o, args)
 	case "crop", "c":
-		return applyCropOption(po, args)
+		return applyCropOption(o, args)
 	case "trim", "t":
-		return applyTrimOption(po, args)
+		return applyTrimOption(o, args)
 	case "padding", "pd":
-		return applyPaddingOption(po, args)
+		return applyPaddingOption(o, args)
 	case "auto_rotate", "ar":
-		return applyAutoRotateOption(po, args)
+		return applyAutoRotateOption(o, args)
 	case "rotate", "rot":
-		return applyRotateOption(po, args)
+		return applyRotateOption(o, args)
 	case "background", "bg":
-		return applyBackgroundOption(po, args)
+		return applyBackgroundOption(o, args)
 	case "blur", "bl":
-		return applyBlurOption(po, args)
+		return applyBlurOption(o, args)
 	case "sharpen", "sh":
-		return applySharpenOption(po, args)
+		return applySharpenOption(o, args)
 	case "pixelate", "pix":
-		return applyPixelateOption(po, args)
+		return applyPixelateOption(o, args)
 	case "watermark", "wm":
-		return applyWatermarkOption(po, args)
+		return applyWatermarkOption(o, args)
 	case "strip_metadata", "sm":
-		return applyStripMetadataOption(po.Main(), args)
+		return applyStripMetadataOption(o.Main(), args)
 	case "keep_copyright", "kcr":
-		return applyKeepCopyrightOption(po.Main(), args)
+		return applyKeepCopyrightOption(o.Main(), args)
 	case "strip_color_profile", "scp":
-		return applyStripColorProfileOption(po.Main(), args)
+		return applyStripColorProfileOption(o.Main(), args)
 	case "enforce_thumbnail", "eth":
-		return applyEnforceThumbnailOption(po.Main(), args)
+		return applyEnforceThumbnailOption(o.Main(), args)
 	// Saving options
 	case "quality", "q":
-		return applyQualityOption(po.Main(), args)
+		return applyQualityOption(o.Main(), args)
 	case "format_quality", "fq":
-		return applyFormatQualityOption(po.Main(), args)
+		return applyFormatQualityOption(o.Main(), args)
 	case "max_bytes", "mb":
-		return applyMaxBytesOption(po.Main(), args)
+		return applyMaxBytesOption(o.Main(), args)
 	case "format", "f", "ext":
-		return applyFormatOption(po.Main(), args)
+		return applyFormatOption(o.Main(), args)
 	// Handling options
 	case "skip_processing", "skp":
-		return applySkipProcessingFormatsOption(po.Main(), args)
+		return applySkipProcessingFormatsOption(o.Main(), args)
 	case "raw":
-		return applyRawOption(po.Main(), args)
+		return applyRawOption(o.Main(), args)
 	case "cachebuster", "cb":
-		return applyCacheBusterOption(po.Main(), args)
+		return applyCacheBusterOption(o.Main(), args)
 	case "expires", "exp":
-		return applyExpiresOption(po.Main(), args)
+		return applyExpiresOption(o.Main(), args)
 	case "filename", "fn":
-		return applyFilenameOption(po.Main(), args)
+		return applyFilenameOption(o.Main(), args)
 	case "return_attachment", "att":
-		return applyReturnAttachmentOption(po.Main(), args)
+		return applyReturnAttachmentOption(o.Main(), args)
 	// Presets
 	case "preset", "pr":
-		return applyPresetOption(p, po, args, usedPresets...)
+		return applyPresetOption(p, o, args, usedPresets...)
 	// Security
 	case "max_src_resolution", "msr":
-		return applyMaxSrcResolutionOption(p, po.Main(), args)
+		return applyMaxSrcResolutionOption(p, o.Main(), args)
 	case "max_src_file_size", "msfs":
-		return applyMaxSrcFileSizeOption(p, po.Main(), args)
+		return applyMaxSrcFileSizeOption(p, o.Main(), args)
 	case "max_animation_frames", "maf":
-		return applyMaxAnimationFramesOption(p, po.Main(), args)
+		return applyMaxAnimationFramesOption(p, o.Main(), args)
 	case "max_animation_frame_resolution", "mafr":
-		return applyMaxAnimationFrameResolutionOption(p, po.Main(), args)
+		return applyMaxAnimationFrameResolutionOption(p, o.Main(), args)
 	case "max_result_dimension", "mrd":
-		return applyMaxResultDimensionOption(p, po.Main(), args)
+		return applyMaxResultDimensionOption(p, o.Main(), args)
 	}
 
 	return newUnknownOptionError("processing", name)
 }
 
-func (p *Parser) applyURLOptions(po *Options, options urlOptions, allowAll bool, usedPresets ...string) error {
+func (p *Parser) applyURLOptions(
+	o *options.Options,
+	options urlOptions,
+	allowAll bool,
+	usedPresets ...string,
+) error {
 	allowAll = allowAll || len(p.config.AllowedProcessingOptions) == 0
 
 	for _, opt := range options {
@@ -118,7 +130,7 @@ func (p *Parser) applyURLOptions(po *Options, options urlOptions, allowAll bool,
 			return newForbiddenOptionError("processing", opt.Name)
 		}
 
-		if err := p.applyURLOption(po, opt.Name, opt.Args, usedPresets...); err != nil {
+		if err := p.applyURLOption(o, opt.Name, opt.Args, usedPresets...); err != nil {
 			return err
 		}
 	}
@@ -126,32 +138,32 @@ func (p *Parser) applyURLOptions(po *Options, options urlOptions, allowAll bool,
 	return nil
 }
 
-func (p *Parser) defaultProcessingOptions(headers http.Header) (*Options, error) {
-	po := New()
+func (p *Parser) defaultProcessingOptions(headers http.Header) (*options.Options, error) {
+	o := options.New()
 
 	headerAccept := headers.Get("Accept")
 
 	if (p.config.AutoWebp || p.config.EnforceWebp) && strings.Contains(headerAccept, "image/webp") {
-		po.Set(keys.PreferWebP, true)
+		o.Set(keys.PreferWebP, true)
 
 		if p.config.EnforceWebp {
-			po.Set(keys.EnforceWebP, true)
+			o.Set(keys.EnforceWebP, true)
 		}
 	}
 
 	if (p.config.AutoAvif || p.config.EnforceAvif) && strings.Contains(headerAccept, "image/avif") {
-		po.Set(keys.PreferAvif, true)
+		o.Set(keys.PreferAvif, true)
 
 		if p.config.EnforceAvif {
-			po.Set(keys.EnforceAvif, true)
+			o.Set(keys.EnforceAvif, true)
 		}
 	}
 
 	if (p.config.AutoJxl || p.config.EnforceJxl) && strings.Contains(headerAccept, "image/jxl") {
-		po.Set(keys.PreferJxl, true)
+		o.Set(keys.PreferJxl, true)
 
 		if p.config.EnforceJxl {
-			po.Set(keys.EnforceJxl, true)
+			o.Set(keys.EnforceJxl, true)
 		}
 	}
 
@@ -165,7 +177,7 @@ func (p *Parser) defaultProcessingOptions(headers http.Header) (*Options, error)
 		if len(headerDPR) > 0 {
 			if d, err := strconv.ParseFloat(headerDPR, 64); err == nil && (d > 0 && d <= maxClientHintDPR) {
 				dpr = d
-				po.Set(keys.Dpr, dpr)
+				o.Set(keys.Dpr, dpr)
 			}
 		}
 
@@ -175,25 +187,25 @@ func (p *Parser) defaultProcessingOptions(headers http.Header) (*Options, error)
 		}
 		if len(headerWidth) > 0 {
 			if w, err := strconv.Atoi(headerWidth); err == nil {
-				po.Set(keys.Width, imath.Shrink(w, dpr))
+				o.Set(keys.Width, imath.Shrink(w, dpr))
 			}
 		}
 	}
 
 	if _, ok := p.presets["default"]; ok {
-		if err := applyPresetOption(p, po, []string{"default"}); err != nil {
-			return po, err
+		if err := applyPresetOption(p, o, []string{"default"}); err != nil {
+			return o, err
 		}
 	}
 
-	return po, nil
+	return o, nil
 }
 
 // ParsePath parses the given request path and returns the processing options and image URL
 func (p *Parser) ParsePath(
 	path string,
 	headers http.Header,
-) (po *Options, imageURL string, err error) {
+) (o *options.Options, imageURL string, err error) {
 	if path == "" || path == "/" {
 		return nil, "", newInvalidURLError("invalid path: %s", path)
 	}
@@ -201,32 +213,35 @@ func (p *Parser) ParsePath(
 	parts := strings.Split(strings.TrimPrefix(path, "/"), "/")
 
 	if p.config.OnlyPresets {
-		po, imageURL, err = p.parsePathPresets(parts, headers)
+		o, imageURL, err = p.parsePathPresets(parts, headers)
 	} else {
-		po, imageURL, err = p.parsePathOptions(parts, headers)
+		o, imageURL, err = p.parsePathOptions(parts, headers)
 	}
 
 	if err != nil {
 		return nil, "", ierrors.Wrap(err, 0)
 	}
 
-	return po, imageURL, nil
+	return o, imageURL, nil
 }
 
 // parsePathOptions parses processing options from the URL path
-func (p *Parser) parsePathOptions(parts []string, headers http.Header) (*Options, string, error) {
-	if _, ok := resizeTypes[parts[0]]; ok {
+func (p *Parser) parsePathOptions(
+	parts []string,
+	headers http.Header,
+) (*options.Options, string, error) {
+	if _, ok := processing.ResizeTypes[parts[0]]; ok {
 		return nil, "", newInvalidURLError("It looks like you're using the deprecated basic URL format")
 	}
 
-	po, err := p.defaultProcessingOptions(headers)
+	o, err := p.defaultProcessingOptions(headers)
 	if err != nil {
 		return nil, "", err
 	}
 
-	options, urlParts := p.parseURLOptions(parts)
+	urlOpts, urlParts := p.parseURLOptions(parts)
 
-	if err = p.applyURLOptions(po, options, false); err != nil {
+	if err = p.applyURLOptions(o, urlOpts, false); err != nil {
 		return nil, "", err
 	}
 
@@ -235,18 +250,18 @@ func (p *Parser) parsePathOptions(parts []string, headers http.Header) (*Options
 		return nil, "", err
 	}
 
-	if !Get(po, keys.Raw, false) && len(extension) > 0 {
-		if err = applyFormatOption(po, []string{extension}); err != nil {
+	if !options.Get(o, keys.Raw, false) && len(extension) > 0 {
+		if err = applyFormatOption(o, []string{extension}); err != nil {
 			return nil, "", err
 		}
 	}
 
-	return po, url, nil
+	return o, url, nil
 }
 
 // parsePathPresets parses presets from the URL path
-func (p *Parser) parsePathPresets(parts []string, headers http.Header) (*Options, string, error) {
-	po, err := p.defaultProcessingOptions(headers)
+func (p *Parser) parsePathPresets(parts []string, headers http.Header) (*options.Options, string, error) {
+	o, err := p.defaultProcessingOptions(headers)
 	if err != nil {
 		return nil, "", err
 	}
@@ -254,7 +269,7 @@ func (p *Parser) parsePathPresets(parts []string, headers http.Header) (*Options
 	presets := strings.Split(parts[0], p.config.ArgumentsSeparator)
 	urlParts := parts[1:]
 
-	if err = applyPresetOption(p, po, presets); err != nil {
+	if err = applyPresetOption(p, o, presets); err != nil {
 		return nil, "", err
 	}
 
@@ -263,11 +278,11 @@ func (p *Parser) parsePathPresets(parts []string, headers http.Header) (*Options
 		return nil, "", err
 	}
 
-	if !Get(po, keys.Raw, false) && len(extension) > 0 {
-		if err = applyFormatOption(po, []string{extension}); err != nil {
+	if !options.Get(o, keys.Raw, false) && len(extension) > 0 {
+		if err = applyFormatOption(o, []string{extension}); err != nil {
 			return nil, "", err
 		}
 	}
 
-	return po, url, nil
+	return o, url, nil
 }

+ 166 - 149
options/processing_options_test.go → options/parser/processing_options_test.go

@@ -1,4 +1,4 @@
-package options
+package optionsparser
 
 import (
 	"encoding/base64"
@@ -12,7 +12,9 @@ import (
 
 	"github.com/imgproxy/imgproxy/v3/config"
 	"github.com/imgproxy/imgproxy/v3/imagetype"
+	"github.com/imgproxy/imgproxy/v3/options"
 	"github.com/imgproxy/imgproxy/v3/options/keys"
+	"github.com/imgproxy/imgproxy/v3/processing"
 	"github.com/imgproxy/imgproxy/v3/testutil"
 	"github.com/imgproxy/imgproxy/v3/vips/color"
 	"github.com/stretchr/testify/suite"
@@ -37,7 +39,7 @@ func (s *ProcessingOptionsTestSuite) SetupSuite() {
 	s.parser, _ = testutil.NewLazySuiteObj(
 		s,
 		func() (*Parser, error) {
-			return NewParser(s.config())
+			return New(s.config())
 		},
 	)
 }
@@ -49,11 +51,11 @@ func (s *ProcessingOptionsTestSuite) SetupSubTest() {
 func (s *ProcessingOptionsTestSuite) TestParseBase64URL() {
 	originURL := "http://images.dev/lorem/ipsum.jpg?param=value"
 	path := fmt.Sprintf("/size:100:100/%s.png", base64.RawURLEncoding.EncodeToString([]byte(originURL)))
-	po, imageURL, err := s.parser().ParsePath(path, make(http.Header))
+	o, imageURL, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 	s.Require().Equal(originURL, imageURL)
-	s.Require().Equal(imagetype.PNG, Get(po, keys.Format, imagetype.Unknown))
+	s.Require().Equal(imagetype.PNG, options.Get(o, keys.Format, imagetype.Unknown))
 }
 
 func (s *ProcessingOptionsTestSuite) TestParseBase64URLWithFilename() {
@@ -61,21 +63,21 @@ func (s *ProcessingOptionsTestSuite) TestParseBase64URLWithFilename() {
 
 	originURL := "http://images.dev/lorem/ipsum.jpg?param=value"
 	path := fmt.Sprintf("/size:100:100/%s.png/puppy.jpg", base64.RawURLEncoding.EncodeToString([]byte(originURL)))
-	po, imageURL, err := s.parser().ParsePath(path, make(http.Header))
+	o, imageURL, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 	s.Require().Equal(originURL, imageURL)
-	s.Require().Equal(imagetype.PNG, Get(po, keys.Format, imagetype.Unknown))
+	s.Require().Equal(imagetype.PNG, options.Get(o, keys.Format, imagetype.Unknown))
 }
 
 func (s *ProcessingOptionsTestSuite) TestParseBase64URLWithoutExtension() {
 	originURL := "http://images.dev/lorem/ipsum.jpg?param=value"
 	path := fmt.Sprintf("/size:100:100/%s", base64.RawURLEncoding.EncodeToString([]byte(originURL)))
-	po, imageURL, err := s.parser().ParsePath(path, make(http.Header))
+	o, imageURL, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 	s.Require().Equal(originURL, imageURL)
-	s.Require().Equal(imagetype.Unknown, Get(po, keys.Format, imagetype.Unknown))
+	s.Require().Equal(imagetype.Unknown, options.Get(o, keys.Format, imagetype.Unknown))
 }
 
 func (s *ProcessingOptionsTestSuite) TestParseBase64URLWithBase() {
@@ -83,11 +85,11 @@ func (s *ProcessingOptionsTestSuite) TestParseBase64URLWithBase() {
 
 	originURL := "lorem/ipsum.jpg?param=value"
 	path := fmt.Sprintf("/size:100:100/%s.png", base64.RawURLEncoding.EncodeToString([]byte(originURL)))
-	po, imageURL, err := s.parser().ParsePath(path, make(http.Header))
+	o, imageURL, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 	s.Require().Equal(fmt.Sprintf("%s%s", s.config().BaseURL, originURL), imageURL)
-	s.Require().Equal(imagetype.PNG, Get(po, keys.Format, imagetype.Unknown))
+	s.Require().Equal(imagetype.PNG, options.Get(o, keys.Format, imagetype.Unknown))
 }
 
 func (s *ProcessingOptionsTestSuite) TestParseBase64URLWithReplacement() {
@@ -98,41 +100,41 @@ func (s *ProcessingOptionsTestSuite) TestParseBase64URLWithReplacement() {
 
 	originURL := "test://lorem/ipsum.jpg?param=value"
 	path := fmt.Sprintf("/size:100:100/%s.png", base64.RawURLEncoding.EncodeToString([]byte(originURL)))
-	po, imageURL, err := s.parser().ParsePath(path, make(http.Header))
+	o, imageURL, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 	s.Require().Equal("http://images.dev/lorem/dolor/ipsum.jpg?param=value", imageURL)
-	s.Require().Equal(imagetype.PNG, Get(po, keys.Format, imagetype.Unknown))
+	s.Require().Equal(imagetype.PNG, options.Get(o, keys.Format, imagetype.Unknown))
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePlainURL() {
 	originURL := "http://images.dev/lorem/ipsum.jpg"
 	path := fmt.Sprintf("/size:100:100/plain/%s@png", originURL)
-	po, imageURL, err := s.parser().ParsePath(path, make(http.Header))
+	o, imageURL, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 	s.Require().Equal(originURL, imageURL)
-	s.Require().Equal(imagetype.PNG, Get(po, keys.Format, imagetype.Unknown))
+	s.Require().Equal(imagetype.PNG, options.Get(o, keys.Format, imagetype.Unknown))
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePlainURLWithoutExtension() {
 	originURL := "http://images.dev/lorem/ipsum.jpg"
 	path := fmt.Sprintf("/size:100:100/plain/%s", originURL)
 
-	po, imageURL, err := s.parser().ParsePath(path, make(http.Header))
+	o, imageURL, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 	s.Require().Equal(originURL, imageURL)
-	s.Require().Equal(imagetype.Unknown, Get(po, keys.Format, imagetype.Unknown))
+	s.Require().Equal(imagetype.Unknown, options.Get(o, keys.Format, imagetype.Unknown))
 }
 func (s *ProcessingOptionsTestSuite) TestParsePlainURLEscaped() {
 	originURL := "http://images.dev/lorem/ipsum.jpg?param=value"
 	path := fmt.Sprintf("/size:100:100/plain/%s@png", url.PathEscape(originURL))
-	po, imageURL, err := s.parser().ParsePath(path, make(http.Header))
+	o, imageURL, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 	s.Require().Equal(originURL, imageURL)
-	s.Require().Equal(imagetype.PNG, Get(po, keys.Format, imagetype.Unknown))
+	s.Require().Equal(imagetype.PNG, options.Get(o, keys.Format, imagetype.Unknown))
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePlainURLWithBase() {
@@ -140,11 +142,11 @@ func (s *ProcessingOptionsTestSuite) TestParsePlainURLWithBase() {
 
 	originURL := "lorem/ipsum.jpg"
 	path := fmt.Sprintf("/size:100:100/plain/%s@png", originURL)
-	po, imageURL, err := s.parser().ParsePath(path, make(http.Header))
+	o, imageURL, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 	s.Require().Equal(fmt.Sprintf("%s%s", s.config().BaseURL, originURL), imageURL)
-	s.Require().Equal(imagetype.PNG, Get(po, keys.Format, imagetype.Unknown))
+	s.Require().Equal(imagetype.PNG, options.Get(o, keys.Format, imagetype.Unknown))
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePlainURLWithReplacement() {
@@ -155,11 +157,11 @@ func (s *ProcessingOptionsTestSuite) TestParsePlainURLWithReplacement() {
 
 	originURL := "test://lorem/ipsum.jpg"
 	path := fmt.Sprintf("/size:100:100/plain/%s@png", originURL)
-	po, imageURL, err := s.parser().ParsePath(path, make(http.Header))
+	o, imageURL, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 	s.Require().Equal("http://images.dev/lorem/dolor/ipsum.jpg", imageURL)
-	s.Require().Equal(imagetype.PNG, Get(po, keys.Format, imagetype.Unknown))
+	s.Require().Equal(imagetype.PNG, options.Get(o, keys.Format, imagetype.Unknown))
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePlainURLEscapedWithBase() {
@@ -167,104 +169,107 @@ func (s *ProcessingOptionsTestSuite) TestParsePlainURLEscapedWithBase() {
 
 	originURL := "lorem/ipsum.jpg?param=value"
 	path := fmt.Sprintf("/size:100:100/plain/%s@png", url.PathEscape(originURL))
-	po, imageURL, err := s.parser().ParsePath(path, make(http.Header))
+	o, imageURL, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 	s.Require().Equal(fmt.Sprintf("%s%s", s.config().BaseURL, originURL), imageURL)
-	s.Require().Equal(imagetype.PNG, Get(po, keys.Format, imagetype.Unknown))
+	s.Require().Equal(imagetype.PNG, options.Get(o, keys.Format, imagetype.Unknown))
 }
 
 func (s *ProcessingOptionsTestSuite) TestParseWithArgumentsSeparator() {
 	s.config().ArgumentsSeparator = ","
 
 	path := "/size,100,100,1/plain/http://images.dev/lorem/ipsum.jpg"
-	po, _, err := s.parser().ParsePath(path, make(http.Header))
+	o, _, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 
-	s.Require().Equal(100, po.GetInt(keys.Width, 0))
-	s.Require().Equal(100, po.GetInt(keys.Height, 0))
-	s.Require().True(po.GetBool(keys.Enlarge, false))
+	s.Require().Equal(100, o.GetInt(keys.Width, 0))
+	s.Require().Equal(100, o.GetInt(keys.Height, 0))
+	s.Require().True(o.GetBool(keys.Enlarge, false))
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePathFormat() {
 	path := "/format:webp/plain/http://images.dev/lorem/ipsum.jpg"
-	po, _, err := s.parser().ParsePath(path, make(http.Header))
+	o, _, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 
-	s.Require().Equal(imagetype.WEBP, Get(po, keys.Format, imagetype.Unknown))
+	s.Require().Equal(imagetype.WEBP, options.Get(o, keys.Format, imagetype.Unknown))
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePathResize() {
 	path := "/resize:fill:100:200:1/plain/http://images.dev/lorem/ipsum.jpg"
-	po, _, err := s.parser().ParsePath(path, make(http.Header))
+	o, _, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 
-	s.Require().Equal(ResizeFill, Get(po, keys.ResizingType, ResizeFit))
-	s.Require().Equal(100, po.GetInt(keys.Width, 0))
-	s.Require().Equal(200, po.GetInt(keys.Height, 0))
-	s.Require().True(po.GetBool(keys.Enlarge, false))
+	s.Require().Equal(processing.ResizeFill, options.Get(o, keys.ResizingType, processing.ResizeFit))
+	s.Require().Equal(100, o.GetInt(keys.Width, 0))
+	s.Require().Equal(200, o.GetInt(keys.Height, 0))
+	s.Require().True(o.GetBool(keys.Enlarge, false))
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePathResizingType() {
 	path := "/resizing_type:fill/plain/http://images.dev/lorem/ipsum.jpg"
-	po, _, err := s.parser().ParsePath(path, make(http.Header))
+	o, _, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 
-	s.Require().Equal(ResizeFill, Get(po, keys.ResizingType, ResizeFit))
+	s.Require().Equal(processing.ResizeFill, options.Get(o, keys.ResizingType, processing.ResizeFit))
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePathSize() {
 	path := "/size:100:200:1/plain/http://images.dev/lorem/ipsum.jpg"
-	po, _, err := s.parser().ParsePath(path, make(http.Header))
+	o, _, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 
-	s.Require().Equal(100, po.GetInt(keys.Width, 0))
-	s.Require().Equal(200, po.GetInt(keys.Height, 0))
-	s.Require().True(po.GetBool(keys.Enlarge, false))
+	s.Require().Equal(100, o.GetInt(keys.Width, 0))
+	s.Require().Equal(200, o.GetInt(keys.Height, 0))
+	s.Require().True(o.GetBool(keys.Enlarge, false))
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePathWidth() {
 	path := "/width:100/plain/http://images.dev/lorem/ipsum.jpg"
-	po, _, err := s.parser().ParsePath(path, make(http.Header))
+	o, _, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 
-	s.Require().Equal(100, po.GetInt(keys.Width, 0))
+	s.Require().Equal(100, o.GetInt(keys.Width, 0))
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePathHeight() {
 	path := "/height:100/plain/http://images.dev/lorem/ipsum.jpg"
-	po, _, err := s.parser().ParsePath(path, make(http.Header))
+	o, _, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 
-	s.Require().Equal(100, po.GetInt(keys.Height, 0))
+	s.Require().Equal(100, o.GetInt(keys.Height, 0))
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePathEnlarge() {
 	path := "/enlarge:1/plain/http://images.dev/lorem/ipsum.jpg"
-	po, _, err := s.parser().ParsePath(path, make(http.Header))
+	o, _, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 
-	s.Require().True(po.GetBool(keys.Enlarge, false))
+	s.Require().True(o.GetBool(keys.Enlarge, false))
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePathExtend() {
 	path := "/extend:1:so:10:20/plain/http://images.dev/lorem/ipsum.jpg"
-	po, _, err := s.parser().ParsePath(path, make(http.Header))
+	o, _, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 
-	s.Require().True(po.GetBool(keys.ExtendEnabled, false))
-	s.Require().Equal(GravitySouth, Get(po, keys.ExtendGravityType, GravityUnknown))
-	s.Require().InDelta(10.0, po.GetFloat(keys.ExtendGravityXOffset, 0.0), 0.0001)
-	s.Require().InDelta(20.0, po.GetFloat(keys.ExtendGravityYOffset, 0.0), 0.0001)
+	s.Require().True(o.GetBool(keys.ExtendEnabled, false))
+	s.Require().Equal(
+		processing.GravitySouth,
+		options.Get(o, keys.ExtendGravityType, processing.GravityUnknown),
+	)
+	s.Require().InDelta(10.0, o.GetFloat(keys.ExtendGravityXOffset, 0.0), 0.0001)
+	s.Require().InDelta(20.0, o.GetFloat(keys.ExtendGravityYOffset, 0.0), 0.0001)
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePathExtendSmartGravity() {
@@ -283,22 +288,25 @@ func (s *ProcessingOptionsTestSuite) TestParsePathExtendReplicateGravity() {
 
 func (s *ProcessingOptionsTestSuite) TestParsePathGravity() {
 	path := "/gravity:soea/plain/http://images.dev/lorem/ipsum.jpg"
-	po, _, err := s.parser().ParsePath(path, make(http.Header))
+	o, _, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 
-	s.Require().Equal(GravitySouthEast, Get(po, keys.GravityType, GravityUnknown))
+	s.Require().Equal(
+		processing.GravitySouthEast,
+		options.Get(o, keys.GravityType, processing.GravityUnknown),
+	)
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePathGravityFocusPoint() {
 	path := "/gravity:fp:0.5:0.75/plain/http://images.dev/lorem/ipsum.jpg"
-	po, _, err := s.parser().ParsePath(path, make(http.Header))
+	o, _, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 
-	s.Require().Equal(GravityFocusPoint, Get(po, keys.GravityType, GravityUnknown))
-	s.Require().InDelta(0.5, po.GetFloat(keys.GravityXOffset, 0.0), 0.0001)
-	s.Require().InDelta(0.75, po.GetFloat(keys.GravityYOffset, 0.0), 0.0001)
+	s.Require().Equal(processing.GravityFocusPoint, options.Get(o, keys.GravityType, processing.GravityUnknown))
+	s.Require().InDelta(0.5, o.GetFloat(keys.GravityXOffset, 0.0), 0.0001)
+	s.Require().InDelta(0.75, o.GetFloat(keys.GravityYOffset, 0.0), 0.0001)
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePathGravityReplicate() {
@@ -310,28 +318,34 @@ func (s *ProcessingOptionsTestSuite) TestParsePathGravityReplicate() {
 
 func (s *ProcessingOptionsTestSuite) TestParsePathCrop() {
 	path := "/crop:100:200/plain/http://images.dev/lorem/ipsum.jpg"
-	po, _, err := s.parser().ParsePath(path, make(http.Header))
+	o, _, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 
-	s.Require().InDelta(100.0, po.GetFloat(keys.CropWidth, 0.0), 0.0001)
-	s.Require().InDelta(200.0, po.GetFloat(keys.CropHeight, 0.0), 0.0001)
-	s.Require().Equal(GravityUnknown, Get(po, keys.CropGravityType, GravityUnknown))
-	s.Require().InDelta(0.0, po.GetFloat(keys.CropGravityXOffset, 0.0), 0.0001)
-	s.Require().InDelta(0.0, po.GetFloat(keys.CropGravityYOffset, 0.0), 0.0001)
+	s.Require().InDelta(100.0, o.GetFloat(keys.CropWidth, 0.0), 0.0001)
+	s.Require().InDelta(200.0, o.GetFloat(keys.CropHeight, 0.0), 0.0001)
+	s.Require().Equal(
+		processing.GravityUnknown,
+		options.Get(o, keys.CropGravityType, processing.GravityUnknown),
+	)
+	s.Require().InDelta(0.0, o.GetFloat(keys.CropGravityXOffset, 0.0), 0.0001)
+	s.Require().InDelta(0.0, o.GetFloat(keys.CropGravityYOffset, 0.0), 0.0001)
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePathCropGravity() {
 	path := "/crop:100:200:nowe:10:20/plain/http://images.dev/lorem/ipsum.jpg"
-	po, _, err := s.parser().ParsePath(path, make(http.Header))
+	o, _, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 
-	s.Require().InDelta(100.0, po.GetFloat(keys.CropWidth, 0.0), 0.0001)
-	s.Require().InDelta(200.0, po.GetFloat(keys.CropHeight, 0.0), 0.0001)
-	s.Require().Equal(GravityNorthWest, Get(po, keys.CropGravityType, GravityUnknown))
-	s.Require().InDelta(10.0, po.GetFloat(keys.CropGravityXOffset, 0.0), 0.0001)
-	s.Require().InDelta(20.0, po.GetFloat(keys.CropGravityYOffset, 0.0), 0.0001)
+	s.Require().InDelta(100.0, o.GetFloat(keys.CropWidth, 0.0), 0.0001)
+	s.Require().InDelta(200.0, o.GetFloat(keys.CropHeight, 0.0), 0.0001)
+	s.Require().Equal(
+		processing.GravityNorthWest,
+		options.Get(o, keys.CropGravityType, processing.GravityUnknown),
+	)
+	s.Require().InDelta(10.0, o.GetFloat(keys.CropGravityXOffset, 0.0), 0.0001)
+	s.Require().InDelta(20.0, o.GetFloat(keys.CropGravityYOffset, 0.0), 0.0001)
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePathCropGravityReplicate() {
@@ -343,84 +357,87 @@ func (s *ProcessingOptionsTestSuite) TestParsePathCropGravityReplicate() {
 
 func (s *ProcessingOptionsTestSuite) TestParsePathQuality() {
 	path := "/quality:55/plain/http://images.dev/lorem/ipsum.jpg"
-	po, _, err := s.parser().ParsePath(path, make(http.Header))
+	o, _, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 
-	s.Require().Equal(55, po.GetInt(keys.Quality, 0))
+	s.Require().Equal(55, o.GetInt(keys.Quality, 0))
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePathBackground() {
 	path := "/background:128:129:130/plain/http://images.dev/lorem/ipsum.jpg"
-	po, _, err := s.parser().ParsePath(path, make(http.Header))
+	o, _, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 
 	s.Require().Equal(
 		color.RGB{R: 128, G: 129, B: 130},
-		Get(po, keys.Background, color.RGB{}),
+		options.Get(o, keys.Background, color.RGB{}),
 	)
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePathBackgroundHex() {
 	path := "/background:ffddee/plain/http://images.dev/lorem/ipsum.jpg"
-	po, _, err := s.parser().ParsePath(path, make(http.Header))
+	o, _, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 
 	s.Require().Equal(
 		color.RGB{R: 0xff, G: 0xdd, B: 0xee},
-		Get(po, keys.Background, color.RGB{}),
+		options.Get(o, keys.Background, color.RGB{}),
 	)
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePathBackgroundDisable() {
 	path := "/background:fff/background:/plain/http://images.dev/lorem/ipsum.jpg"
-	po, _, err := s.parser().ParsePath(path, make(http.Header))
+	o, _, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 
-	s.Require().False(po.Has(keys.Background))
+	s.Require().False(o.Has(keys.Background))
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePathBlur() {
 	path := "/blur:0.2/plain/http://images.dev/lorem/ipsum.jpg"
-	po, _, err := s.parser().ParsePath(path, make(http.Header))
+	o, _, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 
-	s.Require().InDelta(0.2, po.GetFloat(keys.Blur, 0.0), 0.0001)
+	s.Require().InDelta(0.2, o.GetFloat(keys.Blur, 0.0), 0.0001)
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePathSharpen() {
 	path := "/sharpen:0.2/plain/http://images.dev/lorem/ipsum.jpg"
-	po, _, err := s.parser().ParsePath(path, make(http.Header))
+	o, _, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 
-	s.Require().InDelta(0.2, po.GetFloat(keys.Sharpen, 0.0), 0.0001)
+	s.Require().InDelta(0.2, o.GetFloat(keys.Sharpen, 0.0), 0.0001)
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePathDpr() {
 	path := "/dpr:2/plain/http://images.dev/lorem/ipsum.jpg"
-	po, _, err := s.parser().ParsePath(path, make(http.Header))
+	o, _, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 
-	s.Require().InDelta(2.0, po.GetFloat(keys.Dpr, 1.0), 0.0001)
+	s.Require().InDelta(2.0, o.GetFloat(keys.Dpr, 1.0), 0.0001)
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePathWatermark() {
 	path := "/watermark:0.5:soea:10:20:0.6/plain/http://images.dev/lorem/ipsum.jpg"
-	po, _, err := s.parser().ParsePath(path, make(http.Header))
+	o, _, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 
-	s.Require().InDelta(0.5, po.GetFloat(keys.WatermarkOpacity, 0.0), 0.0001)
-	s.Require().Equal(GravitySouthEast, Get(po, keys.WatermarkPosition, GravityUnknown))
-	s.Require().InDelta(10.0, po.GetFloat(keys.WatermarkXOffset, 0.0), 0.0001)
-	s.Require().InDelta(20.0, po.GetFloat(keys.WatermarkYOffset, 0.0), 0.0001)
-	s.Require().InDelta(0.6, po.GetFloat(keys.WatermarkScale, 0.0), 0.0001)
+	s.Require().InDelta(0.5, o.GetFloat(keys.WatermarkOpacity, 0.0), 0.0001)
+	s.Require().Equal(
+		processing.GravitySouthEast,
+		options.Get(o, keys.WatermarkPosition, processing.GravityUnknown),
+	)
+	s.Require().InDelta(10.0, o.GetFloat(keys.WatermarkXOffset, 0.0), 0.0001)
+	s.Require().InDelta(20.0, o.GetFloat(keys.WatermarkYOffset, 0.0), 0.0001)
+	s.Require().InDelta(0.6, o.GetFloat(keys.WatermarkScale, 0.0), 0.0001)
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePathPreset() {
@@ -430,14 +447,14 @@ func (s *ProcessingOptionsTestSuite) TestParsePathPreset() {
 	}
 
 	path := "/preset:test1:test2/plain/http://images.dev/lorem/ipsum.jpg"
-	po, _, err := s.parser().ParsePath(path, make(http.Header))
+	o, _, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 
-	s.Require().Equal(ResizeFill, Get(po, keys.ResizingType, ResizeFit))
-	s.Require().InDelta(float32(0.2), po.GetFloat(keys.Blur, 0.0), 0.0001)
-	s.Require().Equal(50, po.GetInt(keys.Quality, 0))
-	s.Require().ElementsMatch([]string{"test1", "test2"}, Get(po, keys.UsedPresets, []string{}))
+	s.Require().Equal(processing.ResizeFill, options.Get(o, keys.ResizingType, processing.ResizeFit))
+	s.Require().InDelta(float32(0.2), o.GetFloat(keys.Blur, 0.0), 0.0001)
+	s.Require().Equal(50, o.GetInt(keys.Quality, 0))
+	s.Require().ElementsMatch([]string{"test1", "test2"}, options.Get(o, keys.UsedPresets, []string{}))
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePathPresetDefault() {
@@ -446,14 +463,14 @@ func (s *ProcessingOptionsTestSuite) TestParsePathPresetDefault() {
 	}
 
 	path := "/quality:70/plain/http://images.dev/lorem/ipsum.jpg"
-	po, _, err := s.parser().ParsePath(path, make(http.Header))
+	o, _, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 
-	s.Require().Equal(ResizeFill, Get(po, keys.ResizingType, ResizeFit))
-	s.Require().InDelta(float32(0.2), po.GetFloat(keys.Blur, 0.0), 0.0001)
-	s.Require().Equal(70, po.GetInt(keys.Quality, 0))
-	s.Require().ElementsMatch([]string{"default"}, Get(po, keys.UsedPresets, []string{}))
+	s.Require().Equal(processing.ResizeFill, options.Get(o, keys.ResizingType, processing.ResizeFit))
+	s.Require().InDelta(float32(0.2), o.GetFloat(keys.Blur, 0.0), 0.0001)
+	s.Require().Equal(70, o.GetInt(keys.Quality, 0))
+	s.Require().ElementsMatch([]string{"default"}, options.Get(o, keys.UsedPresets, []string{}))
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePathPresetLoopDetection() {
@@ -463,29 +480,29 @@ func (s *ProcessingOptionsTestSuite) TestParsePathPresetLoopDetection() {
 	}
 
 	path := "/preset:test1/plain/http://images.dev/lorem/ipsum.jpg"
-	po, _, err := s.parser().ParsePath(path, make(http.Header))
+	o, _, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 
-	s.Require().ElementsMatch([]string{"test1", "test2"}, Get(po, keys.UsedPresets, []string{}))
+	s.Require().ElementsMatch([]string{"test1", "test2"}, options.Get(o, keys.UsedPresets, []string{}))
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePathCachebuster() {
 	path := "/cachebuster:123/plain/http://images.dev/lorem/ipsum.jpg"
-	po, _, err := s.parser().ParsePath(path, make(http.Header))
+	o, _, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 
-	s.Require().Equal("123", Get(po, keys.CacheBuster, ""))
+	s.Require().Equal("123", options.Get(o, keys.CacheBuster, ""))
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePathStripMetadata() {
 	path := "/strip_metadata:true/plain/http://images.dev/lorem/ipsum.jpg"
-	po, _, err := s.parser().ParsePath(path, make(http.Header))
+	o, _, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 
-	s.Require().True(po.GetBool(keys.StripMetadata, false))
+	s.Require().True(o.GetBool(keys.StripMetadata, false))
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePathWebpDetection() {
@@ -493,12 +510,12 @@ func (s *ProcessingOptionsTestSuite) TestParsePathWebpDetection() {
 
 	path := "/plain/http://images.dev/lorem/ipsum.jpg"
 	headers := http.Header{"Accept": []string{"image/webp"}}
-	po, _, err := s.parser().ParsePath(path, headers)
+	o, _, err := s.parser().ParsePath(path, headers)
 
 	s.Require().NoError(err)
 
-	s.Require().True(po.GetBool(keys.PreferWebP, false))
-	s.Require().False(po.GetBool(keys.EnforceWebP, false))
+	s.Require().True(o.GetBool(keys.PreferWebP, false))
+	s.Require().False(o.GetBool(keys.EnforceWebP, false))
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePathWebpEnforce() {
@@ -506,12 +523,12 @@ func (s *ProcessingOptionsTestSuite) TestParsePathWebpEnforce() {
 
 	path := "/plain/http://images.dev/lorem/ipsum.jpg@png"
 	headers := http.Header{"Accept": []string{"image/webp"}}
-	po, _, err := s.parser().ParsePath(path, headers)
+	o, _, err := s.parser().ParsePath(path, headers)
 
 	s.Require().NoError(err)
 
-	s.Require().True(po.GetBool(keys.PreferWebP, false))
-	s.Require().True(po.GetBool(keys.EnforceWebP, false))
+	s.Require().True(o.GetBool(keys.PreferWebP, false))
+	s.Require().True(o.GetBool(keys.EnforceWebP, false))
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePathAvifDetection() {
@@ -519,12 +536,12 @@ func (s *ProcessingOptionsTestSuite) TestParsePathAvifDetection() {
 
 	path := "/plain/http://images.dev/lorem/ipsum.jpg"
 	headers := http.Header{"Accept": []string{"image/avif"}}
-	po, _, err := s.parser().ParsePath(path, headers)
+	o, _, err := s.parser().ParsePath(path, headers)
 
 	s.Require().NoError(err)
 
-	s.Require().True(po.GetBool(keys.PreferAvif, false))
-	s.Require().False(po.GetBool(keys.EnforceAvif, false))
+	s.Require().True(o.GetBool(keys.PreferAvif, false))
+	s.Require().False(o.GetBool(keys.EnforceAvif, false))
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePathAvifEnforce() {
@@ -532,12 +549,12 @@ func (s *ProcessingOptionsTestSuite) TestParsePathAvifEnforce() {
 
 	path := "/plain/http://images.dev/lorem/ipsum.jpg@png"
 	headers := http.Header{"Accept": []string{"image/avif"}}
-	po, _, err := s.parser().ParsePath(path, headers)
+	o, _, err := s.parser().ParsePath(path, headers)
 
 	s.Require().NoError(err)
 
-	s.Require().True(po.GetBool(keys.PreferAvif, false))
-	s.Require().True(po.GetBool(keys.EnforceAvif, false))
+	s.Require().True(o.GetBool(keys.PreferAvif, false))
+	s.Require().True(o.GetBool(keys.EnforceAvif, false))
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePathJxlDetection() {
@@ -545,12 +562,12 @@ func (s *ProcessingOptionsTestSuite) TestParsePathJxlDetection() {
 
 	path := "/plain/http://images.dev/lorem/ipsum.jpg"
 	headers := http.Header{"Accept": []string{"image/jxl"}}
-	po, _, err := s.parser().ParsePath(path, headers)
+	o, _, err := s.parser().ParsePath(path, headers)
 
 	s.Require().NoError(err)
 
-	s.Require().True(po.GetBool(keys.PreferJxl, false))
-	s.Require().False(po.GetBool(keys.EnforceJxl, false))
+	s.Require().True(o.GetBool(keys.PreferJxl, false))
+	s.Require().False(o.GetBool(keys.EnforceJxl, false))
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePathJxlEnforce() {
@@ -558,12 +575,12 @@ func (s *ProcessingOptionsTestSuite) TestParsePathJxlEnforce() {
 
 	path := "/plain/http://images.dev/lorem/ipsum.jpg@png"
 	headers := http.Header{"Accept": []string{"image/jxl"}}
-	po, _, err := s.parser().ParsePath(path, headers)
+	o, _, err := s.parser().ParsePath(path, headers)
 
 	s.Require().NoError(err)
 
-	s.Require().True(po.GetBool(keys.PreferJxl, false))
-	s.Require().True(po.GetBool(keys.EnforceJxl, false))
+	s.Require().True(o.GetBool(keys.PreferJxl, false))
+	s.Require().True(o.GetBool(keys.EnforceJxl, false))
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePathWidthHeader() {
@@ -571,21 +588,21 @@ func (s *ProcessingOptionsTestSuite) TestParsePathWidthHeader() {
 
 	path := "/plain/http://images.dev/lorem/ipsum.jpg@png"
 	headers := http.Header{"Width": []string{"100"}}
-	po, _, err := s.parser().ParsePath(path, headers)
+	o, _, err := s.parser().ParsePath(path, headers)
 
 	s.Require().NoError(err)
 
-	s.Require().Equal(100, po.GetInt(keys.Width, 0))
+	s.Require().Equal(100, o.GetInt(keys.Width, 0))
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePathWidthHeaderDisabled() {
 	path := "/plain/http://images.dev/lorem/ipsum.jpg@png"
 	headers := http.Header{"Width": []string{"100"}}
-	po, _, err := s.parser().ParsePath(path, headers)
+	o, _, err := s.parser().ParsePath(path, headers)
 
 	s.Require().NoError(err)
 
-	s.Require().Equal(0, po.GetInt(keys.Width, 0))
+	s.Require().Equal(0, o.GetInt(keys.Width, 0))
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePathWidthHeaderRedefine() {
@@ -593,11 +610,11 @@ func (s *ProcessingOptionsTestSuite) TestParsePathWidthHeaderRedefine() {
 
 	path := "/width:150/plain/http://images.dev/lorem/ipsum.jpg@png"
 	headers := http.Header{"Width": []string{"100"}}
-	po, _, err := s.parser().ParsePath(path, headers)
+	o, _, err := s.parser().ParsePath(path, headers)
 
 	s.Require().NoError(err)
 
-	s.Require().Equal(150, po.GetInt(keys.Width, 0))
+	s.Require().Equal(150, o.GetInt(keys.Width, 0))
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePathDprHeader() {
@@ -605,21 +622,21 @@ func (s *ProcessingOptionsTestSuite) TestParsePathDprHeader() {
 
 	path := "/plain/http://images.dev/lorem/ipsum.jpg@png"
 	headers := http.Header{"Dpr": []string{"2"}}
-	po, _, err := s.parser().ParsePath(path, headers)
+	o, _, err := s.parser().ParsePath(path, headers)
 
 	s.Require().NoError(err)
 
-	s.Require().InDelta(2.0, po.GetFloat(keys.Dpr, 1.0), 0.0001)
+	s.Require().InDelta(2.0, o.GetFloat(keys.Dpr, 1.0), 0.0001)
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePathDprHeaderDisabled() {
 	path := "/plain/http://images.dev/lorem/ipsum.jpg@png"
 	headers := http.Header{"Dpr": []string{"2"}}
-	po, _, err := s.parser().ParsePath(path, headers)
+	o, _, err := s.parser().ParsePath(path, headers)
 
 	s.Require().NoError(err)
 
-	s.Require().InDelta(1.0, po.GetFloat(keys.Dpr, 1.0), 0.0001)
+	s.Require().InDelta(1.0, o.GetFloat(keys.Dpr, 1.0), 0.0001)
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePathWidthAndDprHeaderCombined() {
@@ -630,24 +647,24 @@ func (s *ProcessingOptionsTestSuite) TestParsePathWidthAndDprHeaderCombined() {
 		"Width": []string{"100"},
 		"Dpr":   []string{"2"},
 	}
-	po, _, err := s.parser().ParsePath(path, headers)
+	o, _, err := s.parser().ParsePath(path, headers)
 
 	s.Require().NoError(err)
 
-	s.Require().Equal(50, po.GetInt(keys.Width, 0))
-	s.Require().InDelta(2.0, po.GetFloat(keys.Dpr, 1.0), 0.0001)
+	s.Require().Equal(50, o.GetInt(keys.Width, 0))
+	s.Require().InDelta(2.0, o.GetFloat(keys.Dpr, 1.0), 0.0001)
 }
 
 func (s *ProcessingOptionsTestSuite) TestParseSkipProcessing() {
 	path := "/skp:jpg:png/plain/http://images.dev/lorem/ipsum.jpg"
 
-	po, _, err := s.parser().ParsePath(path, make(http.Header))
+	o, _, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 
 	s.Require().ElementsMatch(
 		[]imagetype.Type{imagetype.JPEG, imagetype.PNG},
-		Get(po, keys.SkipProcessing, []imagetype.Type(nil)),
+		options.Get(o, keys.SkipProcessing, []imagetype.Type(nil)),
 	)
 }
 
@@ -662,10 +679,10 @@ func (s *ProcessingOptionsTestSuite) TestParseSkipProcessingInvalid() {
 
 func (s *ProcessingOptionsTestSuite) TestParseExpires() {
 	path := "/exp:32503669200/plain/http://images.dev/lorem/ipsum.jpg"
-	po, _, err := s.parser().ParsePath(path, make(http.Header))
+	o, _, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
-	s.Require().Equal(time.Unix(32503669200, 0), po.GetTime(keys.Expires))
+	s.Require().Equal(time.Unix(32503669200, 0), o.GetTime(keys.Expires))
 }
 
 func (s *ProcessingOptionsTestSuite) TestParseExpiresExpired() {
@@ -685,13 +702,13 @@ func (s *ProcessingOptionsTestSuite) TestParsePathOnlyPresets() {
 	originURL := "http://images.dev/lorem/ipsum.jpg"
 	path := "/test1:test2/plain/" + originURL + "@png"
 
-	po, imageURL, err := s.parser().ParsePath(path, make(http.Header))
+	o, imageURL, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 
-	s.Require().InDelta(0.2, po.GetFloat(keys.Blur, 0.0), 0.0001)
-	s.Require().Equal(50, po.GetInt(keys.Quality, 0))
-	s.Require().Equal(imagetype.PNG, Get(po, keys.Format, imagetype.Unknown))
+	s.Require().InDelta(0.2, o.GetFloat(keys.Blur, 0.0), 0.0001)
+	s.Require().Equal(50, o.GetInt(keys.Quality, 0))
+	s.Require().Equal(imagetype.PNG, options.Get(o, keys.Format, imagetype.Unknown))
 	s.Require().Equal(originURL, imageURL)
 }
 
@@ -705,13 +722,13 @@ func (s *ProcessingOptionsTestSuite) TestParseBase64URLOnlyPresets() {
 	originURL := "http://images.dev/lorem/ipsum.jpg?param=value"
 	path := fmt.Sprintf("/test1:test2/%s.png", base64.RawURLEncoding.EncodeToString([]byte(originURL)))
 
-	po, imageURL, err := s.parser().ParsePath(path, make(http.Header))
+	o, imageURL, err := s.parser().ParsePath(path, make(http.Header))
 
 	s.Require().NoError(err)
 
-	s.Require().InDelta(0.2, po.GetFloat(keys.Blur, 0.0), 0.0001)
-	s.Require().Equal(50, po.GetInt(keys.Quality, 0))
-	s.Require().Equal(imagetype.PNG, Get(po, keys.Format, imagetype.Unknown))
+	s.Require().InDelta(0.2, o.GetFloat(keys.Blur, 0.0), 0.0001)
+	s.Require().Equal(50, o.GetInt(keys.Quality, 0))
+	s.Require().Equal(imagetype.PNG, options.Get(o, keys.Format, imagetype.Unknown))
 	s.Require().Equal(originURL, imageURL)
 }
 

+ 1 - 1
options/url.go → options/parser/url.go

@@ -1,4 +1,4 @@
-package options
+package optionsparser
 
 import (
 	"encoding/base64"

+ 1 - 1
options/url_options.go → options/parser/url_options.go

@@ -1,4 +1,4 @@
-package options
+package optionsparser
 
 import (
 	"strings"

+ 5 - 6
processing/calc_position.go

@@ -4,11 +4,10 @@ import (
 	"math"
 
 	"github.com/imgproxy/imgproxy/v3/imath"
-	"github.com/imgproxy/imgproxy/v3/options"
 )
 
 func calcPosition(width, height, innerWidth, innerHeight int, gravity *GravityOptions, dpr float64, allowOverflow bool) (left, top int) {
-	if gravity.Type == options.GravityFocusPoint {
+	if gravity.Type == GravityFocusPoint {
 		pointX := imath.ScaleToEven(width, gravity.X)
 		pointY := imath.ScaleToEven(height, gravity.Y)
 
@@ -32,19 +31,19 @@ func calcPosition(width, height, innerWidth, innerHeight int, gravity *GravityOp
 		left = imath.ShrinkToEven(width-innerWidth+1, 2) + offX
 		top = imath.ShrinkToEven(height-innerHeight+1, 2) + offY
 
-		if gravity.Type == options.GravityNorth || gravity.Type == options.GravityNorthEast || gravity.Type == options.GravityNorthWest {
+		if gravity.Type == GravityNorth || gravity.Type == GravityNorthEast || gravity.Type == GravityNorthWest {
 			top = 0 + offY
 		}
 
-		if gravity.Type == options.GravityEast || gravity.Type == options.GravityNorthEast || gravity.Type == options.GravitySouthEast {
+		if gravity.Type == GravityEast || gravity.Type == GravityNorthEast || gravity.Type == GravitySouthEast {
 			left = width - innerWidth - offX
 		}
 
-		if gravity.Type == options.GravitySouth || gravity.Type == options.GravitySouthEast || gravity.Type == options.GravitySouthWest {
+		if gravity.Type == GravitySouth || gravity.Type == GravitySouthEast || gravity.Type == GravitySouthWest {
 			top = height - innerHeight - offY
 		}
 
-		if gravity.Type == options.GravityWest || gravity.Type == options.GravityNorthWest || gravity.Type == options.GravitySouthWest {
+		if gravity.Type == GravityWest || gravity.Type == GravityNorthWest || gravity.Type == GravitySouthWest {
 			left = 0 + offX
 		}
 	}

+ 1 - 2
processing/crop.go

@@ -2,7 +2,6 @@ package processing
 
 import (
 	"github.com/imgproxy/imgproxy/v3/imath"
-	"github.com/imgproxy/imgproxy/v3/options"
 	"github.com/imgproxy/imgproxy/v3/vips"
 )
 
@@ -20,7 +19,7 @@ func cropImage(img *vips.Image, cropWidth, cropHeight int, gravity *GravityOptio
 		return nil
 	}
 
-	if gravity.Type == options.GravitySmart {
+	if gravity.Type == GravitySmart {
 		if err := img.CopyMemory(); err != nil {
 			return err
 		}

+ 44 - 44
processing/gravity.go

@@ -5,50 +5,50 @@ import (
 	"github.com/imgproxy/imgproxy/v3/options/keys"
 )
 
-var gravityTypesRotationMap = map[int]map[options.GravityType]options.GravityType{
+var gravityTypesRotationMap = map[int]map[GravityType]GravityType{
 	90: {
-		options.GravityNorth:     options.GravityWest,
-		options.GravityEast:      options.GravityNorth,
-		options.GravitySouth:     options.GravityEast,
-		options.GravityWest:      options.GravitySouth,
-		options.GravityNorthWest: options.GravitySouthWest,
-		options.GravityNorthEast: options.GravityNorthWest,
-		options.GravitySouthWest: options.GravitySouthEast,
-		options.GravitySouthEast: options.GravityNorthEast,
+		GravityNorth:     GravityWest,
+		GravityEast:      GravityNorth,
+		GravitySouth:     GravityEast,
+		GravityWest:      GravitySouth,
+		GravityNorthWest: GravitySouthWest,
+		GravityNorthEast: GravityNorthWest,
+		GravitySouthWest: GravitySouthEast,
+		GravitySouthEast: GravityNorthEast,
 	},
 	180: {
-		options.GravityNorth:     options.GravitySouth,
-		options.GravityEast:      options.GravityWest,
-		options.GravitySouth:     options.GravityNorth,
-		options.GravityWest:      options.GravityEast,
-		options.GravityNorthWest: options.GravitySouthEast,
-		options.GravityNorthEast: options.GravitySouthWest,
-		options.GravitySouthWest: options.GravityNorthEast,
-		options.GravitySouthEast: options.GravityNorthWest,
+		GravityNorth:     GravitySouth,
+		GravityEast:      GravityWest,
+		GravitySouth:     GravityNorth,
+		GravityWest:      GravityEast,
+		GravityNorthWest: GravitySouthEast,
+		GravityNorthEast: GravitySouthWest,
+		GravitySouthWest: GravityNorthEast,
+		GravitySouthEast: GravityNorthWest,
 	},
 	270: {
-		options.GravityNorth:     options.GravityEast,
-		options.GravityEast:      options.GravitySouth,
-		options.GravitySouth:     options.GravityWest,
-		options.GravityWest:      options.GravityNorth,
-		options.GravityNorthWest: options.GravityNorthEast,
-		options.GravityNorthEast: options.GravitySouthEast,
-		options.GravitySouthWest: options.GravityNorthWest,
-		options.GravitySouthEast: options.GravitySouthWest,
+		GravityNorth:     GravityEast,
+		GravityEast:      GravitySouth,
+		GravitySouth:     GravityWest,
+		GravityWest:      GravityNorth,
+		GravityNorthWest: GravityNorthEast,
+		GravityNorthEast: GravitySouthEast,
+		GravitySouthWest: GravityNorthWest,
+		GravitySouthEast: GravitySouthWest,
 	},
 }
 
-var gravityTypesFlipMap = map[options.GravityType]options.GravityType{
-	options.GravityEast:      options.GravityWest,
-	options.GravityWest:      options.GravityEast,
-	options.GravityNorthWest: options.GravityNorthEast,
-	options.GravityNorthEast: options.GravityNorthWest,
-	options.GravitySouthWest: options.GravitySouthEast,
-	options.GravitySouthEast: options.GravitySouthWest,
+var gravityTypesFlipMap = map[GravityType]GravityType{
+	GravityEast:      GravityWest,
+	GravityWest:      GravityEast,
+	GravityNorthWest: GravityNorthEast,
+	GravityNorthEast: GravityNorthWest,
+	GravitySouthWest: GravitySouthEast,
+	GravitySouthEast: GravitySouthWest,
 }
 
 type GravityOptions struct {
-	Type options.GravityType
+	Type GravityType
 	X, Y float64
 }
 
@@ -56,7 +56,7 @@ type GravityOptions struct {
 // It fills the [GravityOptions] struct with the options values under the given prefix.
 // If the gravity type is not set in the options,
 // it returns a [GravityOptions] with the provided default type.
-func NewGravityOptions(o *options.Options, prefix string, defType options.GravityType) GravityOptions {
+func NewGravityOptions(o *options.Options, prefix string, defType GravityType) GravityOptions {
 	gr := GravityOptions{
 		Type: options.Get(o, prefix+keys.SuffixType, defType),
 		X:    o.GetFloat(prefix+keys.SuffixXOffset, 0.0),
@@ -75,9 +75,9 @@ func (g *GravityOptions) RotateAndFlip(angle int, flip bool) {
 		}
 
 		switch g.Type {
-		case options.GravityCenter, options.GravityNorth, options.GravitySouth:
+		case GravityCenter, GravityNorth, GravitySouth:
 			g.X = -g.X
-		case options.GravityFocusPoint:
+		case GravityFocusPoint:
 			g.X = 1.0 - g.X
 		}
 	}
@@ -91,29 +91,29 @@ func (g *GravityOptions) RotateAndFlip(angle int, flip bool) {
 			switch angle {
 			case 90:
 				switch g.Type {
-				case options.GravityCenter, options.GravityEast, options.GravityWest:
+				case GravityCenter, GravityEast, GravityWest:
 					g.X, g.Y = g.Y, -g.X
-				case options.GravityFocusPoint:
+				case GravityFocusPoint:
 					g.X, g.Y = g.Y, 1.0-g.X
 				default:
 					g.X, g.Y = g.Y, g.X
 				}
 			case 180:
 				switch g.Type {
-				case options.GravityCenter:
+				case GravityCenter:
 					g.X, g.Y = -g.X, -g.Y
-				case options.GravityNorth, options.GravitySouth:
+				case GravityNorth, GravitySouth:
 					g.X = -g.X
-				case options.GravityEast, options.GravityWest:
+				case GravityEast, GravityWest:
 					g.Y = -g.Y
-				case options.GravityFocusPoint:
+				case GravityFocusPoint:
 					g.X, g.Y = 1.0-g.X, 1.0-g.Y
 				}
 			case 270:
 				switch g.Type {
-				case options.GravityCenter, options.GravityNorth, options.GravitySouth:
+				case GravityCenter, GravityNorth, GravitySouth:
 					g.X, g.Y = -g.Y, g.X
-				case options.GravityFocusPoint:
+				case GravityFocusPoint:
 					g.X, g.Y = 1.0-g.Y, g.X
 				default:
 					g.X, g.Y = g.Y, g.X

+ 7 - 7
options/gravity_type.go → processing/gravity_type.go

@@ -1,11 +1,11 @@
-package options
+package processing
 
 import "fmt"
 
 type GravityType int
 
 func (gt GravityType) String() string {
-	for k, v := range gravityTypes {
+	for k, v := range GravityTypes {
 		if v == gt {
 			return k
 		}
@@ -14,7 +14,7 @@ func (gt GravityType) String() string {
 }
 
 func (gt GravityType) MarshalJSON() ([]byte, error) {
-	for k, v := range gravityTypes {
+	for k, v := range GravityTypes {
 		if v == gt {
 			return []byte(fmt.Sprintf("%q", k)), nil
 		}
@@ -40,7 +40,7 @@ const (
 	GravityReplicate
 )
 
-var gravityTypes = map[string]GravityType{
+var GravityTypes = map[string]GravityType{
 	"ce":   GravityCenter,
 	"no":   GravityNorth,
 	"ea":   GravityEast,
@@ -67,7 +67,7 @@ var commonGravityTypes = []GravityType{
 	GravitySouthEast,
 }
 
-var cropGravityTypes = append(
+var CropGravityTypes = append(
 	[]GravityType{
 		GravitySmart,
 		GravityFocusPoint,
@@ -75,14 +75,14 @@ var cropGravityTypes = append(
 	commonGravityTypes...,
 )
 
-var extendGravityTypes = append(
+var ExtendGravityTypes = append(
 	[]GravityType{
 		GravityFocusPoint,
 	},
 	commonGravityTypes...,
 )
 
-var watermarkGravityTypes = append(
+var WatermarkGravityTypes = append(
 	[]GravityType{
 		GravityReplicate,
 	},

+ 8 - 8
processing/options.go

@@ -41,8 +41,8 @@ func (po ProcessingOptions) MinHeight() int {
 	return po.GetInt(keys.MinHeight, 0)
 }
 
-func (po ProcessingOptions) ResizingType() options.ResizeType {
-	return options.Get(po.Options, keys.ResizingType, options.ResizeFit)
+func (po ProcessingOptions) ResizingType() ResizeType {
+	return options.Get(po.Options, keys.ResizingType, ResizeFit)
 }
 
 func (po ProcessingOptions) ZoomWidth() float64 {
@@ -66,7 +66,7 @@ func (po ProcessingOptions) Enlarge() bool {
 }
 
 func (po ProcessingOptions) Gravity() GravityOptions {
-	return NewGravityOptions(po.Options, keys.Gravity, options.GravityCenter)
+	return NewGravityOptions(po.Options, keys.Gravity, GravityCenter)
 }
 
 func (po ProcessingOptions) ExtendEnabled() bool {
@@ -74,7 +74,7 @@ func (po ProcessingOptions) ExtendEnabled() bool {
 }
 
 func (po ProcessingOptions) ExtendGravity() GravityOptions {
-	return NewGravityOptions(po.Options, keys.ExtendGravity, options.GravityCenter)
+	return NewGravityOptions(po.Options, keys.ExtendGravity, GravityCenter)
 }
 
 func (po ProcessingOptions) ExtendAspectRatioEnabled() bool {
@@ -82,7 +82,7 @@ func (po ProcessingOptions) ExtendAspectRatioEnabled() bool {
 }
 
 func (po ProcessingOptions) ExtendAspectRatioGravity() GravityOptions {
-	return NewGravityOptions(po.Options, keys.ExtendAspectRatioGravity, options.GravityCenter)
+	return NewGravityOptions(po.Options, keys.ExtendAspectRatioGravity, GravityCenter)
 }
 
 func (po ProcessingOptions) Rotate() int {
@@ -102,7 +102,7 @@ func (po ProcessingOptions) CropHeight() float64 {
 }
 
 func (po ProcessingOptions) CropGravity() GravityOptions {
-	return NewGravityOptions(po.Options, keys.CropGravity, options.GravityUnknown)
+	return NewGravityOptions(po.Options, keys.CropGravity, GravityUnknown)
 }
 
 func (po ProcessingOptions) Format() imagetype.Type {
@@ -225,8 +225,8 @@ func (po ProcessingOptions) DeleteWatermarkOpacity() {
 	po.Delete(keys.WatermarkOpacity)
 }
 
-func (po ProcessingOptions) WatermarkPosition() options.GravityType {
-	return options.Get(po.Options, keys.WatermarkPosition, options.GravityCenter)
+func (po ProcessingOptions) WatermarkPosition() GravityType {
+	return options.Get(po.Options, keys.WatermarkPosition, GravityCenter)
 }
 
 func (po ProcessingOptions) WatermarkXOffset() float64 {

+ 1 - 2
processing/pipeline.go

@@ -4,7 +4,6 @@ import (
 	"context"
 
 	"github.com/imgproxy/imgproxy/v3/imagedata"
-	"github.com/imgproxy/imgproxy/v3/options"
 	"github.com/imgproxy/imgproxy/v3/security"
 	"github.com/imgproxy/imgproxy/v3/server"
 	"github.com/imgproxy/imgproxy/v3/vips"
@@ -126,7 +125,7 @@ func (p Pipeline) newContext(
 	}
 
 	// If crop gravity is not set, use the general gravity option
-	if pctx.CropGravity.Type == options.GravityUnknown {
+	if pctx.CropGravity.Type == GravityUnknown {
 		pctx.CropGravity = po.Gravity()
 	}
 

+ 8 - 9
processing/prepare.go

@@ -4,7 +4,6 @@ import (
 	"math"
 
 	"github.com/imgproxy/imgproxy/v3/imath"
-	"github.com/imgproxy/imgproxy/v3/options"
 	"github.com/imgproxy/imgproxy/v3/vips"
 )
 
@@ -86,26 +85,26 @@ func (c *Context) calcScale(width, height int, po ProcessingOptions) {
 	if wshrink != 1 || hshrink != 1 {
 		rt := po.ResizingType()
 
-		if rt == options.ResizeAuto {
+		if rt == ResizeAuto {
 			srcD := srcW - srcH
 			dstD := dstW - dstH
 
 			if (srcD >= 0 && dstD >= 0) || (srcD < 0 && dstD < 0) {
-				rt = options.ResizeFill
+				rt = ResizeFill
 			} else {
-				rt = options.ResizeFit
+				rt = ResizeFit
 			}
 		}
 
 		switch {
-		case poWidth == 0 && rt != options.ResizeForce:
+		case poWidth == 0 && rt != ResizeForce:
 			wshrink = hshrink
-		case poHeight == 0 && rt != options.ResizeForce:
+		case poHeight == 0 && rt != ResizeForce:
 			hshrink = wshrink
-		case rt == options.ResizeFit:
+		case rt == ResizeFit:
 			wshrink = math.Max(wshrink, hshrink)
 			hshrink = wshrink
-		case rt == options.ResizeFill || rt == options.ResizeFillDown:
+		case rt == ResizeFill || rt == ResizeFillDown:
 			wshrink = math.Min(wshrink, hshrink)
 			hshrink = wshrink
 		}
@@ -180,7 +179,7 @@ func (c *Context) calcSizes(widthToScale, heightToScale int, po ProcessingOption
 	c.ScaledWidth = imath.Scale(widthToScale, c.WScale)
 	c.ScaledHeight = imath.Scale(heightToScale, c.HScale)
 
-	if po.ResizingType() == options.ResizeFillDown && !po.Enlarge() {
+	if po.ResizingType() == ResizeFillDown && !po.Enlarge() {
 		diffW := float64(c.TargetWidth) / float64(c.ScaledWidth)
 		diffH := float64(c.TargetHeight) / float64(c.ScaledHeight)
 

+ 133 - 133
processing/processing_test.go

@@ -92,12 +92,12 @@ func (s *ProcessingTestSuite) checkSize(r *Result, width, height int) {
 
 func (s *ProcessingTestSuite) processImageAndCheck(
 	imgdata imagedata.ImageData,
-	po *options.Options,
+	o *options.Options,
 	expectedWidth, expectedHeight int,
 ) {
-	secops := s.security().NewOptions(po)
+	secops := s.security().NewOptions(o)
 
-	result, err := s.processor().ProcessImage(s.T().Context(), imgdata, po, secops)
+	result, err := s.processor().ProcessImage(s.T().Context(), imgdata, o, secops)
 	s.Require().NoError(err)
 	s.Require().NotNil(result)
 
@@ -107,8 +107,8 @@ func (s *ProcessingTestSuite) processImageAndCheck(
 func (s *ProcessingTestSuite) TestResizeToFit() {
 	imgdata := s.openFile("test2.jpg")
 
-	po := options.New()
-	po.Set(keys.ResizingType, options.ResizeFit)
+	o := options.New()
+	o.Set(keys.ResizingType, ResizeFit)
 
 	testCases := []struct {
 		width     int
@@ -130,10 +130,10 @@ func (s *ProcessingTestSuite) TestResizeToFit() {
 
 	for _, tc := range testCases {
 		s.Run(fmt.Sprintf("%dx%d", tc.width, tc.height), func() {
-			po.Set(keys.Width, tc.width)
-			po.Set(keys.Height, tc.height)
+			o.Set(keys.Width, tc.width)
+			o.Set(keys.Height, tc.height)
 
-			s.processImageAndCheck(imgdata, po, tc.outWidth, tc.outHeight)
+			s.processImageAndCheck(imgdata, o, tc.outWidth, tc.outHeight)
 		})
 	}
 }
@@ -141,9 +141,9 @@ func (s *ProcessingTestSuite) TestResizeToFit() {
 func (s *ProcessingTestSuite) TestResizeToFitEnlarge() {
 	imgdata := s.openFile("test2.jpg")
 
-	po := options.New()
-	po.Set(keys.ResizingType, options.ResizeFit)
-	po.Set(keys.Enlarge, true)
+	o := options.New()
+	o.Set(keys.ResizingType, ResizeFit)
+	o.Set(keys.Enlarge, true)
 
 	testCases := []struct {
 		width     int
@@ -165,10 +165,10 @@ func (s *ProcessingTestSuite) TestResizeToFitEnlarge() {
 
 	for _, tc := range testCases {
 		s.Run(fmt.Sprintf("%dx%d", tc.width, tc.height), func() {
-			po.Set(keys.Width, tc.width)
-			po.Set(keys.Height, tc.height)
+			o.Set(keys.Width, tc.width)
+			o.Set(keys.Height, tc.height)
 
-			s.processImageAndCheck(imgdata, po, tc.outWidth, tc.outHeight)
+			s.processImageAndCheck(imgdata, o, tc.outWidth, tc.outHeight)
 		})
 	}
 }
@@ -176,9 +176,9 @@ func (s *ProcessingTestSuite) TestResizeToFitEnlarge() {
 func (s *ProcessingTestSuite) TestResizeToFitExtend() {
 	imgdata := s.openFile("test2.jpg")
 
-	po := options.New()
-	po.Set(keys.ResizingType, options.ResizeFit)
-	po.Set(keys.ExtendEnabled, true)
+	o := options.New()
+	o.Set(keys.ResizingType, ResizeFit)
+	o.Set(keys.ExtendEnabled, true)
 
 	testCases := []struct {
 		width     int
@@ -200,10 +200,10 @@ func (s *ProcessingTestSuite) TestResizeToFitExtend() {
 
 	for _, tc := range testCases {
 		s.Run(fmt.Sprintf("%dx%d", tc.width, tc.height), func() {
-			po.Set(keys.Width, tc.width)
-			po.Set(keys.Height, tc.height)
+			o.Set(keys.Width, tc.width)
+			o.Set(keys.Height, tc.height)
 
-			s.processImageAndCheck(imgdata, po, tc.outWidth, tc.outHeight)
+			s.processImageAndCheck(imgdata, o, tc.outWidth, tc.outHeight)
 		})
 	}
 }
@@ -211,9 +211,9 @@ func (s *ProcessingTestSuite) TestResizeToFitExtend() {
 func (s *ProcessingTestSuite) TestResizeToFitExtendAR() {
 	imgdata := s.openFile("test2.jpg")
 
-	po := options.New()
-	po.Set(keys.ResizingType, options.ResizeFit)
-	po.Set(keys.ExtendAspectRatioEnabled, true)
+	o := options.New()
+	o.Set(keys.ResizingType, ResizeFit)
+	o.Set(keys.ExtendAspectRatioEnabled, true)
 
 	testCases := []struct {
 		width     int
@@ -235,10 +235,10 @@ func (s *ProcessingTestSuite) TestResizeToFitExtendAR() {
 
 	for _, tc := range testCases {
 		s.Run(fmt.Sprintf("%dx%d", tc.width, tc.height), func() {
-			po.Set(keys.Width, tc.width)
-			po.Set(keys.Height, tc.height)
+			o.Set(keys.Width, tc.width)
+			o.Set(keys.Height, tc.height)
 
-			s.processImageAndCheck(imgdata, po, tc.outWidth, tc.outHeight)
+			s.processImageAndCheck(imgdata, o, tc.outWidth, tc.outHeight)
 		})
 	}
 }
@@ -246,8 +246,8 @@ func (s *ProcessingTestSuite) TestResizeToFitExtendAR() {
 func (s *ProcessingTestSuite) TestResizeToFill() {
 	imgdata := s.openFile("test2.jpg")
 
-	po := options.New()
-	po.Set(keys.ResizingType, options.ResizeFill)
+	o := options.New()
+	o.Set(keys.ResizingType, ResizeFill)
 
 	testCases := []struct {
 		width     int
@@ -269,10 +269,10 @@ func (s *ProcessingTestSuite) TestResizeToFill() {
 
 	for _, tc := range testCases {
 		s.Run(fmt.Sprintf("%dx%d", tc.width, tc.height), func() {
-			po.Set(keys.Width, tc.width)
-			po.Set(keys.Height, tc.height)
+			o.Set(keys.Width, tc.width)
+			o.Set(keys.Height, tc.height)
 
-			s.processImageAndCheck(imgdata, po, tc.outWidth, tc.outHeight)
+			s.processImageAndCheck(imgdata, o, tc.outWidth, tc.outHeight)
 		})
 	}
 }
@@ -280,9 +280,9 @@ func (s *ProcessingTestSuite) TestResizeToFill() {
 func (s *ProcessingTestSuite) TestResizeToFillEnlarge() {
 	imgdata := s.openFile("test2.jpg")
 
-	po := options.New()
-	po.Set(keys.ResizingType, options.ResizeFill)
-	po.Set(keys.Enlarge, true)
+	o := options.New()
+	o.Set(keys.ResizingType, ResizeFill)
+	o.Set(keys.Enlarge, true)
 
 	testCases := []struct {
 		width     int
@@ -304,10 +304,10 @@ func (s *ProcessingTestSuite) TestResizeToFillEnlarge() {
 
 	for _, tc := range testCases {
 		s.Run(fmt.Sprintf("%dx%d", tc.width, tc.height), func() {
-			po.Set(keys.Width, tc.width)
-			po.Set(keys.Height, tc.height)
+			o.Set(keys.Width, tc.width)
+			o.Set(keys.Height, tc.height)
 
-			s.processImageAndCheck(imgdata, po, tc.outWidth, tc.outHeight)
+			s.processImageAndCheck(imgdata, o, tc.outWidth, tc.outHeight)
 		})
 	}
 }
@@ -315,9 +315,9 @@ func (s *ProcessingTestSuite) TestResizeToFillEnlarge() {
 func (s *ProcessingTestSuite) TestResizeToFillExtend() {
 	imgdata := s.openFile("test2.jpg")
 
-	po := options.New()
-	po.Set(keys.ResizingType, options.ResizeFill)
-	po.Set(keys.ExtendEnabled, true)
+	o := options.New()
+	o.Set(keys.ResizingType, ResizeFill)
+	o.Set(keys.ExtendEnabled, true)
 
 	testCases := []struct {
 		width     int
@@ -341,10 +341,10 @@ func (s *ProcessingTestSuite) TestResizeToFillExtend() {
 
 	for _, tc := range testCases {
 		s.Run(fmt.Sprintf("%dx%d", tc.width, tc.height), func() {
-			po.Set(keys.Width, tc.width)
-			po.Set(keys.Height, tc.height)
+			o.Set(keys.Width, tc.width)
+			o.Set(keys.Height, tc.height)
 
-			s.processImageAndCheck(imgdata, po, tc.outWidth, tc.outHeight)
+			s.processImageAndCheck(imgdata, o, tc.outWidth, tc.outHeight)
 		})
 	}
 }
@@ -352,10 +352,10 @@ func (s *ProcessingTestSuite) TestResizeToFillExtend() {
 func (s *ProcessingTestSuite) TestResizeToFillExtendAR() {
 	imgdata := s.openFile("test2.jpg")
 
-	po := options.New()
-	po.Set(keys.ResizingType, options.ResizeFill)
-	po.Set(keys.ExtendAspectRatioEnabled, true)
-	po.Set(keys.ExtendAspectRatioGravityType, options.GravityCenter)
+	o := options.New()
+	o.Set(keys.ResizingType, ResizeFill)
+	o.Set(keys.ExtendAspectRatioEnabled, true)
+	o.Set(keys.ExtendAspectRatioGravityType, GravityCenter)
 
 	testCases := []struct {
 		width     int
@@ -379,10 +379,10 @@ func (s *ProcessingTestSuite) TestResizeToFillExtendAR() {
 
 	for _, tc := range testCases {
 		s.Run(fmt.Sprintf("%dx%d", tc.width, tc.height), func() {
-			po.Set(keys.Width, tc.width)
-			po.Set(keys.Height, tc.height)
+			o.Set(keys.Width, tc.width)
+			o.Set(keys.Height, tc.height)
 
-			s.processImageAndCheck(imgdata, po, tc.outWidth, tc.outHeight)
+			s.processImageAndCheck(imgdata, o, tc.outWidth, tc.outHeight)
 		})
 	}
 }
@@ -390,8 +390,8 @@ func (s *ProcessingTestSuite) TestResizeToFillExtendAR() {
 func (s *ProcessingTestSuite) TestResizeToFillDown() {
 	imgdata := s.openFile("test2.jpg")
 
-	po := options.New()
-	po.Set(keys.ResizingType, options.ResizeFillDown)
+	o := options.New()
+	o.Set(keys.ResizingType, ResizeFillDown)
 
 	testCases := []struct {
 		width     int
@@ -413,10 +413,10 @@ func (s *ProcessingTestSuite) TestResizeToFillDown() {
 
 	for _, tc := range testCases {
 		s.Run(fmt.Sprintf("%dx%d", tc.width, tc.height), func() {
-			po.Set(keys.Width, tc.width)
-			po.Set(keys.Height, tc.height)
+			o.Set(keys.Width, tc.width)
+			o.Set(keys.Height, tc.height)
 
-			s.processImageAndCheck(imgdata, po, tc.outWidth, tc.outHeight)
+			s.processImageAndCheck(imgdata, o, tc.outWidth, tc.outHeight)
 		})
 	}
 }
@@ -424,9 +424,9 @@ func (s *ProcessingTestSuite) TestResizeToFillDown() {
 func (s *ProcessingTestSuite) TestResizeToFillDownEnlarge() {
 	imgdata := s.openFile("test2.jpg")
 
-	po := options.New()
-	po.Set(keys.ResizingType, options.ResizeFillDown)
-	po.Set(keys.Enlarge, true)
+	o := options.New()
+	o.Set(keys.ResizingType, ResizeFillDown)
+	o.Set(keys.Enlarge, true)
 
 	testCases := []struct {
 		width     int
@@ -448,10 +448,10 @@ func (s *ProcessingTestSuite) TestResizeToFillDownEnlarge() {
 
 	for _, tc := range testCases {
 		s.Run(fmt.Sprintf("%dx%d", tc.width, tc.height), func() {
-			po.Set(keys.Width, tc.width)
-			po.Set(keys.Height, tc.height)
+			o.Set(keys.Width, tc.width)
+			o.Set(keys.Height, tc.height)
 
-			s.processImageAndCheck(imgdata, po, tc.outWidth, tc.outHeight)
+			s.processImageAndCheck(imgdata, o, tc.outWidth, tc.outHeight)
 		})
 	}
 }
@@ -459,9 +459,9 @@ func (s *ProcessingTestSuite) TestResizeToFillDownEnlarge() {
 func (s *ProcessingTestSuite) TestResizeToFillDownExtend() {
 	imgdata := s.openFile("test2.jpg")
 
-	po := options.New()
-	po.Set(keys.ResizingType, options.ResizeFillDown)
-	po.Set(keys.ExtendEnabled, true)
+	o := options.New()
+	o.Set(keys.ResizingType, ResizeFillDown)
+	o.Set(keys.ExtendEnabled, true)
 
 	testCases := []struct {
 		width     int
@@ -485,10 +485,10 @@ func (s *ProcessingTestSuite) TestResizeToFillDownExtend() {
 
 	for _, tc := range testCases {
 		s.Run(fmt.Sprintf("%dx%d", tc.width, tc.height), func() {
-			po.Set(keys.Width, tc.width)
-			po.Set(keys.Height, tc.height)
+			o.Set(keys.Width, tc.width)
+			o.Set(keys.Height, tc.height)
 
-			s.processImageAndCheck(imgdata, po, tc.outWidth, tc.outHeight)
+			s.processImageAndCheck(imgdata, o, tc.outWidth, tc.outHeight)
 		})
 	}
 }
@@ -496,9 +496,9 @@ func (s *ProcessingTestSuite) TestResizeToFillDownExtend() {
 func (s *ProcessingTestSuite) TestResizeToFillDownExtendAR() {
 	imgdata := s.openFile("test2.jpg")
 
-	po := options.New()
-	po.Set(keys.ResizingType, options.ResizeFillDown)
-	po.Set(keys.ExtendAspectRatioEnabled, true)
+	o := options.New()
+	o.Set(keys.ResizingType, ResizeFillDown)
+	o.Set(keys.ExtendAspectRatioEnabled, true)
 
 	testCases := []struct {
 		width     int
@@ -520,10 +520,10 @@ func (s *ProcessingTestSuite) TestResizeToFillDownExtendAR() {
 
 	for _, tc := range testCases {
 		s.Run(fmt.Sprintf("%dx%d", tc.width, tc.height), func() {
-			po.Set(keys.Width, tc.width)
-			po.Set(keys.Height, tc.height)
+			o.Set(keys.Width, tc.width)
+			o.Set(keys.Height, tc.height)
 
-			s.processImageAndCheck(imgdata, po, tc.outWidth, tc.outHeight)
+			s.processImageAndCheck(imgdata, o, tc.outWidth, tc.outHeight)
 		})
 	}
 }
@@ -535,7 +535,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 		limit         int
 		width         int
 		height        int
-		resizingType  options.ResizeType
+		resizingType  ResizeType
 		enlarge       bool
 		extend        bool
 		extendAR      bool
@@ -551,7 +551,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:        1000,
 			width:        100,
 			height:       100,
-			resizingType: options.ResizeFit,
+			resizingType: ResizeFit,
 			outWidth:     100,
 			outHeight:    50,
 		},
@@ -559,7 +559,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:        50,
 			width:        100,
 			height:       100,
-			resizingType: options.ResizeFit,
+			resizingType: ResizeFit,
 			outWidth:     50,
 			outHeight:    25,
 		},
@@ -567,7 +567,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:        50,
 			width:        0,
 			height:       0,
-			resizingType: options.ResizeFit,
+			resizingType: ResizeFit,
 			outWidth:     50,
 			outHeight:    25,
 		},
@@ -575,7 +575,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:        100,
 			width:        0,
 			height:       100,
-			resizingType: options.ResizeFit,
+			resizingType: ResizeFit,
 			outWidth:     100,
 			outHeight:    50,
 		},
@@ -583,7 +583,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:        50,
 			width:        150,
 			height:       0,
-			resizingType: options.ResizeFit,
+			resizingType: ResizeFit,
 			outWidth:     50,
 			outHeight:    25,
 		},
@@ -591,7 +591,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:        100,
 			width:        1000,
 			height:       1000,
-			resizingType: options.ResizeFit,
+			resizingType: ResizeFit,
 			outWidth:     100,
 			outHeight:    50,
 		},
@@ -599,7 +599,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:        100,
 			width:        1000,
 			height:       1000,
-			resizingType: options.ResizeFit,
+			resizingType: ResizeFit,
 			enlarge:      true,
 			outWidth:     100,
 			outHeight:    50,
@@ -608,7 +608,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:        100,
 			width:        1000,
 			height:       2000,
-			resizingType: options.ResizeFit,
+			resizingType: ResizeFit,
 			extend:       true,
 			outWidth:     50,
 			outHeight:    100,
@@ -617,7 +617,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:        100,
 			width:        1000,
 			height:       2000,
-			resizingType: options.ResizeFit,
+			resizingType: ResizeFit,
 			extendAR:     true,
 			outWidth:     50,
 			outHeight:    100,
@@ -626,7 +626,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:        100,
 			width:        100,
 			height:       150,
-			resizingType: options.ResizeFit,
+			resizingType: ResizeFit,
 			rotate:       90,
 			outWidth:     50,
 			outHeight:    100,
@@ -635,7 +635,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:        100,
 			width:        0,
 			height:       0,
-			resizingType: options.ResizeFit,
+			resizingType: ResizeFit,
 			rotate:       90,
 			outWidth:     50,
 			outHeight:    100,
@@ -644,7 +644,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:         200,
 			width:         100,
 			height:        100,
-			resizingType:  options.ResizeFit,
+			resizingType:  ResizeFit,
 			paddingTop:    100,
 			paddingRight:  200,
 			paddingBottom: 300,
@@ -656,7 +656,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:        1000,
 			width:        100,
 			height:       100,
-			resizingType: options.ResizeFill,
+			resizingType: ResizeFill,
 			outWidth:     100,
 			outHeight:    100,
 		},
@@ -664,7 +664,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:        50,
 			width:        100,
 			height:       100,
-			resizingType: options.ResizeFill,
+			resizingType: ResizeFill,
 			outWidth:     50,
 			outHeight:    50,
 		},
@@ -672,7 +672,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:        50,
 			width:        1000,
 			height:       50,
-			resizingType: options.ResizeFill,
+			resizingType: ResizeFill,
 			outWidth:     50,
 			outHeight:    13,
 		},
@@ -680,7 +680,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:        50,
 			width:        100,
 			height:       1000,
-			resizingType: options.ResizeFill,
+			resizingType: ResizeFill,
 			outWidth:     50,
 			outHeight:    50,
 		},
@@ -688,7 +688,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:        50,
 			width:        0,
 			height:       0,
-			resizingType: options.ResizeFill,
+			resizingType: ResizeFill,
 			outWidth:     50,
 			outHeight:    25,
 		},
@@ -696,7 +696,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:        100,
 			width:        0,
 			height:       100,
-			resizingType: options.ResizeFill,
+			resizingType: ResizeFill,
 			outWidth:     100,
 			outHeight:    50,
 		},
@@ -704,7 +704,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:        50,
 			width:        150,
 			height:       0,
-			resizingType: options.ResizeFill,
+			resizingType: ResizeFill,
 			outWidth:     50,
 			outHeight:    25,
 		},
@@ -712,7 +712,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:        100,
 			width:        1000,
 			height:       1000,
-			resizingType: options.ResizeFill,
+			resizingType: ResizeFill,
 			outWidth:     100,
 			outHeight:    50,
 		},
@@ -720,7 +720,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:        100,
 			width:        1000,
 			height:       1000,
-			resizingType: options.ResizeFill,
+			resizingType: ResizeFill,
 			enlarge:      true,
 			outWidth:     100,
 			outHeight:    100,
@@ -729,7 +729,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:        100,
 			width:        1000,
 			height:       2000,
-			resizingType: options.ResizeFill,
+			resizingType: ResizeFill,
 			extend:       true,
 			outWidth:     50,
 			outHeight:    100,
@@ -738,7 +738,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:        100,
 			width:        1000,
 			height:       2000,
-			resizingType: options.ResizeFill,
+			resizingType: ResizeFill,
 			extendAR:     true,
 			outWidth:     50,
 			outHeight:    100,
@@ -747,7 +747,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:        100,
 			width:        100,
 			height:       150,
-			resizingType: options.ResizeFill,
+			resizingType: ResizeFill,
 			rotate:       90,
 			outWidth:     67,
 			outHeight:    100,
@@ -756,7 +756,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:        100,
 			width:        0,
 			height:       0,
-			resizingType: options.ResizeFill,
+			resizingType: ResizeFill,
 			rotate:       90,
 			outWidth:     50,
 			outHeight:    100,
@@ -765,7 +765,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:         200,
 			width:         100,
 			height:        100,
-			resizingType:  options.ResizeFill,
+			resizingType:  ResizeFill,
 			paddingTop:    100,
 			paddingRight:  200,
 			paddingBottom: 300,
@@ -777,7 +777,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:        1000,
 			width:        100,
 			height:       100,
-			resizingType: options.ResizeFillDown,
+			resizingType: ResizeFillDown,
 			outWidth:     100,
 			outHeight:    100,
 		},
@@ -785,7 +785,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:        50,
 			width:        100,
 			height:       100,
-			resizingType: options.ResizeFillDown,
+			resizingType: ResizeFillDown,
 			outWidth:     50,
 			outHeight:    50,
 		},
@@ -793,7 +793,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:        50,
 			width:        1000,
 			height:       50,
-			resizingType: options.ResizeFillDown,
+			resizingType: ResizeFillDown,
 			outWidth:     50,
 			outHeight:    3,
 		},
@@ -801,7 +801,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:        50,
 			width:        100,
 			height:       1000,
-			resizingType: options.ResizeFillDown,
+			resizingType: ResizeFillDown,
 			outWidth:     5,
 			outHeight:    50,
 		},
@@ -809,7 +809,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:        50,
 			width:        0,
 			height:       0,
-			resizingType: options.ResizeFillDown,
+			resizingType: ResizeFillDown,
 			outWidth:     50,
 			outHeight:    25,
 		},
@@ -817,7 +817,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:        100,
 			width:        0,
 			height:       100,
-			resizingType: options.ResizeFillDown,
+			resizingType: ResizeFillDown,
 			outWidth:     100,
 			outHeight:    50,
 		},
@@ -825,7 +825,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:        50,
 			width:        150,
 			height:       0,
-			resizingType: options.ResizeFillDown,
+			resizingType: ResizeFillDown,
 			outWidth:     50,
 			outHeight:    25,
 		},
@@ -833,7 +833,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:        100,
 			width:        1000,
 			height:       1000,
-			resizingType: options.ResizeFillDown,
+			resizingType: ResizeFillDown,
 			outWidth:     100,
 			outHeight:    100,
 		},
@@ -841,7 +841,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:        100,
 			width:        1000,
 			height:       1000,
-			resizingType: options.ResizeFillDown,
+			resizingType: ResizeFillDown,
 			enlarge:      true,
 			outWidth:     100,
 			outHeight:    100,
@@ -850,7 +850,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:        100,
 			width:        1000,
 			height:       2000,
-			resizingType: options.ResizeFillDown,
+			resizingType: ResizeFillDown,
 			extend:       true,
 			outWidth:     50,
 			outHeight:    100,
@@ -859,7 +859,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:        100,
 			width:        1000,
 			height:       2000,
-			resizingType: options.ResizeFillDown,
+			resizingType: ResizeFillDown,
 			extendAR:     true,
 			outWidth:     50,
 			outHeight:    100,
@@ -868,7 +868,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:        100,
 			width:        1000,
 			height:       1500,
-			resizingType: options.ResizeFillDown,
+			resizingType: ResizeFillDown,
 			rotate:       90,
 			outWidth:     67,
 			outHeight:    100,
@@ -877,7 +877,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:        100,
 			width:        0,
 			height:       0,
-			resizingType: options.ResizeFillDown,
+			resizingType: ResizeFillDown,
 			rotate:       90,
 			outWidth:     50,
 			outHeight:    100,
@@ -886,7 +886,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:         200,
 			width:         100,
 			height:        100,
-			resizingType:  options.ResizeFillDown,
+			resizingType:  ResizeFillDown,
 			paddingTop:    100,
 			paddingRight:  200,
 			paddingBottom: 300,
@@ -898,7 +898,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 			limit:         200,
 			width:         1000,
 			height:        1000,
-			resizingType:  options.ResizeFillDown,
+			resizingType:  ResizeFillDown,
 			paddingTop:    100,
 			paddingRight:  200,
 			paddingBottom: 300,
@@ -930,31 +930,31 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
 		}
 
 		s.Run(name, func() {
-			po := options.New()
-			po.Set(keys.MaxResultDimension, tc.limit)
-			po.Set(keys.Width, tc.width)
-			po.Set(keys.Height, tc.height)
-			po.Set(keys.ResizingType, tc.resizingType)
-			po.Set(keys.Enlarge, tc.enlarge)
-			po.Set(keys.ExtendEnabled, tc.extend)
-			po.Set(keys.ExtendAspectRatioEnabled, tc.extendAR)
-			po.Set(keys.Rotate, tc.rotate)
-			po.Set(keys.PaddingTop, tc.paddingTop)
-			po.Set(keys.PaddingRight, tc.paddingRight)
-			po.Set(keys.PaddingBottom, tc.paddingBottom)
-			po.Set(keys.PaddingLeft, tc.paddingLeft)
-
-			s.processImageAndCheck(imgdata, po, tc.outWidth, tc.outHeight)
+			o := options.New()
+			o.Set(keys.MaxResultDimension, tc.limit)
+			o.Set(keys.Width, tc.width)
+			o.Set(keys.Height, tc.height)
+			o.Set(keys.ResizingType, tc.resizingType)
+			o.Set(keys.Enlarge, tc.enlarge)
+			o.Set(keys.ExtendEnabled, tc.extend)
+			o.Set(keys.ExtendAspectRatioEnabled, tc.extendAR)
+			o.Set(keys.Rotate, tc.rotate)
+			o.Set(keys.PaddingTop, tc.paddingTop)
+			o.Set(keys.PaddingRight, tc.paddingRight)
+			o.Set(keys.PaddingBottom, tc.paddingBottom)
+			o.Set(keys.PaddingLeft, tc.paddingLeft)
+
+			s.processImageAndCheck(imgdata, o, tc.outWidth, tc.outHeight)
 		})
 	}
 }
 
 func (s *ProcessingTestSuite) TestImageResolutionTooLarge() {
-	po := options.New()
-	po.Set(keys.MaxSrcResolution, 1)
+	o := options.New()
+	o.Set(keys.MaxSrcResolution, 1)
 
 	imgdata := s.openFile("test2.jpg")
-	_, err := s.processor().ProcessImage(s.T().Context(), imgdata, po, s.security().NewOptions(po))
+	_, err := s.processor().ProcessImage(s.T().Context(), imgdata, o, s.security().NewOptions(o))
 
 	s.Require().Error(err)
 	s.Require().Equal(422, ierrors.Wrap(err, 0).StatusCode())

+ 4 - 4
options/resize_type.go → processing/resize_type.go

@@ -1,4 +1,4 @@
-package options
+package processing
 
 import "fmt"
 
@@ -12,7 +12,7 @@ const (
 	ResizeAuto
 )
 
-var resizeTypes = map[string]ResizeType{
+var ResizeTypes = map[string]ResizeType{
 	"fit":       ResizeFit,
 	"fill":      ResizeFill,
 	"fill-down": ResizeFillDown,
@@ -21,7 +21,7 @@ var resizeTypes = map[string]ResizeType{
 }
 
 func (rt ResizeType) String() string {
-	for k, v := range resizeTypes {
+	for k, v := range ResizeTypes {
 		if v == rt {
 			return k
 		}
@@ -30,7 +30,7 @@ func (rt ResizeType) String() string {
 }
 
 func (rt ResizeType) MarshalJSON() ([]byte, error) {
-	for k, v := range resizeTypes {
+	for k, v := range ResizeTypes {
 		if v == rt {
 			return []byte(fmt.Sprintf("%q", k)), nil
 		}

+ 1 - 2
processing/scale_on_load.go

@@ -7,7 +7,6 @@ import (
 	"github.com/imgproxy/imgproxy/v3/imagedata"
 	"github.com/imgproxy/imgproxy/v3/imagetype"
 	"github.com/imgproxy/imgproxy/v3/imath"
-	"github.com/imgproxy/imgproxy/v3/options"
 	"github.com/imgproxy/imgproxy/v3/vips"
 )
 
@@ -118,7 +117,7 @@ func (p *Processor) scaleOnLoad(c *Context) error {
 	if c.CropHeight > 0 {
 		c.CropHeight = max(1, imath.Shrink(c.CropHeight, hpreshrink))
 	}
-	if c.CropGravity.Type != options.GravityFocusPoint {
+	if c.CropGravity.Type != GravityFocusPoint {
 		// Adjust only when crop gravity offsets are absolute
 		if math.Abs(c.CropGravity.X) >= 1.0 {
 			// Round offsets to prevent turning absolute offsets to relative (ex: 1.0 => 0.5)

+ 3 - 3
processing/watermark.go

@@ -25,8 +25,8 @@ func (p *Processor) watermarkPipeline() Pipeline {
 	}
 }
 
-func shouldReplicateWatermark(gt options.GravityType) bool {
-	return gt == options.GravityReplicate
+func shouldReplicateWatermark(gt GravityType) bool {
+	return gt == GravityReplicate
 }
 
 func (p *Processor) prepareWatermark(
@@ -44,7 +44,7 @@ func (p *Processor) prepareWatermark(
 	}
 
 	wmPo := p.NewProcessingOptions(options.New())
-	wmPo.Set(keys.ResizingType, options.ResizeFit)
+	wmPo.Set(keys.ResizingType, ResizeFit)
 	wmPo.Set(keys.Dpr, 1)
 	wmPo.Set(keys.Enlarge, true)
 	wmPo.Set(keys.Format, wmData.Format())