GD Text-Gradient

So, what i’m trying to do is use a gradient to write out text using GD.
The general theory is… put the gradient under the image (scaled to fit the text), put a ‘mask layer’ on top, write the text onto the mask, and make it transparent. Trick is that the transparency doesnt quite work (because the text isnt rendered as single-color pixels but is smoothed), resulting in black border around parts of the gradient text (and in small enough font sizes, removing the gradient entirely). Is there a better way to do this? Or a way to better carve the ‘mask layer’?


<?php
//Size of the text.
$tfbox = imagettfbbox(32,0,"./tahoma.ttf",$_GET['string']);
$newwidth = abs($tfbox[2] - $tfbox[0]);
$newheight = abs($tfbox[7] - $tfbox[1]);

//Canvases
$im = imagecreatefrompng('images/scale.png');
$temp = imagecreatetruecolor($newwidth,$newheight);
$out = imagecreatetruecolor($newwidth,$newheight);

//Color definitions
$grey = imagecolorallocate($out,177,179,180);
$black = imagecolorallocate($out,0,0,0);

//Carve text out of solid grey.
imagefill($out,0,0,$grey);
imagettftext($out,32,0,0,$newheight,$black,"./tahoma.ttf",$_GET['string']);
imagecolortransparent($out,$black);
list($width,$height) = getimagesize('images/scale.jpg');

//Combine Image Assets
imagecopyresized($temp,$im,0,0,0,0,$newwidth,$newheight,$width,$height);
imagecopymerge($temp,$out,0,0,0,0,$newwidth,$newheight,100);

//Output result
header('Content-Type: image/png');
//imagepng($out);
imagepng($temp);

//Cleanup
imagedestroy($temp);
imagedestroy($out);
imagedestroy($im);
?>

(PS: Yes, I know $_GET[‘string’] isnt checked/sanitized/urldecoded)

I have got around that issue before using 2 methods: first, change your black out color to something very close to the background color like this:

$grey = imagecolorallocate($out,177,179,180);
$black = imagecolorallocate($out,177,179,181);

Second, create the image much larger than the final size you need - maybe 5 times as big. Then for your last step scale it back down to the desired size.

So something like this:

$size = 32;
$scaleFactor = 5;


//Size of the text
$tfbox = imagettfbbox($size*$scaleFactor,0,"./tahoma.ttf",$_GET['string']);
$newwidth = abs($tfbox[2] - $tfbox[0]);
$newheight = abs($tfbox[7] - $tfbox[1]);

//Canvases
$im = imagecreatefrompng('images/scale.png');
$temp = imagecreatetruecolor($newwidth,$newheight);
$out = imagecreatetruecolor($newwidth,$newheight);

//Color definitions
$grey = imagecolorallocate($out,177,179,180);
$black = imagecolorallocate($out,177,179,181);

//Carve text out of solid grey.
imagefill($out,0,0,$grey);
imagettftext($out,$size*$scaleFactor,0,0,$newheight,$black,"./tahoma.ttf",$_GET['string']);
imagecolortransparent($out,$black);
list($width,$height) = getimagesize('images/scale.jpg');

//Combine Image Assets
imagecopyresized($temp,$im,0,0,0,0,$newwidth,$newheight,$width,$height);
imagecopymerge($temp,$out,0,0,0,0,$newwidth,$newheight,100);

// size back to final
$final = imagecreatetruecolor($newwidth/$scaleFactor, $newheight/$scaleFactor);
imagecopyresampled($final, $temp, 0, 0, 0, 0, $newwidth/$scaleFactor, $newheight/$scaleFactor, $newwidth, $newheight);

//Output result
header('Content-Type: image/png');
//imagepng($out);
imagepng($final);

//Cleanup
imagedestroy($temp);
imagedestroy($out);
imagedestroy($im);
imagedestroy($final);

Alright, slight addendum.
I decided that what i would then do is try and make the output have a transparent background.

So, just stick imagecolortransparent($temp,$grey); in between “Combine Image Assets” and “Size Back to final”, right?

