Adjusting centered text font size based on image size. How? - php

With some research, I have created the following piece of code to generate a simple image with a text string right on the center showing the resolution of this image. The challenge is, when you change X & Y dimensions to larger or smaller values, the font size remains the same.
My question is, what would be a good way to "grow" or "shrink" the font size as the user chooses between image sizes?
One idea I had would be to calculate predetermined areas, add them to an array of key value pairs where the value is the hard-coded font size for a given range of areas.
Perhaps you have a simpler idea?
PS: The font I am using can be downloaded from Google fonts here:
Montserrat-SemiBold
The output looks like this:
And the code:
<?php
// We Declare our canvas width & height dimensions
$myCanvasWidth = 256;
$myCanvasHeight = 256;
// We Create an empty and dark canvas from these dimensions above
$myCanvas = imagecreate($myCanvasWidth, $myCanvasHeight) or die("Can't create image!");
// We Allocate a color to be used as the canvas background
$colorIndigo = imagecolorallocate($myCanvas, 0x3F, 0x51, 0xB5);
// We Apply color as canvas background
imagefill($myCanvas, 0, 0, $colorIndigo);
// We Allocate a color to be used with the canvas text
$colorWhite = imagecolorallocate($myCanvas, 0xFF, 0xFF, 0xFF);
// We Declare our TTF font path in Windows 10...
$myFont = 'J:\Montserrat-SemiBold.ttf';
// We set the font size
$myFontSize = 16;
// We set the text angle
$myTextAngle = 0;
// We Declare the text string to be drawn on canvas...
$myText = $myCanvasWidth . ' x ' . $myCanvasHeight;
// We Calculate and return the bounding box in pixels for the text string to be drawn on canvas...
$myTextBoundingBox = imageftbbox($myFontSize, $myTextAngle, $myFont, $myText);
// Get the text upper, lower, left and right corner bounds of our text bounding box...
$lower_left_x = $myTextBoundingBox[0];
$lower_left_y = $myTextBoundingBox[1];
$lower_right_x = $myTextBoundingBox[2];
$lower_right_y = $myTextBoundingBox[3];
$upper_right_x = $myTextBoundingBox[4];
$upper_right_y = $myTextBoundingBox[5];
$upper_left_x = $myTextBoundingBox[6];
$upper_left_y = $myTextBoundingBox[7];
// Get Text Width and Height
$myTextWidth = $lower_right_x - $lower_left_x; //or $upper_right_x - $upper_left_x
$myTextHeight = $lower_right_y - $upper_right_y; //or $lower_left_y - $upper_left_y
//Get the starting position for centering
$start_x_offset = ($myCanvasWidth - $myTextWidth) / 2;
$start_y_offset = (($myCanvasHeight - $myTextHeight) + $myFontSize * 2) / 2;
// Write text to the image using TrueType fonts
imagettftext($myCanvas, $myFontSize, $myTextAngle, $start_x_offset, $start_y_offset, $colorWhite, $myFont, $myText);
// Draw a horizontal dashed line for reference only
imagedashedline($myCanvas, 0, $myCanvasHeight/2, $myCanvasWidth, $myCanvasHeight/2, $colorWhite);
// Draw a vertical dashed line for reference only
imagedashedline($myCanvas, $myCanvasWidth/2, 0, $myCanvasWidth/2, $myCanvasHeight, $colorWhite);
// We se the correct http header for png images...
header('Content-Type: image/png');
// We Output a PNG image to either the browser or a file
imagepng($myCanvas);
// Free any memory associated with myCanvas; image.
imagedestroy($myCanvas);
?>

