PHP imagerotate is cropping image - php

I am writing a script that takes an arrow image and rotates it by a set number of degrees. Using the code below, when the angle is a multiple of 90 the image rotates and displays as expected.
The source image looks like this (74 x 74):
Images after rotating by 90:
Images after rotating by any other number (not a multiple of 90) eg 45:
As can be seen in the image, the tip of the arrow has been cropped out of the image. Could anyone please tell me why this is happening? Again, multiples of 90 are fine, it's just any other number where the unusual cropping occurs.
$props = ['w' => 74, 'h' => 74];
$angle = 360 - $_GET['angle'];
$final_img = imagecreatetruecolor($props['w'], $props['h']);
imagesavealpha($final_img, true);
$transColor = imagecolorallocatealpha($final_img, 0, 0, 0, 127);
imagefill($final_img, 0, 0, $transColor);
$rotate = imagecreatefrompng('arrow.png');
$src = imagerotate($rotate, $angle, $transColor); //rotated my image
$src_x = ImageSX($src); //find out new x width
$src_y = ImageSY($src); //find out new y height
$src_widthx = $src_x/2 - $props['w']/2; // divide each by 2 and then subtract desired end width from wider rotated width
$src_heighty = $src_y/2 - $props['h']/2; // and again for height
imagecopy($final_img, $src, 0, 0, $src_widthx, $src_heighty, $props['w'], $props['h']);
header('Content-Type: image/png');
imagepng($final_img);

When you rotate a square of nXm pixels by lets say 45 degrees you will get the diagonals(which are bigger than n or m and equal sqrt(n^2+m^2)) of the image be the new rotated image width and height.
The function crops the rotated image using the original dimensions of the image, namely n and m.
A way to fix the problem would be by crating a bigger blank image with the appropriate size, sqrt(width_original_image^2+height_original_image^2), and than copy the original image to the new image using imagecopy. After that you can use imagerotate on the new image

I installed and used the ImageMagick PHP library and the rotations show uncropped, no matter the degree of rotation.

Related

php resize & crop image only if exceeding certain pixel size

