Rotate and crop

2019-02-07 07:12发布

问题:

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

回答1:

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!