Here is the comment above implemented as an answer. Interestingly enough, even though the lines and the font don't appear to align, they are indeed actually centered.
The "x" at the center creates the illusion of nonalignment; a possible side effect of NOT using mono-spaced fonts in order to achieve a more unnecessarily precise alignment..?!
Then again.. mono-spaced fonts are not that good looking...
Sample resize outputs:
<?php
// We Declare our canvas width & height dimensions
$myCanvasWidth = 640;
$myCanvasHeight = 360;
// We Create an empty and dark canvas from these dimensions above
$myCanvas = imagecreate($myCanvasWidth, $myCanvasHeight) or die("Can't create image!");
// We Allocate a color to be used as the canvas background
$colorIndigo = imagecolorallocate($myCanvas, 0x3F, 0x51, 0xB5);
// We Apply color as canvas background
imagefill($myCanvas, 0, 0, $colorIndigo);
// We Allocate a color to be used with the canvas text
$colorWhite = imagecolorallocate($myCanvas, 0xFF, 0xFF, 0xFF);
// We Declare our TTF font path in Windows 10...
$myFont = 'J:\Montserrat-SemiBold.ttf';
// Static font seed value...
$fontSize = 16;
// We set the dynamic font size
$myFontSize = $myCanvasWidth / $fontSize;
// We set the text angle
$myTextAngle = 0;
// We Declare the text string to be drawn on canvas...
$myText = $myCanvasWidth . ' x ' . $myCanvasHeight;
// We Calculate and return the bounding box in pixels for the text string to be drawn on canvas...
$myTextBoundingBox = imageftbbox($myFontSize, $myTextAngle, $myFont, $myText);
// Get the text upper, lower, left and right corner bounds of our text bounding box...
$lower_left_x = $myTextBoundingBox[0];
$lower_left_y = $myTextBoundingBox[1];
$lower_right_x = $myTextBoundingBox[2];
$lower_right_y = $myTextBoundingBox[3];
$upper_right_x = $myTextBoundingBox[4];
$upper_right_y = $myTextBoundingBox[5];
$upper_left_x = $myTextBoundingBox[6];
$upper_left_y = $myTextBoundingBox[7];
// Get Text Width and Height
$myTextWidth = $lower_right_x - $lower_left_x; //or $upper_right_x - $upper_left_x
$myTextHeight = $lower_right_y - $upper_right_y; //or $lower_left_y - $upper_left_y
//Get the starting position for centering
$start_x_offset = ($myCanvasWidth - $myTextWidth) / 2;
$start_y_offset = (($myCanvasHeight - $myTextHeight) + $myFontSize * 2) / 2;
// Write text to the image using TrueType fonts
imagettftext($myCanvas, $myFontSize, $myTextAngle, $start_x_offset, $start_y_offset, $colorWhite, $myFont, $myText);
// Draw a horizontal dashed line for reference only
imagedashedline($myCanvas, 0, $myCanvasHeight/2, $myCanvasWidth, $myCanvasHeight/2, $colorWhite);
// Draw a vertical dashed line for reference only
imagedashedline($myCanvas, $myCanvasWidth/2, 0, $myCanvasWidth/2, $myCanvasHeight, $colorWhite);
// We set the correct http header for png images...
header('Content-Type: image/png');
// We Output a PNG image to either the browser or a file
imagepng($myCanvas);
// Finally, we free any memory associated with myCanvas; the image.
imagedestroy($myCanvas);
?>
The imageftbbox() function illustrated.
The imageftbbox() returns an array with 8 elements representing four points making the bounding box of the text:

Related

Get width and height of scaled down image after imagerotation?

