Trying to generate 3D bumpmap text in PHP - php

I have a website I monetize with numerous original pictures on it, I want people to visit the website to see the original pictures and have search engines only show the pictures with transparent watermarks.
The following is an example of what I mean by a transparent watermark. Except of course, I replace "Test!" with the company name.
Here's the PHP code I created so far:
<?php
$txt = "TEST!";
header( "Content-type: image/jpeg", true );
$w = imagefontwidth(5) * ( strlen( $txt ) + 1 );
$h = imagefontheight(5) * 2;
$i2 = imagecreatetruecolor( $w,$h );
imagesavealpha( $i2, true );
imagealphablending( $i2, false );
imagefill( $i2, 0, 0, imagecolorallocatealpha( $i2, 255, 255, 255, 127 ) );
imagestring( $i2, 3, 10, 10, $txt, imagecolorallocate( $i2, 255, 0, 0) );
$i = imagecreatefromjpeg( "someimage.jpg" );
imagecopyresized( $i, $i2, 2, 2, 0, 0, $w * 5, $h * 7, $w, $h );
imagejpeg( $i, null, 100 );
imagedestroy( $i );
imagedestroy( $i2 );
?>
$i2 is the resource variable for the image box in which I added the text, and $i1 is for the large image to place the text on. $w and $h represent width and height of the text image box. This code is able to produce the text on top of the image without the background box showing, but it doesn't produce the bump-map effect like what is shown in the above image.
Can anyone guide me as to what functions, mathematics or code I need to use to create the bump-map effect?
I feel my solution requires special manipulation of a block of pixels but I don't know how to do it for this effect.
I also want to stick with the PHP GD Image library.

I'd recommend using a semi-transparent PNG and adding it as a watermark rather than using the imagestring functions.
With the imagestring functions you'd theoretically needs to create two strings, one slightly higher and to the left than the other, and then somehow calculate the pixels that where both the strings are present and make that transparent. This will then mean the left-over parts would be black / white and give that 3d effect.
Example 1
If the watermark is the same for each image, then you can use a semi-transparent PNG. The benefit of this is that it allows for far more effects, and as a result a better result.
<?php
// Watermark will take up 80% of the photo's width
$size = 80;
$im = imagecreatefromjpeg(dirname(__FILE__) . '/photo.jpg');
$oi = imagecreatefrompng(dirname(__FILE__) . '/watermark.png');
$w1 = imagesx($im);
$h1 = imagesy($im);
$w2 = imagesx($oi);
$h2 = imagesy($oi);
$w = $w1 - (($w1 / 100) * (100 - $size));
$h = ($w * $h2) / $w2;
imagecopyresampled($im, $oi, $w1 / 2 - $w / 2, $h1 / 2 - $h / 2, 0, 0, $w, $h, $w2, $h2);
header('Content-type: image/jpg');
imagejpeg($im, null, 100);
imagedestroy($im);
Will produce the following output:
Original Photo / Watermark
Example 2
If the watermark needs to be different for each image, then you can use imagettftext to create the text using a font of your choosing (much better than imagestring but limited as to what effects you can do).
<?php
$size = 55;
$angle = 15;
$text = uniqid();
$font = 'BebasNeue.otf'; // http://www.dafont.com/bebas-neue.font
$im = imagecreatefromjpeg(dirname(__FILE__) . '/photo.jpg');
$w = imagesx($im);
$h = imagesy($im);
$text_colour_1 = imagecolorallocatealpha($im, 0, 0, 0, 99);
$text_colour_2 = imagecolorallocatealpha($im, 255, 255, 255, 90);
$box = imagettfbbox($size, $angle, dirname(__FILE__) . '/' . $font, $text);
imagettftext($im, $size, $angle, (($w - $box[4]) / 2) - 1, (($h - $box[5]) / 2) - 1, $text_colour_1, dirname(__FILE__).'/' . $font, $text);
imagettftext($im, $size, $angle, ($w - $box[4]) / 2, ($h - $box[5]) / 2, $text_colour_2, dirname(__FILE__).'/' . $font, $text);
header('Content-type: image/jpg');
imagejpeg($im, null, 100);
Output:

Related

How to account for font swash with PHP and GD

