I am trying to do the same thing as in the linked question, but with C#. I am showing a scaled image and am allowing a user to select an area to crop. However, I can't just take the x1y1, x2y2 coordinates from the scaled image selection and crop that from the original. I've tried doing some basic math like in the other question, but that's obviously not the right approach either (it's definitely closer).
Edit
Original Image Dimensions: w = 1024 h = 768
Scaled Image Dimensions: w = 550 h = 412
I start with an image, say 1024x768. I want it to fit as large as possible in a 550x550 box. I'm using the following method to get the scaled image size (while maintaining aspect ratio). Then I do a basic resize to those new dimensions.
As for a selection area, it can be anything (0,0) to (100,100).
private static Rectangle MaintainAspectRatio(Image imgPhoto, Rectangle thumbRect)
{
int sourceWidth = imgPhoto.Width; int sourceHeight = imgPhoto.Height; int sourceX = 0; int sourceY = 0; int destX = 0; int destY = 0;
float nPercent = 0;
float nPercentW = 0;
float nPercentH = 0;
nPercentW = ((float)thumbRect.Width / (float)sourceWidth);
nPercentH = ((float)thumbRect.Height / (float)sourceHeight);
//if we have to pad the height pad both the top and the bottom
//with the difference between the scaled height and the desired height
if (nPercentH < nPercentW)
{
nPercent = nPercentH;
destX = (int)((thumbRect.Width - (sourceWidth * nPercent)) / 2);
}
else
{
nPercent = nPercentW;
destY = (int)((thumbRect.Height - (sourceHeight * nPercent)) / 2);
}
int destWidth = (int)(sourceWidth * nPercent);
int destHeight = (int)(sourceHeight * nPercent);
Rectangle retRect = new Rectangle(thumbRect.X, thumbRect.Y, destWidth, destHeight);
return retRect;
}
Without a bit more detail, I'm guessing that you're actually suffering from rounding errors...
- When you scale the (top,left) co-ordinate back to the original, you need to round down (towards the top left).
- When you scale the (bottom,right) co-ordinate back to the original, you need to round up (towards the bottom right)
Take a simple example of a 12x12 grid as the original, and a 4x4 grid as the scaled version.
- (1,1):(2,2) on the scaled version = (3,3):(8,8)
- 2x2 pixel = 25% of the area of the scaled version
- 6x6 pixel = 25% of the area of the original version
If one was to simply multiply by the same scaling factors, this would give (3,3):(6,6).
OriginalTop = INT(ScaledTop * YScalingFactor);
OriginalLeft = INT(ScaledLeft * XScalingFactor);
OriginalBottom = INT((ScaledBottom + 1) * YScalingFactor) - 1;
OriginalRight = INT((ScaledRight + 1) * XScalingFactor) - 1;
EDIT:
A better way of explaining what I'm trying to say would be to draw a picutre. And I suck at ASCII Art. So here's another try with words.
A pixel isn't a point. It's a small rectangle in it's own right.
When you use a pixel to represent the top left of a rectangle, you're including the area from the top-left most Point of the pixel.
When you use a pixel to represent the Bottom Right of a rectangle, you're including the area all the way to the Bottom Right most Point of the pixel.
Using the (12x12) => (4x4) example again, every scaled pixel represents a whole 3x3 set of pixels in the original. When talking about the top left, you pick the top left pixel of the 3x3 pixel group in the original. And when talking about the bottom right, you pick the bottom right of the 3x3 pixel group in the original.
EDIT: Using just integers.
NewTop = (( OldTop ) * NewHeight / OldHeight);
NewLeft = (( OldLeft ) * NewWidth / OldWidth );
NewBottom = ((OldBottom + 1) * NewHeight / OldHeight) - 1;
NewRight = ((OldRight + 1) * NewWidth / OldWidth ) - 1;
The only consideration is making sure that you don't overflow your data type after the multiplication. But with images, you won't, unless it's a hell of an image.