Except… that doesn’t work. I still get the grey background. Did i miss something blindingly obvious?

Have a look at [fphp]imagealphablending[/fphp] and [fphp]imagesavealpha[/fphp] :slight_smile:

Okay… i’ve looked at these… and they… seem to have had no effect. So i’m guessing that I’m not using the functions correctly.

If i define Grey as being transparent to begin with, it will give me the scale background.
So i need to be able to replace grey with the transparent ‘color’ in the finalized (pre-resizing) image…

Have you thought about ImageMagick?


<?php
// Create gradiant
exec("convert -size 460x65 gradient:red-yellow gradiant.png");

// Create the text to use as a mask
$cmd = " -size 460x65 xc:none -font jd.ttf -pointsize 60 ".
" -draw \\"gravity center fill black text 0,0 \\"Anthony\\" \\" ";
exec("convert $cmd text_mask.png");

// Lay the mask over the gradiant
$cmd = " -compose Dst_In text_mask.png -gravity center gradiant.png -matte ";
exec("composite $cmd text_gradiant.png  ");

// Delete tempory images
unlink("gradiant.png");
unlink("text_mask.png");
?> 

This code is old and probably can be improved but it gives you the idea.

Rubble: if you can do that using the Imagick class (instead of command line, most shared server do not allow running command line commands) then you’re officially awesome :slight_smile: And seeing how easy the Imagick class is to use, I’m sure it won’t be more than about 4 lines of code… :smiley:

I’m sure it won’t be more than about 4 lines of code…
:lol:
I think it would be more than 4 lines !

I am afraid that I am not interested in Imagick as it is still limited and there are few instructions also a lot of them are contradictory as there have been a lot of changes.
In a few years when its sorted it may be useful.

This site has lots of examples: Mikko’s blog and I belive it is the site of the guy writing Imagick.
There are also some other useful examples here: PHP Tutorials Examples Imagick

I have lashed together a quick test from the above website but it is using a white background not transparent and I have copied bits from two pieces of code so it has some redundant code.
When I get a bit of spair time I will take another look at it; it may be possible to avoid saving the gradiant image although it does not take much to delete it.

There was a problem with this line " $canvas->newImage( 310, 70, new ImagickPixel(‘white’));" as the example code had one set of operators( wrong word but I can not think of the correct one ) and my version failed with an error and so I had to change it.


<?php
/* Create and save the gradiant */
$Imagick = new Imagick();
$Imagick->newPseudoImage( 500, 100, "gradient:orange-yellow" );
$Imagick->setImageFormat( 'png' );
$Imagick->writeImage("gradiant.png");

/* Create a new imagick object */
$im = new Imagick( 'gradiant.png' );

/* Create imagickdraw object */
$draw = new ImagickDraw();

/* Start a new pattern called "ice" */
$draw->pushPattern( 'ice'  , 0  , 0  , 50  , 50 );

/* Composite the image on the pattern */
$draw->composite( Imagick::COMPOSITE_OVER, 0, 0, 50, 50, $im );

/* Close the pattern */
$draw->popPattern();

/* Use the pattern called "ice" as the fill */
$draw->setFillPatternURL( '#ice' );

/* Set font size to 52 */
$draw->setFontSize( 52 );

/* Annotate some text */
$draw->annotation( 5, 50, "Hello World!" );

/* Create a new canvas and white image */
$canvas = new Imagick();
$canvas->newImage( 310, 70, new ImagickPixel('white'));

/* Draw the ImagickDraw on to the canvas */
$canvas->drawImage( $draw );

/* Set the format to PNG */
$canvas->setImageFormat( 'png' );

/* Output the image */
$canvas->writeImage("text.png");

unlink("gradiant.png");
?>

<img src="text.png">

WAHOO I am officially awesome :cool:

/* Create and save the gradiant */
$Imagick = new Imagick();
$Imagick->newPseudoImage( 500, 100, "gradient:red-orange" );
$Imagick->setImageFormat( 'png' );
$Imagick->writeImage("gradiant.png");

