Search code examples
c#.netmathdouble-precision

Double precision in image ratio comparison


I'm really weak with math and having a problem with an image resize algorithm. I'm trying to resize an image to a a specific ratio.

double neededRatio = 1.6d;
while (!AboutEqual(imageRatio, neededRatio))
{
    var cropPixels = 10;
    //crop code
    ...
    imageRatio = (double)img.Width / img.Height;
}

public static bool AboutEqual(double x, double y)
{
    double epsilon = Math.Max(Math.Abs(x), Math.Abs(y)) * 1E-15;
    return Math.Abs(x - y) <= epsilon;
}

The problem is, I can't seem to find the right number of pixels to crop to actually make the AboutEqual method work (I found it here). Sometimes it fails and the image get cropped indefinitely, I tried to log the inner-workings of the AboutEqual method and it's showing things I find weird, like:

X: 1.5249500998004  Y: 1.6 result: false
X: 1.55600814663951 Y: 1.6 result: false
X: 1.55600814663951 Y: 1.6 result: false
X: 1.58835758835759 Y: 1.6 result: false
X: 1.62208067940552 Y: 1.6 result: false
X: 1.60084925690021 Y: 1.6 result: false
X: 1.5796178343949  Y: 1.6 result: false
X: 1.61388286334056 Y: 1.6 result: false
X: 1.59219088937093 Y: 1.6 result: false
X: 1.62749445676275 Y: 1.6 result: false
X: 1.60532150776053 Y: 1.6 result: false
X: 1.58314855875831 Y: 1.6 result: false
X: 1.61904761904762 Y: 1.6 result: false
X: 1.59637188208617 Y: 1.6 result: false
X: 1.63341067285383 Y: 1.6 result: false

The linked question says "If both x and y are computed values then you have to increase the epsilon." - How do I do that and find the best number of pixels to crop?


Solution

  • You don't need to iterate to get your value. Calculate the target x value which would give you the desired ratio, and remove extra pixels.

    Presuming that x is too large and you want to crop it:

    // x/y should be 1.6
    double neededRatio = 1.6d;
    
    // so if we know y, then x should be:
    int xTarget = (int)Math.Round(y * neededRatio);
    
    // pixels to crop
    int pixelsToCrop = x - xTarget;
    

    [Edit]

    The point is, this code gets the target x you will need to get to (presuming you need the ratio). If you still thing you need a loop, nothing stops you from doing:

    while (x > xTarget)
    {
        // crop 10 pixels
        // and do magic stuff
    }