[javascript] What's the algorithm to calculate aspect ratio?

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.

This question is related to javascript algorithm crop aspect-ratio

The answer is


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

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".


in my case i want something like

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

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


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%

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

Width / Height

?


I believe that aspect ratio is width divided by height.

 r = w/h

You can always start by making a lookup table based on common aspect ratios. Check https://en.wikipedia.org/wiki/Display_aspect_ratio Then you can simply do the division

For real life problems, you can do something like below

let ERROR_ALLOWED = 0.05
let STANDARD_ASPECT_RATIOS = [
  [1, '1:1'],
  [4/3, '4:3'],
  [5/4, '5:4'],
  [3/2, '3:2'],
  [16/10, '16:10'],
  [16/9, '16:9'],
  [21/9, '21:9'],
  [32/9, '32:9'],
]
let RATIOS = STANDARD_ASPECT_RATIOS.map(function(tpl){return tpl[0]}).sort()
let LOOKUP = Object()
for (let i=0; i < STANDARD_ASPECT_RATIOS.length; i++){
  LOOKUP[STANDARD_ASPECT_RATIOS[i][0]] = STANDARD_ASPECT_RATIOS[i][1]
}

/*
Find the closest value in a sorted array
*/
function findClosest(arrSorted, value){
  closest = arrSorted[0]
  closestDiff = Math.abs(arrSorted[0] - value)
  for (let i=1; i<arrSorted.length; i++){
    let diff = Math.abs(arrSorted[i] - value)
    if (diff < closestDiff){
      closestDiff = diff
      closest = arrSorted[i]
    } else {
      return closest
    }
  }
  return arrSorted[arrSorted.length-1]
}

/*
Estimate the aspect ratio based on width x height (order doesn't matter)
*/
function estimateAspectRatio(dim1, dim2){
  let ratio = Math.max(dim1, dim2) / Math.min(dim1, dim2)
  if (ratio in LOOKUP){
    return LOOKUP[ratio]
  }

  // Look by approximation
  closest = findClosest(RATIOS, ratio)
  if (Math.abs(closest - ratio) <= ERROR_ALLOWED){
    return '~' + LOOKUP[closest]
  }

  return 'non standard ratio: ' + Math.round(ratio * 100) / 100 + ':1'
}

Then you simply give the dimensions in any order

estimateAspectRatio(1920, 1080) // 16:9
estimateAspectRatio(1920, 1085) // ~16:9
estimateAspectRatio(1920, 1150) // non standard ratio: 1.65:1
estimateAspectRatio(1920, 1200) // 16:10
estimateAspectRatio(1920, 1220) // ~16:10

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


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

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 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'
        return find_closest_ratio(divided)
    except TypeError:
        return None

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);

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:

_x000D_
_x000D_
/* the binary Great Common Divisor calculator */_x000D_
function gcd (u, v) {_x000D_
    if (u === v) return u;_x000D_
    if (u === 0) return v;_x000D_
    if (v === 0) return u;_x000D_
_x000D_
    if (~u & 1)_x000D_
        if (v & 1)_x000D_
            return gcd(u >> 1, v);_x000D_
        else_x000D_
            return gcd(u >> 1, v >> 1) << 1;_x000D_
_x000D_
    if (~v & 1) return gcd(u, v >> 1);_x000D_
_x000D_
    if (u > v) return gcd((u - v) >> 1, v);_x000D_
_x000D_
    return gcd((v - u) >> 1, u);_x000D_
}_x000D_
_x000D_
/* returns an array with the ratio */_x000D_
function ratio (w, h) {_x000D_
 var d = gcd(w,h);_x000D_
 return [w/d, h/d];_x000D_
}_x000D_
_x000D_
/* example */_x000D_
var r1 = ratio(1600, 900);_x000D_
var r2 = ratio(1440, 900);_x000D_
var r3 = ratio(1366, 768);_x000D_
var r4 = ratio(1280, 1024);_x000D_
var r5 = ratio(1280, 720);_x000D_
var r6 = ratio(1024, 768);_x000D_
_x000D_
_x000D_
/* will output this: _x000D_
r1: [16, 9]_x000D_
r2: [8, 5]_x000D_
r3: [683, 384]_x000D_
r4: [5, 4]_x000D_
r5: [16, 9]_x000D_
r6: [4, 3]_x000D_
*/
_x000D_
_x000D_
_x000D_


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)

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.


Examples related to javascript

need to add a class to an element How to make a variable accessible outside a function? Hide Signs that Meteor.js was Used How to create a showdown.js markdown extension Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Summing radio input values How to execute an action before close metro app WinJS javascript, for loop defines a dynamic variable name Getting all files in directory with ajax

Examples related to algorithm

How can I tell if an algorithm is efficient? Find the smallest positive integer that does not occur in a given sequence Efficiently getting all divisors of a given number Peak signal detection in realtime timeseries data What is the optimal algorithm for the game 2048? How can I sort a std::map first by value, then by key? Finding square root without using sqrt function? Fastest way to flatten / un-flatten nested JSON objects Mergesort with Python Find common substring between two strings

Examples related to crop

Crop image to specified size and picture location How to "crop" a rectangular image into a square with CSS? How to crop an image using PIL? Android Crop Center of Bitmap How to Split Image Into Multiple Pieces in Python Crop image in PHP What's the algorithm to calculate aspect ratio?

Examples related to aspect-ratio

Maintain aspect ratio of div but fill screen width and height in CSS? Grid of responsive squares Android Camera Preview Stretched How to style a div to be a responsive square? CSS force image resize and keep aspect ratio Resize UIImage by keeping Aspect ratio and width Is there a list of screen resolutions for all Android based phones and tablets? Maintain the aspect ratio of a div with CSS What's the algorithm to calculate aspect ratio?