/* Create and save the canvas */
$im = new Imagick();
$im->newPseudoImage( 500, 100, "null:" );
$im->setImageFormat( 'png' );
$im->writeImage("canvas.png");

/* Add the text to the canvas ( Make the mask )*/
$im = new Imagick( "canvas.png" ); 
$draw = new ImagickDraw();
$draw->setFontSize( 90 ); 
$draw->setFillColor( new ImagickPixel("black"));

// Set gravity to the center.
$draw->setGravity( Imagick::GRAVITY_CENTER ); 
 
// Write the text on the image 
$im->annotateImage( $draw, 0, 0, 0, "Anthony" );

/* Final image */
$canvas = new Imagick( "gradiant.png" ); 
$canvas->compositeImage( $im, imagick::COMPOSITE_DSTIN, 0, 0 );
$canvas->setImageFormat( 'png' );
$canvas->writeImage("final.png");

unlink("canvas.png");
unlink("gradiant.png");
?>

This works and should give someone a starting point BUT some settings may be wrong for your version of Imagick AND in my case setFont() had no effect !
I am sure I have some more faults in the code though and I could not have done it without the example sites linked in my previous post.

1 Like

Hah, you’re officially awesome!

We now need an ‘awesome’ badge that you can wear for a month.

I love Imagick. I use it in my ImageTools library (https://bitbucket.org/fruml/imagetools) and in my CMS for a whole bunch of image manipulations. If Imagick isn’t installed, it the library falls back to GD, but then there are less options ('cos I haven’t implemented the GD versions yet). I much prefer Imagick to GD as it gives better results, and it’s a lot less verbose:

Grayscale in Imagick:


class ImageTransformationGrayscale {

    public static function transform($img, $params) {
        $img->setImageType(Imagick::IMGTYPE_GRAYSCALE);
        return $img;
    }

}

Grayscale in GD:


class ImageTransformationGrayscale {

    public static function transform($gd, $params) {
    
        $params["value"] = !isset($params["value"]) ? 100 : $params["value"];
        $fact = $params["value"] / 100;
        
        $w = imagesx($gd);
        $h = imagesy($gd);
        for($i=0; $i<$h; $i++) {
            for($j=0; $j<$w; $j++) {
                $pos = imagecolorat($gd, $j, $i);
                if (!imageistruecolor($gd)) {
                    $f = imagecolorsforindex($gd, $pos);
                    $gst = $f["red"]*0.15 + $f["green"]*0.5 + $f["blue"]*0.35;
                    list($r, $g, $b) = array($fact * $gst + (1-$fact) * $f["red"],
                                             $fact * $gst + (1-$fact) * $f["green"],
                                             $fact * $gst + (1-$fact) * $f["blue"]);
                    $col = imagecolorexact($gd, $r, $g, $b);
                    if ($col = -1) $col = imagecolorallocate($gd, $r, $g, $b);
                } else {
                    list($r, $g, $b) = array((($pos>>16)&0xFF), (($pos>>8)&0xFF), ($pos&0xFF));
                    $gst = $r*0.15 + $g*0.5 + $b*0.35;
                    $col = imagecolorallocate($gd,
                                              $fact * $gst + (1-$fact) * $r,
                                              $fact * $gst + (1-$fact) * $g,
                                              $fact * $gst + (1-$fact) * $b);
                }
                imagesetpixel($gd, $j, $i, $col);
            }
        }
        return $gd;

    }

}

:smiley:

(sorry for the huge off-topic there :slight_smile: )

Huh??


image_filter($gd, IMG_FILTER_GRAYSCALE);

[fphp]image_filter[/fphp] (PHP > 5)

:cool:

it’s a lot less verbose

That is why I prefer Imagemagick/Imagick over GD as well as a lot more options.

I was just posting a link to that as well ScallioXTX but you beat me to it.
PHP: imagefilter - Manual

Yep, I had actually seen that one too. :slight_smile:

But, I found out that not all hosts have image_filter functions installed/ enabled. (which is strange, as it’s a core part of PHP, isn’t it?). At least, there was some reason why I couldn’t use it…