Algorithm for cropping image to specific ratio - algorithm

I need an algorithm that given an image's width, height and a target ratio will calculate the number of pixels to be shaved from the image's sides to get to that ratio, that has the smallest change in the image's area.
How might one implement such an algorithm?
Edit
Sorry for the inconsistency in my original question; I have revised my it.

Bring the ratio into reduced form, so that gcd(ratio_width, ratio_height) = 1.
Calculate floor(width / ratio_width) and floor(height / ratio_height). Your factor is the minimum of these two.
Multiply ratio_width and ratio_height by that factor to obtain the new image dimensions.
Shave the difference.

To minimize the change in area, you want to find the largest rectangle of the desired aspect ratio that will fit inside the original image bounds.
So, if the original image is too wide, then make the final image's height = original height, and shave off the extra width.
If the original image is too tall, make the final image's width = original width, and shave off the extra height.
Note: This assumes that you are not allowed to increase the width or height beyond the original dimensions. If that is not the case, the algorithm would be:
Constraint 1: x_final * y_final = x_initial * y_initial
Contraint 2: x_final / y_final = r
The solution is:
x_final = sqrt(r*x_initial*y_initial)
y_final = sqrt(x_initial*y_initial/r)

Related

resizing image with rmagick/imagemagick to be less than a specified product of its width and height

I'm running a script that resizes images that are too large. I've used "resize_to_fit" to reduce images to a specific pixel size depending on the longer side, but I'm wondering if it's possible to do it with this logic instead: for any image whose width x height product is greater than a set value, resize the image so that the new width and height values are as large as possible while still being under that value. In other words, I don't want to arbitrarily resize the dimensions more than necessary, and I'd want to retain aspect ratio in this conversion. This may be more of a math question than a ruby one, but in any case, this is what I've tried:
image = Magick::Image.read(image_file)[0];
dimensions = image.columns, image.rows
resolution = dimensions[0] * dimensions[1]
if resolution > 4000000
resolution_ratio = 4000000 / resolution.to_f
dimension_ratio = dimensions[0].to_f * resolution_ratio
img = img.resize_to_fit(dimension_ratio,dimension_ratio)
img.write("#{image}")
end
So let's say an image has a width of 2793px and a height of 1970px. The resolution would be 5,502,210. It thus goes through the conditional statement, and as of right now, outputs a new width of 2030 and height of 1432. The product of these two is 2,906,960—which is obviously well under 4,000,000. But there are other possible width x height combinations whose product could be much closer to 4,000,000 pixels than 2,906,960 is. Is there a way of determining that information, and then resizing it accordingly?
You need to properly calculate the ratio, which is a square root from your desired dimension divided by (row multiplied by col):
row, col = [2793, 1970]
ratio = Math.sqrt(4_000_000.0 / (row * col))
[row, col].map &ratio.method(:*)
#⇒ [
# [0] 2381.400006266842,
# [1] 1679.6842149465374
#]
[row, col].map(&ratio.method(:*)).reduce(:*)
#∞ 3999999.9999999995

How to scale a rotated rectangle to always fit another rectangle

the background of my question is the following.
I have a picture and a crop rectangle which describes how the picture should be cropped to produce the resulting picture. The crop rectangle is always smaller or at maximum the size of the picture.
Now it should be possible to rotate the crop rectangle.
This means that when rotating the crop regtanle inside the picture, the crop must be scaled in order that its extends does not exceed the photo.
Can anybode help me with a formula of how to compute the scale of the crop rectanlge based on the axis aligned photo regtancle?
My first attempt was to compute a axis aligned bounding box of the crop rectanlge and than make this fit it the photo rectangle. But somehow i get stuck with this approach,
Edited:
One more think to note:
- The crop rectangle can have other dimension and another center point inside the surrounding rectangle. This means the crop rectangle can be much smaller but for example is located at the lower left bound of the picture rectangle. So when rotating the smaller crop it will also exceed its limits
Thanks in advance
Sebastian
When you rotate an axis-aligned rectangle of width w and height h by an angle φ, the width and height of the rotated rectangle's axis-aligned bounding box are:
W = w·|cos φ| + h·|sin φ|
H = w·|sin φ| + h·|cos φ|
(The notation |x| denotes an absolute value.) This is the bounding box of the rotated crop rectangle which you can scale to fit the original rectangle of width wo and height ho with the factor
a = min(wo / W, ho / H)
if a is less than 1, the rotated crop rectangle fits inside the original rectangle and you don't have to scale. Otherwise, reduce the crop rectangle to the scaled dimensions
W′ = a·W
H′ = a·H
You could start checking if the dimension of the cropped rectangle fit in the old rectangle:
bound_x = a * cos(theta) + b * sin(theta)
bound_y = b * cos(theta) + a * sin(theta)
Where a and b are the new dimensions, theta us the angle and bound_x and bound_y should be smaller of the original rectangle.

