I have a simple PHP avatar generator that takes someone's username, and makes an image with their initials on it. This works well, but somehow the text is always off center and I cannot figure out have to always have the text properly centered.
This uses the Ubuntu font.
$im = imagecreatetruecolor(125, 125);
$str = 'dpoint';
if (str_word_count($str) > 1)
{
$words = explode(' ', $str);
$text = $words[0][0]. $words[1][0];
}
else
{
$text = substr($str, 0, 2);
}
$font_size = '50';
$white = imagecolorallocate($im, 255, 255, 255);
$angle = 0;
// Get image dimensions
$width = imagesx($im);
$height = imagesy($im);
// Get center coordinates of image
$centerX = $width / 2;
$centerY = $height / 2;
// Get size of text
list($left, $bottom, $right, , , $top) = imageftbbox($font_size, $angle, './Ubuntu-L.ttf', $text);
// Determine offset of text
$left_offset = ($right - $left) / 2;
$top_offset = ($bottom - $top) / 2;
// Generate coordinates
$x = $centerX - $left_offset;
$y = $centerY + $top_offset;
// Add text to image
imagettftext($im, $font_size, $angle, $x, $y, $white, './Ubuntu-L.ttf', $text);
// Set the content type header
header('Content-Type: image/jpeg');
// Skip the file parameter using NULL, then set the quality
imagejpeg($im, NULL, 100);
// Free up memory
imagedestroy($im);
That for example ends up like this: You can see it's much lower down that it should be. What positioning am I doing wrong here?
Ideally I just want it so no matter the text size or lower-case or upper-case, it just centers it vertically and horizontally properly inside the box.
The y
parameter of imagettftext()
is the position
of the baseline, not of the bottom of the character:
The y-ordinate. This sets the position of the font's baseline, not the very bottom of the character.
See the illustration from the Wikipedia page (red line).
There is no clean way to fix this issue, but here is a workaround:
Check if the string contains a known character with a descender
If it does, compute the descender height by comparing the heights of a
and p
, and subtract it from $y
Example:
function hasDescender(string $text): bool
{
// Only English letters are tested here
return preg_match('/[gjpqyQ]/', $text);
}
function getDescenderHeight(float $font_size, string $font): int
{
$box_a = imageftbbox($font_size, 0, $font, 'a');
$box_p = imageftbbox($font_size, 0, $font, 'p');
return $box_p[1] - $box_a[1];
}
$width = 125;
$height = 125;
$font_size = 50;
$font = './Ubuntu-L.ttf';
$text = 'dp';
$centerX = $width / 2;
$centerY = $height / 2;
list($left, $bottom, $right, , , $top) = imageftbbox($font_size, 0, $font, $text);
$left_offset = ($right - $left) / 2;
$top_offset = ($bottom - $top) / 2;
$x = $centerX - $left_offset;
$y = $centerY + $top_offset;
if(hasDescender($text))
$y -= getDescenderHeight($font_size, $font);
$im = imagecreatetruecolor($width, $height);
$white = imagecolorallocate($im, 255, 255, 255);
imagettftext($im, $font_size, 0, (int)$x, (int)$y, $white, $font, $text);
header('Content-Type: image/png');
imagepng($im);
imagedestroy($im);
Output: