elboletaire/Watimage

View on GitHub
src/Normalize.php

Summary

Maintainability
D
1 day
Test Coverage
<?php
namespace Elboletaire\Watimage;

use Elboletaire\Watimage\Exception\InvalidArgumentException;

/**
 * The Normalize class. It takes params in a lot of different ways, but it
 * always return our expected params :D
 *
 * @author Òscar Casajuana <elboletaire at underave dot net>
 * @copyright 2015 Òscar Casajuana <elboletaire at underave dot net>
 * @license https://opensource.org/licenses/MIT MIT
 * @link https://github.com/elboletaire/Watimage
 */
class Normalize
{
    /**
     * Returns the proper color array for the given color.
     *
     * It accepts any (or almost any) imaginable type.
     *
     * @param  mixed  $color Can be an array (sequential or associative) or
     *                       hexadecimal. In hexadecimal allows 3 and 6 characters
     *                       for rgb and 4 or 8 characters for rgba.
     * @return array         Containing all 4 color channels.
     * @throws InvalidArgumentException
     */
    public static function color($color)
    {
        if ($color === Image::COLOR_TRANSPARENT) {
            return [
                'r' => 0,
                'g' => 0,
                'b' => 0,
                'a' => 127
            ];
        }

        // rgb(a) arrays
        if (is_array($color) && in_array(count($color), [3, 4])) {
            $allowedKeys = [
                'associative' => ['red', 'green', 'blue', 'alpha'],
                'reduced'     => ['r', 'g', 'b', 'a'],
                'numeric'     => [0, 1, 2, 3]
            ];

            foreach ($allowedKeys as $keys) {
                list($r, $g, $b, $a) = $keys;

                if (!isset($color[$r], $color[$g], $color[$b])) {
                    continue;
                }

                return [
                    'r' => self::fitInRange($color[$r], 0, 255),
                    'g' => self::fitInRange($color[$g], 0, 255),
                    'b' => self::fitInRange($color[$b], 0, 255),
                    'a' => self::fitInRange(isset($color[$a]) ? $color[$a] : 0, 0, 127),
                ];
            }

            throw new InvalidArgumentException("Invalid array color value %s.", $color);
        }

        // hexadecimal
        if (!is_string($color)) {
            throw new InvalidArgumentException("Invalid color value \"%s\"", $color);
        }

        $color = ltrim($color, '#');
        if (in_array(strlen($color), [3, 4])) {
            $color = str_split($color);
            $color = array_map(function ($item) {
                return str_repeat($item, 2);
            }, $color);
            $color = implode($color);
        }
        if (strlen($color) == 6) {
            list($r, $g, $b) = [
                $color[0] . $color[1],
                $color[2] .$color[3],
                $color[4] . $color[5]
            ];
        } elseif (strlen($color) == 8) {
            list($r, $g, $b, $a) = [
                $color[0] . $color[1],
                $color[2] . $color[3],
                $color[4] . $color[5],
                $color[6] . $color[7]
            ];
        } else {
            throw new InvalidArgumentException("Invalid hexadecimal color value \"%s\"", $color);
        }

        return [
            'r' => hexdec($r),
            'g' => hexdec($g),
            'b' => hexdec($b),
            'a' => isset($a) ? hexdec($a) : 0
        ];
    }

    /**
     * Normalizes crop arguments returning an array with them.
     *
     * You can pass arguments one by one or an array passing arguments
     * however you like.
     *
     * @param  int $x      X position where start to crop.
     * @param  int $y      Y position where start to crop.
     * @param  int $width  New width of the image.
     * @param  int $height New height of the image.
     * @return array       Array with numeric keys for x, y, width & height
     * @throws InvalidArgumentException
     */
    public static function crop($x, $y = null, $width = null, $height = null)
    {
        if (!isset($y, $width, $height) && is_array($x)) {
            $values = $x;
            $allowedKeys = [
                'associative' => ['x', 'y', 'width', 'height'],
                'reduced'     => ['x', 'y', 'w', 'h'],
                'numeric'     => [0, 1, 2, 3]
            ];

            foreach ($allowedKeys as $keys) {
                list($x, $y, $width, $height) = $keys;
                if (isset($values[$x], $values[$y], $values[$width], $values[$height])) {
                    return [
                        $values[$x],
                        $values[$y],
                        $values[$width],
                        $values[$height]
                    ];
                }
            }
        }

        if (!isset($x, $y, $width, $height)) {
            throw new InvalidArgumentException(
                "Invalid options for crop %s.",
                compact('x', 'y', 'width', 'height')
            );
        }

        return [$x, $y, $width, $height];
    }

    /**
     * Normalizes flip type from any of the allowed values.
     *
     * @param  mixed $type  Can be either:
     *                      v, y, vertical or IMG_FLIP_VERTICAL
     *                      h, x, horizontal or IMG_FLIP_HORIZONTAL
     *                      b, xy, yx, both or IMG_FLIP_BOTH
     * @return int
     * @throws InvalidArgumentException
     */
    public static function flip($type)
    {
        switch (strtolower($type)) {
            case 'x':
            case 'h':
            case 'horizontal':
            case IMG_FLIP_HORIZONTAL:
                return IMG_FLIP_HORIZONTAL;
                break;

            case 'y':
            case 'v':
            case 'vertical':
            case IMG_FLIP_VERTICAL:
                return IMG_FLIP_VERTICAL;
                break;

            case 'b':
            case 'both':
            case IMG_FLIP_BOTH:
                return IMG_FLIP_BOTH;
                break;

            default:
                throw new InvalidArgumentException("Incorrect flip type \"%s\"", $type);
                break;
        }
    }

