Jump to content

Recommended Posts

I need to draw various font sizes onto a canvas to create a web service. Wanting to protect my HD assets, the intent is to use PHP to populate the image with the necessary text, and then scale down the image before presenting it to the user.

A similar service already exists, a tool to allow the public to make user generated content for a card game.

The problem ever, is that PHP does not seem to handle font sizes very well. I created this sample code to demonstrate my issue.
Here is a sample output from the code below
 

<?php
    $img = imagecreatetruecolor(750, 530);
    
    $black = imagecolorallocate($img, 0, 0, 0);
    $gray = imagecolorallocate($img, 125, 125, 125);
    $white = imagecolorallocate($img, 255, 255, 255);
    
    $font = realpath('../fonts/micross.ttf');
    
    $size = 12;
    $spacing = 20;
    
    for ($x = 0; $x <= 25; $x++) {
        $box = imageftbbox($size + $x/10, 0, $font, "The longer the phase the more apparent the size difference should be");
        $boxWidth = $box[2] - $box[0];
        
        imagefilledrectangle($img, 5, $x*$spacing+5, $boxWidth+5, ($x+1)*$spacing+5, $gray);
        
        imagefttext($img, $size + $x/10, 0, 5, $spacing*($x+1), $white, $font, "The longer the phase the more apparent the size difference should be Font Size ".($size+($x/10)));
    }
    
    imagejpeg($img);
    imagedestroy($img);
?>

You can see how despite the font size steadily increasing by 0.1, it sporadically jumps at what seem at first like random intervals, however if you increase the number of loops and log the data, you can see that it alternates between increasing every 0.7, and 0.8. Unfortunately that doesn't help me any, just some insight.

if($last != $boxWidth)
{
    $last = $boxWidth;
    echo $boxWidth." ".($size+($x/10))."<br>";
}

This outputs the current width and font size each time the text width changes.
I've compiled a sample output for you guys to look at here

Hopefully I've provided enough information to get some help, this has been very frustrating.

2 hours ago, BlackDragonIV said:

The problem ever, is that PHP does not seem to handle font sizes very well.

Yeah. Though technically it's libgd doing the work, not PHP itself.
Stick with integral font sizes. Or you can upscale the image even further.

But you haven't mentioned what you're trying to get out of this. What is your actual goal here?

Well, now that I'm armed with the information at hand, I've got a few options:

  • Settle with what I've got and work in increments of 0.75pt size
  • Try the imagemagick functions
  • Upscale & Downscale
  • Use an entirely different language to build the card image with a better font renderer and then pull the image into the site via PHP

Any thoughts or opinions?

Edited by BlackDragonIV
Accidentally posted prematurely with CTRL+ENTER

You can settle for 0.75pt, if that's an option. Honestly I would still stick with 1pt increments, given that I wasn't able to see an explanation when I checked the libgd source (which actually shells out this work to the FreeType library).

ImageMagick is rather good, but requires a bit more stuff installed on the server, and using it tends to involve calling the program - it's not often used as a library like GD. If you need to do this "seriously" then I recommend it.

Upscaling would be easy and get you the 0.1pt resolution you want. At the expense of higher memory usage, but that may not be a problem.

Going to an entirely different language seems like a lot of effort and is, frankly, an overreaction.

I decided to make a sample program using ImageMagick to test it's font-size loss and got significantly better results.

I've updated the spreadsheet from earlier to include my ImageMagick results, GD has a font-size pool of only 13.33%, where ImageMagick has a font-size pool of 54.5%. That's a 4x increase!

