Source

imagettftextgradient.php

<?php

/**
 * Imagettftextgradient v1.1.0
 *
 * Copyright (c) 2017-2026 Andrew G. Johnson <andrew@andrewgjohnson.com>
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to the following conditions:
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
 * Software.
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * PHP version 5
 *
 * @category  Andrewgjohnson
 * @package   Imagettftextgradient
 * @author    Andrew G. Johnson <andrew@andrewgjohnson.com>
 * @copyright 2017-2026 Andrew G. Johnson <andrew@andrewgjohnson.com>
 * @license   https://opensource.org/licenses/mit/ The MIT License
 * @link      https://github.com/andrewgjohnson/imagettftextgradient
 */

if (!function_exists('imagettftextgradient')) {
    /**
     * Imagettftextgradient is a drop-in replacement for imagettftext with added parameters to add gradient coloring
     * effects to your PHP GD images.
     *
     * Examples:
     * ```
     * <?php
     * // In PHP 8.0 a ninth parameter ($options) was added to imagettftext()
     * imagettftext($im, 20, 0, 0, 0, $color, $font, $string, array()); // Add text to a GD image
     * imagettftextgradient($im, 20, 0, 0, 0, $color, $font, $string, array()); // This works the same as the line above
     * imagettftextgradient($im, 20, 0, 0, 0, $color, $font, $string, array(), $secondColor); // This will add the same
     * // text only with a vertical gradient instead of a solid color
     * imagettftextgradient($im, 20, 0, 0, 0, $color, $font, $string, array(), $secondColor, true); // This will add the
     * // same text only with a horizontal gradient instead of a solid color
     *
     * // We also support previous versions of PHP back to 5.0 and the previous version of imagettftext()
     * imagettftext($im, 20, 0, 0, 0, $color, $font, $string); // Add text to a GD image
     * imagettftextgradient($im, 20, 0, 0, 0, $color, $font, $string); // This works the same as the line above
     * imagettftextgradient($im, 20, 0, 0, 0, $color, $font, $string, $secondColor); // This will add the same text only
     * // with a vertical gradient instead of a solid color
     * imagettftextgradient($im, 20, 0, 0, 0, $color, $font, $string, $secondColor, true); // This will add the same
     * // text only with a horizontal gradient instead of a solid color
     * ?>
     * ```
     *
     * @param \GdImage|resource $image                             A GdImage object (PHP 8 and newer) or an image
     * resource (older versions of PHP), returned by one of the image creation functions, such as
     * imagecreatetruecolor().
     * @param float             $size                              The font size in points.
     * @param float             $angle                             The angle in degrees, with 0 degrees being
     * left-to-right reading text. Higher values represent a counter-clockwise rotation. For example, a value of 90
     * would result in bottom-to-top reading text.
     * @param int               $x                                 The coordinates given by x and y will define the
     * basepoint of the first character (roughly the lower-left corner of the character). This is different from the
     * imagestring(), where x and y define the upper-left corner of the first character. For example, "top left" is 0,
     * 0.
     * @param int               $y                                 The y-ordinate. This sets the position of the fonts
     * baseline, not the very bottom of the character.
     * @param int               $color                             The color index. Using the negative of a color index
     * has the effect of turning off antialiasing. See imagecolorallocate().
     * @param string            $fontFilename                      The path to the TrueType font you wish to use.
     *
     * Depending on which version of the GD library PHP is using, when fontfile does not begin with a leading / then
     * .ttf will be appended to the filename and the library will attempt to search for that filename along a
     * library-defined font path.
     *
     * When using versions of the GD library lower than 2.0.18, a space character, rather than a semicolon, was used as
     * the 'path separator' for different font files. Unintentional use of this feature will result in the warning
     * message: Warning: Could not find/open font. For these affected versions, the only solution is moving the font to
     * a path which does not contain spaces.
     *
     * In many cases where a font resides in the same directory as the script using it the following trick will
     * alleviate any include problems.
     *
     * ```
     * <?php
     * // Set the environment variable for GD
     * putenv('GDFONTPATH=' . realpath('.'));
     *
     * // Name the font to be used (note the lack of the .ttf extension)
     * $font = 'SomeFont';
     * ?>
     * ```
     * @param string            $text                              The text string in UTF-8 encoding.
     *
     * May include decimal numeric character references (of the form: €) to access characters in a font beyond
     * position 127. The hexadecimal format (like ©) is supported. Strings in UTF-8 encoding can be passed
     * directly.
     *
     * Named entities, such as ©, are not supported. Consider using html_entity_decode() to decode these named
     * entities into UTF-8 strings.
     *
     * If a character is used in the string which is not supported by the font, a hollow rectangle will replace the
     * character.
     * @param array|int         $optionsOrGradientColor            An array with linespacing key holding a float value.
     *
     * Note: A ninth parameter ($options) was added to imagettftext() in PHP 8. This package supports both the old and
     * new variants so these parameters are set up to handle both cases. The parameter descriptions assume you will be
     * using the PHP 8+ version with $options as the ninth parameter.
     * @param int|bool|null     $gradientColorOrHorizontalGradient The color index. Using the negative of a color index
     * has the effect of turning off antialiasing. See imagecolorallocate().
     *
     * Note: A ninth parameter ($options) was added to imagettftext() in PHP 8. This package supports both the old and
     * new variants so these parameters are set up to handle both cases. The parameter descriptions assume you will be
     * using the PHP 8+ version with $options as the ninth parameter.
     * @param ?bool             $horizontalGradient                Whether or not to use a horizontal gradient versus a
     * vertical gradient.
     *
     * Note: A ninth parameter ($options) was added to imagettftext() in PHP 8. This package supports both the old and
     * new variants so these parameters are set up to handle both cases. The parameter descriptions assume you will be
     * using the PHP 8+ version with $options as the ninth parameter.
     *
     * @return array|false Returns an array with 8 elements representing four points making the bounding box of the
     * text. The order of the points is lower left, lower right, upper right, upper left. The points are relative to the
     * text regardless of the angle, so "upper left" means in the top left-hand corner when you see the text
     * horizontally. Returns FALSE on error.
     */
    function imagettftextgradient(
        &$image,
        $size,
        $angle,
        $x,
        $y,
        $color,
        $fontFilename,
        $text,
        $optionsOrGradientColor = array(),
        $gradientColorOrHorizontalGradient = null,
        $horizontalGradient = null
    ) {
        // If the $optionsOrGradientColor parameter is an array we assume the caller is using the PHP 8+ version of
        // imagettftext() with the $options parameter otherwise we assume the PHP 5/7 version is being used without the
        // $options parameter
        if (is_array($optionsOrGradientColor)) {
            $options       = $optionsOrGradientColor;
            $gradientColor = $gradientColorOrHorizontalGradient;
        } else {
            $options            = array();
            $gradientColor      = $optionsOrGradientColor;
            $horizontalGradient = $gradientColorOrHorizontalGradient;
        }

        // If either grdient parameter is set to null, fall back to a default value
        $gradientColor      = $gradientColor === null || !is_int($gradientColor) ? 0 : $gradientColor;
        $horizontalGradient = $horizontalGradient === null || !is_bool($horizontalGradient)
            ? false
            : $horizontalGradient;

        // $gradientColor needs to be an integer, presumably generated by imagecolorallocate()
        if (is_int($gradientColor)) {
            // $returnArray will be returned once all calculations are complete
            $returnArrayDefault = array(
                imagesx($image), // Lower left (x coordinate)
                -1,              // Lower left (y coordinate)
                -1,              // Lower right (x coordinate)
                -1,              // Lower right (y coordinate)
                -1,              // Upper right (x coordinate)
                imagesy($image), // Upper right (y coordinate)
                imagesx($image), // Upper left (x coordinate)
                imagesy($image)  // Upper left (y coordinate)
            );

            $returnArray = $returnArrayDefault;

            // $temporaryImage is a GD image that is the same size as our original GD image
            $temporaryImage = imagecreatetruecolor(
                imagesx($image),
                imagesy($image)
            );

            // Fill $temporaryImage with a black background
            imagefill(
                $temporaryImage,
                0,
                0,
                imagecolorallocate($temporaryImage, 0x00, 0x00, 0x00)
            );

            // Add white text to $temporaryImage with the function call’s parameters
            $imagettftext = version_compare(PHP_VERSION, '8.0.0', '>=') ? imagettftext(
                $temporaryImage,
                $size,
                $angle,
                $x,
                $y,
                imagecolorallocate($temporaryImage, 0xFF, 0xFF, 0xFF),
                $fontFilename,
                $text,
                $options
            ) : imagettftext(
                $temporaryImage,
                $size,
                $angle,
                $x,
                $y,
                imagecolorallocate($temporaryImage, 0xFF, 0xFF, 0xFF),
                $fontFilename,
                $text
            );

            if ($imagettftext === false) { // If imagettftext() failed, there’s no need to continue
                imagedestroy($temporaryImage);
                return false;
            }

            // Calculate the text’s edges
            $textLeft   = min($imagettftext[0], $imagettftext[6]);
            $textRight  = max($imagettftext[2], $imagettftext[4]);
            $textTop    = min($imagettftext[5], $imagettftext[7]);
            $textBottom = max($imagettftext[1], $imagettftext[3]);
            $textHeight = $textBottom - $textTop;
            $textWidth  = $textRight - $textLeft;

            // Loop through each pixel in $temporaryImage
            for ($temporaryX = 0; $temporaryX < imagesx($temporaryImage); $temporaryX++) {
                for ($temporaryY = 0; $temporaryY < imagesy($temporaryImage); $temporaryY++) {
                    // $visibility is the grayscale of the current pixel
                    $visibility = (imagecolorat(
                        $temporaryImage,
                        $temporaryX,
                        $temporaryY
                    ) & 0xFF) / 255;

                    // If the current pixel would be visible, add it to $image
                    if ($visibility > 0) {
                        // We are on an affected pixel so update $returnArray accordingly
                        $returnArray = array(
                            min($returnArray[0], $temporaryX),
                            max($returnArray[1], $temporaryY),
                            max($returnArray[2], $temporaryX),
                            max($returnArray[3], $temporaryY),
                            max($returnArray[4], $temporaryX),
                            min($returnArray[5], $temporaryY),
                            min($returnArray[6], $temporaryX),
                            min($returnArray[7], $temporaryY)
                        );

                        // Determine the current pixel’s RGB color code and alpha value by first calculating its
                        // position within the text
                        if ($horizontalGradient) {
                            $gradientPosition = ($temporaryX - $textLeft) / $textWidth;
                        } else {
                            $gradientPosition = ($temporaryY - $textTop) / $textHeight;
                        }

                        $colorRed  = ($color >> 16) & 0xFF;
                        $colorRed *= 1 - $gradientPosition;
                        $gradientColorRed  = ($gradientColor >> 16) & 0xFF;
                        $gradientColorRed *= $gradientPosition;
                        $red = round($colorRed + $gradientColorRed);

                        $colorGreen  = ($color >> 8) & 0xFF;
                        $colorGreen *= 1 - $gradientPosition;
                        $gradientColorGreen  = ($gradientColor >> 8) & 0xFF;
                        $gradientColorGreen *= $gradientPosition;
                        $green = round($colorGreen + $gradientColorGreen);

                        $colorBlue  = $color & 0xFF;
                        $colorBlue *= 1 - $gradientPosition;
                        $gradientColorBlue  = $gradientColor & 0xFF;
                        $gradientColorBlue *= $gradientPosition;
                        $blue = round($colorBlue + $gradientColorBlue);

                        $colorData = imagecolorsforindex(
                            $image,
                            $color
                        );
                        $colorOpacity = $colorData['alpha'] * (1 - $gradientPosition);

                        $gradientColorData = imagecolorsforindex(
                            $image,
                            $gradientColor
                        );
                        $gradientColorOpacity = $gradientColorData['alpha'] * $gradientPosition;

                        $opacity = $colorOpacity + $gradientColorOpacity;
                        $opacity = (127 - $opacity) / 127;

                        // Set the current pixel in $image
                        imagesetpixel(
                            $image,
                            $temporaryX,
                            $temporaryY,
                            imagecolorallocatealpha(
                                $image,
                                $red,
                                $green,
                                $blue,
                                (int)round((1 - $visibility) * 127 * $opacity)
                            )
                        );
                    }
                }
            }

            // Destroy our $temporaryImage
            imagedestroy($temporaryImage);

            if ($returnArray === $returnArrayDefault) {
                // Return false if $returnArray hasn’t changed to indicate a failure
                return false;
            } else {
                // Return the array of affected coordinates
                return $returnArray;
            }
        } else {
            // Return a call to imagettftext() as no $gradientColor was received
            return version_compare(PHP_VERSION, '8.0.0', '>=') ? imagettftext(
                $image,
                $size,
                $angle,
                $x,
                $y,
                $color,
                $fontFilename,
                $text,
                $options
            ) : imagettftext(
                $image,
                $size,
                $angle,
                $x,
                $y,
                $color,
                $fontFilename,
                $text
            );
        }
    }
}