I'm trying to write a script that generates a PNG image from the text, size and the font it gets in the $_GET arguments, but I can't figure out how to make size of the image fit exactly to the text. I'm already using imagettfbox:
$widthPx = abs($ttfBox[2] - $ttfBox[0]);
$heightPx = abs($ttfBox[1] - $ttfBox[7]);
which probably gives me the correct measurements but when I draw my text, it gets a little bit out of bounds. For example if I try to draw an "a" using arial.ttf its at least 5 pixels out of bounds. Is there a way to draw a text of any font exactly fitting to the image without testing out?
$text = $_GET["text"];
$cmToPixel = 15.0;
$sizeCm = floatval($_GET["sizeCm"]);
$sizePx = $cmToPixel * $sizeCm;
$fontFile = "fonts/".pathinfo($_GET["font"])["filename"].".".pathinfo($_GET["font"])["extension"];
if(!file_exists($fontFile)){
die;
}
$ttfBox = imagettfbbox($sizePx, 0, $fontFile, $text);
$widthPx = abs($ttfBox[2] - $ttfBox[0]);
$heightPx = abs($ttfBox[1] - $ttfBox[7]);
$image = ImageCreate($widthPx, $heightPx);
$x = $ttfBox[0] + (imagesx($image)-$ttfBox[4] )/ 2 - 0;
$y = $ttfBox[1] + (imagesy($image) / 2) - ($ttfBox[5] / 2) - 5;
ImageRectangle($image,0,0,imagesx($image),imagesy($image), ImageColorAllocate($image,255,255,255));
imagettftext($image, $sizePx,0,$x,$y, ImageColorAllocate($image, 0, 0, 0), $fontFile, $text);
header("Content-Type: image/png");
ImagePng($image);
ImageDestroy($image);
Your calculations from the bounding box are off. This works:
<?php
/*-
* $MirOS: www/mk/ttf2png,v 1.8 2016/11/02 16:16:26 tg Exp $
*-
* Copyright (c) 2009, 2016
* mirabilos <m#mirbsd.org>
*
* Provided that these terms and disclaimer and all copyright notices
* are retained or reproduced in an accompanying document, permission
* is granted to deal in this work without restriction, including un-
* limited rights to use, publicly perform, distribute, sell, modify,
* merge, give away, or sublicence.
*
* This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
* the utmost extent permitted by applicable law, neither express nor
* implied; without malicious intent or gross negligence. In no event
* may a licensor, author or contributor be held liable for indirect,
* direct, other damage, loss, or other issues arising in any way out
* of dealing in the work, even if advised of the possibility of such
* damage or existence of a defect, except proven that it results out
* of said person's immediate fault when using the work as intended.
*-
* Syntax:
* php ttf2png [text [size [/path/to/font.ttf]]] >out.png
*/
if (!function_exists('gd_info'))
die("Install php5-gd first.");
$gd = gd_info();
if ($gd["FreeType Support"] == false)
die("Compile php5-gd with FreeType 2 support.");
$font = "/usr/src/www/files/FNT/GenI102.ttf";
$fontsize = 30;
$text = "EINVAL";
if (isset($argv[1]))
$text = $argv[1];
if (isset($argv[2]))
$fontsize = $argv[2];
if (isset($argv[3]))
$font = $argv[3];
// Get bounding box
$bbox = imageftbbox($fontsize, 0, $font, $text);
// Transform coordinates into width+height and position
$ascender = abs($bbox[7]);
$descender = abs($bbox[1]);
$size_w = abs($bbox[0]) + abs($bbox[2]);
$size_h = $ascender + $descender;
$x = -$bbox[0];
$y = $ascender;
// Create image
$im = imagecreatetruecolor($size_w, $size_h);
// Allocate colours
$bgcol = imagecolorallocate($im, 0x24, 0x24, 0x24);
$fgcol = imagecolorallocate($im, 0xFF, 0xFF, 0xFF);
// Fill image with background colour
imagefilledrectangle($im, 0, 0, $size_w - 1, $size_h - 1, $bgcol);
// Render text into image
imagefttext($im, $fontsize, 0, $x, $y, $fgcol, $font, $text);
// Convert true colour image (needed for above) to palette image
imagetruecolortopalette($im, FALSE, 256);
// Output created image
imagepng($im, NULL, 9);
exit(0);
If you write multiple strings on the same line and need to calculate the total height and offset of the line, it’s the maximum of all ascenders plus the maximum of all descenders, and $y for all imagefttext calls for that line is similarily the maximum of all ascenders.
Related
I'm trying to only horizontally align text on an image in php gd library like you'd do with "text-align: center" so the text isn't going out of the image if it's too large.
<?php
// (A) OPEN IMAGE
$img = imagecreatefrompng('LOGO.PNG');
// (B) WRITE TEXT
$txt = "Divine Magnificent Endurance";
$font = "C:/xampp/htdocs/testImages/ArialCE.ttf";
$white = imagecolorallocate($img, 0, 0, 0);
$width = imagesx($img);
$height = imagesy($img);
$text_size = imagettfbbox(24, 0, $font, $txt);
$text_width = max([$text_size[2], $text_size[4]]) - min([$text_size[0], $text_size[6]]);
$text_height = max([$text_size[5], $text_size[7]]) - min([$text_size[1], $text_size[3]]);
$centerX = CEIL(($width - $text_width) / 2);
$centerX = $centerX<0 ? 0 : $centerX;
$centerY = CEIL(($height - $text_height) / 1.3);
$centerY = $centerY<0 ? 0 : $centerY;
imagettftext($img, 18, 0, $centerX, $centerY, $white, $font, $txt);
imagesavealpha($img, true);
// (C) OUTPUT IMAGE
header('Content-type: image/png');
imagepng($img);
imagedestroy($img);
I had to implement a similar functionality when generating avatars, I solved that by grabbing the third and sixth value of imagettfbbox, that is the lower right corner in the X position and the upper right in the Y position, having those I subtract that from the image height and width, then divide that value in half, round up and grab the absolute value, and those will be my x and y coordinates.
I played around with which values and math operations would produce the best results and this gave me the desired centering.
$centerY = abs(ceil(($height - $text_size[5]) / 2));
$centerX = abs(ceil(($width - $text_size[2]) / 2));
I wrote a post about it on my Dev account that could be of use. I hope that gives you some help in the right direction.
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 7 years ago.
Improve this question
I am trying to automate separating fonts or another project, but I just can't find the answer. What I mean by separate is to save each character with an ascii value from 32 to 126 into multiple svg files (preferrably convert text to path). My question is,how do I generate multiple svg files, each containing a specific character from a specific (ttf) font.
I wrote https://github.com/Pomax/PHP-Font-Parser for this quite a while ago; the fonttest.php generates JSON, but if you actually run the code you'll see that the JSON also contains the SVG path outline, which you can drop into an SVG skeleton with a <path d="..."/> element, and a viewbox set up to match the dimensions that the glyph's JSON provides.
My code below will create an image from a letter using a given font. Follow that to get your image, then look here for how to cenvert it to SVG..
// Create an image from the letter "A" using a certain font at 12 points
$image = createTextImage("A", "12", "font.ttf");
/**
* Make an image of text
* #param type $text
* #param type $size
* #param type $color
* #param type $font
*/
function createTextImage($text, $font_size, $font_file){
// Make sure font file exists
if(!file_exists($font_file)) throw new Exception("Font file does not exist: {$font_file}");
// Retrieve bounding box:
$type_space = imagettfbbox($font_size, 0, $font_file, $text);
// Determine image width and height, 10 pixels are added for 5 pixels padding:
$image_width = abs($type_space[4] - $type_space[0]) + 10;
$image_height = abs($type_space[5] - $type_space[1]) + 10;
$line_height = getLineHeight($font_size, $font_file) +10;
// Create image:
$image = imagecreatetruecolor($image_width, $image_height);
// Allocate text and background colors (RGB format):
$text_color = imagecolorallocate($image, 250, 250, 250);
// Fill with transparent background
imagealphablending($image, false);
imagesavealpha($image, true);
$transparent = imagecolorallocatealpha($image, 255, 255, 255, 127);
imagefill($image, 0, 0, $transparent);
// Fix starting x and y coordinates for the text:
$x = 5; // Padding of 5 pixels.
$y = $line_height - 5; // So that the text is vertically centered.
// Add TrueType text to image:
imagettftext($image, $font_size, 0, $x, $y, $text_color, $font_file, $text);
// Save the image to a temp png file to use in our constructor
$tmpname = tempnam('/tmp', 'IMG');
// Generate and save image
imagepng($image, $tmpname, 9);
return $tmpname;
}
/**
* Returns the line height based on the font and font size
* #param type $fontSize
* #param type $fontFace
*/
function getLineHeight($fontSize, $fontFace){
// Arbitrary text is drawn, can't be blank or just a space
$type_space = imagettfbbox($fontSize, 0, $fontFace, "Robert is awesome!");
$line_height = abs($type_space[5] - $type_space[1]);
return $line_height;
}
Given the result of a call to imagettfbbox(), what is the correct, pixel-perfect point to provide to imagettftext() such that the text will not extend beyond its bounding box?
I am determining the width/height and x/y of the baseline from the bounding box like this:
$box = imagettfbbox($size, $angle, $font, $text);
$boxXCoords = array($box[0], $box[2], $box[4], $box[6]);
$boxYCoords = array($box[1], $box[3], $box[5], $box[7]);
$boxWidth = max($boxXCoords) - min($boxXCoords);
$boxHeight = max($boxYCoords) - min($boxYCoords);
$boxBaseX = abs(min($boxXCoords));
$boxBaseY = abs(min($boxYCoords));
I then draw a filled rectangle on my image of the dimensions of the bounding box:
imagefilledrectangle($image, 0, 0, $boxWidth - 1, $boxHeight - 1, $color);
After that, I draw the text:
imagettftext($image, $size, $angle, $boxBaseX, $boxBaseY, $color, $font, $text);
However, this causes the text to extend beyond the rectangle by a pixel or two. I have seen several attempts to fix this issue on PHP's imagettfbbox() documentation, but they all just suggest substracting a pixel or two here and there, which seems like a hack to me. What's happening here, and why should we need to fudge the numbers to get things right?
I believe there is no perfect way to place text with single-pixel precision on an image based on what imagettfbbox() returns and also using .ttf non-monospaced fonts. Over at the PHP manual many users have posted ways to accomplish this (with and without fudging the numbers); I recommend using jodybrabec's simple function over at the PHP manual, which calculates the exact bounding box. I have tested this one and only in extreme cases is the text positioned at most 1 pixel off in one direction. Nonetheless, if you add some padding (even if it is just 2 or 3 pixels) to your image your text will be within the dimensions of the image 100% of the time.
What happens when you don't subtract one from each of the dimensions in this line:
imagefilledrectangle($image, 0, 0, $boxWidth - 1, $boxHeight - 1, $color);
and instead do this:
imagefilledrectangle($image, 0, 0, $boxWidth, $boxHeight, $color);
The SlightlyMagic HQ Card Generator project renders cards for the strategy card game Magic: the Gathering. The generator is powered by PHP with an advanced text rendering engine built in. I don't know about logic behind the calculations, but the renderer is dead accurate for the purposes of this application. Here's the function that calculates proper bounding boxes (HQ Card Generator 8.x/scripts/classes/font.php):
private function convertBoundingBox ($bbox) {
// Transform the results of imagettfbbox into usable (and correct!) values.
if ($bbox[0] >= -1)
$xOffset = -abs($bbox[0] + 1);
else
$xOffset = abs($bbox[0] + 2);
$width = abs($bbox[2] - $bbox[0]);
if ($bbox[0] < -1) $width = abs($bbox[2]) + abs($bbox[0]) - 1;
$yOffset = abs($bbox[5] + 1);
if ($bbox[5] >= -1) $yOffset = -$yOffset;
$height = abs($bbox[7]) - abs($bbox[1]);
if ($bbox[3] > 0) $height = abs($bbox[7] - $bbox[1]) - 1;
return array(
'width' => $width,
'height' => $height,
'xOffset' => $xOffset, // Using xCoord + xOffset with imagettftext puts the left most pixel of the text at xCoord.
'yOffset' => $yOffset, // Using yCoord + yOffset with imagettftext puts the top most pixel of the text at yCoord.
'belowBasepoint' => max(0, $bbox[1])
);
}
I know this is a little late but imagettfbbox is in points not pixels.
pixel font size in imagettftext instead of point size
Hello Stackoverflow community,
for a little game I need to display an octagon (Like this one)
The shape adapts itself to certain values I get from the database. My Problem is, I have absolutely no idea how to launch into it. I neither know the formula for my purpose, nor I know how I could draw such a shape in PHP.
In general I'm relatively good at PHP. So I`d be happy about theoretical approaches to a solution and not necessarily code =)
Thanks in advance
Whipped this up. It calculates the coordinates for you already, but you can easily specify your own coordinates in the $vertices array (and remove the generation).
<?php
$radius = 100;
$sides = 8;
$points = array();
for ($i = 1; $i <= $sides; $i++) {
$points[] = round( $radius * cos($i*2 * pi() / $sides) + $radius ); // x
$points[] = round( $radius * sin($i*2 * pi() / $sides) + $radius ); // y
}
// Draw the image.
$im = imagecreate($radius*2 + 10, $radius*2 + 10);
$black = imagecolorallocate($im, 0, 0, 0);
$white = imagecolorallocate($im, 255, 255, 255);
imagefill($im, 0, 0, $white); // White background
imagefilledpolygon($im, $points, $sides, $black);
header('Content-type: image/png');
imagepng($im);
I can't give you a formula for this, but once you figured one out you can make use of the GD extension and draw your shape.
Google Charts API Supports this, and is fairly easy to use.
Example
Lets say I have elements (E1, E2, ... ,En) that have individual values which varies in [1; 100]. I have a circle (see figure below) and each two circle represent range of data.
The problem is how to show distribution of these elements ofEin this circle. Figure below depicts distribution of some elements ofEin the circle, where for example E1=10, E2=35,...,E6=100, E7=91. Are there any ready libraries in PHP or any plugins in jQuery or any ready solution?
I need to implement this problem in my web application using HTML+CSS+jQuery (don't offer solution with flash technologies, please).
Note: It is like creating charts in MS Excel. For example in MS Excel there is a chart type called Radar which more or less implements this problem, but in my case I have circles instead of polygon and I have only limited range of [1;100].
Edit
I have forgotten to mention that in this figure object element which is in the center is the object based on which we are showing distribution. If element matches object with more percentage so close it to the object and vice versa.
This is incomplete, see the GD manual on how to add text, etc.
<?php
$size = 501; //odd
$img = imagecreatetruecolor($size, $size);
$center = array((int) ($size/2), (int) ($size/2));
$colors['white'] = imagecolorallocate($img, 255, 255, 255);
$colors['black'] = imagecolorallocate($img, 0, 0, 0);
$colors['blue'] = imagecolorallocate($img, 0, 0, 255);
imagefill($img, 0, 0, $colors['white']);
$valid_rad = array((int) (($center[0] - 1) * .2), (int) (($center[0] - 1) * .9));
function radius_for($percentage, $valid_rad) {
return $valid_rad[1] - ($valid_rad[1] - $valid_rad[0]) *
($percentage/100);
}
foreach (array(0,25,50,75,100) as $perc) {
$radius = radius_for($perc, $valid_rad);
imagearc($img, $center[0], $center[1], $radius*2, $radius*2,
0, 360, $colors['black']);
}
foreach (array(100,85,70,36,23,2) as $perc) {
$radius = radius_for($perc, $valid_rad);
$angle = pi() * 1.25 + ((rand(0,100) - 50) / 50) * .5;
$x = (int) ($radius * cos($angle)) + $center[0];
$y = (int) ($radius * sin($angle)) + $center[1];
imagefilledellipse($img, $x, $y , 20, 20, $colors['blue']);
}
header("Content-type: image/png");
imagepng($img);
This gives a picture like this:
I don't know of any pre-made graphs like that. However you may be able to replicate it with a scatter plot with a custom background. Here are some jquery ones. You'd have to do the math to create the x,y coordinates with the appropriate distance from the center point but the chart plug-in should take care of the rest.