I have a script that scale an image on server and save onto itself, which is fine, but I like to modify it so this script applies only if the original image exceeds certain pixel size and crop the result finally,
so the flow is
1. if source image is higher than 80 pixel or wider than 300 pixel then continue
2. scale the source image proportionally to 80 pixel high
3. if the new width is over 300 pixel then crop the image to 300 pixels starting from left edge
4. save the image onto itself
the php I used for scaling is
<?php
$org_info = getimagesize("test.jpg");
$rsr_org = imagecreatefromjpeg("test.jpg");
$rsr_scl = imagescale($rsr_org, 320, 80, IMG_BICUBIC_FIXED);
imagejpeg($rsr_scl, "test.jpg");
imagedestroy($rsr_org);
imagedestroy($rsr_scl);
?>
Any help is appreciated, thanks
You can use getimagesize (http://php.net/manual/en/function.getimagesize.php) to get image size in php.
And imagecopyresized to resize image with new dimensions (http://php.net/manual/fr/function.imagecopyresized.php).
$size = getimagesize($filename);
// $size[0] is width
// $size[1] is height
$thumb = imagecreatetruecolor($newwidth, $newheight);
$source = imagecreatefromjpeg($filename);
imagecopyresized($thumb, $source, 0, 0, 0, 0, $newwidth, $newheight, $width, $height);

How to make any "extra space" white when using imagecopy in PHP

I am using imagecopy to crop a PNG image to a user specification.
Currently, if the crop area is bigger than the image, any "extra space" becomes black, but I would like it to be white.
Have searched around a bunch, I have discovered that you can use imagefill or imagefilledrectangle to make the background white, however if this is done before imagecopy, then it has no effect and if it is done after imagecopy, it also makes any black parts of the image white.
My code currently looks like this and suffers from black parts of the original image being turned white as well as the extra space:
// Input and output files
$infile = "[Image]";
$outfile = "[Output path for image]"
// Make the image
$orig =imagecreatefromjpeg($infile);
$width = imagesx($orig);
$height = imagesy($orig);
$new = imagecreatetruecolor($width, $height);
// Crop the image
imagecopy($new, $orig, 0, 0, -100, 100, $width, $height);
// Try and make the extra space white
$white = imagecolorallocate($new, 255,255,255);
imagefill($new, 0, 0, $white);
// Save the file
imagepng($new, $outfile);
How can I make that extra space white without affecting the original image? I have no control over what image users might upload, so I can't really pick a transparent color as that color might be part of their original image.
EDIT: This scenario arises when a user chooses a crop size outside of the original image dimensions, something that I do want to be a valid option. The crop is to force a square image, but if the user uploads, say, a landscape rectangle and wants all of their image in the final crop, then the crop will be outside of the image on the top and bottom (which is where I want it to be white instead of black)
This happens because you are supplying 'invalid' values to imagecopy() (that is, the crop coordinates are outside the bounds of the source image). GD simply fills in the out of bounds area with black pixels. It would be lovely if it instead used transparent (or any colour) pixels but unfortunately that's not an option.
I don't completely understand what you are trying to do (your source doesn't seem to match your stated goal) but a possible solution involves restricting the crop to the bounds of the image:
$src = imagecreatefromjpeg('JPEG FILE'); // 100x100 image in my test.
$src_w = imagesx($src);
$src_h = imagesy($src);
$user_crop = [
'x' => -50,
'y' => -50,
'width' => 150,
'height' => 150
];
if ($user_crop['x'] < 0) {
$user_crop['x'] = 0;
}
if ($user_crop['y'] < 0) {
$user_crop['y'] = 0;
}
if ($user_crop['x'] + $user_crop['width'] > $src_w) {
$user_crop['width'] = $src_w - $user_crop['x'];
}
if ($user_crop['y'] + $user_crop['height'] > $src_h) {
$user_crop['height'] = $src_h - $user_crop['y'];
}
$dest = imagecreatetruecolor($src_w, $src_h);
imagefill($dest, 0, 0, 0x00ffffff); // opaque white.
imagecopy(
$dest,
$src,
$user_crop['x'],
$user_crop['y'],
$user_crop['x'],
$user_crop['y'],
$user_crop['width'],
$user_crop['height']
);
header('Content-type: image/png;');
imagepng($dest);
imagedestroy($src);
imagedestroy($dest);
exit;
Note that I've made a few assumptions in this code about placement of the cropped image.

imagecopyresampled() results in split color background imagefill()

I have a script that takes an image and when resampled centers the short dimension (width or height) on a square coloured background.
This works fine for images with a longer WIDTH but for some reason any image with a longer HEIGHT the result is a split background fill - the correct colour on the left but the default black on the right. If I play with the x-axis offset the background fill only extends to the right as far as the image placement.
The calculated values are as expected for the vertical images so I cannot figure out what is happening here. Note that 'imagecopy()' produces the exact same behaviour.
original image is 155 x 400px
adjusted source dimensions for square aspect ratio = 400 x 400px
resulting thumbnail to be 250 x 250px
Here is the code with static values for one example:
$thumb = imagecreatetruecolor(250, 250);
imagecopyresampled($thumb, $source, 77, 0, 0, 0, 250, 250, 400, 400);
$blue = imagecolorallocate($thumb, 0xDE, 0xE6, 0xF9);
imagefill($thumb, 0, 0, $blue);
Using the same image rotated 90 degrees (400 x 155 px) so it is longer horizontally DOES apply the full background fill:
imagecopyresampled($thumb, $source, 0, 77, 0, 0, 250, 250, 400, 400);
For the vertical image, my coordinate values (77, 0) place the image on the imagecreatetruecolor() canvas centered exactly where I want it but changing any of the other imagecopyresampled() values stretch or squeeze the resampled image or crop it.
Am I overlooking something simple? View the screenshots here:
http://i.stack.imgur.com/5CxHU.jpg (vertical issue) and
http://i.stack.imgur.com/wvhzP.jpg (OK horizontally)
This vertical issue must have something to do with PHP's resampling/imagefill algorithm (?) but here is a workaround that now works for centering all of my vertical images within my square canvas:
1) You need to first pad your image placeholder so the background fill will extend to the right edge in the resampled image by extending the thumbnail height with your x-axis offset (sounds odd but it works)...we will trim this off later:
$thadj_height = $th_height + $th_x;
$thumb = imagecreatetruecolor($th_width, $thadj_height);
2) Resample as usual with the background fill (note that the fill is applied AFTER the resampling statement, odd but just works that way)...remember that $thumb has more height than what $th_width, $th_height will occupy:
imagecopyresampled($thumb, $source, $th_x, $th_y, 0, 0, $th_width, $th_height, $src_width, $src_height);
imagefill($thumb, 0, 0, $bgcolor);
3) Temporarily save the image output so a new function can be applied to it next - set quality to lossless since we'll be reusing it:
imagejpeg($thumb, "resampled/output_temp.jpg", 100);
imagedestroy($thumb);
4) Retrieve the temporary file and grab the new dimensions (overwrite the previous variables):
$file = "resampled/output_temp.jpg";
$image = file_get_contents($file);
$source = imagecreatefromstring($image);
list($src_width, $src_height) = getimagesize($file);
5) Create a new image placeholder, square as originally intended in my case:
$thumb = imagecreatetruecolor($th_width, $th_height);
6) Now copy the temporary padded thumbnail into the square placeholder which will result in cropping off the padding:
imagecopy($thumb, $source, 0, 0, 0, 0, $src_width, $src_height);
header('Content-Type: image/jpeg');
echo imagejpeg($thumb);
imagedestroy($thumb);
Again, none of this is necessary for centering my horizontal images on a square canvas but this is a workaround that will work to eliminate the split fill background.