A followup to this question:
How to get new width and height after an image has been rotated with imagerotate()?
The answer I got is based on the filenames actual image size, but if I want to start from another width and height.
How would I achieve that? See below code for my attempt...
$ps['product_angle'] = 77; //Could be any angle
$filename = 'test.png' //filename to the original product
list($source_width, $source_height) = getimagesize($filename);
$source_image = imagecreatefromjpeg($filename);
//Example for clarification (this is parameters that is used to save new image)
$ps['product_width'] = 40; //for example $source_width = 200
$ps['product_height'] = 80; //and $source_height = 400
$angle = $ps['product_angle'];
if (intval($angle) <> 0) {
//Actual dimensions of image from filename
$current_source_x = imagesx($source_image);
$current_source_y = imagesy($source_image);
//Get current ratio of "scaled down image"
$ratio_x = $ps['product_width'] / $current_source_x;
$ratio_y = $ps['product_height'] / $current_source_y;
$source_image = imagerotate(
$source_image,
360-$angle,
imageColorAllocateAlpha($source_image, 255, 255, 255, 127)
);
//New dimensions from actual filename
//This would be fine if I just wanted the new width and height
//based on the filenames dimension, but I want new dimensions
//for the "scaled downed version" of the image (40, 80)
$new_image_source_x = imagesx($source_image);
$new_image_source_y = imagesy($source_image);
//I tried this, but obviously I'm doing something totally wrong
$new_width = ($new_image_source_x - $current_source_x) * $ratio_x;
$new_height = ($new_image_source_y - $current_source_y) * $ratio_y;
//Set new width after rotation
$ps['product_width'] = $new_width;
$ps['product_height'] = $new_height;
}
$ps['source_image'] = $source_image;
list($source_width, $source_height) = getimagesize($filename);
$dest_width = (int)$ps['product_width'];
$dest_height = (int)$ps['product_height'];
//Resize source-image to new width and height
//
imagecopyresized($dest_image, $ps['source_image'], 0, 0, 0, 0, $dest_width, $dest_height, $source_width, $source_height);
Probably I'm missing something very essential....
UPDATE
An example of real values...
image current width63.224619257754
image current height80.210337864315
//after calculation
image new width37.583523669887
image newt height21.716336015666
where angle is 41.10419020401479
Looking at my comment
"I've actually came into a solution"
.. It wasn't the day when I used my best english skills...
I guess you know what I meant though and here is how I solved my issue:
In my approach I was trying to calculate the new values based on ratio from the "scaled down image" and then get new width and height based on the difference from the "scaled down image" and "the image after rotated".
Something like this:
Set the relation/ratio between "scaled down image" and original image.
Do the actual image-rotation
Get the difference between original dimensions from the rotated image and multiply that with ratio-factor set before image was rotated.
Get the new width and height based on the rotation angle
This really didn't work correctly (failed at step4). I've been looking around a lot to find answers how to calculate width and height after an image is rotated. But these width and heights didn't return the same dimensions that the GD-functions imagesx() and imagesy() returned after the image was rotated. I've tried out several of calculations using sin() and cos() for retrieving the width and height but still didn't get exact same values as imagesx() and imagesy().
That got me thinking... What if I change my approach to:
Set the relation/ratio between "scaled down image" and original image.
Do the actual image-rotation
Apply the new dimensions based on the ratio multiplied with imagesx() and imagesy() - values return after the rotation
New code:
//Rotate resized image (if it should be)
$angle = $ps['product_angle'];
if (intval($angle) <> 0) {
//Get current dimensions from file
$current_source_x = imagesx($source_image);
$current_source_y = imagesy($source_image);
//Get current ratio of "scaled down image"
$ratio_x = $ps['product_width'] / $current_source_x;
$ratio_y = $ps['product_height'] / $current_source_y;
//Rotate image
$source_image = imagerotate($source_image, 360-$angle, imageColorAllocateAlpha($source_image, 255, 255, 255, 127));
//Now we get a new width from the imagerotate()-function, use those to set new_width from
//ratio/propoprtions is used from origin width and height
$ps['product_width'] = imagesx($source_image) * $ratio_x;
$ps['product_height'] = imagesy($source_image) * $ratio_y;
}
This worked fine - almost... the issue now was that the if the rotated image's new width and/or height got larger then the origin dimensions of the image, then the proportions wouldn't be accurate (and would cut off the imagesometimes (depending the rotation angle)).
The code was modified so heights and widths would be in proportion to the image from the file when creating the resized image.
//Rotate resized image (if it should be)
$angle = $ps['product_angle'];
if (intval($angle) <> 0) {
//Get current dimensions from file
$current_source_x = imagesx($source_image);
$current_source_y = imagesy($source_image);
//Get current ratio of "scaled down image"
$ratio_x = $ps['product_width'] / $current_source_x;
$ratio_y = $ps['product_height'] / $current_source_y;
//Rotate image
$source_image = imagerotate($source_image, 360-$angle, imageColorAllocateAlpha($source_image, 255, 255, 255, 127));
//Now we get a new width from the imagerotate()-function, use those to set new_width from
//ratio/propoprtions is used from origin width and height
$ps['product_width'] = imagesx($source_image) * $ratio_x;
$ps['product_height'] = imagesy($source_image) * $ratio_y;
//Set these so we can modifiy the width and height given from getimagesize()-function below
$ps['source_width'] = imagesx($source_image) ;
$ps['source_height'] = imagesy($source_image);
}
//If image is rotated, then width and height are adjusted with these values
if (isset($ps['source_width']) && isset($ps['source_height']) ) {
$source_width = $ps['source_width'];
$source_height = $ps['source_height'];
}
//Set position where to place in the image to save
$dest_x = $ps['product_left'];
$dest_y = $ps['product_top'];
$dest_width = (int)$ps['product_width'];
$dest_height = (int)$ps['product_height'];
//Resize source-image to new width and height and then copy from source to destination point
imagecopyresized($dest_image, $ps['source_image'], $dest_x, $dest_y, 0, 0, $dest_width, $dest_height, $source_width, $source_height);
So my final solution would include these steps:
Set the relation/ratio between "scaled down image" and original image.
Do the actual image-rotation
Apply the new dimensions based on the ratio multiplied with imagesx() and imagesy() - values return after the rotation
Set "fake" width and height when image has been rotated, so the resizing would work in proportion to the original image.
I hope this would help anyone who's struggling with the same issue I've struggled with (a few hours to much)!

ImageMagick move watermark from top left to bottom right?

I've got this working adding a watermark onto my image at the top left, How can i change it so it adds in the bottom right?
Here is my current code:
$watermark = new Imagick();
$watermark->readImage("images/watermark_boxart.png");
$im->compositeImage($watermark, imagick::COMPOSITE_OVER, 3, 3);
Thanks
You're setting the watermark at (3,3). You need to set it in the bottom right by placing these numbers accordingly.
Methodology:
Calculate width and height on $im [$im_height, $im_width]
Calculate width and height of $watermerk [$wm_height, $wm_width]
Find the coordinates to place the watermark at via calculation of differences in dimensions
Create composite with calculated locations
Code:
$im_d = $im->getImageGeometry();
$im_w = $im_d['width'];
$im_h = $im_d['height'];
$watermark = new Imagick();
$watermark->readImage("images/watermark_boxart.png");
$watermark_d = $watermark->getImageGeometry();
$watermark_w = $watermark_d['width'];
$watermark_h = $watermark_d['height'];
$margin = 3;
$x_loc = $im_w - $watermark_w - $margin;
$y_loc = $im_h - $watermark_h - $margin;
$im->compositeImage($watermark, imagick::COMPOSITE_OVER, $x_loc, $y_loc);

Resize images of every size to fixed width and height

