Question

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.

Was it helpful?

Solution

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.

OTHER TIPS

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

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%

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

Here is a version of 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.

<html>
<body>
<script type="text/javascript">
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;
        }
    }
}

document.write (aspect_ratio(800 / 600, 50) +"<br/>");
document.write (aspect_ratio(1035 / 582, 50) + "<br/>");
document.write (aspect_ratio(2560 / 1440, 50) + "<br/>");

    </script>
</body></html>

The result:

 4,3  // (1.33333333333) (800 x 600)
 16,9 // (1.77777777778) (2560.0 x 1440)
 16,9 // (1.77835051546) (1035.0 x 582)

Just in case you're a performance freak...

The Fastest way (in JavaScript) to compute a rectangle ratio it o use a true binary Great Common Divisor algorithm.

(All speed and timing tests have been done by others, you can check one benchmark here: https://lemire.me/blog/2013/12/26/fastest-way-to-compute-the-greatest-common-divisor/)

Here is it:

/* the binary Great Common Divisor calculator */
function gcd (u, v) {
    if (u === v) return u;
    if (u === 0) return v;
    if (v === 0) return u;

    if (~u & 1)
        if (v & 1)
            return gcd(u >> 1, v);
        else
            return gcd(u >> 1, v >> 1) << 1;

    if (~v & 1) return gcd(u, v >> 1);

    if (u > v) return gcd((u - v) >> 1, v);

    return gcd((v - u) >> 1, u);
}

/* returns an array with the ratio */
function ratio (w, h) {
	var d = gcd(w,h);
	return [w/d, h/d];
}

/* example */
var r1 = ratio(1600, 900);
var r2 = ratio(1440, 900);
var r3 = ratio(1366, 768);
var r4 = ratio(1280, 1024);
var r5 = ratio(1280, 720);
var r6 = ratio(1024, 768);


/* will output this: 
r1: [16, 9]
r2: [8, 5]
r3: [683, 384]
r4: [5, 4]
r5: [16, 9]
r6: [4, 3]
*/

I think this does what you are asking for:

webdeveloper.com - decimal to fraction

Width/height gets you a decimal, converted to a fraction with ":" in place of '/' gives you a "ratio".

This algorithm in Python gets you part of the way there.


Tell me what happens if the windows is a funny size.

Maybe what you should have is a list of all acceptable ratios (to the 3rd party component). Then, find the closest match to your window and return that ratio from the list.

As an alternative solution to the GCD searching, I suggest you to check against a set of standard values. You can find a list on Wikipedia.

Im assuming your talking about video here, in which case you may also need to worry about pixel aspect ratio of the source video. For example.

PAL DV comes in a resolution of 720x576. Which would look like its 4:3. Now depending on the Pixel aspect ratio (PAR) the screen ratio can be either 4:3 or 16:9.

For more info have a look here http://en.wikipedia.org/wiki/Pixel_aspect_ratio

You can get Square pixel Aspect Ratio, and a lot of web video is that, but you may want to watch out of the other cases.

Hope this helps

Mark

Based on the other answers, here is how I got the numbers I needed in Python;

from decimal import Decimal

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

def closest_aspect_ratio(width, height):
    g = gcd(width, height)
    x = Decimal(str(float(width)/float(g)))
    y = Decimal(str(float(height)/float(g)))
    dec = Decimal(str(x/y))
    return dict(x=x, y=y, dec=dec)

>>> closest_aspect_ratio(1024, 768)
{'y': Decimal('3.0'), 
 'x': Decimal('4.0'), 
 'dec': Decimal('1.333333333333333333333333333')}

Here is my solution it is pretty straight forward since all I care about is not necessarily GCD or even accurate ratios: because then you get weird things like 345/113 which are not human comprehensible.

I basically set acceptable landscape, or portrait ratios and their "value" as a float... I then compare my float version of the ratio to each and which ever has the lowest absolute value difference is the ratio closest to the item. That way when the user makes it 16:9 but then removes 10 pixels from the bottom it still counts as 16:9...

accepted_ratios = {
    'landscape': (
        (u'5:4', 1.25),
        (u'4:3', 1.33333333333),
        (u'3:2', 1.5),
        (u'16:10', 1.6),
        (u'5:3', 1.66666666667),
        (u'16:9', 1.77777777778),
        (u'17:9', 1.88888888889),
        (u'21:9', 2.33333333333),
        (u'1:1', 1.0)
    ),
    'portrait': (
        (u'4:5', 0.8),
        (u'3:4', 0.75),
        (u'2:3', 0.66666666667),
        (u'10:16', 0.625),
        (u'3:5', 0.6),
        (u'9:16', 0.5625),
        (u'9:17', 0.5294117647),
        (u'9:21', 0.4285714286),
        (u'1:1', 1.0)
    ),
}


def find_closest_ratio(ratio):
    lowest_diff, best_std = 9999999999, '1:1'
    layout = 'portrait' if ratio < 1.0 else 'landscape'
    for pretty_str, std_ratio in accepted_ratios[layout]:
        diff = abs(std_ratio - ratio)
        if diff < lowest_diff:
            lowest_diff = diff
            best_std = pretty_str
    return best_std


def extract_ratio(width, height):
    try:
        divided = float(width)/float(height)
        if divided == 1.0:
            return '1:1'
        else:
            return find_closest_ratio(divided)
    except TypeError:
        return None

I believe that aspect ratio is width divided by height.

 r = w/h

bit of a strange way to do this but use the resolution as the aspect. E.G.

1024:768

or you can try

var w = screen.width;
var h = screen.height;
for(var i=1,asp=w/h;i<5000;i++){
  if(asp*i % 1==0){
    i=9999;
    document.write(asp*i,":",1*i);
  }
}
function ratio(w, h) {
    function mdc(w, h) {
        var resto;
        do {
            resto = w % h;

            w = h;
            h = resto;

        } while (resto != 0);

        return w;
    }

    var mdc = mdc(w, h);


    var width = w/mdc;
    var height = h/mdc;

    console.log(width + ':' + height);
}

ratio(1920, 1080);

in my case i want something like

[10,5,15,20,25] -> [ 2, 1, 3, 4, 5 ]

function ratio(array){
  let min = Math.min(...array);
  let ratio = array.map((element)=>{
    return element/min;
  });
  return ratio;
}
document.write(ratio([10,5,15,20,25]));  // [ 2, 1, 3, 4, 5 ]

Width / Height

?

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top