howardjones/network-weathermap

View on GitHub
lib/Weathermap/Core/ImageUtility.php

Summary

Maintainability
A
3 hrs
Test Coverage
<?php

namespace Weathermap\Core;

/**
 * Utility functions related to manipulating GD images
 *
 * @package Weathermap\Core
 */
class ImageUtility
{
    /**
     * @param $boxWidth
     * @param $boxHeight
     * @return resource
     */
    public static function createTransparentImage($boxWidth, $boxHeight)
    {
        $gdScaleImage = imagecreatetruecolor($boxWidth, $boxHeight);

        // Start with a transparent box, in case the fill or outline colour is 'none'
        imagesavealpha($gdScaleImage, true);
        $nothing = imagecolorallocatealpha($gdScaleImage, 128, 0, 0, 127);
        imagefill($gdScaleImage, 0, 0, $nothing);

        return $gdScaleImage;
    }

    public static function drawMarkerCross($gdImage, $colour, $point, $size = 5)
    {
        $relativeMoves = array(
            array(-1, 0),
            array(2, 0),
            array(-1, 0),
            array(0, -1),
            array(0, 2)
        );

        self::drawMarkerPolygon($gdImage, $colour, $point, $size, $relativeMoves);
    }

    /**
     * @param resource $gdImage
     * @param int $colour
     * @param Point $point
     * @param float $size
     * @param float[][] $relativeMoves
     */
    public static function drawMarkerPolygon($gdImage, $colour, $point, $size, $relativeMoves)
    {
        $points = array();

        foreach ($relativeMoves as $move) {
            $point->translate($move[0] * $size, $move[1] * $size);
            $points[] = $point->x;
            $points[] = $point->y;
        }

        imagepolygon($gdImage, $points, count($relativeMoves), $colour);
    }

    public static function drawMarkerDiamond($gdImage, $colour, $point, $size = 10)
    {
        $relativeMoves = array(
            array(-1, 0),
            array(1, -1),
            array(1, 1),
            array(-1, 1),
        );

        self::drawMarkerPolygon($gdImage, $colour, $point, $size, $relativeMoves);
    }

    public static function wmDrawMarkerBox($gdImage, $colour, $point, $size = 10)
    {
        $relativeMoves = array(
            array(-1, -1),
            array(2, 0),
            array(0, 2),
            array(-2, 0),
        );

        self::drawMarkerPolygon($gdImage, $colour, $point, $size, $relativeMoves);
    }

    public static function wmDrawMarkerCircle($gdImage, $colour, $point, $size = 10)
    {
        imagearc($gdImage, $point->x, $point->y, $size, $size, 0, 360, $colour);
    }


// wrapper around imagecolorallocate to try and re-use palette slots where possible
    public static function myImageColorAllocate($image, $red, $green, $blue)
    {
        // it's possible that we're being called early - just return straight away, in that case
        if (!isset($image)) {
            return -1;
        }
        return imagecolorallocate($image, $red, $green, $blue);
    }

// take the same set of points that imagepolygon does, but don't close the shape
    public static function imagepolyline($imageRef, $points, $npoints, $color)
    {
        for ($i = 0; $i < ($npoints - 1); $i++) {
            imageline($imageRef, $points [$i * 2], $points [$i * 2 + 1], $points [$i * 2 + 2], $points [$i * 2 + 3], $color);
        }
    }

// draw a filled round-cornered rectangle
    public static function imageFilledRoundedRectangle($imageRef, $left, $top, $right, $bottom, $radius, $color)
    {
        imagefilledrectangle($imageRef, $left, $top + $radius, $right, $bottom - $radius, $color);
        imagefilledrectangle($imageRef, $left + $radius, $top, $right - $radius, $bottom, $color);

        imagefilledarc($imageRef, $left + $radius, $top + $radius, $radius * 2, $radius * 2, 0, 360, $color, IMG_ARC_PIE);
        imagefilledarc($imageRef, $right - $radius, $top + $radius, $radius * 2, $radius * 2, 0, 360, $color, IMG_ARC_PIE);

        imagefilledarc($imageRef, $left + $radius, $bottom - $radius, $radius * 2, $radius * 2, 0, 360, $color, IMG_ARC_PIE);
        imagefilledarc($imageRef, $right - $radius, $bottom - $radius, $radius * 2, $radius * 2, 0, 360, $color, IMG_ARC_PIE);
    }

// draw a round-cornered rectangle
    public static function imageRoundedRectangle($imageRef, $left, $top, $right, $bottom, $radius, $color)
    {
        imageline($imageRef, $left + $radius, $top, $right - $radius, $top, $color);
        imageline($imageRef, $left + $radius, $bottom, $right - $radius, $bottom, $color);
        imageline($imageRef, $left, $top + $radius, $left, $bottom - $radius, $color);
        imageline($imageRef, $right, $top + $radius, $right, $bottom - $radius, $color);

        imagearc($imageRef, $left + $radius, $top + $radius, $radius * 2, $radius * 2, 180, 270, $color);
        imagearc($imageRef, $right - $radius, $top + $radius, $radius * 2, $radius * 2, 270, 360, $color);
        imagearc($imageRef, $left + $radius, $bottom - $radius, $radius * 2, $radius * 2, 90, 180, $color);
        imagearc($imageRef, $right - $radius, $bottom - $radius, $radius * 2, $radius * 2, 0, 90, $color);
    }

