For instance, if I need to fill a bounding box that is 100px wide by 50px tall, the following input images would have the following behavior:
200w x 200h gets scaled down 50% and
25% gets chopped off the top and
bottom.
200w x 100h gets scaled down 50%
with no cropping.
100w x 200h gets is not scaled, but
75px get chopped off top and bottom.
This seems like it'd be a common resizing function, but I haven't been able to track down an example of the algorithm.
Will accept answer in any language including pseudo code. A link to a page with the answer is great too!
What you're asking for is pretty easy. Calculate the different scaling factors for the width and the height, then pick the larger one for your actual scale factor. Multiply your input size by the scale, and crop whichever one comes out too large.
scale = max(maxwidth/oldwidth, maxheight/oldheight)
scaledwidth = oldwidth * scale
scaledheight = oldheight * scale
if scaledheight > maxheight:
croptop = (scaledheight - maxheight) / 2
cropbottom = (scaledheight - maxheight) - croptop
if scaledwidth > maxwidth:
cropleft = (scaledwidth - maxwidth) / 2
cropright = (scaledwidth - maxwidth) - cropleft
Here we make sure that we only scale if X is greater than 100%; then after we've done that, we ensure that we are only 50 px on our Y. If we're greater than 50, then we take the difference and divide by 2 to get the amount removed from the top/bottom.
double percent_x = 1.0;
if(X > 100) {
percent_x = (float)100/X;
X *= percent_x;
Y *= percent_x;
}
int diff_y;
int top_cut, bott_cut;
if( Y > 50 ) {
diff_y = (Y - 50) / 2;
top_cut = bott_cut = diff_y;
}
Largely inspired by Mark Ransom's answer (thank you so much - you saved me). For anyone who would like to do this without cropping the image (just fit within the bounds), I've found that this works:
if (maxWidth > width && maxHeight > height) {
return { width, height };
}
aspectRatio = width / height,
scale = max(maxWidth / width, maxHeight / height);
scaledHeight = height * scale,
scaledWidth = width * scale;
if (scaledHeight > maxHeight) {
scaledHeight = maxHeight;
scaledWidth = aspectRatio * scaledHeight;
} else if (scaledWidth > maxWidth) {
scaledWidth = maxWidth;
scaledHeight = scaledWidth / aspectRatio;
}
return { scaledHeight, scaledWidth };
Related
I tried to find out, but I couldn't.
A image, for example, 241x76 has a total of 18,316 pixels (241 * 76).
The resize rule is, the amount of pixels cannot pass 10,000.
Then, how can I get the new size keeping the aspect ratio and getting less than 10,000 pixels?
Pseudocode:
pixels = width * height
if (pixels > 10000) then
ratio = width / height
scale = sqrt(pixels / 10000)
height2 = floor(height / scale)
width2 = floor(ratio * height / scale)
ASSERT width2 * height2 <= 10000
end if
Remember to use floating-point math for all calculations involving ratio and scale when implementing.
Python
import math
def capDimensions(width, height, maxPixels=10000):
pixels = width * height
if (pixels <= maxPixels):
return (width, height)
ratio = float(width) / height
scale = math.sqrt(float(pixels) / maxPixels)
height2 = int(float(height) / scale)
width2 = int(ratio * height / scale)
return (width2, height2)
An alternative function in C# which takes and returns an Image object:
using System.Drawing.Drawing2D;
public Image resizeMaxPixels(int maxPixelCount, Image originalImg)
{
Double pixelCount = originalImg.Width * originalImg.Height;
if (pixelCount < maxPixelCount) //no downsize needed
{
return originalImg;
}
else
{
//EDIT: not actually needed - scaleRatio takes care of this
//Double aspectRatio = originalImg.Width / originalImg.Height;
//scale varies as the square root of the ratio (width x height):
Double scaleRatio = Math.Sqrt(maxPixelCount / pixelCount);
Int32 newWidth = (Int32)(originalImg.Width * scaleRatio);
Int32 newHeight = (Int32)(originalImg.Height * scaleRatio);
Bitmap newImg = new Bitmap(newWidth, newHeight);
//this keeps the quality as good as possible when resizing
using (Graphics gr = Graphics.FromImage(newImg))
{
gr.SmoothingMode = SmoothingMode.AntiAlias;
gr.InterpolationMode = InterpolationMode.HighQualityBicubic;
gr.PixelOffsetMode = PixelOffsetMode.HighQuality;
gr.DrawImage(originalImg, new Rectangle(0, 0, newWidth, newHeight));
}
return newImg;
}
}
with graphics code from the answer to Resizing an Image without losing any quality
EDIT: Calculating the aspect ratio is actually irrelevant here as we're already scaling the width and height by the (square root) of the total pixel ratio. You could use it to calculate the newWidth based on the newHeight (or vice versa) but this isn't necessary.
Deestan's code works for square images, but in situations where the aspect ratio is different than 1, a square root won't do. You need to take scale to the power of aspect ratio divided by 2.
Observe (Python):
def capDimensions(width, height, maxPixels):
pixels = width * height
if (pixels <= maxPixels):
return (width, height)
ratio = float(width) / height
scale = (float(pixels) / maxPixels)**(width/(height*2))
height2 = round(float(height) / scale)
width2 = round(ratio * height2)
return (width2, height2)
Let's compare the results.
initial dimensions: 450x600
initial pixels: 270000
I'm trying to resize to get as close as possible to 119850 pixels.
with Deestan's algorithm:
capDimensions: 300x400
resized pixels: 67500
with the modified algorithm:
capDimensions. 332x442
resized pixels: 82668
width2 = int(ratio * height / scale)
would better be
width2 = int(ratio * height2)
because this would potentially preserve the aspect ratio better (as height2 has been truncated).
Without introducing another variable like 'sc', one can write
new_height = floor(sqrt(m / r))
and
new_width = floor(sqrt(m * r))
given m=max_pixels (here: 10.000), r=ratio=w/h (here: 241/76 = 3.171)
Both results are independent of each other! From each new_value, you can calculate the other dimension, with
(given: new_height) new_width = floor(new_height * r)
(given: new_width) new_height = floor(new_width / r)
Because of clipping the values (floor-function), both pairs of dimensions may differ in how close their ratio is to the original ratio; you'd choose the better pair.
Scaling images down to max number of pixels, while maintaining aspect ratio
This is what I came up with this afternoon, while trying to solve the math problem on my own, for fun. My code seems to work fine, I tested with a few different shapes and sizes of images. Make sure to use floating point variables or the math will break.
Pseudocode
orig_width=1920
orig_height=1080
orig_pixels=(orig_width * orig_height)
max_pixels=180000
if (orig_pixels <= max_pixels) {
# use original image
}
else {
# scale image down
ratio=sqrt(orig_pixels / max_pixels)
new_width=floor(orig_width / ratio)
new_height=floor(orig_height / ratio)
}
Example results
1920x1080 (1.77778 ratio) becomes 565x318 (1.77673 ratio, 179,670 pixels)
1000x1000 (1.00000 ratio) becomes 424x424 (1.00000 ratio, 179,776 pixels)
200x1200 (0.16667 ratio) becomes 173x1039 (0.16651 ratio, 179,747 pixels)
I need help with math / algorithm to take an image of known size and fit to one of two screen dimensions:
720 x 480 or 1280 x 1024.
The image dimensions are coming from an XML file, however those dimensions are the web dimensions, I also get a selection of images from the XML that may be of higher and lower resolution than the web dimensions.
What I want is to use the aspect ration of the web dimensions to display the higher resolution image, if available, on an HD (1280x720) screen, or, if the user is on an SD screen (720x480) display the image on that screen.
Other things that would be useful for this, but lower priority, would be, if I know the resolution of the image is smaller in both dimensions than an SD screen (in this case, all I know is the web dimension, and the horizontal dimension of the image file), to display it as actual size on that screen.
Generic as can be:
Image data: (wi, hi) and define ri = wi / hi
Screen resolution: (ws, hs) and define rs = ws / hs
Scaled image dimensions:
rs > ri ? (wi * hs/hi, hs) : (ws, hi * ws/wi)
So for example:
20
|------------------|
10
|---------|
-------------------- --- ---
| | | | 7 |
| | | | | 10
|---------- | --- |
| | |
-------------------- ---
ws = 20
hs = 10
wi = 10
hi = 7
20/10 > 10/7 ==> (wi * hs/hi, hs) = (10 * 10/7, 10) = (100/7, 10) ~ (14.3, 10)
Which as you can see clearly scales to the screen size, because the height is that of the screen but clearly keeps aspect ratio since 14.3/10 ~ 10/7
UPDATE
Center the image as follows:
call (wnew, hnew) the new dimensions.
top = (hs - hnew)/2
left = (ws - wnew)/2
I understand the accepted answer and it works, but I've always found the following method to be simpler and succinct for "best fit":
// prep
let maxWidth = 190,
maxHeight = 150;
let imgWidth = img.width,
imgHeight = img.height;
// calc
let widthRatio = maxWidth / imgWidth,
heightRatio = maxHeight / imgHeight;
let bestRatio = Math.min(widthRatio, heightRatio);
// output
let newWidth = imgWidth * bestRatio,
newHeight = imgHeight * bestRatio;
... which of course can be distilled down to:
const maxWidth = 190, maxHeight = 150;
const bestRatio = Math.min(maxWidth / img.width, maxHeight / img.height);
img.width *= bestRatio;
img.height *= bestRatio;
Here it is in straightforward C.
You want to scale both coordinates by the returned scale factor.
/* For a rectangle inside a screen, get the scale factor that permits the rectangle
to be scaled without stretching or squashing. */
float
aspect_correct_scale_for_rect(const float screen[2], const float rect[2])
{
float screenAspect = screen[0] / screen[1];
float rectAspect = rect[0] / rect[1];
float scaleFactor;
if (screenAspect > rectAspect)
scaleFactor = screen[1] / rect[1];
else
scaleFactor = screen[0] / rect[0];
return scaleFactor;
}
Aspect ratio correction with letterboxing or fit-to-screen
I wrote up a method recently to handle this exact problem in iOS. I'm using the Eigen matrix library to do scaling, but the the principle (scaling factor) is the same without matrices.
Eigen::Matrix4x4f aspectRatioCorrection(bool fillScreen, const Eigen::Vector2f &screenSize, const Eigen::Vector2f &imageSize)
{
Eigen::Matrix4x4f scalingMatrix(Eigen::Matrix4x4f::Identity());
float screenWidth = screenSize.x();
float screenHeight = screenSize.y();
float screenAspectRatio = screenWidth / screenHeight;
float imageWidth = imageSize.x();
float imageHeight = imageSize.y();
float imageAspectRatio = imageWidth / imageHeight;
float scalingFactor;
if (fillScreen) {
if (screenAspectRatio > imageAspectRatio) {
scalingFactor = screenWidth / imageWidth;
} else {
scalingFactor = screenHeight / imageHeight;
}
} else {
if (screenAspectRatio > imageAspectRatio) {
scalingFactor = screenHeight / imageHeight;
} else {
scalingFactor = screenWidth / imageWidth;
}
}
scalingMatrix(0, 0) = scalingFactor;
scalingMatrix(1, 1) = scalingFactor;
return scalingMatrix;
}
I want to write a function to downsize an image to fit specified bounds. For example i want to resize a 2000x2333 image to fit into 1280x800. The aspect ratio must be maintained. I've come up with the following algorithm:
NSSize mysize = [self pixelSize]; // just to get the size of the original image
int neww, newh = 0;
float thumbratio = width / height; // width and height are maximum thumbnail's bounds
float imgratio = mysize.width / mysize.height;
if (imgratio > thumbratio)
{
float scale = mysize.width / width;
newh = round(mysize.height / scale);
neww = width;
}
else
{
float scale = mysize.height / height;
neww = round(mysize.width / scale);
newh = height;
}
And it seemed to work. Well ... seemed. But then i tried to resize a 1280x1024 image to a 1280x800 bounds and it gave me a result of 1280x1024 (which obviously doesn't fit in 1280x800).
Does anybody have any ideas how this algorithm should work?
The way I usually do this is to look at the ratio between the original width and the new width and the ratio between the original height and the new height.
After this shrink the image by the biggest ratio. For example, if you wanted to resize an 800x600 image into a 400x400 image the width ratio would be 2, and the height ratio would be 1.5. Shrinking the image by a ratio of 2 gives a 400x300 image.
NSSize mysize = [self pixelSize]; // just to get the size of the original image
int neww, newh = 0;
float rw = mysize.width / width; // width and height are maximum thumbnail's bounds
float rh = mysize.height / height;
if (rw > rh)
{
newh = round(mysize.height / rw);
neww = width;
}
else
{
neww = round(mysize.width / rh);
newh = height;
}
Here's a way to approach the problem:
You know that either the image's height or width will be equal to that of the bounding box.
Once you've determined which dimension will equal the bounding box's, you use the image's aspect ratio to calculate the other dimension.
double sourceRatio = sourceImage.Width / sourceImage.Height;
double targetRatio = targetRect.Width / targetRect.Height;
Size finalSize;
if (sourceRatio > targetRatio)
{
finalSize = new Size(targetRect.Width, targetRect.Width / sourceRatio);
}
else
{
finalSize = new Size(targetRect.Height * sourceRatio, targetRect.Height);
}
$max_width = MAX_SIZE;
$max_height = MAX_SIZE;
if ($width >= $height) // with bigger than height
{
if ($width >= $max_width)
{
$new_width = $max_width;
$new_height = round($height*$max_width/$width); // scale in height
}
else
{
$new_width = $width; // smaller than max dimentions
$new_height = $height; // maintain dimentions
}
}
else // height bigger than width
{
if ($height >= $max_height)
{
$new_width = round($width*$max_height/$height); // scale in width
$new_height = $max_height;
}
else
{
$new_width = $width; // smaller than max dimentions
$new_height = $height; // maintain dimentions
}
}
I have an application in which end-users can size and position images in a designer. Since the spec calls for the image to be "stretched" to the containing control, the end user can end up with an awkwardly stretched image.
To help the user with image sizing I am thinking of implementing a smart resizer function which would allow the the user to easily fix the aspect ratio of the picture so that it no longer appears stretched.
The quick way to solve this is to actually provide two options: 1) scale from width 2) scale from height. The user chooses the method and the algorithm adjusts the size of the picture by using the original aspect ratio. For example: A picture is displayed as 200x200 on the designer but the original image is 1024x768 pixels. The user chooses "Smart Size from width" and the new size becomes ~200x150 since the original aspect ratio is ~1.333
That's OK, but how could I make the algorithm smarter and not bother the user by asking which dimension the recalculation should be based on?
If I'm interpreting your spec correctly, you want a result that is no larger than the one the end-user laid out originally; you want one of the two dimensions to shrink, and the other to stay the same. In other words, the new size should fill the designer space in one direction while shortening the size in the other direction to retain the original aspect ratio.
original_ratio = original_width / original_height
designer_ratio = designer_width / designer_height
if original_ratio > designer_ratio
designer_height = designer_width / original_ratio
else
designer_width = designer_height * original_ratio
Often you'll be working with integer coordinates, but the divisions to produce the ratios above need to be floating point. Here's a rearrangement of the formula to be more integer friendly. Make sure your integers have the range to handle the maximum width*height.
if original_width * designer_height > designer_width * original_height
designer_height = (designer_width * original_height) / original_width
else
designer_width = (designer_height * original_width) / original_height
Here's a solution I came up with when having to deal with this problem. Turned out pretty short and straightforward imo, just wanted to share it.
Handy "formulas": ratio = W / H → W = H * ratio → H = W / ratio
Calculate the ratio.
Calculate the height of the image if you would set the width to the new size width (maximum allowed width).
Calculate the width of the image if you would set the height to the new size height (maximum allowed height).
See which of the two sizes does not override the max size in any dimension. If the ratio is not 1 one of them will always be wrong and one will always be correct.
In Javascript
// Returns array with new width and height
function proportionalScale(originalSize, newSize)
{
var ratio = originalSize[0] / originalSize[1];
var maximizedToWidth = [newSize[0], newSize[0] / ratio];
var maximizedToHeight = [newSize[1] * ratio, newSize[1]];
if (maximizedToWidth[1] > newSize[1]) { return maximizedToHeight; }
else { return maximizedToWidth; }
}
originalSize and newSize is an array [0] = width, [1] = height
I also wanted to know this and all I saw were endless examples of scaling width OR height but could leave the other overflowing.
Resize width AND height without the need for a loop
Doesn't exceed the images original dimensions
.
private void ResizeImage(Image img, double maxWidth, double maxHeight)
{
double srcWidth = img.Source.Width;
double srcHeight = img.Source.Height;
double resizeWidth = srcWidth;
double resizeHeight = srcHeight;
double aspect = resizeWidth / resizeHeight;
if (resizeWidth > maxWidth)
{
resizeWidth = maxWidth;
resizeHeight = resizeWidth / aspect;
}
if (resizeHeight > maxHeight)
{
aspect = resizeWidth / resizeHeight;
resizeHeight = maxHeight;
resizeWidth = resizeHeight * aspect;
}
img.Width = resizeWidth;
img.Height = resizeHeight;
}
Calculate the new dimensions for both variants ("scale by width" and "scale by height"), then use the one that fits in the display.
Alternatively you could also calculate the the aspect ratio of the "bounding box" and compare it against the aspect ratio of the original image. Depending on which aspect ratio is larger, the height or the width needs to be scaled.
You also could restrict the resize process so that in all cases "scale by width" is done. Then, to change the size of the image, the user always has to change its width. The height will always be adjusted automatically.
Took the suggestion above and made it scale up/down within max height / width. Here the python code for it and also added support for rotating things while keeping within limites:
def _resize(image, dimensions, rotate=None):
"""
Resizes an image to be as close as possible to specified dimensions. Image is a
django image-model-field.
Will both scale up and down the image to meet this while keeping the proportions
in width and height
"""
if image and os.path.isfile(image.path):
im = pil.open(image.path)
logging.debug('resizing image from %s x %s --> %s x %s ' % (im.size[0], im.size[1], dimensions[0], dimensions[1]))
if rotate:
logging.debug('first rotating image %s' % rotate)
im = im.rotate(90)
srcWidth = Decimal(im.size[0])
srcHeight = Decimal(im.size[1])
resizeWidth = srcWidth
resizeHeight = srcHeight
aspect = resizeWidth / resizeHeight # Decimal
logging.debug('resize aspect is %s' % aspect)
if resizeWidth > dimensions[0] or resizeHeight > dimensions[1]:
# if width or height is bigger we need to shrink things
if resizeWidth > dimensions[0]:
resizeWidth = Decimal(dimensions[0])
resizeHeight = resizeWidth / aspect
if resizeHeight > dimensions[1] :
aspect = resizeWidth / resizeHeight
resizeHeight = Decimal(dimensions[1])
resizeWidth = resizeHeight * aspect
else:
# if both width and height are smaller we need to increase size
if resizeWidth < dimensions[0]:
resizeWidth = Decimal(dimensions[0])
resizeHeight = resizeWidth / aspect
if resizeHeight > dimensions[1] :
aspect = resizeWidth / resizeHeight
resizeHeight = Decimal(dimensions[1])
resizeWidth = resizeHeight * aspect
im = im.resize((resizeWidth, resizeHeight), pil.ANTIALIAS)
logging.debug('resized image to %s %s' % im.size)
im.save(image.path)
else:
# no action, due to no image or no image in path
pass
return image
Because you want to maximize showing as much as possible the scaled image (of the original) in your window, i.e. the area in your designer, you would take the larger of either the width or height of the original image, and scale that to 200. Pseudo-code (width, height are dimensions of original):
if (width > height) {
scaledWidth = 200;
scaledHeight = (height * 200) / width;
} else {
scaledHeight = 200;
scaledWidth = (width * 200) / height;
}
here is my solution,
a = aspect
sw = original image width
sh = original image height
dw = requested max width
dh = requested max height
sw and sh will contain the final resized values
code is PHP:
$a = $sw / $sh;
if($a > 1){
// wider image
if($sw != $dw){
$rt = $dw / $sw;
$sw = $sw * $rt;
$sh = $sh * $rt;
}
if($sh > $dh){
$rt = $dh / $sh;
$sw = $sw * $rt;
$sh = $sh * $rt;
}
}else{
// taller image
if($sh != $dh){
$rt = $dh / $sh;
$sh = $sh * $rt;
$sw = $sw * $rt;
}
if($sw > $dw){
$rt = $dw / $sw;
$sh = $sh * $rt;
$sw = $sw * $rt;
}
}
My solution to shrink and grow size in javascript based on https://stackoverflow.com/a/5654847/1055015
var scale = function (srcWidth, srcHeight, maxWidth, maxHeight) {
let resizeWidth = srcWidth;
let resizeHeight = srcHeight;
let aspect = resizeWidth / resizeHeight;
let scaleX = maxWidth / srcWidth;
let scaleY = maxHeight / srcHeight;
let scale = Math.min(scaleX, scaleY);
resizeWidth *= scale;
resizeHeight *= scale;
if (resizeWidth > maxWidth) {
resizeWidth = maxWidth;
resizeHeight = resizeWidth / aspect;
}
if (resizeHeight > maxHeight) {
aspect = resizeWidth / resizeHeight;
resizeHeight = maxHeight;
resizeWidth = resizeHeight * aspect;
}
return {
width : resizeWidth,
height: resizeHeight,
};
}
You just need to work out the scale needed for both dimensions and then take the smaller of the 2.
I used this way to resize the image to FHD way
if ( width >= height) {
var original_ratio = width / height
new_width = 1080 * original_ratio
console.log("new new_width = " + Math.round(new_width) );
console.log("new new_height = 1080");
} else {
var original_ratio = height / width
new_height = 1080 * original_ratio
console.log("new new_height = " + Math.round(new_height) );
console.log("new new_width = 1080");
}
you can change the 1080 to the new size.
I hope it's useful for someone at least.
I've read a few dozen questions on this topic, but none seem to be exactly what I'm looking for, so I'm hoping this isn't a duplicate.
I have an image, whose aspect ratio I want to maintain, because it's an image.
I want to find the largest scale factor, and corresponding angle between 0 and 90 degrees inclusive, such that the image will fit wholly inside a given rectangle.
Example 1: If the image and rectangle are the same ratio, the angle will be 0, and the scale factor will be the ratio of the rectangle's width to the image's width. (Or height-to-height.)
Example 2: If the image and rectangle ratios are the inverse of each other, the scale factor will be the same as the first example, but the angle will be 90 degrees.
So, for the general case, given image.width, image.height, rect.width, rect.height, how do I find image.scale and image.angle?
OK, I figured it out on my own.
First, calculate the aspect ratio. If your image is 1:1, there's no point in this, because the angle is always zero, and the scale is always min(Width, Height). Degeneration.
Otherwise, you can use this:
// assuming below that Width and Height are the rectangle's
_imageAspect = _image.width / _image.height;
if (_imageAspect == 1) { // div by zero implied
trace( "square image...this does not lend itself to rotation ;)" );
return;
}
_imageAspectSq = Math.pow( _imageAspect, 2 );
var rotate:Float;
var newHeight:Float;
if (Width > Height && Width / Height > _imageAspect) { // wider aspect than the image
newHeight = Height;
rotate = 0;
} else if (Height > Width && Height / Width > _imageAspect) { // skinnier aspect than the image rotated 90 degrees
newHeight = Width;
rotate = Math.PI / 2;
} else {
var hPrime = (_imageAspect * Width - _imageAspectSq * Height) / ( 1 - _imageAspectSq );
var wPrime = _imageAspect * (Height - hPrime);
rotate = Math.atan2( hPrime, wPrime );
var sine = Math.sin(rotate);
if (sine == 0) {
newHeight = Height;
} else {
newHeight = (Width - wPrime) / sine;
}
}
The first two cases are also degenerate: the image's aspect ratio is less than the rectangle. This is similar to the square-within-a-rectangle case, except that in that case, the square is always degenerate.
The code assumes radians instead of degrees, but it's not hard to convert.
(Also I'm a bit shocked that my browser's dictionary didn't have 'radians'.)