487 lines
11 KiB
Go
487 lines
11 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/h2non/bimg"
|
|
)
|
|
|
|
var ErrUnsupportedValue = errors.New("unsupported value")
|
|
|
|
// Coercion is the type that type coerces a parameter and defines the appropriate field on ImageOptions
|
|
type Coercion func(*ImageOptions, interface{}) error
|
|
|
|
var paramTypeCoercions = map[string]Coercion{
|
|
"width": coerceWidth,
|
|
"height": coerceHeight,
|
|
"quality": coerceQuality,
|
|
"top": coerceTop,
|
|
"left": coerceLeft,
|
|
"areawidth": coerceAreaWidth,
|
|
"areaheight": coerceAreaHeight,
|
|
"compression": coerceCompression,
|
|
"rotate": coerceRotate,
|
|
"margin": coerceMargin,
|
|
"factor": coerceFactor,
|
|
"dpi": coerceDPI,
|
|
"textwidth": coerceTextWidth,
|
|
"opacity": coerceOpacity,
|
|
"flip": coerceFlip,
|
|
"flop": coerceFlop,
|
|
"nocrop": coerceNoCrop,
|
|
"noprofile": coerceNoProfile,
|
|
"norotation": coerceNoRotation,
|
|
"noreplicate": coerceNoReplicate,
|
|
"force": coerceForce,
|
|
"embed": coerceEmbed,
|
|
"stripmeta": coerceStripMeta,
|
|
"text": coerceText,
|
|
"image": coerceImage,
|
|
"font": coerceFont,
|
|
"type": coerceImageType,
|
|
"color": coerceColor,
|
|
"colorspace": coerceColorSpace,
|
|
"gravity": coerceGravity,
|
|
"background": coerceBackground,
|
|
"extend": coerceExtend,
|
|
"sigma": coerceSigma,
|
|
"minampl": coerceMinAmpl,
|
|
"operations": coerceOperations,
|
|
"interlace": coerceInterlace,
|
|
"aspectratio": coerceAspectRatio,
|
|
}
|
|
|
|
func coerceTypeInt(param interface{}) (int, error) {
|
|
if v, ok := param.(int); ok {
|
|
return v, nil
|
|
}
|
|
|
|
if v, ok := param.(float64); ok {
|
|
return int(v), nil
|
|
}
|
|
|
|
if v, ok := param.(string); ok {
|
|
return parseInt(v)
|
|
}
|
|
|
|
return 0, ErrUnsupportedValue
|
|
}
|
|
|
|
func coerceTypeFloat(param interface{}) (float64, error) {
|
|
if v, ok := param.(float64); ok {
|
|
return v, nil
|
|
}
|
|
|
|
if v, ok := param.(int); ok {
|
|
return float64(v), nil
|
|
}
|
|
|
|
if v, ok := param.(string); ok {
|
|
result, err := parseFloat(v)
|
|
if err != nil {
|
|
return 0, ErrUnsupportedValue
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
return 0, ErrUnsupportedValue
|
|
}
|
|
|
|
func coerceTypeBool(param interface{}) (bool, error) {
|
|
if v, ok := param.(bool); ok {
|
|
return v, nil
|
|
}
|
|
|
|
if v, ok := param.(string); ok {
|
|
result, err := parseBool(v)
|
|
if err != nil {
|
|
return false, ErrUnsupportedValue
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
return false, ErrUnsupportedValue
|
|
}
|
|
|
|
func coerceTypeString(param interface{}) (string, error) {
|
|
if v, ok := param.(string); ok {
|
|
return v, nil
|
|
}
|
|
|
|
return "", ErrUnsupportedValue
|
|
}
|
|
|
|
func coerceHeight(io *ImageOptions, param interface{}) (err error) {
|
|
io.Height, err = coerceTypeInt(param)
|
|
return err
|
|
}
|
|
|
|
func coerceWidth(io *ImageOptions, param interface{}) (err error) {
|
|
io.Width, err = coerceTypeInt(param)
|
|
return err
|
|
}
|
|
|
|
func coerceQuality(io *ImageOptions, param interface{}) (err error) {
|
|
io.Quality, err = coerceTypeInt(param)
|
|
return err
|
|
}
|
|
|
|
func coerceTop(io *ImageOptions, param interface{}) (err error) {
|
|
io.Top, err = coerceTypeInt(param)
|
|
return err
|
|
}
|
|
|
|
func coerceLeft(io *ImageOptions, param interface{}) (err error) {
|
|
io.Left, err = coerceTypeInt(param)
|
|
return err
|
|
}
|
|
|
|
func coerceAreaWidth(io *ImageOptions, param interface{}) (err error) {
|
|
io.AreaWidth, err = coerceTypeInt(param)
|
|
return err
|
|
}
|
|
|
|
func coerceAreaHeight(io *ImageOptions, param interface{}) (err error) {
|
|
io.AreaHeight, err = coerceTypeInt(param)
|
|
return err
|
|
}
|
|
|
|
func coerceCompression(io *ImageOptions, param interface{}) (err error) {
|
|
io.Compression, err = coerceTypeInt(param)
|
|
return err
|
|
}
|
|
|
|
func coerceRotate(io *ImageOptions, param interface{}) (err error) {
|
|
io.Rotate, err = coerceTypeInt(param)
|
|
return err
|
|
}
|
|
|
|
func coerceMargin(io *ImageOptions, param interface{}) (err error) {
|
|
io.Margin, err = coerceTypeInt(param)
|
|
return err
|
|
}
|
|
|
|
func coerceFactor(io *ImageOptions, param interface{}) (err error) {
|
|
io.Factor, err = coerceTypeInt(param)
|
|
return err
|
|
}
|
|
|
|
func coerceDPI(io *ImageOptions, param interface{}) (err error) {
|
|
io.DPI, err = coerceTypeInt(param)
|
|
return err
|
|
}
|
|
|
|
func coerceTextWidth(io *ImageOptions, param interface{}) (err error) {
|
|
io.TextWidth, err = coerceTypeInt(param)
|
|
return err
|
|
}
|
|
|
|
func coerceOpacity(io *ImageOptions, param interface{}) (err error) {
|
|
v, err := coerceTypeFloat(param)
|
|
io.Opacity = float32(v)
|
|
return err
|
|
}
|
|
|
|
func coerceFlip(io *ImageOptions, param interface{}) (err error) {
|
|
io.Flip, err = coerceTypeBool(param)
|
|
io.IsDefinedField.Flip = true
|
|
return err
|
|
}
|
|
|
|
func coerceFlop(io *ImageOptions, param interface{}) (err error) {
|
|
io.Flop, err = coerceTypeBool(param)
|
|
io.IsDefinedField.Flop = true
|
|
return err
|
|
}
|
|
|
|
func coerceNoCrop(io *ImageOptions, param interface{}) (err error) {
|
|
io.NoCrop, err = coerceTypeBool(param)
|
|
io.IsDefinedField.NoCrop = true
|
|
return err
|
|
}
|
|
|
|
func coerceNoProfile(io *ImageOptions, param interface{}) (err error) {
|
|
io.NoProfile, err = coerceTypeBool(param)
|
|
io.IsDefinedField.NoProfile = true
|
|
return err
|
|
}
|
|
|
|
func coerceNoRotation(io *ImageOptions, param interface{}) (err error) {
|
|
io.NoRotation, err = coerceTypeBool(param)
|
|
io.IsDefinedField.NoRotation = true
|
|
return err
|
|
}
|
|
|
|
func coerceNoReplicate(io *ImageOptions, param interface{}) (err error) {
|
|
io.NoReplicate, err = coerceTypeBool(param)
|
|
io.IsDefinedField.NoReplicate = true
|
|
return err
|
|
}
|
|
|
|
func coerceForce(io *ImageOptions, param interface{}) (err error) {
|
|
io.Force, err = coerceTypeBool(param)
|
|
io.IsDefinedField.Force = true
|
|
return err
|
|
}
|
|
|
|
func coerceEmbed(io *ImageOptions, param interface{}) (err error) {
|
|
io.Embed, err = coerceTypeBool(param)
|
|
io.IsDefinedField.Embed = true
|
|
return err
|
|
}
|
|
|
|
func coerceStripMeta(io *ImageOptions, param interface{}) (err error) {
|
|
io.StripMetadata, err = coerceTypeBool(param)
|
|
io.IsDefinedField.StripMetadata = true
|
|
return err
|
|
}
|
|
|
|
func coerceText(io *ImageOptions, param interface{}) (err error) {
|
|
io.Text, err = coerceTypeString(param)
|
|
return err
|
|
}
|
|
|
|
func coerceImage(io *ImageOptions, param interface{}) (err error) {
|
|
io.Image, err = coerceTypeString(param)
|
|
return err
|
|
}
|
|
|
|
func coerceFont(io *ImageOptions, param interface{}) (err error) {
|
|
io.Font, err = coerceTypeString(param)
|
|
return err
|
|
}
|
|
|
|
func coerceImageType(io *ImageOptions, param interface{}) (err error) {
|
|
io.Type, err = coerceTypeString(param)
|
|
return err
|
|
}
|
|
|
|
func coerceColor(io *ImageOptions, param interface{}) error {
|
|
if v, ok := param.(string); ok {
|
|
io.Color = parseColor(v)
|
|
return nil
|
|
}
|
|
|
|
return ErrUnsupportedValue
|
|
}
|
|
|
|
func coerceColorSpace(io *ImageOptions, param interface{}) error {
|
|
if v, ok := param.(string); ok {
|
|
io.Colorspace = parseColorspace(v)
|
|
return nil
|
|
}
|
|
|
|
return ErrUnsupportedValue
|
|
}
|
|
|
|
func coerceGravity(io *ImageOptions, param interface{}) error {
|
|
if v, ok := param.(string); ok {
|
|
io.Gravity = parseGravity(v)
|
|
return nil
|
|
}
|
|
|
|
return ErrUnsupportedValue
|
|
}
|
|
|
|
func coerceBackground(io *ImageOptions, param interface{}) error {
|
|
if v, ok := param.(string); ok {
|
|
io.Background = parseColor(v)
|
|
return nil
|
|
}
|
|
|
|
return ErrUnsupportedValue
|
|
}
|
|
|
|
func coerceAspectRatio(io *ImageOptions, param interface{}) (err error) {
|
|
io.AspectRatio, err = coerceTypeString(param)
|
|
return err
|
|
}
|
|
|
|
func coerceExtend(io *ImageOptions, param interface{}) error {
|
|
if v, ok := param.(string); ok {
|
|
io.Extend = parseExtendMode(v)
|
|
return nil
|
|
}
|
|
|
|
return ErrUnsupportedValue
|
|
}
|
|
|
|
func coerceSigma(io *ImageOptions, param interface{}) (err error) {
|
|
io.Sigma, err = coerceTypeFloat(param)
|
|
return err
|
|
}
|
|
|
|
func coerceMinAmpl(io *ImageOptions, param interface{}) (err error) {
|
|
io.MinAmpl, err = coerceTypeFloat(param)
|
|
return err
|
|
}
|
|
|
|
func coerceOperations(io *ImageOptions, param interface{}) (err error) {
|
|
if v, ok := param.(string); ok {
|
|
ops, err := parseJSONOperations(v)
|
|
if err == nil {
|
|
io.Operations = ops
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
return ErrUnsupportedValue
|
|
}
|
|
|
|
func coerceInterlace(io *ImageOptions, param interface{}) (err error) {
|
|
io.Interlace, err = coerceTypeBool(param)
|
|
io.IsDefinedField.Interlace = true
|
|
return err
|
|
}
|
|
|
|
func buildParamsFromOperation(op PipelineOperation) (ImageOptions, error) {
|
|
var options ImageOptions
|
|
|
|
// Apply defaults
|
|
options.Extend = bimg.ExtendCopy
|
|
|
|
for key, value := range op.Params {
|
|
fn, ok := paramTypeCoercions[key]
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
err := fn(&options, value)
|
|
if err != nil {
|
|
return ImageOptions{}, fmt.Errorf(`error while processing parameter "%s" with value %q, error: %s`, key, value, err)
|
|
}
|
|
}
|
|
|
|
return options, nil
|
|
}
|
|
|
|
// buildParamsFromQuery builds the ImageOptions type from untyped parameters
|
|
func buildParamsFromQuery(query url.Values) (ImageOptions, error) {
|
|
var options ImageOptions
|
|
|
|
// Apply defaults
|
|
options.Extend = bimg.ExtendCopy
|
|
|
|
// Extract only known parameters
|
|
for key := range query {
|
|
fn, ok := paramTypeCoercions[key]
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
value := query.Get(key)
|
|
err := fn(&options, value)
|
|
if err != nil {
|
|
return ImageOptions{}, fmt.Errorf(`error while processing parameter "%s" with value %q, error: %s`, key, value, err)
|
|
}
|
|
}
|
|
|
|
return options, nil
|
|
}
|
|
|
|
func parseBool(val string) (bool, error) {
|
|
if val == "" {
|
|
return false, nil
|
|
}
|
|
|
|
return strconv.ParseBool(val)
|
|
}
|
|
|
|
func parseInt(param string) (int, error) {
|
|
if param == "" {
|
|
return 0, nil
|
|
}
|
|
|
|
f, err := parseFloat(param)
|
|
return int(math.Floor(f + 0.5)), err
|
|
}
|
|
|
|
func parseFloat(param string) (float64, error) {
|
|
if param == "" {
|
|
return 0.0, nil
|
|
}
|
|
|
|
val, err := strconv.ParseFloat(param, 64)
|
|
return math.Abs(val), err
|
|
}
|
|
|
|
func parseColorspace(val string) bimg.Interpretation {
|
|
if val == "bw" {
|
|
return bimg.InterpretationBW
|
|
}
|
|
return bimg.InterpretationSRGB
|
|
}
|
|
|
|
func parseColor(val string) []uint8 {
|
|
const max float64 = 255
|
|
var buf []uint8
|
|
if val != "" {
|
|
for _, num := range strings.Split(val, ",") {
|
|
n, _ := strconv.ParseUint(strings.Trim(num, " "), 10, 8)
|
|
buf = append(buf, uint8(math.Min(float64(n), max)))
|
|
}
|
|
}
|
|
return buf
|
|
}
|
|
|
|
func parseJSONOperations(data string) (PipelineOperations, error) {
|
|
var operations PipelineOperations
|
|
|
|
// Fewer than 2 characters cannot be valid JSON. We assume empty operation.
|
|
if len(data) < 2 {
|
|
return operations, nil
|
|
}
|
|
|
|
d := json.NewDecoder(strings.NewReader(data))
|
|
d.DisallowUnknownFields()
|
|
|
|
err := d.Decode(&operations)
|
|
return operations, err
|
|
}
|
|
|
|
func parseExtendMode(val string) bimg.Extend {
|
|
val = strings.TrimSpace(strings.ToLower(val))
|
|
if val == "white" {
|
|
return bimg.ExtendWhite
|
|
}
|
|
if val == "black" {
|
|
return bimg.ExtendBlack
|
|
}
|
|
if val == "mirror" {
|
|
return bimg.ExtendMirror
|
|
}
|
|
if val == "background" {
|
|
return bimg.ExtendBackground
|
|
}
|
|
if val == "lastpixel" {
|
|
return bimg.ExtendLast
|
|
}
|
|
return bimg.ExtendCopy
|
|
}
|
|
|
|
func parseGravity(val string) bimg.Gravity {
|
|
var m = map[string]bimg.Gravity{
|
|
"south": bimg.GravitySouth,
|
|
"north": bimg.GravityNorth,
|
|
"east": bimg.GravityEast,
|
|
"west": bimg.GravityWest,
|
|
"smart": bimg.GravitySmart,
|
|
}
|
|
|
|
val = strings.TrimSpace(strings.ToLower(val))
|
|
if g, ok := m[val]; ok {
|
|
return g
|
|
}
|
|
|
|
return bimg.GravityCentre
|
|
}
|