13

I have an area on a site that I am working on that will display a users profile image that is pulled from an external source (therefore no control on its original size).

What I am looking to do is take an image (in this example 1000px x 800px and resize it to 200px x 150px. Obviously with this there is an aspect ratio difference.

What I want to do is resize the original image without distortion, which in this case would produce a 200px x 160px image. What I then want to do is crop any excess from the edges to produce the correct image size. So in this case crop 5px off the top and bottom of the image finally producing a 200px x 150px.

I have the WideImage library currently and would like to use that. I have seen several similar questions on SO but nothing that I can say exactly does as I am trying to achieve.

binaryLV
  • 9,002
  • 2
  • 40
  • 42
lethalMango
  • 4,433
  • 13
  • 54
  • 92
  • What is your question? And please stop writing tags in titles. This is not a message board; we have our own semantic post tagging system. – Lightness Races in Orbit Jun 15 '11 at 10:05
  • Math behind aspect ratios should not be complicated, as `resize()` method of `WideImage` already does the math. I'm not sure though if `$fit` should be set to `inside` or `outside`. Also, I can't understand from docs if `crop('center', 'center', 200, 150)` would have `center-100, center-75` or `center, center` as *top-left* corner. – binaryLV Jun 15 '11 at 10:07
  • I wasn't sure on the API reference on how to do it, if it is possible directly via the API itself or whether it requires calculation beforehand (which I'm not sure how to do as I've never had to do it before). – lethalMango Jun 15 '11 at 10:07

5 Answers5

27

You might try:

$img->resize(200, 150, 'outside')->crop('center', 'middle', 200, 150);

Some users post their versions of calculations... Here's also my version:

$sourceWidth = 1000;
$sourceHeight = 250;

$targetWidth = 200;
$targetHeight = 150;

$sourceRatio = $sourceWidth / $sourceHeight;
$targetRatio = $targetWidth / $targetHeight;

if ( $sourceRatio < $targetRatio ) {
    $scale = $sourceWidth / $targetWidth;
} else {
    $scale = $sourceHeight / $targetHeight;
}

$resizeWidth = (int)($sourceWidth / $scale);
$resizeHeight = (int)($sourceHeight / $scale);

$cropLeft = (int)(($resizeWidth - $targetWidth) / 2);
$cropTop = (int)(($resizeHeight - $targetHeight) / 2);

var_dump($resizeWidth, $resizeHeight, $cropLeft, $cropTop);
binaryLV
  • 9,002
  • 2
  • 40
  • 42
  • 1
    Thank you I'll try that out and let you know how I get on – lethalMango Jun 15 '11 at 10:10
  • 1
    @Salman A, you're right, `outside` should be used, not `inside`. You were also right about using `center` as value of `$left` and `$top` arguments in `crop()`. – binaryLV Jun 15 '11 at 11:51
9

I tried all of your solutions and came up with strange numbers every time. So, this is my way of doing the calculations.

Basic proportional formula: a/b=x/y -> ay = bx then solve for the unknown value. So, let's put this into code.

This function assumes you've opened the image into a variable. If you pass the path you'll have to open the image in the function, do the math, kill it, return the values and then open the image again when you resize it, that's inefficient...

function resize_values($image){
    #for the purpose of this example, we'll set this here
    #to make this function more powerful, i'd pass these 
    #to the function on the fly
    $maxWidth = 200; 
    $maxHeight = 200;

    #get the size of the image you're resizing.
    $origHeight = imagesy($image);
    $origWidth = imagesx($image);

    #check for longest side, we'll be seeing that to the max value above
    if($origHeight > $origWidth){ #if height is more than width
         $newWidth = ($maxHeight * $origWidth) / $origHeight;

         $retval = array(width => $newWidth, height => $maxHeight);
    }else{
         $newHeight= ($maxWidth * $origHeight) / $origWidth;

         $retval = array(width => $maxWidth, height => $newHeight);
    }
return $retval;
}

Above function returns an array, obviously. You can work the formula into whatever script you're working on. This has proven to be the right formula for the job. So... up to you whether or not you use it. LATER!

Community
  • 1
  • 1
Chad A
  • 91
  • 1
  • 1
  • Isn't this good to add another if before those if to check whether they are equals in order not to calculate? – kodfire May 01 '22 at 08:06
2

Answer #1 (edited)

After having a look at this and this, you probably need to do something like this:

$img->resize(200, 150, 'outside')->crop("center", "center", 200, 150);

While resizing it will resize the image so that it either fits exactly within the box (box = 200x150) or one of the dimension fits while the other exceeds the box. While cropping, the portion of the image that bleeds outside the box will be trimmed. Specifying the center smart coordinate means the top+bottom or left+right portion will be removed.

Answer #2

If you are having problems in calculating what to crop, try this:

<?php
$target_wide = 200;
$target_tall = 150;

$test_case = array(
    array(1000, 800),
    array(800, 1000),
    array(1000, 750), // perfect fit
    array(750, 1000)
);

foreach($test_case as $test) {
    list(
        $source_wide,
        $source_tall
    ) = $test;
    $source_aspect_ratio = $source_wide / $source_tall;
    $target_aspect_ratio = $target_wide / $target_tall;
    if ($source_aspect_ratio > $target_aspect_ratio)
    {
        $output_tall = $target_tall;
        $output_wide = (int) ($target_tall * $source_aspect_ratio);
    }
    else
    {
        $output_wide = $target_wide;
        $output_tall = (int) ($target_wide / $source_aspect_ratio);
    }
    $output_crop_hori = (int) (($output_wide - $target_wide) / 2);
    $output_crop_vert = (int) (($output_tall - $target_tall) / 2);
    var_dump($source_wide, $source_tall, $output_wide, $output_tall, $output_crop_hori, $output_crop_vert);
    echo PHP_EOL;
}

Output:

int(1000)
int(800)
int(200)
int(160)
int(0)
int(5)

int(800)
int(1000)
int(200)
int(250)
int(0)
int(50)

int(1000)
int(750)
int(200)
int(150)
int(0)
int(0)

int(750)
int(1000)
int(200)
int(266)
int(0)
int(58)
Community
  • 1
  • 1
Salman A
  • 262,204
  • 82
  • 430
  • 521
  • About the "Answer #1" (*which was actually "Answer #2" :)* )... First two arguments of `crop()` are `$left` and `$right`. I believe `center` as `$left` and `$right` would specify that corner of cropped image is in a center of image, i.e., if he has 200x160 image, `crop('center', 'center', 200, 150)` would be the same as `crop(100, 80, 200, 150)` rather than `crop(0, 5, 200, 150)`. Though, this should be tested. P.S. I already wrote about this method an hour ago... – binaryLV Jun 15 '11 at 11:17
  • @binary: yes it was answer #2 :) first two arguments are left and *top*, not right. Therefore `(0, 5, 200, 150)` and `('center', 'center', 200, 150)` are equivalent; specifying constants such as 'center' rids you of doing the calculation yourself. – Salman A Jun 15 '11 at 11:35
  • Just tested it, `$left` and `$right` should indeed be just `center`. – binaryLV Jun 15 '11 at 11:52
