What's the algorithm to calculate aspect ratio?

134,947

Solution 1

I gather you're looking for an usable aspect ratio integer:integer solution like 16:9 rather than a float:1 solution like 1.77778:1.

If so, what you need to do is find the greatest common divisor (GCD) and divide both values by that. The GCD is the highest number that evenly divides both numbers. So the GCD for 6 and 10 is 2, the GCD for 44 and 99 is 11.

For example, a 1024x768 monitor has a GCD of 256. When you divide both values by that you get 4x3 or 4:3.

A (recursive) GCD algorithm:

function gcd (a,b):
    if b == 0:
        return a
    return gcd (b, a mod b)

In C:

static int gcd (int a, int b) {
    return (b == 0) ? a : gcd (b, a%b);
}

int main(void) {
    printf ("gcd(1024,768) = %d\n",gcd(1024,768));
}

And here's some complete HTML/Javascript which shows one way to detect the screen size and calculate the aspect ratio from that. This works in FF3, I'm unsure what support other browsers have for screen.width and screen.height.

<html><body>
    <script type="text/javascript">
        function gcd (a, b) {
            return (b == 0) ? a : gcd (b, a%b);
        }
        var w = screen.width;
        var h = screen.height;
        var r = gcd (w, h);
        document.write ("<pre>");
        document.write ("Dimensions = ", w, " x ", h, "<br>");
        document.write ("Gcd        = ", r, "<br>");
        document.write ("Aspect     = ", w/r, ":", h/r);
        document.write ("</pre>");
    </script>
</body></html>

It outputs (on my weird wide-screen monitor):

Dimensions = 1680 x 1050
Gcd        = 210
Aspect     = 8:5

Others that I tested this on:

Dimensions = 1280 x 1024
Gcd        = 256
Aspect     = 5:4

Dimensions = 1152 x 960
Gcd        = 192
Aspect     = 6:5

Dimensions = 1280 x 960
Gcd        = 320
Aspect     = 4:3

Dimensions = 1920 x 1080
Gcd        = 120
Aspect     = 16:9

I wish I had that last one at home but, no, it's a work machine unfortunately.

