Skip to content

Instantly share code, notes, and snippets.

@Alanaktion
Last active June 8, 2018 20:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Alanaktion/9df3da0bacd2e619d150e1c7128b652a to your computer and use it in GitHub Desktop.
Save Alanaktion/9df3da0bacd2e619d150e1c7128b652a to your computer and use it in GitHub Desktop.
Messing with basic blurs in PHP
<?php
/**
* Output a map
*
* @param array $map
* @param bool $int Round output to integer values
*/
function out(array $map, $int = true) {
foreach($map as $r) {
foreach($r as $p) {
if ($int) {
echo round($p), " ";
} else {
echo "$p ";
}
}
echo "\n";
}
}
/**
* Generate a blank map
*
* @param int $width
* @param int $height
*/
function canvas($width, $height) {
$row = array_fill(0, $width, 0);
return array_fill(0, $height, $row);
}
/**
* Blur values horizontally
*
* This could be altered to make $dest = $src on start and return $dest instead
* of having it as a parameter
*
* @param array $src
* @param array &$dest
* @param int $radius
*/
function bHoriz($src, &$dest, $radius) {
$height = count($src);
$width = count($src[0]);
for ($y = 0; $y < $height; ++$y) {
for ($x = 0; $x < $width; ++$x) {
$total = 0;
for ($kx = -$radius; $kx <= $radius; ++$kx) {
if (array_key_exists($x + $kx, $src[$y])) {
$total += $src[$y][$x + $kx];
}
}
$dest[$y][$x] = $total / ($radius * 2 + 1);
}
}
}
/**
* Blur values horizontally, reusing values where possible
*
* @param array $src
* @param array &$dest
* @param int $radius
*/
function bHorizFast($src, &$dest, $radius) {
$height = count($src);
$width = count($src[0]);
for ($y = 0; $y < $height; ++$y) {
$total = 0;
// Process entire window for first pixel
for ($kx = -$radius; $kx <= $radius; ++$kx) {
if (array_key_exists($kx, $src[$y])) {
$total += $src[$y][$kx];
}
}
$src[$y][0] = $total / ($radius * 2 + 1);
// Subsequent pixels just update window total
for ($x = 1; $x < $width; ++$x) {
// Subtract pixel leaving window
if (array_key_exists($x - $radius - 1, $src[$y])) {
$total -= $src[$y][$x - $radius - 1];
}
// Add pixel entering window
if (array_key_exists($x + $radius, $src[$y])) {
$total += $src[$y][$x + $radius];
}
// Can't go *too* low now
if ($total < 0) {
$total = 0;
}
$dest[$y][$x] = $total / ($radius * 2 + 1);
}
}
}
/**
* Blur values vertically
*
* This could be altered to make $dest = $src on start and return $dest instead
* of having it as a parameter
*
* @param array $src
* @param array &$dest
* @param int $radius
*/
function bVert($src, &$dest, $radius) {
$height = count($src);
$width = count($src[0]);
for ($x = 0; $x < $width; ++$x) {
for ($y = 0; $y < $height; ++$y) {
$total = 0;
for ($ky = -$radius; $ky <= $radius; ++$ky) {
if (array_key_exists($y + $ky, $src)) {
$total += $src[$y + $ky][$x];
}
}
$dest[$y][$x] = $total / ($radius * 2 + 1);
}
}
}
/**
* Blur values horizontally, reusing values where possible
*
* @param array $src
* @param array &$dest
* @param int $radius
*/
function bVertFast($src, &$dest, $radius) {
$height = count($src);
$width = count($src[0]);
for ($x = 0; $x < $width; ++$x) {
$total = 0;
// Process entire window for first pixel
for ($ky = -$radius; $ky <= $radius; ++$ky) {
if (array_key_exists($ky, $src)) {
$total += $src[$ky][$x];
}
}
$src[0][$x] = $total / ($radius * 2 + 1);
// Subsequent pixels just update window total
for ($y = 1; $y < $height; ++$y) {
// Subtract pixel leaving window
if (array_key_exists($y - $radius - 1, $src)) {
$total -= $src[$y - $radius - 1][$x];
}
// Add pixel entering window
if (array_key_exists($y + $radius, $src)) {
$total += $src[$y + $radius][$x];
}
// Can't go *too* low now
if ($total < 0) {
$total = 0;
}
$dest[$y][$x] = $total / ($radius * 2 + 1);
}
}
}
/**
* Transpose image (flip X and Y coordinates)
*
* @param array $img
* @return array
*/
function transpose($img) {
return array_map(null, ...$img);
// Non-5.6+ version:
array_unshift($img, null);
return call_user_func_array('array_map', $img);
}
/**
* Blur values in both directions
*
* @param array $src
* @param array $dest
* @param int $radius
*/
function blur($src, &$dest, $radius) {
$temp = $src;
bHoriz($src, $temp, $radius);
bVert($temp, $dest, $radius);
}
/**
* Blur values in both directions, optimized
*
* @param array $src
* @param array $dest
* @param int $radius
*/
function blurFast($src, &$dest, $radius) {
$temp = $src;
bHorizFast($src, $temp, $radius);
$temp = transpose($temp);
bHorizFast($temp, $dest, $radius);
$dest = transpose($dest);
}
/**
* Blur values in both directions, optimized more
*
* @param array $src
* @param array $dest
* @param int $radius
*/
function blurFaster($src, &$dest, $radius) {
$temp = $src;
bHorizFast($src, $temp, $radius);
bVertFast($temp, $dest, $radius);
}
$src = [ // 16x16 0-9
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,9,9,9,9,9,9,9,9,0,0,0,0],
[0,0,0,0,9,9,9,9,9,9,9,9,0,0,0,0],
[0,0,0,0,9,9,9,9,9,9,9,9,0,0,0,0],
[0,0,0,0,9,9,9,9,9,9,9,9,0,0,0,0],
[0,0,0,0,9,9,9,9,9,9,9,9,0,0,0,0],
[0,0,0,0,9,9,9,9,9,9,9,9,0,0,0,0],
[0,0,0,0,9,9,9,9,9,9,9,9,0,0,0,0],
[0,0,0,0,9,9,9,9,9,9,9,9,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
];
$dest = canvas(16, 16);
echo "src:\n";
out($src);
echo "blur:\n";
blur($src, $dest, 3);
$b1 = $dest;
out($dest);
echo "blurFast:\n";
blurFast($src, $dest, 3);
$b2 = $dest;
out($dest);
echo "blurFaster:\n";
blurFaster($src, $dest, 3);
$b2 = $dest;
out($dest);
/*echo "\n";
echo "speed test, 10K ops...\n";
$s1 = microtime(true);
for($i=0;$i<9999;$i++) {
blur($src, $dest, 3);
}
$s2 = microtime(true);
for($i=0;$i<9999;$i++) {
blurFast($src, $dest, 3);
}
$s3 = microtime(true);
for($i=0;$i<9999;$i++) {
blurFaster($src, $dest, 3);
}
$s4 = microtime(true);
echo "base: " . round($s2 - $s1, 4) . "ms/10000 ops\n";
echo "fast: " . round($s3 - $s2, 4) . "ms/10000 ops\n";
echo "notr: " . round($s4 - $s3, 4) . "ms/10000 ops\n";*/
/*echo "\n";
echo "Checking results...\n";
$fail = false;
foreach ($b1 as $y=>$r) {
foreach ($r as $x=>$p) {
if (round($b1[$y][$x], 2) != round($b2[$y][$x], 2)) {
$fail = true;
echo "Pixel $x,$y does not match!\n";
echo "Values: {$b1[$y][$x]}, {$b2[$y][$x]}\n";
}
}
}
if ($fail) {
echo "FAIL\n";
} else {
echo "PASS!\n";
}*/
@Alanaktion
Copy link
Author

Speed test, 10K ops, PHP 7.1.0RC3 on an A10-5800K

blur       3.4508ms/10000 ops
blurFast   1.6458ms/10000 ops
blurFaster 1.6235ms/10000 ops

@Alanaktion
Copy link
Author

This was just done on PHP arrays as a proof-of-concept. I also have a full RGB implementation using PHP GD.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment