ama-team/php-projection-framework

View on GitHub
src/Conversion/Processor/FXAAProcessor.php

Summary

Maintainability
A
3 hrs
Test Coverage
<?php

namespace AmaTeam\Image\Projection\Conversion\Processor;

use AmaTeam\Image\Projection\API\Conversion\ProcessorInterface;
use AmaTeam\Image\Projection\API\Image\ImageFactoryInterface;
use AmaTeam\Image\Projection\API\SpecificationInterface;
use AmaTeam\Image\Projection\API\Tile\TileInterface;
use AmaTeam\Image\Projection\Conversion\Processor\FXAA\ColorGrid;
use AmaTeam\Image\Projection\Conversion\Processor\FXAA\EdgeLumaCalculator;
use AmaTeam\Image\Projection\Conversion\Processor\FXAA\EdgeOrientationCalculator;
use AmaTeam\Image\Projection\Conversion\Processor\FXAA\EdgeDistanceCalculator;
use AmaTeam\Image\Projection\Conversion\Processor\FXAA\Edge;
use AmaTeam\Image\Projection\Conversion\Processor\FXAA\EdgeDirectionCalculator;
use AmaTeam\Image\Projection\Conversion\Processor\FXAA\LumaGrid;
use AmaTeam\Image\Projection\Conversion\Processor\FXAA\SubPixelOffsetCalculator;
use AmaTeam\Image\Projection\Conversion\Processor\FXAA\TargetColorCalculator;
use AmaTeam\Image\Projection\Image\Utility\Color;
use AmaTeam\Image\Projection\Tile\Tile;

/**
 * @see https://developer.download.nvidia.com/assets/gamedev/files/sdk/11/FXAA_WhitePaper.pdf
 * @see http://blog.simonrodriguez.fr/articles/30-07-2016_implementing_fxaa.html
 */
class FXAAProcessor implements ProcessorInterface
{
    const DEBUG_OFF = 0;
    const DEBUG_EDGE = 1;
    // Horizontal edges are
    const DEBUG_ORIENTATION = 2;
    // Edge is painted in blue, it's contrast opposite - in green
    const DEBUG_PAIR = 3;

    const EDGE_THRESHOLD_TOO_LITTLE = 1 / 3;
    const EDGE_THRESHOLD_LOW_QUALITY = 1 / 4;
    const EDGE_THRESHOLD_HIGH_QUALITY = 1 / 8;
    const EDGE_THRESHOLD_OVERKILL = 1 / 16;

    const MINIMUM_EDGE_THRESHOLD_VISIBLE_LIMIT = 1 / 32;
    const MINIMUM_EDGE_THRESHOLD_HIGH_QUALITY = 1 / 16;
    const MINIMUM_EDGE_THRESHOLD_UPPER_LIMIT = 1 / 12;

    private $edgeThreshold = self::EDGE_THRESHOLD_LOW_QUALITY;
    private $minimumEdgeThreshold = self::MINIMUM_EDGE_THRESHOLD_HIGH_QUALITY;
    private $debug = self::DEBUG_OFF;

    /**
     * @var ImageFactoryInterface
     */
    private $imageFactory;

    /**
     * @param ImageFactoryInterface $imageFactory
     */
    public function __construct(ImageFactoryInterface $imageFactory)
    {
        $this->imageFactory = $imageFactory;
    }

    /**
     * @return float|int
     */
    public function getEdgeThreshold()
    {
        return $this->edgeThreshold;
    }

    /**
     * @param float|int $edgeThreshold
     * @return $this
     */
    public function setEdgeThreshold($edgeThreshold)
    {
        $this->edgeThreshold = $edgeThreshold;
        return $this;
    }

    /**
     * @return float|int
     */
    public function getMinimumEdgeThreshold()
    {
        return $this->minimumEdgeThreshold;
    }

    /**
     * @param float|int $minimumEdgeThreshold
     * @return $this
     */
    public function setMinimumEdgeThreshold($minimumEdgeThreshold)
    {
        $this->minimumEdgeThreshold = $minimumEdgeThreshold;
        return $this;
    }

    /**
     * @inheritDoc
     *
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
     */
    public function process(TileInterface $tile, SpecificationInterface $specification)
    {
        $image = $tile->getImage();
        $width = $image->getWidth();
        $height = $image->getHeight();
        $colorGrid = new ColorGrid();
        $lumaGrid = new LumaGrid();
        $target = $this->imageFactory->create($width, $height);
        $edge = new Edge();
        $edge->image = $image;
        $edge->color = $colorGrid;
        $edge->luma = $lumaGrid;
        for ($x = 0; $x < $width; $x++) {
            for ($y = 0; $y < $height; $y++) {
                $edge->x = $x;
                $edge->y = $y;

                // Gathering 3x3 grid data
                $colorGrid->fill($image, $x, $y);
                $lumaGrid->fill($colorGrid);

                // High luma range tells there is an edge in 3x3 box
                if ($lumaGrid->range < max($lumaGrid->max * $this->edgeThreshold, $this->minimumEdgeThreshold)) {
                    // So if there's no such thing, let's just jump to next pixel
                    $target->setColorAt($x, $y, $colorGrid->center);
                    continue;
                }
                EdgeOrientationCalculator::apply($edge);
                EdgeDirectionCalculator::apply($edge);
                EdgeLumaCalculator::apply($edge);
                EdgeDistanceCalculator::apply($edge);
                $offset = SubPixelOffsetCalculator::calculate($edge);
                switch ($this->debug) {
                    case self::DEBUG_EDGE:
                        $alpha = (int) ($offset * 2 * 255);
                        $yellow = 0xFFFF00FF;
                        $yellow = $yellow & ($alpha | 0xFFFFFF00);
                        $color = Color::blend(0xFF0000FF, $yellow);
                        $target->setColorAt($x, $y, $color);
                        break;
                    case self::DEBUG_ORIENTATION:
                        $color = $edge->horizontal ? 0xFFFF00FF : 0x0000FFFF;
                        $target->setColorAt($x, $y, $color);
                        break;
                    case self::DEBUG_PAIR:
                        $target->setColorAt($x, $y, 0x0000FFFF);
                        $value = $edge->inward ? -1 : 1;
                        $xOffset = $edge->horizontal ? 0 : $value;
                        $yOffset = $edge->horizontal ? $value : 0;
                        $target->setColorAt($x + $xOffset, $y + $yOffset, 0x00FF00FF);
                        break;
                    default:
                        $color = TargetColorCalculator::calculate($edge, $offset);
                        $target->setColorAt($x, $y, $color);
                }
            }
        }
        return new Tile($tile->getPosition(), $target);
    }

    public function setDebugMode($mode)
    {
        $this->debug = $mode;
        return $this;
    }
}