I tried a lot of methods to do that, but I still have a lot of troubles with that.
Is it possible to resize all images to fixed width and height?
I want every uploaded image with width>=200px and height>=260px to be resized to width=200px and height=260px, but I want to keep a bit of proportionality and if image is bigger than 200x260px to resize it proportionally and then to capture center of image 200x260px.
I just need idea to know where to start and what to do, but if you have an example I would like to see it. Thanks.
If you want to trim the image you can do in the following way:-
//Your Image
$imgSrc = "image.jpg";
//getting the image dimensions
list($width, $height) = getimagesize($imgSrc);
//saving the image into memory (for manipulation with GD Library)
$myImage = imagecreatefromjpeg($imgSrc);
// calculating the part of the image to use for thumbnail
if ($width > $height) {
$y = 0;
$x = ($width - $height) / 2;
$smallestSide = $height;
} else {
$x = 0;
$y = ($height - $width) / 2;
$smallestSide = $width;
}
// copying the part into thumbnail
$thumbSize = 100;
$thumb = imagecreatetruecolor($thumbSize, $thumbSize);
imagecopyresampled($thumb, $myImage, 0, 0, $x, $y, $thumbSize, $thumbSize, $smallestSide, $smallestSide);
//final output
header('Content-type: image/jpeg');
imagejpeg($thumb);
To begin writing the function, we have to declare it as such… Then we have to throw in our attributes. We want to restrict our image, so we have to let the function know the dimensions to which we want to restrict it, and we have to know what the original image size is to begin with (we’ll get to that part in a second).
<?php
function imageResize($width, $height, $target) {
//takes the larger size of the width and height and applies the
formula accordingly...this is so this script will work
dynamically with any size image
if ($width > $height) {
$percentage = ($target / $width);
} else {
$percentage = ($target / $height);
}
//gets the new value and applies the percentage, then rounds the value
$width = round($width * $percentage);
$height = round($height * $percentage);
//returns the new sizes in html image tag format...this is so you
can plug this function inside an image tag and just get the
return "width=\"$width\" height=\"$height\"";
}
?>
Before we take our new function on a test drive, we need to get the width and height of the image that we want to display. There is a magical command in PHP called getimagesize(). This command, used properly, will return the image width, height, type, and even the width and height in HTML image tag format (width=”x” height=”y”).
$mysock = getimagesize("images/sock001.jpg");
Now, $mysock is an array that holds vital information about the particular image we want to display. In index 0, we have the width ($mysock[0]), and in index 1, we have the height ($mysock[1]). That’s really all we need, in order to get what we want done. Want to see the function… well, function? Here we go!
Let’s say you want to display a list of your beautiful socks, but you want room on the page to show them neatly in a row, and to do that they cannot be larger than 150 pixels tall or wide.
<?php
//get the image size of the picture and load it into an array
$mysock = getimagesize("images/sock001.jpg");
?>
<!-using a standard html image tag, where you would have the
width and height, insert your new imageResize() function with
the correct attributes -->
<img src="images/sock001.jpg" <?php imageResize($mysock[0],
$mysock[1], 150); ?>>
That’s it! Now, no matter what the original file size, it will be restricted to no more than 150 pixels in width or height (or whatever you specify).

PHP imagettftext baseline workaround

I am writing to print text to an image using PHP. However, the function imagettftext() uses the baseline, whereas I need the text vertically centered.
So, I either need a method to print text with y not the distance from top to baseline, but from top to top of bounding box OR I need a method using which I could determine the distance between top of bounding box and baseline.
Apparently, I am confusing you. So, to make it clear: I am aware of the function imagettfbbox(). Using that function I can determine height and width of resulting text box. Its height, however, is utterly useless for vertical alignment when printing with imagettftext(), because the Y parameter is not the distance to the top of the box (or even the bottom, but at least something I could have used having the height) but the distance to the baseline of the text within.
EDIT: Why am I not accepting the latest answer?
See my latest comment below the answer, and use this image as a reference.
I do not know if the answer still interested.However, the imagettfbbox() function give you more information than simply the height and the width of the bounding box. It's designed exactly to return information needed by the imagettftext() to manage the text as you want.
The trick lies in the fact that the coordinates returned from imagettfbbox() are not related to the absolute top left corner, but to the baseline of the font for the particular text. This is the reason because the box is specified in point coordinates, and these are often negative.
In short:
$dims = imagettfbbox($fontsize, 0, $font, $text);
$ascent = abs($dims[7]);
$descent = abs($dims[1]);
$width = abs($dims[0])+abs($dims[2]);
$height = $ascent+$descent;
...
// In the example code, for the vertical centering of the text, consider
// the simple following formula
$y = (($imageHeight/2) - ($height/2)) + $ascent;
This works perfectly for my projects.
Hope this help.
Sorry for english.
Marco.
Not entirely sure what your asking...can you give an example? Perhaps imagettfbbox is what you need?
// get bounding box dims
$dims = imagettfbbox($fontsize, 0, $font, $quote);
// do some math to find out the actual width and height
$width = $dims[4] - $dims[6]; // upper-right x minus upper-left x
$height = $dims[3] - $dims[5]; // lower-right y minus upper-right y
edit: Here is an example of vertically centered text
<?php
$font = 'arial.ttf';
$fontsize = 100;
$imageX = 500;
$imageY = 500;
// text
$text = "FOOBAR";
// create a bounding box for the text
$dims = imagettfbbox($fontsize, 0, $font, $text);
// height of bounding box (your text)
$bbox_height = $dims[3] - $dims[5]; // lower-right y minus upper-right y
// Create image
$image = imagecreatetruecolor($imageX,$imageY);
// background color
$bgcolor = imagecolorallocate($image, 0, 0, 0);
// text color
$fontcolor = imagecolorallocate($image, 255, 255, 255);
// fill in the background with the background color
imagefilledrectangle($image, 0, 0, $imageX, $imageY, $bgcolor);
$x = 0;
$y = (($imageY/2) - ($bbox_height/2)) + $fontsize;
imagettftext($image, $fontsize, 0, $x, $y , $fontcolor, $font, $text);
// tell the browser that the content is an image
header('Content-type: image/png');
// output image to the browser
imagepng($image);
// delete the image resource
imagedestroy($image);
?>