    /**
     * An alias of self::position but returning a customized message for Watermark.
     *
     * @param  mixed  $x Can be just x or an array containing both params.
     * @param  int    $y Can only be y.
     * @return array     With x and y in a sequential array.
     * @throws InvalidArgumentException
     */
    public static function margin($x, $y = null)
    {
        try {
            list($x, $y) = self::position($x, $y);
        } catch (InvalidArgumentException $e) {
            throw new InvalidArgumentException("Invalid margin %s.", compact('x', 'y'));
        }

        return [$x, $y];
    }

    /**
     * Normalizes position (x and y).
     *
     * @param  mixed  $x Can be just x or an array containing both params.
     * @param  int    $y Can only be y.
     * @return array     With x and y in a sequential array.
     * @throws InvalidArgumentException
     */
    public static function position($x, $y = null)
    {
        if (is_array($x)) {
            if (isset($x['x']) || isset($x['y'])) {
                extract($x);
            } else {
                @list($x, $y) = $x;
            }
        }

        if (is_numeric($x) && !isset($y)) {
            $y = $x;
        }

        if (!isset($x, $y) || !(is_numeric($x) && is_numeric($y))) {
            throw new InvalidArgumentException("Invalid position %s.", compact('x', 'y'));
        }

        return [$x, $y];
    }

    /**
     * Normalizes cropMeasures (origin X, origin Y, destiny X & destiny Y)
     *
     * @param  mixed $ox Can be just ox or an array containing all the params.
     * @param  int   $oy Origin Y.
     * @param  int   $dx Destiny X.
     * @param  int   $dy Destiny Y.
     * @return array
     */
    public static function cropMeasures($ox, $oy = null, $dx = null, $dy = null)
    {
        if (!isset($oy, $dx, $dy, $width, $height) && is_array($ox)) {
            $values = $ox;
            $allowedKeys = [
                'associative' => ['ox', 'oy', 'dx', 'dy'],
                'numeric'     => [0, 1, 2, 3]
            ];

            foreach ($allowedKeys as $keys) {
                list($oxk, $oyk, $dxk, $dyk) = $keys;
                $isset = isset(
                    $values[$oxk],
                    $values[$oyk],
                    $values[$dxk],
                    $values[$dyk]
                );
                if (!$isset) {
                    continue;
                }
                return [
                    $values[$oxk],
                    $values[$oyk],
                    $values[$dxk],
                    $values[$dyk]
                ];
            }
        }

        if (!isset($ox, $oy, $dx, $dy)) {
            throw new InvalidArgumentException(
                "Invalid options for cropMeasures %s.",
                compact('ox', 'oy', 'dx', 'dy')
            );
        }

        return [$ox, $oy, $dx, $dy];
    }

    /**
     * Normalizes size (width and height).
     *
     * @param  mixed  $width  Can be just width or an array containing both params.
     * @param  int    $height Can only be height.
     * @return array          With width and height in a sequential array.
     * @throws InvalidArgumentException
     */
    public static function size($width, $height = null)
    {
        if (!isset($height) && is_array($width)) {
            $allowedKeys = [
                [0, 1],
                ['x', 'y'],
                ['w', 'h'],
                ['width', 'height'],
            ];

            foreach ($allowedKeys as $keys) {
                list($x, $y) = $keys;


                if (isset($width[$x])) {
                    if (isset($width[$y])) {
                        $height = $width[$y];
                    }
                    $width = $width[$x];
                    break;
                }
            }
        }

        if (isset($width) && !isset($height)) {
            $height = $width;
        }

        if (!isset($width, $height) || !(is_numeric($width) && is_numeric($height))) {
            throw new InvalidArgumentException(
                "Invalid resize arguments %s",
                compact('width', 'height')
            );
        }

        return [
            self::fitInRange($width, 0),
            self::fitInRange($height, 0)
        ];
    }

    /**
     * Checks that the given value is between our defined range.
     *
     * Can check just for min or max if setting the other value to false.
     *
     * @param  int  $value Value to be checked,
     * @param  bool $min   Minimum value. False to just use max.
     * @param  bool $max   Maximum value. False to just use min.
     * @return int         The value itself.
     */
    public static function fitInRange($value, $min = false, $max = false)
    {
        if ($min !== false && $value < $min) {
            $value = $min;
        }

        if ($max !== false && $value > $max) {
            $value = $max;
        }

        return $value;
    }

    /**
     * Normalizes position + position ala css.
     *
     * @param  mixed $position Array with x,y or string ala CSS.
     * @return mixed           Returns what you pass (array or string).
     * @throws InvalidArgumentException
     */
    public static function cssPosition($position)
    {
        try {
            $position = self::position($position);
        } catch (InvalidArgumentException $e) {
            if (!is_string($position)) {
                throw new InvalidArgumentException("Invalid watermark position %s.", $position);
            }

            if (in_array($position, ['center', 'centered'])) {
                $position = 'center center';
            }

            if (!preg_match('/((center|top|bottom|right|left) ?){2}/', $position)) {
                throw new InvalidArgumentException("Invalid watermark position %s.", $position);
            }
        }

        return $position;
    }

    /**
     * Returns proper size argument for Watermark.
     *
     * @param  mixed  $width  Can be a percentage, just width or an array containing both params.
     * @param  int    $height Can only be height.
     * @return mixed
     */
    public static function watermarkSize($width, $height = null)
    {
        try {
            $width = self::size($width, $height);
        } catch (InvalidArgumentException $e) {
            if (!is_string($width) || !preg_match('/([0-9]{1,3}%|full)$/', $width)) {
                throw new InvalidArgumentException(
                    "Invalid size arguments %s",
                    compact('width', 'height')
                );
            }
        }

        return $width;
    }
}