PrivateBin/vendor/jdenticon/jdenticon/src/Canvas/Rasterization/Rasterizer.php

380 lines
14 KiB
PHP

<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Canvas\Rasterization;
use Jdenticon\Canvas\ColorUtils;
use Jdenticon\Canvas\Rasterization\LayerManager;
use Jdenticon\Canvas\Rasterization\SuperSampleBuffer;
use Jdenticon\Canvas\Rasterization\SuperSampleRange;
use Jdenticon\Canvas\Rasterization\EdgeSuperSampleIntersection;
class Rasterizer
{
/**
* A higher number of samples per pixel horizontally does not affect the
* performance in the same way as SAMPLES_PER_PIXEL_Y, since the rasterizer
* does not scan every subpixel horizontally.
*/
const SAMPLES_PER_PIXEL_X = 10;
/**
* A higher number of samples vertically means lower performance, since
* the rasterizer does a scan for every subpixel vertically.
*/
const SAMPLES_PER_PIXEL_Y = 3;
const SAMPLE_HEIGHT = 0.33333; // 1 / self::SAMPLES_PER_PIXEL_Y
/**
* Rasterizes the edges in the edge table to a list of color ranges. No
* range will span multiple scanlines.
*/
public static function rasterize(& $colorData, $edgeTable, $width, $height)
{
$edgeTable->sort();
$superSampleBuffer = new SuperSampleBuffer(
$width, self::SAMPLES_PER_PIXEL_X);
$layers = array();
$color = 0;
// Keeps track of how many of the subpixellayers that are used for
// the currently rendered scanline. Until a range requiring
// supersampling is encountered only a single layer is needed.
$usedLayers = 0;
for ($i = 0; $i < self::SAMPLES_PER_PIXEL_Y; $i++) {
$layers[] = new LayerManager();
}
for ($ey = 0; $ey < $height; $ey++) {
$scanline = $edgeTable->getScanline($ey);
if ($scanline === null) {
$colorData[] = $width;
$colorData[] = 0;
continue;
}
$superSampleRanges = self::getSuperSampleRanges($scanline, $width);
$superSampleRangeCount = count($superSampleRanges);
foreach ($layers as $layer) {
$layer->topLayer = null;
$layer->color = ColorUtils::TRANSPARENT;
}
$usedLayers = 1;
if ($superSampleRanges[0]->fromX) {
$colorData[] = $superSampleRanges[0]->fromX;
$colorData[] = 0;
}
for (
$rangeIndex = 0;
$rangeIndex < $superSampleRangeCount;
$rangeIndex++
) {
$superSampleRange = $superSampleRanges[$rangeIndex];
$edge = $superSampleRange->edges[0];
// If there is exactly one edge in the supersample range, and it
// is crossing the entire scanline, we can perform the
// antialiasing by integrating the edge function.
if (!isset($superSampleRange->edges[1]) && (
$edge->y0 <= $ey && $edge->y1 >= $ey + 1 ||
$edge->y0 >= $ey + 1 && $edge->y1 <= $ey
)) {
// Determine the lower and upper x value where the edge
// intersects the scanline.
$xey = $edge->intersection($ey);
$xey1 = $edge->intersection($ey + 1);
if ($xey < $xey1) {
$x0 = $xey;
$x1 = $xey1;
} else {
$x0 = $xey1;
$x1 = $xey;
}
$rangeWidth = $x1 - $x0;
if ($usedLayers === 1) {
$subScanlineLayers = $layers[0];
$fromColor = $subScanlineLayers->color;
$subScanlineLayers->add($edge);
$toColor = $subScanlineLayers->color;
} else {
$fromColorR = 0;
$fromColorG = 0;
$fromColorB = 0;
$fromColorA = 0;
$toColorR = 0;
$toColorG = 0;
$toColorB = 0;
$toColorA = 0;
// Compute the average color of all subpixel layers
// before and after the edge intersection.
// The calculation is inlined for increased performance.
for ($i = 0; $i < $usedLayers; $i++) {
$subScanlineLayers = $layers[$i];
// Add to average from-color
$color = $subScanlineLayers->color;
$alpha = $color & 0xff;
if ($alpha > 0) {
$fromColorA += $alpha;
$fromColorR += (($color >> 24) & 0xff) * $alpha;
$fromColorG += (($color >> 16) & 0xff) * $alpha;
$fromColorB += (($color >> 8) & 0xff) * $alpha;
}
// Add the new layer
$subScanlineLayers->add($edge);
// Add to average to-color
$color = $subScanlineLayers->color;
$alpha = $color & 0xff;
if ($alpha > 0) {
$toColorA += $alpha;
$toColorR += (($color >> 24) & 0xff) * $alpha;
$toColorG += (($color >> 16) & 0xff) * $alpha;
$toColorB += (($color >> 8) & 0xff) * $alpha;
}
}
$fromColor = $fromColorA === 0 ? 0 : ColorUtils::from(
(int)($fromColorA / $usedLayers),
(int)($fromColorR / $fromColorA),
(int)($fromColorG / $fromColorA),
(int)($fromColorB / $fromColorA));
$toColor = $toColorA === 0 ? 0 : ColorUtils::from(
(int)($toColorA / $usedLayers),
(int)($toColorR / $toColorA),
(int)($toColorG / $toColorA),
(int)($toColorB / $toColorA));
}
// Render pixels
for (
$x = $superSampleRange->fromX;
$x < $superSampleRange->toXExcl;
$x++
) {
if ($x0 >= $x + 1) {
// Pixel not covered
$colorData[] = 1;
$colorData[] = $fromColor;
continue;
}
if ($x1 <= $x) {
// Pixel fully covered
$colorData[] = 1;
$colorData[] = $toColor;
continue;
}
// toColor coverage in the range [0.0, 1.0]
// Initialize to the fully covered range of the pixel.
$coverage = $x1 < $x + 1 ? $x + 1 - $x1 : 0;
// Compute integral for non-vertical edges
if ($rangeWidth > 0.001) {
// Range to integrate
$integralFrom = $x0 > $x ? $x0 : $x;;
$integralTo = $x1 < $x + 1 ? $x1 : $x + 1;
$coverage +=
(
(
$integralTo * $integralTo -
$integralFrom * $integralFrom
) / 2 +
$x0 * ($integralFrom - $integralTo)
) / $rangeWidth;
}
$colorData[] = 1;
$colorData[] = ColorUtils::mix(
$fromColor, $toColor, $coverage);
}
$color = $toColor;
} // /simplified antialiasing
else {
// Super sampling
$y = $ey + self::SAMPLE_HEIGHT / 2;
// Ensure all subpixel layers are initialized
while ($usedLayers < self::SAMPLES_PER_PIXEL_Y) {
$layers[0]->copyTo($layers[$usedLayers]);
$usedLayers++;
}
foreach ($layers as $subScanlineLayers) {
$color = $subScanlineLayers->color;
$intersections = self::getIntersections(
$superSampleRange->edges, $y);
foreach ($intersections as $intersection) {
$superSampleBuffer->add($color,
$intersection->x - $superSampleRange->fromX);
$subScanlineLayers->add($intersection->edge);
$color = $subScanlineLayers->color;
}
$superSampleBuffer->add(
$color, $superSampleRange->width + 1);
$superSampleBuffer->rewind();
$y += self::SAMPLE_HEIGHT;
} // /subpixel
// Blend subpixels
$color = $superSampleBuffer->colorAt(
$superSampleRange->width);
$superSampleBuffer->emptyTo(
$colorData, $superSampleRange->width);
//$color = end($colorData);
} // /super sampling
// Forward last color
if ($rangeIndex + 1 < $superSampleRangeCount) {
$count =
$superSampleRanges[$rangeIndex + 1]->fromX -
$superSampleRange->toXExcl;
if ($count > 0) {
$colorData[] = $count;
$colorData[] = $color;
}
} else {
$count = $width - $superSampleRange->toXExcl;
if ($count > 0) {
$colorData[] = $count;
$colorData[] = $color;
}
}
} // /range
}
return $colorData;
}
private static function intersection_cmp($a, $b)
{
if ($a->x < $b->x) {
return -1;
}
if ($a->x > $b->x) {
return 1;
}
return 0;
}
/**
* Determines what edges that intersect a horizontal line with the specified
* y coordinate. For each intersecting edge the intersecting x coordinate is
* returned.
*
* @param array $edges Array of edges in the current scanline.
* @param int $y Y coordinate of the current scanline.
* @return array Array containing EdgeSuperSampleIntersection. Objects
* are sorted ascending by x coordinate.
*/
private static function getIntersections($edges, $y)
{
$intersections = array();
foreach ($edges as $edge) {
if ($edge->y0 < $y && $edge->y1 >= $y ||
$edge->y0 >= $y && $edge->y1 < $y
) {
$x = $edge->x0 +
($edge->x1 - $edge->x0) * ($y - $edge->y0) /
($edge->y1 - $edge->y0);
$intersections[] = new EdgeSuperSampleIntersection($x, $edge);
}
}
usort($intersections, array(
'Jdenticon\\Canvas\\Rasterization\\Rasterizer',
'intersection_cmp'));
return $intersections;
}
/**
* Determines what ranges of a scanline that needs to be supersampled.
*
* @param array $scanline Array of edges in the current scanline.
* @return array Array of SuperSampleRange.
*/
private static function getSuperSampleRanges(&$scanline, $width)
{
$superSampleRanges = array();
$rangeIndex = 0;
$scanlineCount = count($scanline);
while ($rangeIndex < $scanlineCount) {
$range = $scanline[$rangeIndex];
if ($range->fromX >= $width) {
break;
}
$superSampleRange = new SuperSampleRange(
$range->fromX,
$range->fromX + $range->width
);
$superSampleRange->edges[] = $range->edge;
$rangeIndex++;
for ($i = $rangeIndex; $i < $scanlineCount; $i++) {
$range = $scanline[$i];
if ($range->fromX < $superSampleRange->toXExcl) {
$superSampleRange->toXExcl = max(
$superSampleRange->toXExcl,
$range->fromX + $range->width);
$superSampleRange->edges[] = $range->edge;
$rangeIndex++;
} else {
break;
}
}
$superSampleRange->toXExcl = min($superSampleRange->toXExcl, $width);
$superSampleRange->width =
$superSampleRange->toXExcl - $superSampleRange->fromX;
$superSampleRanges[] = $superSampleRange;
}
return $superSampleRanges;
}
}