Reputation: 2271
When I'm using PHP's GD image library to draw shapes it always shows hard edges. I've tried to use GD's imageantialias()
function but that is for straight lines only.
In order to solve the problem, I've searched some anti-aliasing algorithms and found FXAA works pretty well so I'm going to give it a try. I tried to port the FXAA anti-aliasing filter from the GLSL shader here.
Then, when I finished porting the FXAA shader to PHP, it doesn't give me the correct result. I test the FXAA filter using the imagecolorallocatealpha()
example on PHP.net:
<?php
require('fxaa.php');
$size = 300;
$image=imagecreatetruecolor($size, $size);
// something to get a white background with black border
$back = imagecolorallocate($image, 255, 255, 255);
$border = imagecolorallocate($image, 0, 0, 0);
imagefilledrectangle($image, 0, 0, $size - 1, $size - 1, $back);
imagerectangle($image, 0, 0, $size - 1, $size - 1, $border);
$yellow_x = 100;
$yellow_y = 75;
$red_x = 120;
$red_y = 165;
$blue_x = 187;
$blue_y = 125;
$radius = 150;
// allocate colors with alpha values
$yellow = imagecolorallocatealpha($image, 255, 255, 0, 75);
$red = imagecolorallocatealpha($image, 255, 0, 0, 75);
$blue = imagecolorallocatealpha($image, 0, 0, 255, 75);
// drawing 3 overlapped circle
imagefilledellipse($image, $yellow_x, $yellow_y, $radius, $radius, $yellow);
imagefilledellipse($image, $red_x, $red_y, $radius, $radius, $red);
imagefilledellipse($image, $blue_x, $blue_y, $radius, $radius, $blue);
FXAA::process($image);
// don't forget to output a correct header!
header('Content-Type: image/png');
// and finally, output the result
imagepng($image);
imagedestroy($image);
?>
Here's the original image:
And this is the processed image:
Here's another test image. (The left is FXAA-processed and the right is not.)
The example images' colour are messed up with the background and the edges is smoothened too much. This is not the expected result as I think. I don't understand what's wrong with my code so I seek for your help.
Also, here's the FXAA class I wrote & the original GLSL shader:
<?php
class FXAA {
const FXAA_REDUCE_MIN = 0.0078125;
const FXAA_REDUCE_MUL = 0.125;
const FXAA_SPAN_MAX = 8;
static $w = 0;
static $h = 0;
private static function add($a, $b){
return array($a[0] + $b[0], $a[1] + $b[1], $a[2] + $b[2]);
}
private static function dot($a, $b){
return $a[0] * $b[0] + $a[1] * $b[1] + $a[2] * $b[2];
}
private static function texture2D($img, $pos){
if(($pos[0] >= self::$w || $pos[0] < 0) || ($pos[1] >= self::$h || $pos[1] < 0)){
return array(0, 0, 0, 0);
}
$color = imagecolorat($img, $pos[0], $pos[1]);
$a = ($color >> 24) & 0xFF;
$r = ($color >> 16) & 0xFF;
$g = ($color >> 8) & 0xFF;
$b = $color & 0xFF;
return array($r, $g, $b, $a);
}
public static function process($img){
self::$w = imagesx($img);
self::$h = imagesy($img);
for($x = 0; $x < imagesx($img); $x++){
for($y = 0; $y < imagesy($img); $y++){
$rgbNW = self::texture2D($img,array($x - 1, $y - 1));
$rgbNE = self::texture2D($img,array($x + 1, $y - 1));
$rgbSW = self::texture2D($img,array($x - 1, $y + 1));
$rgbSE = self::texture2D($img,array($x + 1, $y + 1));
$rgbaM = $rgbM = self::texture2D($img,array($x, $y));
$opacity = array_pop($rgbM);
$luma = array(76, 149, 29);
$lumaNW = self::dot($rgbNW, $luma);
$lumaNE = self::dot($rgbNE, $luma);
$lumaSW = self::dot($rgbSW, $luma);
$lumaSE = self::dot($rgbSE, $luma);
$lumaM = self::dot($rgbM, $luma);
$lumaMin = min($lumaM, min(min($lumaNW, $lumaNE ), min($lumaSW, $lumaSE)));
$lumaMax = max($lumaM, max(max($lumaNW, $lumaNE), max($lumaSW, $lumaSE)));
$dir = array(
-(($lumaNW + $lumaNE) - ($lumaSW + $lumaSE)),
(($lumaNW + $lumaSW) - ($lumaNE + $lumaSE))
);
$dirReduce = max(($lumaNW + $lumaNE + $lumaSW + $lumaSE ) * ( 0.25 * self::FXAA_REDUCE_MUL ), self::FXAA_REDUCE_MIN);
$rcpDirMin = 1 / (min(abs($dir[0]), abs($dir[1])) + $dirReduce);
$dir[0] = min(self::FXAA_SPAN_MAX, max(-self::FXAA_SPAN_MAX, $dir[0] * $rcpDirMin));
$dir[1] = min(self::FXAA_SPAN_MAX, max(-self::FXAA_SPAN_MAX, $dir[1] * $rcpDirMin));
$rgbA = self::add(
self::texture2D($img, array($x + $dir[0] * (1 / 3 - 0.5), $y + $dir[1] * (1 / 3 - 0.5))),
self::texture2D($img, array($x + $dir[0] * (2 / 3 - 0.5), $y + $dir[1] * (1 / 3 - 0.5)))
);
$rgbA[0] *= 0.5;
$rgbA[1] *= 0.5;
$rgbA[2] *= 0.5;
$rgbB = self::add(
self::texture2D($img, array($x + $dir[0] * -0.5, $y + $dir[1] * -0.5)),
self::texture2D($img, array($x + $dir[0] * 0.5, $y + $dir[1] * 0.5))
);
$rgbB[0] = $rgbB[0] * 0.25 + $rgbA[0] * 0.5;
$rgbB[1] = $rgbB[1] * 0.25 + $rgbA[1] * 0.5;
$rgbB[2] = $rgbB[2] * 0.25 + $rgbA[2] * 0.5;
$lumaB = self::dot($rgbB, $luma);
if(($lumaB < $lumaMin) || ($lumaB > $lumaMax)){
imagesetpixel($img, $x, $y, imagecolorallocatealpha($img, $rgbA[0], $rgbA[1], $rgbA[2], $opacity));
}
else {
imagesetpixel($img, $x, $y, imagecolorallocatealpha($img, $rgbB[0], $rgbB[1], $rgbB[2], $opacity));
}
}
}
}
}
Original GLSL Shader:
uniform sampler2D tDiffuse;
uniform vec2 resolution;
varying vec2 vUv;
#define FXAA_REDUCE_MIN (1.0/128.0)
#define FXAA_REDUCE_MUL (1.0/8.0)
#define FXAA_SPAN_MAX 8.0
void main() {
vec3 rgbNW = texture2D( tDiffuse, ( gl_FragCoord.xy + vec2( -1.0, -1.0 ) ) * resolution ).xyz;
vec3 rgbNE = texture2D( tDiffuse, ( gl_FragCoord.xy + vec2( 1.0, -1.0 ) ) * resolution ).xyz;
vec3 rgbSW = texture2D( tDiffuse, ( gl_FragCoord.xy + vec2( -1.0, 1.0 ) ) * resolution ).xyz;
vec3 rgbSE = texture2D( tDiffuse, ( gl_FragCoord.xy + vec2( 1.0, 1.0 ) ) * resolution ).xyz;
vec4 rgbaM = texture2D( tDiffuse, gl_FragCoord.xy * resolution );
vec3 rgbM = rgbaM.xyz;
float opacity = rgbaM.w;
vec3 luma = vec3( 0.299, 0.587, 0.114 );
float lumaNW = dot( rgbNW, luma );
float lumaNE = dot( rgbNE, luma );
float lumaSW = dot( rgbSW, luma );
float lumaSE = dot( rgbSE, luma );
float lumaM = dot( rgbM, luma );
float lumaMin = min( lumaM, min( min( lumaNW, lumaNE ), min( lumaSW, lumaSE ) ) );
float lumaMax = max( lumaM, max( max( lumaNW, lumaNE) , max( lumaSW, lumaSE ) ) );
vec2 dir;
dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));
dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));
float dirReduce = max( ( lumaNW + lumaNE + lumaSW + lumaSE ) * ( 0.25 * FXAA_REDUCE_MUL ), FXAA_REDUCE_MIN );
float rcpDirMin = 1.0 / ( min( abs( dir.x ), abs( dir.y ) ) + dirReduce );
dir = min( vec2( FXAA_SPAN_MAX, FXAA_SPAN_MAX),max( vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX),dir * rcpDirMin)) * resolution;
vec3 rgbA = 0.5 * (
texture2D( tDiffuse, gl_FragCoord.xy * resolution + dir * ( 1.0 / 3.0 - 0.5 ) ).xyz +
texture2D( tDiffuse, gl_FragCoord.xy * resolution + dir * ( 2.0 / 3.0 - 0.5 ) ).xyz );
vec3 rgbB = rgbA * 0.5 + 0.25 * (
texture2D( tDiffuse, gl_FragCoord.xy * resolution + dir * -0.5 ).xyz +
texture2D( tDiffuse, gl_FragCoord.xy * resolution + dir * 0.5 ).xyz );
float lumaB = dot( rgbB, luma );
if ( ( lumaB < lumaMin ) || ( lumaB > lumaMax ) ) {
gl_FragColor = vec4( rgbA, opacity );
} else {
gl_FragColor = vec4( rgbB, opacity );
}
}
Upvotes: 3
Views: 350
Reputation: 578
The problem with your code is that it operates on a single image, and thus the anti-aliasing operation reads its own output.
Drawing the output of the anti-aliasing operation into a a new image will work.
Upvotes: 3