PHPUnit Test Suite Source

You can view the full source code for ImagettftextgradientTest.php below.

ImagettftextgradientTest.php

<?php

/**
 * 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.
 */

namespace AndrewGJohnson\AgjGd\Tests;

use PHPUnit\Framework\TestCase;

class ImagettftextgradientTest extends TestCase
{
    private const FONT_ANGLE   = 0;
    private const FONT_PATH    = __DIR__ . '/NotoSans-Regular.ttf';
    private const FONT_SIZE    = 40;
    private const FONT_X       = 10;
    private const FONT_Y       = 120;
    private const IMAGE_WIDTH  = 400;
    private const IMAGE_HEIGHT = 200;

    public function testFunctionExists(): void
    {
        $this->assertTrue(function_exists('imagettftextgradient'));
    }

    public function testReturnsArrayOnSuccess(): void
    {
        $image  = $this->createImage();
        $color  = imagecolorallocate($image, 0xFF, 0x00, 0x00);
        $color2 = imagecolorallocate($image, 0x00, 0x00, 0xFF);

        $result = imagettftextgradient(
            $image,
            self::FONT_SIZE,
            self::FONT_ANGLE,
            self::FONT_X,
            self::FONT_Y,
            $color,
            self::FONT_PATH,
            'Hello',
            [],
            $color2
        );

        $this->assertIsArray($result);
    }

    public function testReturnedArrayHasEightElements(): void
    {
        $image  = $this->createImage();
        $color  = imagecolorallocate($image, 0xFF, 0x00, 0x00);
        $color2 = imagecolorallocate($image, 0x00, 0x00, 0xFF);

        $result = imagettftextgradient(
            $image,
            self::FONT_SIZE,
            self::FONT_ANGLE,
            self::FONT_X,
            self::FONT_Y,
            $color,
            self::FONT_PATH,
            'Hello',
            [],
            $color2
        );

        $this->assertIsArray($result);
        $this->assertCount(8, $result);
    }

    public function testBoundingBoxCoordinatesAreWithinImageBounds(): void
    {
        $image  = $this->createImage();
        $color  = imagecolorallocate($image, 0xFF, 0x00, 0x00);
        $color2 = imagecolorallocate($image, 0x00, 0x00, 0xFF);

        $result = imagettftextgradient(
            $image,
            self::FONT_SIZE,
            self::FONT_ANGLE,
            self::FONT_X,
            self::FONT_Y,
            $color,
            self::FONT_PATH,
            'Hello',
            [],
            $color2
        );

        $this->assertIsArray($result);

        // Even indices are x coordinates, odd indices are y coordinates
        for ($i = 0; $i < 8; $i += 2) {
            $this->assertGreaterThanOrEqual(0, $result[$i]);
            $this->assertLessThan(self::IMAGE_WIDTH, $result[$i]);
        }

        for ($i = 1; $i < 8; $i += 2) {
            $this->assertGreaterThanOrEqual(0, $result[$i]);
            $this->assertLessThan(self::IMAGE_HEIGHT, $result[$i]);
        }
    }

    public function testReturnsFalseForInvalidFont(): void
    {
        $image  = $this->createImage();
        $color  = imagecolorallocate($image, 0xFF, 0x00, 0x00);
        $color2 = imagecolorallocate($image, 0x00, 0x00, 0xFF);

        // @ suppresses the GD warning emitted by imagettftext when the font is missing
        $result = @imagettftextgradient(
            $image,
            self::FONT_SIZE,
            self::FONT_ANGLE,
            self::FONT_X,
            self::FONT_Y,
            $color,
            '/nonexistent/font.ttf',
            'Hello',
            [],
            $color2
        );

        $this->assertFalse($result);
    }

    public function testPhp5SevenApiStyle(): void
    {
        // PHP 5/7 style: gradient color passed as the 9th parameter (no options array)
        $image  = $this->createImage();
        $color  = imagecolorallocate($image, 0xFF, 0x00, 0x00);
        $color2 = imagecolorallocate($image, 0x00, 0x00, 0xFF);

        $result = imagettftextgradient(
            $image,
            self::FONT_SIZE,
            self::FONT_ANGLE,
            self::FONT_X,
            self::FONT_Y,
            $color,
            self::FONT_PATH,
            'Hello',
            $color2
        );

        $this->assertIsArray($result);
        $this->assertCount(8, $result);
    }

