php resize & crop image only if exceeding certain pixel size - php

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);

Related

PHP imagerotate is cropping image

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.

PHP imagecopyresampled() produces image border on one side

I have a resampling script that centers resampled images on a square canvas.
For horizontally-oriented images that are centered vertically on a white background a line is added along the BOTTOM edge of the resampled image.
For vertically-oriented images that are centered horizontally the line appears on the RIGHT edge of the resampled image.
$thumb = imagecreatetruecolor($th_width, $th_height);
imagecopyresampled($thumb, $source, $th_x, $th_y, 0, 0, $th_width, $th_height, $src_width, $src_height);
$bgcolor = imagecolorallocate($thumb, 255, 255, 255);
imagefill($thumb, 0, 0, $bgcolor);
The line is there regardless of background fill, it just shows up most on white.
What causes this? No amount of adjusting parameter values will get rid of it (they just offset the resampled image on the background or distort it).
I am providing my own workaround for this issue.
It appears the image border artifact is a COMBINATION result of 'imagecopyresampled()' AND centering offsets. If I just resample the image as-is THEN center and fill the image, the border is avoided. Here is the workaround:
1) RESAMPLE your image as-is (same aspect ratio) then SAVE to retain changes:
$thumb = imagecreatetruecolor($res_width, $res_height);
imagecopyresampled($thumb, $source, 0, 0, 0, 0, $res_width, $res_height, $src_width, $src_height);
// SAVE new image - set quality to lossless since reusing it:
imagejpeg($thumb, "resampled/output_temp.jpg", 100);
2) Retrieve the temporary image:
$file = "resampled/output_temp.jpg";
$image = file_get_contents($file);
$source = imagecreatefromstring($image);
// Get the RESAMPLED image dimensions
list($src_width, $src_height) = getimagesize($file);
3) NOW apply CENTERING and FILL:
$thumb = imagecreatetruecolor($th_width, $th_height);
// COPY the image
imagecopy($thumb, $source, $th_x, $th_y, 0, 0, $src_width, $src_height);
$bgcolor = imagecolorallocate($thumb, 255, 255, 255);
// Replace default black background - this must be placed AFTER imagecopy()
imagefill($thumb, 0, 0, $bgcolor);
// Save image (uses default quality setting 75)
imagejpeg($thumb, "resampled/" . $newfile);
// Optionally free up memory:
imagedestroy($thumb);
The result is a clean image.
For correcting incomplete imagefill() on the background when centering vertically-oriented images see this post:
imagecopyresampled() results in split color background imagefill()

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.

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!

How do i resize image file to optional sizes

I have image upload form, user attaches aimage file, and selects image size to resize the uploaded image file(200kb, 500kb, 1mb, 5mb, Original). Then my script needs to resize image file size based on user's optional size, but im not sure how to implement this feature,
For example, user uploads image with one 1mb size, and if user selects 200KB to resize, then my script should save it with 200kb size.
Does anyone know or have an experience on similar task ?
Thanks for you reply in advance.
With the GD library, use imagecopyresampled().
<?php
// The file
$filename = 'test.jpg';
$percent = 0.5;
// Content type
header('Content-type: image/jpeg');
// Get new dimensions
list($width, $height) = getimagesize($filename);
$new_width = $width * $percent;
$new_height = $height * $percent;
// Resample
$image_p = imagecreatetruecolor($new_width, $new_height);
$image = imagecreatefromjpeg($filename);
imagecopyresampled($image_p, $image, 0, 0, 0, 0, $new_width, $new_height, $width, $height);
// Output
imagejpeg($image_p, null, 100);
?>
Edit: If you want to resize the image file to a specified size, that's a little harder. All the major image formats use compression and compression rates vary by the nature of what's being compressed. Compress clear blue sky and you'll get a better compression ratio than you will sea of people.
The best you can do is try a particular size is try a particular size and see what the file size is, adjusting if necessary.
Resize ratio = desired file size / actual file size
Resize multipler = square root (resize ratio)
New height = resize multiplier * actual height
New width = resize multiplier * actual width
This basically factors in an approximation of the expected compression ratio. I would expect that you would have some tolerance (like +/- 5%) and you can tweak the numbers as necessary.
There is no direct way to resize to a particular file size. Lastly I'll add that resizing to a particular file size is rather unusual. Resizing to a particular height and/or width (maintaining aspect ratio) is far more common and expected (by users).
Update: as correctly pointed out, this gets the file size wrong. The ratio needs to be the square root of the file size ratios as you're applying it twice (once to height, once to width).
Using the GD Library provided in PHP:
// $img is the image resource created by opening the original file
// $w and $h is the final width and height respectively.
$width = imagesx($img);$height = imagesy($img);
$ratio = $width/$height;
if($ratio > 1){
// width is greater than height
$nh = $h;
$nw = floor($width * ($nh/$height));
}else{
$nw = $w;
$nh = floor($height * ($nw/$width));
}
//centralize image
$nx = floor(($nw- $w) / 2.0);
$ny = floor(($nh-$h) / 2.0);
$tmp2 = imagecreatetruecolor($nw,$nh);
imagecopyresized($tmp2, $img,0,0,0,0,$nw,$nh,$width,$height);
$tmp = imagecreatetruecolor($w,$h);
imagecopyresized($tmp, $tmp2,0,0,$nx,$ny,$w,$h,$w,$h);
imagedestroy($tmp2);imagedestroy($img);
imagejpeg($tmp, $final_file);
This piece of code will take the original image, resize to the specified dimensions. It will first try to ratio aspect resize the image, then crop off + centralize the image, making it fall nicely into the dimensions specified.

Categories