<?php
function setFont($fillColor, $backgroundColor) {

    $draw = new \ImagickDraw();
    $imagick = new \Imagick();
    
    $draw->setStrokeColor('none');
    $draw->setFillColor($fillColor);
    $draw->setTextAntialias(true);

    $draw->setFont("../fonts/pala.ttf");
    //$draw->setFont("../fonts/micross.ttf");
    
    $text = "The longer the phrase the more apparent the size should be";
    $startSize = 12;
    $loops = 200;
    
    $draw->setFontSize($startSize + ($loops*0.1));
    $fontMetrics = $imagick->queryFontMetrics($draw, $text." Width: 9999");
    
    //var_dump($fontMetrics);
    //echo "Length of array: ".sizeof($fontMetrics)."<br>";
    
    $width = $fontMetrics["textWidth"];//->textWidth();
    $spacing = $fontMetrics["textHeight"];//->textWidth();
    
    $last = 0;
    
    for($x = 0; $x < $loops; $x++)
    {
        $draw->setFontSize($startSize + ($x*0.1));
        
        $fontMetrics = $imagick->queryFontMetrics($draw, $text);
        $width = $fontMetrics["textWidth"];
        
        if($last != $width)
        {
            $last = $width;
            //echo $width." ".($startSize+($x*0.1))."<br>";
        }
        
        $draw->annotation(0, $spacing*($x+1), $text." Width: ".$fontMetrics["textWidth"]);
    }

    $fontMetrics = $imagick->queryFontMetrics($draw, $text." Width: ".$fontMetrics["textWidth"]);
    $width = $fontMetrics["textWidth"];
  
    $imagick->newImage($width, $spacing*$loops, $backgroundColor);
    $imagick->setImageFormat("png");
    $imagick->drawImage($draw);

    header("Content-Type: image/png");
    echo $imagick->getImageBlob();
}
setFont("black","white");
?>

Here's that new sample code.
 

3 hours ago, Barand said:

Don't know what you're trying to produce but have you considered SVG instead of bitmapped graphics? They rescale better.

Unfortunately since I am only a community member of this card game I am unable to acquire SVG graphics -- Needless to say, if I can just draw SVG text onto my bitmaps, then I don't see why that wouldn't work.
 

3 hours ago, requinix said:

Upscaling would be easy and get you the 0.1pt resolution you want. At the expense of higher memory usage, but that may not be a problem.

I think re-scaling would introduce artifacts into the background images (But I haven't tested it). I considered re-scaling the font by itself before layering it onto the card, but I don't think GD supports a transparent canvas.

Edited by BlackDragonIV
Fixed code
3 hours ago, BlackDragonIV said:

I think re-scaling would introduce artifacts into the background images (But I haven't tested it). I considered re-scaling the font by itself before layering it onto the card, but I don't think GD supports a transparent canvas.

Not upscale the whole image - just the parts that involve rendering text. Scale up, add text and draw stuff, scale down, layer on images and whatever other assets. And GD does support transparency, but it's a bit tricky to get working.

All in all ImageMagick is just generally better quality. GD is more about quick and dirty.

On the whole the dimensions returned by imagettfbbox() are pretty accurate. In the image below, the rectangles and baseline position are the dimensions returned by imagettfbbox().  The text then output into the box at the baseline position to check. ("Broadway" font is a couple of pixels out but the rest are spot on) Note that all coordinates returned for the bbox are integers.

image.thumb.png.c88963f58ea5edd862f9bf6cd062e007.png

The code used:

<?php
 
$fonts = [ 
           'arial'  => 'c:/windows/fonts/arial.ttf',
           'verdana'  => 'c:/windows/fonts/verdana.ttf',
           'broadway'  => 'c:/windows/fonts/broadw.ttf',
           'gabriola' => 'c:/windows/fonts/gabriola.ttf',
           'vivaldi'  => 'c:/windows/fonts/vivaldii.ttf',
         ];
$fsize = 42.5;
$str = 'The quick brown lazy fox';

foreach ($fonts as $fam => $font) {
    echo textInABox($fam, $font, $fsize, $str) . '<br><br>';
}

function textInABox($fam, $font, $fsize, $str)
{
    list($wd, $ht, $asc, $tx) = array_values(metrics($font,$fsize,$str));
    return <<<SVG
        <svg width='$wd' height='$ht' viewBox='0 0 $wd $ht'>
        <style type='text/css'>
           .txt { fill:#A91723; }
        </style>
        <rect x='0' y='0' width='$wd' height='$ht' fill='#FFF' stroke='#000'/>
        <text class='txt' x='$tx' y='$asc' style='font-family:"{$fam}"; font-size:{$fsize}pt'>$str</text>
        <path d='M 0 $asc l $wd 0' stroke='#ccc'/>
        </svg>
        &emsp;$fam  {$fsize}pt&emsp;$wd x $ht 
SVG;
}

function metrics($font, $fsize, $str) 
{
    $box = imagettfbbox($fsize, 0, $font, $str);
    $ht = abs($box[5] - $box[1]);
    $wd = abs($box[4] - $box[0]);
    $base = -$box[5];
    $tx = -$box[0];
    return [ 'width' => $wd,
             'height' => $ht,
             'ascent' => $base,
             'offsetx' => $tx
           ];
}
?>

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.