    public static function imageCreateFromFile($filename)
    {
        $imageRef = null;

        if (!is_readable($filename)) {
            MapUtility::warn("Image file $filename is unreadable. Check permissions. [WMIMG05]\n");

            return $imageRef;
        }

        $conversion = array(
            IMAGETYPE_GIF => array(IMG_GIF, 'GIF', "Image file $filename is GIF, but GIF is not supported by your GD library. [WMIMG01]\n", 'imagecreatefromgif'),
            IMAGETYPE_JPEG => array(IMG_JPEG, 'JPEG', "Image file $filename is JPEG, but JPEG is not supported by your GD library. [WMIMG02]\n", 'imagecreatefromjpeg'),
            IMAGETYPE_PNG => array(IMG_PNG, 'PNG', "Image file $filename is PNG, but PNG is not supported by your GD library. [WMIMG03]\n", 'imagecreatefrompng')
        );

        list (, , $type,) = getimagesize($filename);

        if (array_key_exists($type, $conversion)) {
            list($typeBitfield, $typeName, $failureMessage, $loadFunction) = $conversion[$type];

            if (!(imagetypes() & $typeBitfield)) {
                MapUtility::warn($failureMessage);

                return $imageRef;
            }

            $imageRef = $loadFunction($filename);
        } else {
            MapUtility::warn("Image file $filename wasn't recognised (type=$type). Check format is supported by your GD library. [WMIMG04]\n");
        }

        return $imageRef;
    }

    public static function imageTrueColorToPalette2($image, $dither, $numColours)
    {
        $width = imagesx($image);
        $height = imagesy($image);
        $imageTCRef = ImageCreateTrueColor($width, $height);
        ImageCopyMerge($imageTCRef, $image, 0, 0, 0, 0, $width, $height, 100);
        ImageTrueColorToPalette($image, $dither, $numColours);
        ImageColorMatch($imageTCRef, $image);
        ImageDestroy($imageTCRef);
    }

// taken from here:
// http://www.php.net/manual/en/function.imagefilter.php#62395
// ( with some bugfixes and changes)
//
// Much nicer colorization than imagefilter does, AND no special requirements.
// Preserves white, black and transparency.
//
    public static function imageColorize($imageRef, $red, $green, $blue)
    {
        // The function only accepts indexed colour images, so we pass off to a different method for truecolor images
        if (imageistruecolor($imageRef)) {
            return self::imageColorizeTrueColor($imageRef, $red, $green, $blue);
        }

        $pal = self::createColorizePalette($red, $green, $blue);

        // --- End of palette creation

        // Now,let's change the original palette into the one we created
        for ($index = 0; $index < imagecolorstotal($imageRef); $index++) {
            $inputColour = imagecolorsforindex($imageRef, $index);
            $sourceLuminosity = round(255 * ($inputColour['red'] + $inputColour['green'] + $inputColour['blue']) / 765);
            $outputColour = $pal[$sourceLuminosity];

            imagecolorset($imageRef, $index, $outputColour['r'], $outputColour['g'], $outputColour['b']);
        }

        return $imageRef;
    }