    public function testPhp8ApiStyle(): void
    {
        // PHP 8+ style: options array as 9th parameter, gradient color as 10th
        $image  = $this->createImage();
        $color  = imagecolorallocate($image, 0xFF, 0x00, 0x00);
        $color2 = imagecolorallocate($image, 0x00, 0x00, 0xFF);

        $result = imagettftextgradient(
            $image,
            self::FONT_SIZE,
            self::FONT_ANGLE,
            self::FONT_X,
            self::FONT_Y,
            $color,
            self::FONT_PATH,
            'Hello',
            [],
            $color2
        );

        $this->assertIsArray($result);
        $this->assertCount(8, $result);
    }

    public function testPhp5SevenAndPhp8ApiStylesReturnSameBoundingBox(): void
    {
        $image1 = $this->createImage();
        $red1   = imagecolorallocate($image1, 0xFF, 0x00, 0x00);
        $blue1  = imagecolorallocate($image1, 0x00, 0x00, 0xFF);

        $image2 = $this->createImage();
        $red2   = imagecolorallocate($image2, 0xFF, 0x00, 0x00);
        $blue2  = imagecolorallocate($image2, 0x00, 0x00, 0xFF);

        $result1 = imagettftextgradient(
            $image1,
            self::FONT_SIZE,
            self::FONT_ANGLE,
            self::FONT_X,
            self::FONT_Y,
            $red1,
            self::FONT_PATH,
            'Hello',
            $blue1
        );
        $result2 = imagettftextgradient(
            $image2,
            self::FONT_SIZE,
            self::FONT_ANGLE,
            self::FONT_X,
            self::FONT_Y,
            $red2,
            self::FONT_PATH,
            'Hello',
            [],
            $blue2
        );

        $this->assertIsArray($result1);
        $this->assertIsArray($result2);
        $this->assertSame($result1, $result2);
    }

    public function testHorizontalGradientReturnsArray(): void
    {
        $image  = $this->createImage();
        $color  = imagecolorallocate($image, 0xFF, 0x00, 0x00);
        $color2 = imagecolorallocate($image, 0x00, 0x00, 0xFF);

        $result = imagettftextgradient(
            $image,
            self::FONT_SIZE,
            self::FONT_ANGLE,
            self::FONT_X,
            self::FONT_Y,
            $color,
            self::FONT_PATH,
            'Hello',
            [],
            $color2,
            true
        );

        $this->assertIsArray($result);
        $this->assertCount(8, $result);
    }

    public function testVerticalGradientColorInterpolation(): void
    {
        $image = $this->createImage();
        $red   = imagecolorallocate($image, 0xFF, 0x00, 0x00);
        $blue  = imagecolorallocate($image, 0x00, 0x00, 0xFF);

        $result = imagettftextgradient(
            $image,
            self::FONT_SIZE,
            self::FONT_ANGLE,
            self::FONT_X,
            self::FONT_Y,
            $red,
            self::FONT_PATH,
            'HELLO',
            [],
            $blue
        );

        $this->assertIsArray($result);

        $textTop    = min($result[5], $result[7]);
        $textBottom = max($result[1], $result[3]);
        $textLeft   = min($result[0], $result[6]);
        $textRight  = max($result[2], $result[4]);
        $band       = max(1, (int)(($textBottom - $textTop) * 0.15));

        [$topRedSum, $topBlueSum, $topCount] = $this->sumRGBInRegion(
            $image,
            $textLeft,
            $textTop,
            $textRight,
            $textTop + $band
        );

        [$botRedSum, $botBlueSum, $botCount] = $this->sumRGBInRegion(
            $image,
            $textLeft,
            $textBottom - $band,
            $textRight,
            $textBottom
        );

        $this->assertGreaterThan(0, $topCount, 'Expected text pixels near the top of the bounding box');
        $this->assertGreaterThan(0, $botCount, 'Expected text pixels near the bottom of the bounding box');
        $this->assertGreaterThan(
            $topBlueSum / $topCount,
            $topRedSum / $topCount,
            'Top pixels should have more red than blue'
        );
        $this->assertGreaterThan(
            $botRedSum / $botCount,
            $botBlueSum / $botCount,
            'Bottom pixels should have more blue than red'
        );
    }

