15

Related Question

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;
}
Community
  • 1
  • 1
scottm
  • 27,829
  • 22
  • 107
  • 159
  • 1
    where is the zero of the scaled image ? On the left top or on the left bottom ? – Felice Pollano Feb 14 '11 at 16:43
  • 1
    You scale an image with a transformation (2x2 matrix). You perform coordinate conversion with an inverse of that matrix. Please provide specific numerical examples of the original image size, location, and the transformed image size, location - it makes it easier for some to think that way, plus it removes some ambiguity. – Hamish Grubijan Feb 14 '11 at 16:44
  • Is this WPF or Winfomrs? – Conrad Frix Feb 14 '11 at 16:48
  • @Conrad, it's actually in a browser. I'm using a jquery library to get a crop selection on my scaled image, and sending that back to the server. – scottm Feb 14 '11 at 16:51
  • a matrix transform will not cope with the fact that a pixel prepresentation is not a point representation. A pixel is a rectangle used to represent a point. In the case of the (top,left) pixel chosen by the user, the point being represent is the (top,left) POINT of that pixel. And in the case of the (bottom,right) pixel, the pixel represents the (bottom,right) POINT of the pixel. Only when a pixel is transformed into a point can the matrix transformation succeed. I find this hard to explain succinctly, please see my answer and shoot me down if I'm wrong :) – MatBailie Feb 14 '11 at 17:15

2 Answers2

13

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.

MatBailie
  • 83,401
  • 18
  • 103
  • 137
  • This could be true. My crop comes out in the general area, but doesn't line up quite right. – scottm Feb 14 '11 at 16:59
  • How do you determine the scaling factor? Just scaledY/origY? – scottm Feb 14 '11 at 17:24
  • Yup, just make sure to keep it as a Double or something nice and floaty accurate. (And calculate different X and Y scaling factors) – MatBailie Feb 14 '11 at 17:26
  • Even better, you're using numbers small enough that this can be done with just integers... See my next edit... – MatBailie Feb 14 '11 at 17:37
  • you got me confused with that last bit new/old vs original/scaled. – scottm Feb 14 '11 at 17:51
  • As coded it doesn't matter whether you're translating from scaled to original, or original to scaled. "Old" simply relates to the image your user clicked on, and "new" relates to the image on which you want to find the equivilent pixels. – MatBailie Feb 14 '11 at 17:55
  • You just saved me. Thanks :-) – vignesh Jun 25 '20 at 12:25
0

You can get the percent locations for the scaled images and turn them back into coords for the unscaled image:

pX1 = scaledX1/scaled_width
pY1 = scaledY1/scaled_height

unscaledX1 = ceiling(unscaled_width * pX1)
unscaledY1 = ceiling(unscaled_height * pY1)
Justin808
  • 20,859
  • 46
  • 160
  • 265
  • 1
    Although intuitive, it does not deal with the bottom right co-ordinates. A pixel isn't a point, it is a small rectangle of it's own. You want to capture the new co-ordinate representation of the bottom right of the pixel's rectangle. – MatBailie Feb 14 '11 at 17:07
  • Using this method seems to get me about where I was. It's still in the general area, but not quite. – scottm Feb 14 '11 at 17:24