Given aspect ratio of a rectangle, find maximum scale and angle to fit it inside another rectangle - algorithm

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'.)

Related

How to make clock ticks stuck to the clock border?

I try to make a clock, in swift, but now i want to make something strange. I want make border radius settable. This is the easy part (is easy because I already did that). I drew 60 ticks around the clock. The problem is that 60 ticks are a perfect circle. If I change the border radius I obtain this clock:
All ticks are made with NSBezierPath, and code for calculate position for every tick is :
tickPath.moveToPoint(CGPoint(
x: center.x + cos(angle) * point1 ,
y: center.y + sin(angle) * point1
))
tickPath.lineToPoint(CGPoint(
x: center.x + cos(angle) * point2,
y: center.y + sin(angle) * point2
))
point1 and point2 are points for 12 clock tick.
My clock background is made with bezier path:
let bezierPath = NSBezierPath(roundedRect:self.bounds, xRadius:currentRadius, yRadius:currentRadius)
currentRadius - is a settable var , so my background cam be, from a perfect circle (when corner radius = height / 2) to a square (when corner radius = 0 ).
Is any formula to calculate position for every tick so, for any border radius , in the end all ticks to be at same distance to border ?
The maths is rather complicated to explain without recourse to graphics diagrams, but basically if you consider a polar coordinates approach with the origin at the clock centre then there are two cases:
where the spoke from the origin hits the straight side of the square - easy by trigonometry
where it hits the circle arc at the corner - we use the cosine rule to solve the triangle formed by the centre of the clock, the centre of the corner circle and the point where the spoke crosses the corner. The origin-wards angle of that triangle is 45º - angleOfSpoke, and two of the sides are of known length. Solve the cosine equation as a quadratic and you have it.
This function does it:
func radiusAtAngle(angleOfSpoke: Double, radius: Double, cornerRadius: Double) -> Double {
// radius is the half-width of the square, = the full radius of the circle
// cornerRadius is, of course, the corner radius.
// angleOfSpoke is the (maths convention) angle of the spoke
// the function returns the radius of the spoke.
let theta = atan((radius - cornerRadius) / radius) // This determines which case
let modAngle = angleOfSpoke % M_PI_2 // By symmetry we need only consider the first quadrant
if modAngle <= theta { // it's on the vertical flat
return radius / cos(modAngle)
} else if modAngle > M_PI_2 - theta { // it's on the horizontal flat
return radius / cos(M_PI_2 - modAngle)
} else { // it's on the corner arc
// We are using the cosine rule to solve the triangle formed by
// the clock centre, the curved corner's centre,
// and the point of intersection of the spoke.
// Then use quadratic solution to solve for the radius.
let diagonal = hypot(radius - cornerRadius, radius - cornerRadius)
let rcosa = diagonal * cos(M_PI_4 - modAngle)
let sqrTerm = rcosa * rcosa - diagonal * diagonal + cornerRadius * cornerRadius
if sqrTerm < 0.0 {
println("Aaargh - Negative term") // Doesn't happen - use assert in production
return 0.0
} else {
return rcosa + sqrt(sqrTerm) // larger of the two solutions
}
}
}
In the diagram OP = diagonal, OA = radius, PS = PB = cornerRadius, OS = function return, BÔX = theta, SÔX = angleOfSpoke

Scaling an image proportionally based on the image's dimensions and my restriction size

Looking for an expression that allows me to accomplish this:
I have an image of arbitrary width/height, whose dimensions I can grab before I draw it.
Because the image may be very large, I want to scale it down.
My canvas is going to have width w and height h.
For illustration purposes let's just say it's 320x240.
If the dimensions of the image are equal or smaller than the dimensions of the canvas, then the scale ratio is just 1.
If they are larger, I will scale it proportional to how much larger it is compared to the canvas size.
So for example if my image is 640x480, my scale ratio will be 0.5
If my image is 640x240, my scale ratio would still be 0.5
Similarly if it were 320x480
Can this be written in a single math expression? For ex:
def scale_ratio(canvas_width, canvas_height, image_width, image_height)
#math formula for calculating scale
return scale
function scale(canvas_width, canvas_height, image_width, image_height) {
return Math.min(Math.max(canvas_width / image_width, canvas_height / image_height), 1);
}
EDIT: You might want to do something like this to reduce rounding errors:
var scale_width = image_width;
var scale_height = image_height;
if (image_width > canvas_width || image_height > canvas_height) {
var image_ratio = image_height / image_width;
if (image_ratio * canvas_width > canvas_height) {
scale_width = canvas_height / image_ratio;
scale_height = canvas_height;
} else {
scale_width = canvas_width;
scale_height = image_ratio * canvas_width;
}
}

Resize image by pixel amount

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)

How do I find a dimension of aspect ratio 4:3 which fits within a predetermined size?

The problem here is I have a display window of size x by y, and I need to display an image inside the window without any scrolling, and to maintain the aspect ratio of 4:3. I have the following snippet of code:
// Lock the current height, calculate new width of the canvas and scale the viewport.
// get width of the movie canvas
qreal width = canvas_->width();
// Find the height of the video
qreal height = (width/4.0) * 3;
// find original width and height for video to calculate scaling factor
qreal videoWidth = movieData_->GetWidth();
qreal videoHeight = movieData_->GetHeight();
// calculate scaling factor
qreal scaleFactorWidth = width/videoWidth;
qreal scaleFactorHeight = height/videoHeight;
Of course, by using either the height, or the width as the 'anchor', one way or other the new image will cause scrolling (assuming the original image is larger than the window in the first place). How do I find a dimension of aspect ratio 4:3 which fits within a predetermined size?
Edit
I would need to pass in a scale factor for both x and y to do the scaling
canvas_->scale(scaleFactorWidth, scaleFactorHeight);
Just take the minimum of the both calculated values:
scale = min(scaleFactorWidth, scaleFactorHeight)
or (if you want outer-fit)
scale = max(scaleFactorWidth, scaleFactorHeight)
struct dimensions resize_to_fit_in(struct dimensions a, struct dimensions b) {
double wf, hf, f;
struct dimensions out;
wf = (double) b.w / a.w;
hf = (double) b.h / a.h;
if (wf > hf)
f = hf;
else
f = wf;
out.w = a.w * f;
out.h = a.h * f;
return out;
}
An here is a C version where the returned dimension will be a dimension 'a' fitted in dimension 'b' without loosing aspect ratio.
Find the largest of the two values width, w and height h. Say your maximum width x height is 100 x 80. Note that 100/80 = 1.25.
Case 1: If w/h > 1.25, then divide w by 100 to get the ratio of your original size to the new size. Then multiply h by that ratio.
Case 2: Otherwise, then divide h by 80 to get the ratio of your original size to the new size. Then multiply w by that ratio.
Here's an ActionScript version of what you ask (resize while maintaining aspect ratio)... shouldn't be too hard to port to whatever:
private static function resizeTo(dispObj:DisplayObject, width:Number, height:Number) : void
{
var ar:Number = width / height;
var dispObjAr:Number = dispObj.width/dispObj.height;
if (ar < dispObjAr)
{
dispObj.width = width;
dispObj.height = width / dispObjAr;
}
else
{
dispObj.height = height;
dispObj.width = height * dispObjAr;
}
return;
}
EDIT: In order to maintain 4:3 the source images would need to be 4:3

Scale image to completely fill bounding box

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

Resources