Generate colour palette from an image

Just for fun I've been looking at how to use the GD library to create a colour palette from an image. So far I've used GD to resize a user uploaded image to an appropriate size for displaying on a webpage.
Now I'd like to be able to get about five or so different colours from the image that represent the range of colours present in it. Once I've done that I'd like to generate a complementary palette based upon those colours, which I can then use to colour different elements on the page.
Any help I can get about how I would find the initial colour palette would be much appreciated!
EDIT:
I've come to my own solution which you can see below.
Well I've spent a couple of days fiddling around and this is how I managed to build my colour palette. Its worked fairly well for me and you can change the size of the colour palette to return more or less colours from the image.
// The function takes in an image resource (the result from one
// of the GD imagecreate... functions) as well as a width and
// height for the size of colour palette you wish to create.
// This defaults to a 3x3, 9 block palette.
function build_palette($img_resource, $palette_w = 3, $palette_h = 3) {
$width = imagesx($img_resource);
$height = imagesy($img_resource);
// Calculate the width and height of each palette block
// based upon the size of the input image and the number
// of blocks.
$block_w = round($width / $palette_w);
$block_h = round($height / $palette_h);
for($y = 0; $y < $palette_h; $y++) {
for($x = 0; $x < $palette_w; $x++) {
// Calculate where to take an image sample from the soruce image.
$block_start_x = ($x * $block_w);
$block_start_y = ($y * $block_h);
// Create a blank 1x1 image into which we will copy
// the image sample.
$block = imagecreatetruecolor(1, 1);
imagecopyresampled($block, $img_resource, 0, 0, $block_start_x, $block_start_y, 1, 1, $block_w, $block_h);
// Convert the block to a palette image of just one colour.
imagetruecolortopalette($block, true, 1);
// Find the RGB value of the block's colour and save it
// to an array.
$colour_index = imagecolorat($block, 0, 0);
$rgb = imagecolorsforindex($block, $colour_index);
$colour_array[$x][$y]['r'] = $rgb['red'];
$colour_array[$x][$y]['g'] = $rgb['green'];
$colour_array[$x][$y]['b'] = $rgb['blue'];
imagedestroy($block);
}
}
imagedestroy($img_resource);
return $colour_array;
}
this may help you
<?php
$im = ImageCreateFromJpeg($source_file);
$imgw = imagesx($im);
$imgh = imagesy($im);
// n = total number or pixels
$n = $imgw*$imgh;
$colors = array();
for ($i=0; $i<$imgw; $i++)
{
for ($j=0; $j<$imgh; $j++)
{
$rgb = ImageColorAt($im, $i, $j);
if (isset($colors[$rgb])) {
$colors[$rgb]++;
}
else {
$colors[$rgb] = 1;
}
}
}
asort($colors);
print_r($colors);
The imagecolorat function will give you the colour value at a pixel which you can use to scan the image and create a colour histogram:
http://www.php.net/manual/en/function.imagecolorat.php

Categories