    public function testHorizontalGradientColorInterpolation(): void
    {
        $image = $this->createImage();
        $red   = imagecolorallocate($image, 0xFF, 0x00, 0x00);
        $blue  = imagecolorallocate($image, 0x00, 0x00, 0xFF);

        $result = imagettftextgradient(
            $image,
            self::FONT_SIZE,
            self::FONT_ANGLE,
            self::FONT_X,
            self::FONT_Y,
            $red,
            self::FONT_PATH,
            'HELLO',
            [],
            $blue,
            true
        );

        $this->assertIsArray($result);

        $textTop    = min($result[5], $result[7]);
        $textBottom = max($result[1], $result[3]);
        $textLeft   = min($result[0], $result[6]);
        $textRight  = max($result[2], $result[4]);
        $band       = max(1, (int)(($textRight - $textLeft) * 0.15));

        [$leftRedSum, $leftBlueSum, $leftCount] = $this->sumRGBInRegion(
            $image,
            $textLeft,
            $textTop,
            $textLeft + $band,
            $textBottom
        );

        [$rightRedSum, $rightBlueSum, $rightCount] = $this->sumRGBInRegion(
            $image,
            $textRight - $band,
            $textTop,
            $textRight,
            $textBottom
        );

        $this->assertGreaterThan(0, $leftCount, 'Expected text pixels near the left edge of the bounding box');
        $this->assertGreaterThan(0, $rightCount, 'Expected text pixels near the right edge of the bounding box');
        $this->assertGreaterThan(
            $leftBlueSum / $leftCount,
            $leftRedSum / $leftCount,
            'Left pixels should have more red than blue'
        );
        $this->assertGreaterThan(
            $rightRedSum / $rightCount,
            $rightBlueSum / $rightCount,
            'Right pixels should have more blue than red'
        );
    }

    public function testAlphaColorsAreInterpolated(): void
    {
        $image  = $this->createImage();
        $color  = imagecolorallocatealpha($image, 0xFF, 0x00, 0x00, 0);
        $color2 = imagecolorallocatealpha($image, 0x00, 0x00, 0xFF, 64);

        $result = imagettftextgradient(
            $image,
            self::FONT_SIZE,
            self::FONT_ANGLE,
            self::FONT_X,
            self::FONT_Y,
            $color,
            self::FONT_PATH,
            'Hello',
            [],
            $color2
        );

        $this->assertIsArray($result);
        $this->assertCount(8, $result);
    }

    public function testCalledWithoutGradientColorRendersText(): void
    {
        // Omitting the gradient color defaults it to black (0x000000) internally
        $image = $this->createImage();
        $color = imagecolorallocate($image, 0xFF, 0xFF, 0xFF);

        $result = imagettftextgradient(
            $image,
            self::FONT_SIZE,
            self::FONT_ANGLE,
            self::FONT_X,
            self::FONT_Y,
            $color,
            self::FONT_PATH,
            'Hello'
        );

        $this->assertIsArray($result);
        $this->assertCount(8, $result);
    }

    /**
     * Scans a rectangular region and sums the red and blue channel values of non-black pixels.
     *
     * @return array{0: int, 1: int, 2: int} [redSum, blueSum, pixelCount]
     */
    private function sumRGBInRegion(\GdImage $image, int $x1, int $y1, int $x2, int $y2): array
    {
        $redSum = $blueSum = $count = 0;
        for ($x = $x1; $x <= $x2; $x++) {
            for ($y = $y1; $y <= $y2; $y++) {
                $rgb = imagecolorat($image, $x, $y);
                $r   = ($rgb >> 16) & 0xFF;
                $b   = $rgb & 0xFF;
                if ($r + $b > 0) {
                    $redSum  += $r;
                    $blueSum += $b;
                    $count++;
                }
            }
        }
        return [$redSum, $blueSum, $count];
    }

    private function createImage(): \GdImage
    {
        return imagecreatetruecolor(self::IMAGE_WIDTH, self::IMAGE_HEIGHT);
    }
}