I have the following code to print text on an image. I am also adding a debug box around the text. However, I noticed the text on the left lies outside of the box that PHP gives me with imagettfbbox.
This looks like an issue with the font swash. Is there anyway to account for this? Can I figure out the distance between the start of the swash and the actual position imagettfbbox gives to me?
I don't think this is an issue with the font, as I tried it with a few script style fonts and the results were similar.
<?php
$font = 'scriptin.ttf';
$text = 'Ipsum';
$size = 30;
$image = imagecreatetruecolor(200, 200);
$fontColour = imagecolorallocate($image, hexdec('11'), hexdec('11'), hexdec('11'));
$bgColour = imagecolorallocate($image, hexdec('CC'), hexdec('CC'), hexdec('CC'));
imagefilledrectangle($image, 0, 0, 200, 200, $bgColour);
$dimensions = imagettfbbox($size, 0, $font, $text);
imagefilledrectangle(
$image,
$dimensions[0] + 40,
$dimensions[7] + 50,
$dimensions[2] + 40,
$dimensions[3] + 50,
imagecolorallocate($image, mt_rand(1, 180), mt_rand(1, 180), mt_rand(1, 180))
);
imagettftext(
$image,
$size,
0,
40,
50,
$fontColour,
$font,
$text
);
header('Content-Type: image/png');
imagepng($image);
The code and font is available here: https://github.com/AydinHassan/image-swash-example
If you point a VHOST at the repository, you can just hit swash.php
Edit: This appears to be fixed in PHP 7.0.12 (bug #53504) so the code below shouldn't be required.
Based on a comment in the PHP manual I've written the following function to calculate and return the difference between where GD thinks the left side of the bounding box is and where the leftmost pixel is found:
function xadjust($size, $angle, $fontfile, $text)
{
$bbox = imagettfbbox($size, $angle, $fontfile, $text);
$width = $bbox[4] - $bbox[6]; // upper right x - upper left x;
$height = $bbox[1] - $bbox[7]; // lower left y - upper left y;
// create an image with height and width doubled to fit any 'swash'.
$im = imagecreatetruecolor($width * 2, $height * 2);
// set background color to opaque black.
imagefill($im, 0, 0, 0x00000000);
// draw the text in opaque white.
imagettftext(
$im,
$size,
0,
$width / 2,
$height,
0x00ffffff,
$fontfile,
$text
);
// set the min-width to its possible maximum.
$min_x = $width * 2;
for ($x = 0; $x < $width * 2; $x++) {
// each x-pixel (horizontal)
for ($y = 0; $y < $height * 2; $y++) {
// each y-pixel (vertical)
if (imagecolorat($im, $x, $y) > 0) {
// non-black pixel found!
$min_x = min($x, $min_x);
}
}
}
imagedestroy($im);
// return the difference between where GD thinks the bounding box is and
// where we found the leftmost non-black pixel.
return (($width / 2) - $min_x) - abs($bbox[0]);
}
This can be integrated to your script fairly easily:
$font = 'scriptin.ttf';
$text = 'Ipsum';
$size = 30;
$image = imagecreatetruecolor(200, 200);
$fontColour = imagecolorallocate($image, hexdec('11'), hexdec('11'), hexdec('11'));
$bgColour = imagecolorallocate($image, hexdec('CC'), hexdec('CC'), hexdec('CC'));
imagefilledrectangle($image, 0, 0, 200, 200, $bgColour);
$xadjust = xadjust($size, 0, $font, $text); // 1. get the adjust value.
$dimensions = imagettfbbox($size, 0, $font, $text);
imagefilledrectangle(
$image,
$dimensions[0] + 40 - $xadjust, // 2. move the left-side of the box to the left.
$dimensions[7] + 50,
$dimensions[2] + 40 - $xadjust, // 3. move the right-side of the box to the left.
$dimensions[3] + 50,
imagecolorallocate($image, mt_rand(1, 180), mt_rand(1, 180), mt_rand(1, 180))
);
imagettftext(
$image,
$size,
0,
40,
50,
$fontColour,
$font,
$text
);
header('Content-Type: image/png');
imagepng($image);
This gives me the following output:
I've run it with a few other fonts and sizes and it seems to be accurate to within 1 pixel.

How to write text to images with different sizes using PHP GD

I'm creating images with different sizes. How can I write text on those images so that the texts always fit to the images?
$text = "some text as example";
$font = "arialbd.ttf"
$fontsize = 26;
offset_x = 0;
offset_y = 0;
$image01 = imagecreate( 1120 , 900 );
$image02 = imagecreate( 400 , 300 );
$image03 = imagecreate( 1120 , 900 );
I know that there is the imagefttext function that can apply text to the images but with that function I can only change the font size to make the text bigger. How can I find out that it fits into my image?
If you are looking to scale the font size so that text string fills the image width, try this.
Using imagettfbbox, determine the current text box width:
$bbox = imagettfbbox($fontsize,0,$fontpath,$string);
$tbwidth = $bbox[2];
Then divide the image width by the $tbwidth to get a factor
$factor = round(($imgwidth / $tbwidth), 0, PHP_ROUND_HALF_DOWN); //ie. 800/240=3
Multiple the $factor by you $fontsize to get an approximate increase.
$newfontsize = $fontsize * $factor; //ie. 12(pt) * 3 = 36
Keep in minds, if you're using GD 2.0, fontsize is in Points and not pixels. Your algorithm is going to calculate the difference between your default font size text box (expressed as a text box width) and the image width.
// Set the content-type
header('Content-Type: image/png');
// Create the image
$im = imagecreatetruecolor(800, 600);
// Create some colors
$white = imagecolorallocate($im, 255, 255, 255);
$grey = imagecolorallocate($im, 128, 128, 128);
$black = imagecolorallocate($im, 0, 0, 0);
imagefill($im, 0, 0, $white);
// The text to draw
$text = 'Testing...';
// Replace path by your own font path
$font_file = 'arial.ttf';
$font_size = '15';
$bbox = imageftbbox($font_size, 0, $font_file, $text);
$width = $bbox[2] - $bbox[6];
$height = $bbox[3] - $bbox[7];
// Add the text
imagettftext($im, $font_size, 0, 10, 20, $black, $font_file, $text);
// Using imagepng() results in clearer text compared with imagejpeg()
imagepng($im);
imagedestroy($im);
I recently came across the same situation with transparent backgrounds but the current examples are either not a solution but clues or a solution that doesn't work, so hereby a combined and working solution if anyone need it.
function imagecreater($width = 600, $height = 600) {
//Create an empty transparent image
$img = imagecreatetruecolor($width, $height);
imagealphablending($img, false);
imagesavealpha($img, true);
$transparent = imagecolorallocatealpha($img, 255, 255, 255, 127);
imagefill($img, 0, 0, $transparent);
//Text information
$text = "some text as example";
$font = "arialbd.ttf"
$fontsize = 26; //default font to be altered later
//simulate a complete text box and get the width
$bbox = imageftbbox($fontsize, 0, $font, $text);
$tbwidth = $bbox[2];
//Calculate different between our transparent image and text box
//I've added a little padding (20px) since the text sometimes crossing the edge..
$factor = (($width - 20) / $tbwidth);
//Alter font size with difference to fit fully image
$newfontsize = $fontsize * $factor;
//Find the horisontal center
$bbox = imageftbbox($newfontsize, 0, $font, $text);
$newheight = ($height / 2) + (($bbox[3] - $bbox[7]) / 2);
//Set Color of text
$color = imagecolorallocate($img, 200, 0, 0);
//Produce our image
imagettftext($img, $newfontsize, 0, 0, $newheight, $color, $font, $text);
//Copy image to file and free the cached image
$target = "testimage.png";
imagepng($img, $target);
imagedestroy($img);
}
As rwhite35 mentioning here, please keep in mind that GD 2.0 write font size is in points and not pixels.
Use the imageftbbox function to get the size of the bounding box of the text. You can then adjust the text size to fit the size of the image exactly.
http://www.php.net/manual/en/function.imageftbbox.php

Unable to horizontally center 'm' with GD2

My goal is to draw a horizontally centered m. I therefore calculate the width of the letter, substract that value from the total width and finally divide by 2. The result should be the distance from the left (or equally from the right).
However, the 'm' is always misplaced. I also noticed that some fonts may not trigger the problematic behavior. Note that my script correctly works for all other latin characters.
Arial:
Bitstream Vera Sans:
<?php
$totalWidth = 100;
$totalHeight = 100;
$font = 'Arial.ttf';
$img = imagecreatetruecolor($totalWidth, $totalHeight);
$red = imagecolorallocate($img, 255, 0, 0);
$fontSize = 100;
$bbox = imagettfbbox($fontSize, 0, $font, 'm');
$width = max($bbox[2], $bbox[4]) - max($bbox[0], $bbox[6]);
$centeredX = ($totalWidth - $width) / 2;
imagettftext($img, 100, 0, $centeredX, 100, $red, $font, 'm');
imagepng($img, 'testcase.png');
imagedestroy($img);
There is a small space left of each letter, and this is different each letter. Somebody on PHP.net wrote a solution for this: http://www.php.net/manual/en/function.imagettfbbox.php#97357
You need to adjust your code a little bit.
$totalWidth = 100;
$totalHeight = 100;
$font = 'Arial.ttf';
// change letter to see it with different letters
$letter = "m";
$img = imagecreatetruecolor($totalWidth, $totalHeight);
$red = imagecolorallocate($img, 255, 0, 0);
$fontSize = 100;
$bbox = calculateTextBox($fontSize, 0, $font, $letter);
$centeredX = (($totalWidth - $bbox['width']) / 2);
// here left coordinate is subtracted (+ negative value) from centeredX
imagettftext($img, 100, 0, $centeredX + $bbox['left'], 100, $red, $font, $letter);
header('Content-Type: image/png');
imagepng($img);
imagedestroy($img);

Changing the center of rotation of Imagerotate

Imagerotate rotates the image using the given angle in degrees.
The center of rotation is the center of the image, and the rotated image may have different dimensions than the original image.
How do I change the center of rotation to coordinate x_new and y_new and avoid automatic resizing?
Example: Rotation around red dot.
First idea that comes to mind is to move the image so that its new center is at x_new, y_new rotate it and move back.
assumptions:
0 < x_new < w
0 < y_new < h
Pseudocode:
new_canter_x = MAX(x_new, w - x_new)
new_center_y = MAX(y_new, h - y_new)
create new image (plain or transparent background):
width = new_canter_x * 2
height = new_center_y * 2
copy your old image to new one to coords:
new_center_x - x_new
new_center_y - y_new
imagerotate the new image.
now you just have to cut the part that you are interested in out of it.
The right way to do it is rotate and then crop with right params of transformation.
Another way is move, rotate then move again (simpler math but more code).
$x and $y are coordinates of the red dot.
private function rotateImage($image, $x, $y, $angle)
{
$widthOrig = imagesx($image);
$heightOrig = imagesy($image);
$rotatedImage = $this->createLayer($widthOrig * 2, $heightOrig * 2);
imagecopyresampled($rotatedImage, $image, $widthOrig - $x, $heightOrig - $y, 0, 0, $widthOrig, $heightOrig, $widthOrig, $heightOrig);
$rotatedImage = imagerotate($rotatedImage, $angle, imageColorAllocateAlpha($rotatedImage, 0, 0, 0, 127));
$width = imagesx($rotatedImage);
$height = imagesy($rotatedImage);
$image = $this->createLayer();
imagecopyresampled($image, $rotatedImage, 0, 0, $width / 2 - $x, $height / 2 - $y, $widthOrig, $heightOrig, $widthOrig, $heightOrig);
return $image;
}
private function createLayer($width = 1080, $height = 1080)
{
$image = imagecreatetruecolor($width, $height);
$color = imagecolorallocatealpha($image, 0, 0, 0, 127);
imagefill($image, 0, 0, $color);
return $image;
}

Empty space getting filled with black after GD imagecopy but only in some scenarios

I am experiencing an issue where I am allowing the user to resize images in a container and then need to create a resulting image that is the size of the container but with the image scaled and adjusted as per the users choices.
So for example say the container is 400 x 200 and the user wants to be able to put in a logo that is 600 x 100 they may wish to shrink the logo so it fits and leave space at the top and bottom. I need to be able to save that as an image that is 400x200 with the appropriate gaps at the top and bottom.
What I have found though is that if the image content (the logo in this example) extends beyond BOTH the top and the right of the container everything is fine OR if it DOESNT extend beyond either it is fine but if it extends beyond one and not the other then I get black fill- or something like that- see examples below...
Below are some examples of the results and this is the code I am using...
$cropped = wp_imagecreatetruecolor( $frame_w, $frame_h);
$backgroundColor = imagecolorallocatealpha($cropped, 0, 0, 0, 127);
//imageantialias( $cropped, true );
//if $img_y or $img_x are negative we need to apply the value to $img_x and $img_y
//if $img_y or $img_x are positive we need to apply the value to $dest_x and $dest_y
$dest_x = strstr($img_x,'-') ? 0 : abs($img_x);//if neg is true = 0 else offset inside
$dest_y = strstr($img_y,'-') ? 0 : abs($img_y);
$img_x = strstr($img_x,'-') ? abs($img_x) : 0;//if neg is true offset outside else 0
$img_y = strstr($img_y,'-') ? abs($img_y) : 0;
$img_w = $img_w > $frame_w ? $frame_w : $img_w;
$img_h = $img_h > $frame_h ? $frame_h : $img_h;
imagecopy( $cropped, $resized, $dest_x, $dest_y, $img_x, $img_y, $img_w, $img_h);
//imagecopymerge( $cropped, $resized, $dest_x, $dest_y, $img_x, $img_y, $img_w, $img_h,100);
//imagecopyresampled( $cropped, $resized, $dest_x, $dest_y, $img_x, $img_y, $frame_w, $frame_h, $img_w, $img_h );
imagefill($cropped, 0, 0, $backgroundColor);//putting this after the copy makes any black borders transparent again unless $resized does not extend beyond both dimensions
Examples
Image does not extend beyond top or beyond right (fine)
Image extends beyond bottom but not right (not fine)
Image extends beyond both (fine)
Image extends beyond right but not bottom (not fine)
Image Extends beyond neither (fine)
I have been literally tearing my hair out trying to fix this and tried every possible combination of imagesavealpha, imagecopymerged, imagecolorallocatealpha, imagealphablending etc I can think of but nothing seems to fix this...
So is this a bug/limitation of GD? Or can someone out there come to my rescue!
I don't know if this will help you, but I had an issue with this earlier today. The box expands but the area was black. Here's my code (which fixes it):
<?php
function createImage($text)
{
// Adds an extra space to fill underline
$text = " $text";
// Adds one line at the end
$text .= "\n";
// Wrap the text to fit the image
$text = wordwrap($text, 40, "\n");
// Count new lines
$newlines = substr_count($text, "\n");
// Count how long to expand
if($newlines == 0)
{
$height = 30;
}
else
{
$height = 30*$newlines-$newlines*5;
}
putenv('GDFONTPATH=' . realpath('.'));
header('Content-Type: image/png');
// Adding underline
$e = explode('<', $text);
for($i=0;$i<count($e);$i++)
{
$e[$i] = implode('̲', str_split($e[$i]));
}
// Creating image
$text = implode(' ', $e);
$im = imagecreatetruecolor(315, $height);
$white = imagecolorallocate($im, 255, 255, 255);
$grey = imagecolorallocate($im, 128, 128, 128);
$black = imagecolorallocate($im, 0, 0, 0);
$purple = imagecolorallocate($im, 97, 26, 139);
imagefilledrectangle($im, 0, 0, 399, $height, $white);
$font = 'arialbd.ttf';
imagettftext($im, 11, 0, 10, 20, $purple, $font, $text);
imagepng($im);
imagedestroy($im);
}
createImage("asbdasddsa");
?>
i think this will happen only for PNG check with other formats
I'm not sure if this is an actual answer since it is basically 'Use ImageMagick' but anyway for those for whom ImageMagick is an option the code below might help them achieve the same thing as I was trying to above... Basically ImageMagick seems far superior to GD, no borders around rotated images, no hassles with transparency, no onwanted black fill, clearer resizing if you are enlarging...
$img_x = -50; //left offset of the image within the frame
$img_y = 50; //top offset of the image within the frame
$img_w = 400; //width of the image to be put in the frame
$img_h = 200; // height of the image to be put in the frame
$angle = 45; //rotation to be applied to the image before it is put into the frame
$frame_w = 300; //width of the frame the image is going into
$frame_h = 300; //height of the frame the image is going into
$img_path = 'path/to/image/file.jpg';
$image = new Imagick( $img_path );
$size = $image->getImageGeometry();
$orig_w = $size['width']; $orig_h = $size['height'];
$image->scaleImage( $img_w, $img_h );
//rotate if necessary
if($angle)
{
$image->rotateImage( new ImagickPixel('none'), $angle );
$size = $image->getImageGeometry();
$img_w = $size['width']; $img_h = $size['height'];
}
//composite into frame
//in imagemagick we create an image that is the size of the frame and make it transparent
$frame = new Imagick();
$frame->newImage($frame_w, $frame_h, new ImagickPixel("none"));
//then we composite the image itself into this with the respective offset values
$frame->compositeImage( $image, Imagick::COMPOSITE_DEFAULT, $img_x, $img_y );
//save it
$destfilename = "{$dir}/{$name}-{$img_suffix}.{$ext}";
$frame->writeImage($destfilename);
$frame->clear();
$frame->destroy();
$image->clear();
$image->destroy();
The above code produces this...yay!

Categories