    public static function imageColorizeTrueColor($imageRef, $red, $green, $blue)
    {
        $pal = self::createColorizePalette($red, $green, $blue);

        imagesavealpha($imageRef, true);
        imagefilter($imageRef, IMG_FILTER_GRAYSCALE);

        // This monstrosity seems to be the quickest way to map a grayscale image to a palette while preserving alpha :-(
        for ($y = 0; $y < imagesy($imageRef); $y++) {
            for ($x = 0; $x < imagesx($imageRef); $x++) {
                $rgba = imagecolorat($imageRef, $x, $y);
                $r = ($rgba >> 16) & 0xFF;
                $alpha = ($rgba & 0x7F000000) >> 24;
                $newR = $pal[$r]['r'];
                $newG = $pal[$r]['g'];
                $newB = $pal[$r]['b'];
                $col = imagecolorallocatealpha($imageRef, $newR, $newG, $newB, $alpha);
                imagesetpixel($imageRef, $x, $y, $col);
            }
        }

        return $imageRef;
    }


    /**
     * Calculate the palette to map greyscale values to colourized, when colorizing icons
     *
     * @param int $red
     * @param int $green
     * @param int $blue
     * @return mixed
     */
    public static function createColorizePalette($red, $green, $blue)
    {
        // We will create a monochromatic palette based on the input color
        // which will go from black to white

        $pal = array();

        // Input color luminosity: this is equivalent to the
        // position of the input color in the monochromatic palette 765=255*3
        $inputLuminosity = intval(round(255 * ($red + $green + $blue) / 765));

        // We fill the palette entry with the input color at its
        // corresponding position

        $pal[$inputLuminosity]['r'] = $red;
        $pal[$inputLuminosity]['g'] = $green;
        $pal[$inputLuminosity]['b'] = $blue;

        // Now we complete the palette, first we'll do it to
        // the black,and then to the white.

        // FROM input to black
        // ===================
        // how many colors between black and input
        $stepsToBlack = $inputLuminosity;

        // The step size for each component
        if ($stepsToBlack) {
            $stepSizeRed = $red / $stepsToBlack;
            $stepSizeGreen = $green / $stepsToBlack;
            $stepSizeBlue = $blue / $stepsToBlack;
        }

        for ($i = $stepsToBlack; $i >= 0; $i--) {
            $pal[$stepsToBlack - $i]['r'] = $red - round($stepSizeRed * $i);
            $pal[$stepsToBlack - $i]['g'] = $green - round($stepSizeGreen * $i);
            $pal[$stepsToBlack - $i]['b'] = $blue - round($stepSizeBlue * $i);
        }

        // From input to white:
        // ===================
        // how many colors between input and white
        $stepsToWhite = 255 - $inputLuminosity;

        $stepSizeRed = $stepSizeGreen = $stepSizeBlue = 0;

        if ($stepsToWhite) {
            $stepSizeRed = (255 - $red) / $stepsToWhite;
            $stepSizeGreen = (255 - $green) / $stepsToWhite;
            $stepSizeBlue = (255 - $blue) / $stepsToWhite;
        }

        // The step size for each component
        for ($i = ($inputLuminosity + 1); $i <= 255; $i++) {
            $pal[$i]['r'] = $red + round($stepSizeRed * ($i - $inputLuminosity));
            $pal[$i]['g'] = $green + round($stepSizeGreen * ($i - $inputLuminosity));
            $pal[$i]['b'] = $blue + round($stepSizeBlue * ($i - $inputLuminosity));
        }
        return $pal;
    }
}