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.
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.
Share Improve this question edited Feb 2 at 12:48 Olivier 18.3k1 gold badge11 silver badges31 bronze badges asked Feb 2 at 12:42 NaughtySquidNaughtySquid 2,0973 gold badges32 silver badges45 bronze badges2 Answers
Reset to default 5The 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).
Here is a way to fix this issue:
Check if the string contains a known character with a descender
If it does, compute the descender height by comparing the heights of
a
andp
, 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:
EDIT: it turns out there is a better solution, see Hakre's answer.
As @Olivier has correctly answered the y coordinate is the baseline.
And, here is the catch: As the $angle is 0 and the bounding-box at hand, we only need to correct by an upper y value, e.g. offsets 5 or 7:
The Four Corners of GD FreeType2 Bounding Boxes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
x,y (y: positive)
(4) <-- (3) (1) lower-left corner 0,1 .--------.
: ^ (2) lower-right corner 2,3 / array |
: | \below/ \ offsets |
-----:-------|-----< baseline (angle 0°) `--------'
(1) --> (2) /above\ x,y (y: negative)
(3) upper-right corner 4,5
(4) upper-left corner 6,7
The value is negative by the number of pixels above the baseline.
$y = $centerY - $top - $top_offset;
It can make sense to also do an $offsetX correction (upper-left.x, offset 6), but it is often minimal only:
$x = $centerX - $offsetX + intdiv($offsetX, 2) - $left_offset;
Images with these two corrections (X+Y), and the projected bounding box from the imageftbbox() function as a red rectangle on top:
Image | Image | Image | Image |
---|---|---|---|
References:
- PHP: imageftbbox — Give the bounding box of a text using fonts via freetype2 - Manual (php)
- The FreeType Project (freetype.)