What you do if you find out the aspect ratio is not supported by your graphic resize tool is another matter. I suspect the best bet there would be to add letter-boxing lines (like the ones you get at the top and bottom of your old TV when you're watching a wide-screen movie on it). I'd add them at the top/bottom or the sides (whichever one results in the least number of letter-boxing lines) until the image meets the requirements.

One thing you may want to consider is the quality of a picture that's been changed from 16:9 to 5:4 - I still remember the incredibly tall, thin cowboys I used to watch in my youth on television before letter-boxing was introduced. You may be better off having one different image per aspect ratio and just resize the correct one for the actual screen dimensions before sending it down the wire.

Solution 2

aspectRatio = width / height

if that is what you're after. You can then multiply it by one of the dimensions of the target space to find out the other (that maintains the ratio) e.g.

widthT = heightT * aspectRatio
heightT = widthT / aspectRatio

Solution 3

paxdiablo's answer is great, but there are a lot of common resolutions that have just a few more or less pixels in a given direction, and the greatest common divisor approach gives horrible results to them.

Take for example the well behaved resolution of 1360x765 which gives a nice 16:9 ratio using the gcd approach. According to Steam, this resolution is only used by 0.01% of it's users, while 1366x768 is used by a whoping 18.9%. Let's see what we get using the gcd approach:

1360x765 - 16:9 (0.01%)
1360x768 - 85:48 (2.41%)
1366x768 - 683:384 (18.9%)

We'd want to round up that 683:384 ratio to the closest, 16:9 ratio.

I wrote a python script that parses a text file with pasted numbers from the Steam Hardware survey page, and prints all resolutions and closest known ratios, as well as the prevalence of each ratio (which was my goal when I started this):

# Contents pasted from store.steampowered.com/hwsurvey, section 'Primary Display Resolution'
steam_file = './steam.txt'

# Taken from http://upload.wikimedia.org/wikipedia/commons/thumb/f/f0/Vector_Video_Standards4.svg/750px-Vector_Video_Standards4.svg.png
accepted_ratios = ['5:4', '4:3', '3:2', '8:5', '5:3', '16:9', '17:9']

#-------------------------------------------------------
def gcd(a, b):
    if b == 0: return a
    return gcd (b, a % b)

#-------------------------------------------------------
class ResData:

    #-------------------------------------------------------
    # Expected format: 1024 x 768 4.37% -0.21% (w x h prevalence% change%)
    def __init__(self, steam_line):
        tokens = steam_line.split(' ')
        self.width  = int(tokens[0])
        self.height = int(tokens[2])
        self.prevalence = float(tokens[3].replace('%', ''))

        # This part based on pixdiablo's gcd answer - http://stackoverflow.com/a/1186465/828681
        common = gcd(self.width, self.height)
        self.ratio = str(self.width / common) + ':' + str(self.height / common)
        self.ratio_error = 0

        # Special case: ratio is not well behaved
        if not self.ratio in accepted_ratios:
            lesser_error = 999
            lesser_index = -1
            my_ratio_normalized = float(self.width) / float(self.height)

            # Check how far from each known aspect this resolution is, and take one with the smaller error
            for i in range(len(accepted_ratios)):
                ratio = accepted_ratios[i].split(':')
                w = float(ratio[0])
                h = float(ratio[1])
                known_ratio_normalized = w / h
                distance = abs(my_ratio_normalized - known_ratio_normalized)
                if (distance < lesser_error):
                    lesser_index = i
                    lesser_error = distance
                    self.ratio_error = distance

            self.ratio = accepted_ratios[lesser_index]

    #-------------------------------------------------------
    def __str__(self):
        descr = str(self.width) + 'x' + str(self.height) + ' - ' + self.ratio + ' - ' + str(self.prevalence) + '%'
        if self.ratio_error > 0:
            descr += ' error: %.2f' % (self.ratio_error * 100) + '%'
        return descr

#-------------------------------------------------------
# Returns a list of ResData
def parse_steam_file(steam_file):
    result = []
    for line in file(steam_file):
        result.append(ResData(line))
    return result

#-------------------------------------------------------
ratios_prevalence = {}
data = parse_steam_file(steam_file)

print('Known Steam resolutions:')
for res in data:
    print(res)
    acc_prevalence = ratios_prevalence[res.ratio] if (res.ratio in ratios_prevalence) else 0
    ratios_prevalence[res.ratio] = acc_prevalence + res.prevalence

# Hack to fix 8:5, more known as 16:10
ratios_prevalence['16:10'] = ratios_prevalence['8:5']
del ratios_prevalence['8:5']

print('\nSteam screen ratio prevalences:')
sorted_ratios = sorted(ratios_prevalence.items(), key=lambda x: x[1], reverse=True)
for value in sorted_ratios:
    print(value[0] + ' -> ' + str(value[1]) + '%')

For the curious, these are the prevalence of screen ratios amongst Steam users (as of October 2012):

16:9 -> 58.9%
16:10 -> 24.0%
5:4 -> 9.57%
4:3 -> 6.38%
5:3 -> 0.84%
17:9 -> 0.11%

Solution 4

I guess you want to decide which of 4:3 and 16:9 is the best fit.

function getAspectRatio(width, height) {
    var ratio = width / height;
    return ( Math.abs( ratio - 4 / 3 ) < Math.abs( ratio - 16 / 9 ) ) ? '4:3' : '16:9';
}

Solution 5

James Farey's best rational approximation algorithm with adjustable level of fuzziness ported to Javascript from the aspect ratio calculation code originally written in python.

The method takes a float (width/height) and an upper limit for the fraction numerator/denominator.

In the example below I am setting an upper limit of 50 because I need 1035x582 (1.77835051546) to be treated as 16:9 (1.77777777778) rather than 345:194 which you get with the plain gcd algorithm listed in other answers.

function aspect_ratio(val, lim) {

    var lower = [0, 1];
    var upper = [1, 0];

    while (true) {
        var mediant = [lower[0] + upper[0], lower[1] + upper[1]];

        if (val * mediant[1] > mediant[0]) {
            if (lim < mediant[1]) {
                return upper;
            }
            lower = mediant;
        } else if (val * mediant[1] == mediant[0]) {
            if (lim >= mediant[1]) {
                return mediant;
            }
            if (lower[1] < upper[1]) {
                return lower;
            }
            return upper;
        } else {
            if (lim < mediant[1]) {
                return lower;
            }
            upper = mediant;
        }
    }
}

console.log(aspect_ratio(801/600, 50));
console.log(aspect_ratio(1035/582, 50));
console.log(aspect_ratio(2560/1441, 50));
Share:
134,947
Nathan
Author by

Nathan

Updated on May 11, 2021

Comments

  • Nathan
    Nathan about 3 years

    I plan to use it with JavaScript to crop an image to fit the entire window.

    Edit: I'll be using a 3rd party component that only accepts the aspect ratio in the format like: 4:3, 16:9.

    ~12 year old edit: this kind of question is rather interesting! There is something here right? Absolutely!

  • Nosredna
    Nosredna almost 15 years
    This was the first answer I thought of giving, but I was worried that it wouldn't return results useful to his 3rd party component if his window is sized to something like 1021x711, for example.
  • Chetan S
    Chetan S almost 15 years
    Seems like an overkill. And it doesn't work for cases Nosredna mentioned. I have a solution based on approximation.
  • Nathan
    Nathan almost 15 years
    My client told me that he needs the aspect ratio of the viewer. Its a service for a print shop. Its for statistics I think
  • Falaina
    Falaina almost 15 years
    While your solution is fine for 4x3 and 16x9, this doesn't seem like it would support all possible aspect ratios (though maybe that's not important for the OP). The ratio for most wide-screen monitors, for example, is 16x10 (1920x1200, 1600x1000)?
  • Nosredna
    Nosredna almost 15 years
    We really don't have enough information to answer the question well. :-)
  • Rafael Herscovici
    Rafael Herscovici almost 6 years
    test case: 728x90 -> 364:45 am not sure that is the wanted result
  • paxdiablo
    paxdiablo almost 6 years
    @Dementic, that is the simplest form of the fraction, hence the correct aspect ratio, and 158 other people (including the OP) seem to agree :-). If you have some other idea of what would be better, please let me know and I'll look at adjusting the answer.
  • maswerdna
    maswerdna over 4 years
    @Dementic I wonder what kind of device would have this aspect ratio. Lol. But even if there is, developers don't have to kill themselves by trying to please everybody. If my algorithm or logic can work for 80% or more times without any problems, what else do I need? In a nutshell, I support the accepted answer too. (Where is that upvote arrow...?)
  • Rafael Herscovici
    Rafael Herscovici over 4 years
    @maswerdna Why does it matter what device it is? Aspect Ratio calculation is an Alogritm and not depended on a device.
  • maswerdna
    maswerdna over 4 years
    Oh. I was referring to your hypothetical device of dimension 728x90 like I haven't seen a screen with that dimension only ads banners. But the OP is working with devices that can display full size images.
  • paxdiablo
    paxdiablo over 4 years
    @maswerdna, I've actually seen weird resolutions in non-full-screen VMs where the guest itself has the screen size minus the border taken up by the host window. For example, a 1920x1080 screen may give only 1900x972 to the guest. We're also working on embedded hardware at the moment where we have a screen 1920x178, meant for showing dynamic journey information on trains.