PHP GD image merge changing my image to black

I have a very frustrating situation. I am using PHP GD for the first time, and it's been a bit of a rollercoaster relationship. Basically, I am trying to merge 2 images, a square one (with a height/width of x) onto a rectangle (with a width of x and a height of y).
The square needs to be centered vertically. But this isn't the issue - I've managed to position it correctly.
Whats happening is, my rectangle is white. My square has a white background, so when the images are merged, it should just look like my asset on a white rectangluar background.
When I merge the image though, GD is for some reason changing my background white rectangle to black - so you can see the white square in the middle, with black "bars" on top and bottom. Can anyone help?
Code is:
//create copy of original image to correct size
imagecopyresized($dst_image, $src_image, 0,0,0,0,$x_width,$x_height,$orig_img_x_width,$orig_img_x_height);
imagejpeg($dst_image, "resized_copy.jpg", 100);
$img = imagecreatetruecolor(1333, 2000);
$white = imagecolorallocate($img, 255, 255, 255);
imagefill ( $img, 0, 0, $white );
imagefilledrectangle($img,0,0,1333,2000, $white);
imagejpeg($img, "rectangle.jpg", 100);
//merge images
$dest2 = imagecreatefromjpeg("rectangle.jpg");
$src2 = imagecreatefromjpeg('resized_copy.jpg');
imagecopymerge($dest2, $src2, 0, 0, 0, -333.5, $x_width, $x_height, 100);
imagejpeg($dest2, "final_image.jpg", 100);
I've tried using imagecopy instead of imagecopymerge, but I get the same result. I'm sure there is a simple explanation, but I cant seem to find it trawling through the php manual.
I've read your question a few times but I'm not convinced I understand exactly what you are trying to achieve so I've made a few assumptions in producing the below code.
For the sake of simplicity I've created a 'square.jpg' test image file like so:
(Note that I've used small image sizes here so I can show them inline.)
// read in the square test image.
$square = imagecreatefromjpeg('square.jpg');
$square_x = imagesx($square); // 100px
$square_y = imagesy($square); // 100px
// create the rectangular image to merge with.
$rectangle = imagecreatetruecolor(100, 200);
$rectangle_x = imagesx($rectangle); // 100px
$rectangle_y = imagesy($rectangle); // 200px
// note that this isn't white, but rather a lovely shade of blue to better
// show the image on the white SO background!
$white = imagecolorallocate($rectangle, 128, 128, 255);
imagefill($rectangle, 0, 0, $white);
// merge the images.
imagecopymerge(
$rectangle,
$square,
0,
($rectangle_y / 2) - ($square_y / 2), // to vertically centre the square.
0,
0,
$square_x,
$square_y,
75 // Just to show the merge clearly; change back to 100 for your usage.
);
imagejpeg($rectangle, 'final_image.jpg', 100);
imagedestroy($rectangle);
imagedestroy($square);
This gives me the following image in final_image.jpg:

Rotate and crop

I'm rotating and cropping a image with PHP, but I get the black border showing, I know you can change the background color, but I want to rotate and crop the image to fill the whole image. Basically something similar to background-size: cover; (left) in CSS versus background-size: contain; (right).
See the image below, at right is what I got now, left is what I want to achieve. The number of degrees to rotate is dynamic and the image to be produced and the source-image are both square (200x200).
EDIT: Here is my quick and dirty code:
$rotate = imagecreatefromjpeg($image);
// part of code created by www.thewebhelp.com, modified
$square_size = 200;
$original_width = imagesx($rotate);
$original_height = imagesy($rotate);
if($original_width > $original_height){
$new_height = $square_size;
$new_width = $new_height*($original_width/$original_height);
}
if($original_height > $original_width){
$new_width = $square_size;
$new_height = $new_width*($original_height/$original_width);
}
if($original_height == $original_width){
$new_width = $square_size;
$new_height = $square_size;
}
$new_width = round($new_width);
$new_height = round($new_height);
$smaller_image = imagecreatetruecolor($new_width, $new_height);
$square_image = imagecreatetruecolor($square_size, $square_size);
imagecopyresampled($smaller_image, $rotate, 0, 0, 0, 0, $new_width, $new_height, $original_width, $original_height);
if($new_width>$new_height){
$difference = $new_width-$new_height;
$half_difference = round($difference/2);
imagecopyresampled($square_image, $smaller_image, 0-$half_difference+1, 0, 0, 0, $square_size+$difference, $square_size, $new_width, $new_height);
}
if($new_height>$new_width){
$difference = $new_height-$new_width;
$half_difference = round($difference/2);
imagecopyresampled($square_image, $smaller_image, 0, 0-$half_difference+1, 0, 0, $square_size, $square_size+$difference, $new_width, $new_height);
}
if($new_height == $new_width){
imagecopyresampled($square_image, $smaller_image, 0, 0, 0, 0, $square_size, $square_size, $new_width, $new_height);
}
$degrees = rand(1,360);
$square_image = imagerotate($square_image, $degrees, 0);
imagejpeg($square_image,NULL,100);
Replace these lines at around the end of your code:
$degrees = rand(1,360);
$square_image = imagerotate($square_image, $degrees, 0);
imagejpeg($square_image,NULL,100);
With this:
$degrees = rand(1,360);
$square_image = imagerotate($square_image, $degrees, 0);
$rotated_size = imagesx($square_image);
$enlargement_coeff = ($rotated_size - $square_size) * 1.807;
$enlarged_size = round($rotated_size + $enlargement_coeff);
$enlarged_image = imagecreatetruecolor($enlarged_size, $enlarged_size);
$final_image = imagecreatetruecolor($square_size, $square_size);
imagecopyresampled($enlarged_image, $square_image, 0, 0, 0, 0, $enlarged_size, $enlarged_size, $rotated_size, $rotated_size);
imagecopyresampled($final_image, $enlarged_image, 0, 0, round($enlarged_size / 2) - ($square_size / 2), round($enlarged_size / 2) - ($square_size / 2), $square_size, $square_size, $square_size, $square_size);
imagejpeg($final_image,NULL,100);
Here's the logic behind that:
1) After performing imagerotate() our new image has changed its dimensions, since every rotation generally results in a larger image. Since the source is a square image we take either the width or the height in order to determine the dimensions of the rotated image.
2) When the original image is rotated, even a little bit, the dimensions of the largest square of usable pixel data from the original image will always be smaller than the original unrotated square image. Therefore, in order to generate a new square image of the same size as the initial square image, but without the "black border" artifact, as you call it, we need to enlarge the rotated image, so that the largest square of usable pixel data from the original image in the rotated image can become as big as the initial square image.
The key value here is 1.807. This value basically shows how many pixels you need to enlarge a rotated image for each pixel of difference between its dimensions and the dimensions of the original unrotated image. There's probably a better Math formula to retrieve this value, unfortunately I suck at Math, so here's the hard way of coming up with that value.
A rotation of 45 / 135 / 225 / 315 degrees will always produce the largest image with the smallest usable pixel data square.
Knowing this, you compare the dimensions of the original image and its 45-degrees-rotated version. In our case the original image is 200x200 and a 45-degrees-rotated version is about 283x283
In a program like Photoshop, you determine how many times you need to enlarge the 45-degrees-rotated version of the image in order to be able to extract a 200x200 square from it, without a "black border" - in our case the 283x283 image needed to be enlarged to a 433x433 image, so we could extract a 200x200 square
433 - 283 = 150 -> meaning we need to enlarge the largest possible rotated image with 150 pixels in order to be able to extract a 200x200 square from it.
283 - 200 = 83 -> 83 pixels is the difference between the largest possible rotated image and the original unrotated image.
The "smaller" the transformation - the "larger" the square area we can use and thus - the "smaller" the amount of enlargement we need to apply. And since a 45 degree rotation resulted in a difference of 83 pixels between the original image and the transformed image that required a 150 pixel enlargement, we can do:
150 / 83 = 1.807 -> meaning a difference of 1 pixel between the original image and the rotated image requires that the rotated image is enlarged with 1.807 pixels, so that we can extract a square from it that has the same dimensions as the original image
3) Knowing that for each 1 pixel difference we need to enlarge with 1.807 pixels, we check what's the difference between our rotated image size and original image size and multiply it by that value, to see what dimensions should the enlarged image have:
$enlargement_coeff = ($rotated_size - $square_size) * 1.807;
$enlarged_size = round($rotated_size + $enlargement_coeff);
4) We go ahead and generate the enlarged rotated image.
imagecopyresampled($enlarged_image, $square_image, 0, 0, 0, 0, $enlarged_size, $enlarged_size, $rotated_size, $rotated_size);
5) Finally, we extract a 200x200 square from our enlarged rotated image, using its center coordinates as reference
imagecopyresampled($final_image, $enlarged_image, 0, 0, round($enlarged_size / 2) - ($square_size / 2), round($enlarged_size / 2) - ($square_size / 2), $square_size, $square_size, $square_size, $square_size);
To break that down ($square_size / 2) returns the X and Y coordinates of the center point in the enlarged rotated image. round($enlarged_size / 2) returns the amount of pixels that you need left from the center along the X axis, and above the center along the Y axis, in order to get a 200x200 square.
I hope you understand the logic, although I'm understanding my explanation may sound a bit ambiguous, so please feel free to ask more!

Categories