I'm working on allowing users to upload profile pictures for my site. The classic example of what I'm trying to avoid is plentyoffish.com where each users image is skewed and looks very ugly:
So, how can I progmatically crop/create standard sized versions of an image without the skewing demonstrated above?
Well, you must have a maximum height and width, lets assume the image size you have available is square, say 100x100.
When a user uploads an image get the dimensions of it, then work out which is greater, the height or the width.
Then take the greatest measurement and get the ratio of that measurement to your target measurement, then use that ratio to scale both the height and width.
So if the user uploads a picture of 500 height and 450 width, as the height is greatest you'd divide 100 by 500, your thumbnail size. This gives us .2 as the ratio. which means the width will become 90, so you would shrink to 100x90, and no distortion would occur.
Here's some code (C#) I used to do a resize, similar to the method suggested by blowdart. Just replace the "300"s with the maximum size of one side in your case:
private Bitmap ScaleImage(Image oldImage)
{
double resizeFactor = 1;
if (oldImage.Width > 300 || oldImage.Height > 300)
{
double widthFactor = Convert.ToDouble(oldImage.Width) / 300;
double heightFactor = Convert.ToDouble(oldImage.Height) / 300;
resizeFactor = Math.Max(widthFactor, heightFactor);
}
int width = Convert.ToInt32(oldImage.Width / resizeFactor);
int height = Convert.ToInt32(oldImage.Height / resizeFactor);
Bitmap newImage = new Bitmap(width, height);
Graphics g = Graphics.FromImage(newImage);
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
g.DrawImage(oldImage, 0, 0, newImage.Width, newImage.Height);
return newImage;
}
OR: If you would still like fixed dimensions, you follow blowdart's instructions but calculate the greatest ratio instead: 100px / 450px = .22..
- width: 100px
- height: 111.11..px -> crop from floor((111.11 - 100) / 2) on top and 100px down.
EDIT: Or let the user select how to crop the greatest dimension.
Use ImageMagick. On the command line use:
convert -thumbnail geometry
I made this function for PHP a while ago that works great for this and some other scenarios:
<?php
function Image($source, $crop = null, $resize = null)
{
$source = ImageCreateFromString(file_get_contents($source));
if (is_resource($source) === true)
{
$width = imagesx($source);
$height = imagesy($source);
if (isset($crop) === true)
{
$crop = array_filter(explode('/', $crop), 'is_numeric');
if (count($crop) == 2)
{
if (($width / $height) > ($crop[0] / $crop[1]))
{
$width = $height * ($crop[0] / $crop[1]);
$crop = array((imagesx($source) - $width) / 2, 0);
}
else if (($width / $height) < ($crop[0] / $crop[1]))
{
$height = $width / ($crop[0] / $crop[1]);
$crop = array(0, (imagesy($source) - $height) / 2);
}
}
else
{
$crop = array(0, 0);
}
}
else
{
$crop = array(0, 0);
}
if (isset($resize) === true)
{
$resize = array_filter(explode('*', $resize), 'is_numeric');
if (count($resize) >= 1)
{
if (empty($resize[0]) === true)
{
$resize[0] = round($resize[1] * $width / $height);
}
else if (empty($resize[1]) === true)
{
$resize[1] = round($resize[0] * $height / $width);
}
}
else
{
$resize = array($width, $height);
}
}
else
{
$resize = array($width, $height);
}
$result = ImageCreateTrueColor($resize[0], $resize[1]);
if (is_resource($result) === true)
{
ImageCopyResampled($result, $source, 0, 0, $crop[0], $crop[1], $resize[0], $resize[1], $width, $height);
ImageDestroy($source);
header('Content-Type: image/jpeg');
ImageJPEG($result, null, 90);
ImageDestroy($result);
}
}
return false;
}
Image('/path/to/your/image.jpg', '1/1', '100*');
Image('/path/to/your/image.jpg', '1/1', '100*100');
Image('/path/to/your/image.jpg', '1/1', '100*500');
?>
Here's a bash command I threw together to accomplish this using ImageMagick's convert tool. For a set of images sitting in the parent directory, some portrait, some landscape, to create images in the current directory scaled to 600x400, cropping portrait images from the centre and simply scaling the landscape images:
for f in ../*jpg; do
echo $f;
size=`identify $f|cut -d' ' -f 3`;
w=`echo $size|cut -dx -f 1`;
h=`echo $size|cut -dx -f 2`;
if [ $w -gt $h ]; then
convert $f -thumbnail 600x400 `basename $f`;
else
convert $f -scale 600x -crop 600x400+0+`echo "((600*($h/$w))/2)" | bc | sed 's/\..*//'` `basename $f`;
fi;
done;