2
//Following code for keeping aspect ratio

$srcWidth=1000;
$srcHeight=800;

$targetWidth=200;
$targetHeight=150;

$diff=0;
//get difference

if( $srcWidth > $srcHeight ) // 1000 > 800
{
    $diff = $srcWidth - $srcHeight; // 1000 - 800 = 200
    $diff = ($diff / $srcWidth) * 100; // ( 200 / 1000 ) * 100 = 20
}
else
{
    $diff = $srcHeight - $srcWidth;
    $diff = ($diff / $srcHeight)*100;
}

if( $targetWidth > $targetHeight)
{
    $targetHeight = (( $targetHeight * $diff ) / 100) - $targetWidth; // (( 200 * 20 ) /  100) - 200 = 160
}
else
{
    $targetWidth = (( $targetWidth * $diff ) / 100) - $targetHeight;
}
mrjimoy_05
  • 3,452
  • 9
  • 58
  • 95
1

That's how you can calculate size of crop:

$src_width = 1000;
$src_height = 500;
$src_ratio = $src_width/$src_height;

$width = 200;
$height = 150;
$ratio = $width/$height;

$crop_height = 0;
$crop_width = 0;

if ($src_height > $src_width)
{
    $new_height = $width/$src_ratio;
    $crop_height = $new_height-$height;
}
else
{
    $new_width = $height*$src_ratio;
    $crop_width = $new_width-$width;
}

print 'Crop height: '.$crop_height.PHP_EOL
      .'Crop width: '.$crop_width;
OZ_
  • 12,492
  • 7
  • 50
  • 68
  • I think something is still wrong... Try setting `$src_height` to 1500. I am getting 200 x 133.333 instead of 200 x 300. – binaryLV Jun 15 '11 at 11:03
  • @binaryLV, it's calculation of crop, not of new width and height. With `$src_height = 1500;` result will be: `Crop height: -16.6(6)`. – OZ_ Jun 15 '11 at 11:11
  • I'm talking about `$new_width` and `$new_height`. And how can `crop height` be negative? Position of corner for cropping should be *in* image. – binaryLV Jun 15 '11 at 11:13
  • @binaryLV, it's just temporary variables to count size of crop. But you was right, in one place `*` should be replaced by `/` - copypaste error :) Thank you. – OZ_ Jun 15 '11 at 11:20