2
\$\begingroup\$

we are trying to justify a long text into a image and we need to justify it. We have solved the problem with this function, but it's too heavy and takes a long time to process the text justification. Any faster solution in order to justify a text?

$image = ImageCreateFromJPEG( "sample.jpg" );
$color = imagecolorallocate($image, 0, 0, 0);
$font = 'arial.ttf';
$text1 = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum';
$a = imagettftextjustified($image, 20, 0, 50, 50, $color, $font,$text1, 500, $minspacing=3,$linespacing=1);
header('Content-type: image/jpeg');
imagejpeg($image,NULL,100);
function imagettftextjustified(&$image, $size, $angle, $left, $top, $color, $font, $text, $max_width, $minspacing=3,$linespacing=1)
{
 $wordwidth = array();
 $linewidth = array();
 $linewordcount = array();
 $largest_line_height = 0;
 $lineno=0;
 $words=explode(" ",$text);
 $wln=0;
 $linewidth[$lineno]=0;
 $linewordcount[$lineno]=0;
 foreach ($words as $word)
 {
 $dimensions = imagettfbbox($size, $angle, $font, $word);
 $line_width = $dimensions[2] - $dimensions[0];
 $line_height = $dimensions[1] - $dimensions[7];
 if ($line_height>$largest_line_height) $largest_line_height=$line_height;
 if (($linewidth[$lineno]+$line_width+$minspacing)>$max_width)
 {
 $lineno++;
 $linewidth[$lineno]=0;
 $linewordcount[$lineno]=0;
 $wln=0;
 }
 $linewidth[$lineno]+=$line_width+$minspacing;
 $wordwidth[$lineno][$wln]=$line_width;
 $wordtext[$lineno][$wln]=$word;
 $linewordcount[$lineno]++;
 $wln++;
 }
 for ($ln=0;$ln<=$lineno;$ln++)
 {
 $slack=$max_width-$linewidth[$ln];
 if (($linewordcount[$ln]>1)&&($ln!=$lineno)) $spacing=($slack/($linewordcount[$ln]-1));
 else $spacing=$minspacing;
 $x=0;
 for ($w=0;$w<$linewordcount[$ln];$w++)
 {
 imagettftext($image, $size, $angle, $left + intval($x), $top + $largest_line_height + ($largest_line_height * $ln * $linespacing), $color, $font, $wordtext[$ln][$w]);
 $x+=$wordwidth[$ln][$w]+$spacing+$minspacing;
 }
 }
 return true;
}
asked Feb 27, 2024 at 16:46
\$\endgroup\$
2
  • 2
    \$\begingroup\$ Can you provide sample calls to this function? \$\endgroup\$ Commented Feb 27, 2024 at 17:42
  • \$\begingroup\$ We have edited and added a sample of a call to this function \$\endgroup\$ Commented Feb 27, 2024 at 19:52

1 Answer 1

1
\$\begingroup\$

This submission is about performance, yet it contains no timing measurements.

Your summary complaint seems to be: For N words we have N calls to bbox() and to render text(), which will take a Long Time.

Ok. Let's see how we could make fewer calls. Absent timing measurements I'm going to assume that both calls have approximately equal cost. I'm going to assume there's some call overhead, so that

 imagettftext( ... , "hello world", ... )

runs quicker than

 imagettftext( ... , "hello", ... )
 imagettftext( ... , "world", ... )

OP approach

extract helper

 foreach ($words as $word)
 {
 ...
 }
 for ($ln = 0; $ln <= $lineno; $ln++)
 {
 ...
 for ($w = 0; $w < $linewordcount[$ln]; $w++)

Please break out the foreach code as a helper, and that first for as another helper function. Each function will have a single responsibility.

composite a line at a time

That nested for executes, IDK, more than ten times per line? And yet you already know from bbox() which words will appear on the line. Glue some of them together, at least in the case where $spacing is "small" or is equal to $minspacing. Pick one or two break locations, and send 1/2 the words or 1/3 of the words into text() rendering at a time. Downside is slightly different word spacing within the line, even though you still justify to avoid ragged right.

LaTeX approach

Use a professional solution that achieves high quality typesetting results. Ask LaTeX to render several lines of text as a .PNG bitmap, and composite that into your final graphic image.

letter metrics approach

We don't exactly have to call bbox() at all, not in this program.

Write a program that calls bbox() for 26 upper + lower letters, plus a few punctuation marks and whatever other characters appear in your input corpus. Remember the widths, and write them out to a JSON database.

When compositing text, start by reading in that JSON file. Author a helper function that given a word or phrase will use the recorded widths to estimate what bbox() would say. It won't get kerning 100% right, but it will be close enough, as it will know that "l" is skinnier than "m". You might even feed the entire text to the helper, and let it make line break decisions.

answered Feb 28, 2024 at 0:26
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.