zoom an image to fit a screen horizontally - algorithm

This is a general question regarding an algorithm to zoom an image to fit the width of a screen, there are some givens and some constraints. We can assume we are using Java but this question is more mathematical that language dependent.
First of all, the image loads and fits into the dimensions of the screen vertically first, not horizontally.
We can get the dimensions of the screen and the dimensions of the image with methods, but we cannot set the dimensions of either (We only have getters not setters).
imageWidth = image.getWidth(); //integer
imageHeight = image.getHeight(); //integer
screenWidth = screen.getWidth(); //integer
screenHeight = screen.getHeight(); //integer
The only method to resize the image is by setting scale (zooming essentially).
image.setScale(some float); // optionally image.setZoom(integer);
What I would like to know is how to calculate the scale (zoom) level for some l x h image so that it fits a L x H screen horizontally?
All you have to do to make the Image fill your screen is scale along the x axis:
scaling_factor = screen.getWidth()/image.getWidth()
image.setScale(zoom_factor);
The formula is very intuitive:
The image height is irrelevant. The scaling you desire would be the same for a landscape and vertical image, as long as the width of both images are the same
When the image's width increases, your scaling factor decreases
When your screen size increses, the scaling factor increases.

Resize an image to specific ratio without enlarging it

I need to generate new dimensions for an image to match a ratio of a given width and height ...but without increasing the size of the original.
The concept seems oh so simple yet I can't seem to join the dots.
Also, for code samples the language is PHP.
Update:
This is what I have so far:
http://codepad.org/fTdCNhQf
This is the output I need:
Example Image • (can't embed yet)
Since enlarging is not an option, your only options are cropping and extending.
Try this: let's say your image is W*H, and the desired aspect ratio of width to height is R.
Using the width and the aspect ratio, calculate the target height TH = W/R
Using the height and the aspect ratio, calculate the target width TW = H*R
Calculate area changes aH = ABS(TH-H)*W and aW = ABS(TW-W)*H
if aH is less than aW, use target width; pad or crop the image horizontally based on the sign of TH-H
Otherwise, use target height; pad or crop the image vertically based on the sigh of TW-W
Here is a quick example:
Target R: 5/6
Image: W=200, H= 300;
TH = 200/5*6 = 240
TW = 300*5/6 = 250
aH = 60*200=12000
aW = 50*300=15000
Resulting action: since aH is less than aW, crop image vertically to 240
Are you using something like ImageMagick libraries to generate an image or do you just need to generate the new dimensions based on a known ratio? Also, do you need to discover the ratio from the existing image?
This may be useful then:
http://www.zedwood.com/article/119/php-resize-an-image-with-gd

Scale down image until one side reaches goal dimension

How do you scale down an image until one side reaches it's goal dimension with Carrierwave and rmagick?
Example:
Goal dimensions: 600x400
Picture being uploaded: 700x450
I want this image to be scaled down until the height reaches 400 pixels keeping the original aspect ratio.
That would result in a image with the following dimensions: 622x400
You might take a look at resize_to_limit. From the carrierwave docs:
Resize the image to fit within the specified dimensions while retaining the original aspect ratio. Will only resize the image if it is larger than the specified dimensions. The resulting image may be shorter or narrower than specified in the smaller dimension but will not be larger than the specified values.
So you could do something like this in your uploader:
process :resize_to_fill => [600, 400]
If you don't mind to crop the image, you could go for resize_to_fit instead, and use the gravity value that you desire:
From the RMagick documentation: “Resize the image to fit within the specified dimensions while retaining the original aspect ratio. The image may be shorter or narrower than specified in the smaller dimension but will not be larger than the specified values.“
Edit:
You can read the documentation for these processors for more options on resizing
For a resize_to_min implementation that would only enforce minimum dimensions for your width and height, you can take resize_to_limit as base and just modify the geometry setting to MinimumGeometry to create a custom processor:
process :resize_to_min => [600, 400]
def resize_to_min(width, height)
manipulate! do |img|
geometry = Magick::Geometry.new(width, height, 0, 0, Magick::MinimumGeometry)
new_img = img.change_geometry(geometry) do |new_width, new_height|
img.resize(new_width, new_height)
end
destroy_image(img)
new_img = yield(new_img) if block_given?
new_img
end
end
Use algebra: http://www.algebrahelp.com/lessons/proportionbasics/pg2.htm
Since 622px > 600px, you need to set the width to 600px and calculate the correct height which maintains aspect ratio:
700/450 = 600/x
(700/450)*x = 600
x = 600/(700/450)
x ~= 386
Your desired size is: 600px x 386px
This will fit within the goal dimensions, maximizing size, while maintaining aspect ratio.

Resources