Quel est l'algorithme pour calculer le rapport hauteur / largeur?


87

Je prévois de l'utiliser avec JavaScript pour recadrer une image pour l'adapter à toute la fenêtre.

Edit : J'utiliserai un composant tiers qui n'accepte que le rapport hauteur / largeur au format comme: 4:3, 16:9.


Il semble qu'il y ait une pièce manquante dans cette question. Si vous connaissez déjà le rapport hauteur / largeur de la source .. le titre du q n'a pas de sens pour moi.
Gishu

Lorsque vous dites «fenêtre», voulez-vous dire «écran»?
Nosredna

En fait, j'ai besoin de: faire correspondre l'image à la fenêtre, envoyer via ajax le rapport hauteur / largeur à la base de données.
Nathan

Eh bien, les fenêtres peuvent être de n'importe quelle taille géniale, non? Ils pourraient rendre la fenêtre principalement verticale.
Nosredna

Mon mauvais, je veux dire que l'image s'adapte à l'écran. (L'utilisateur l'utilisera comme fond d'écran)
Nathan

Réponses:


203

Je suppose que vous recherchez une integer:integersolution de rapport hauteur / largeur utilisable comme 16:9plutôt qu'une float:1solution comme 1.77778:1.

Si c'est le cas, ce que vous devez faire est de trouver le plus grand diviseur commun (GCD) et de diviser les deux valeurs par celui-ci. Le GCD est le nombre le plus élevé qui divise également les deux nombres. Ainsi, le GCD pour 6 et 10 est 2, le GCD pour 44 et 99 est 11.

Par exemple, un moniteur 1024x768 a un GCD de 256. Lorsque vous divisez les deux valeurs par cela, vous obtenez 4x3 ou 4: 3.

Un algorithme GCD (récursif):

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

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

Et voici un HTML / Javascript complet qui montre une façon de détecter la taille de l'écran et de calculer le rapport hauteur / largeur à partir de cela. Cela fonctionne dans FF3, je ne suis pas sûr de la compatibilité des autres navigateurs screen.widthet 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>

Il sort (sur mon étrange moniteur à écran large):

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

D'autres sur lesquels j'ai testé ceci:

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

J'aurais aimé avoir ce dernier à la maison mais, non, c'est une machine de travail malheureusement.

Ce que vous faites si vous découvrez que le rapport hauteur / largeur n'est pas pris en charge par votre outil de redimensionnement graphique est une autre affaire. Je soupçonne que le meilleur pari serait d'ajouter des lignes de boîte aux lettres (comme celles que vous obtenez en haut et en bas de votre ancien téléviseur lorsque vous regardez un film grand écran dessus). Je les ajouterais en haut / en bas ou sur les côtés (selon celui qui entraîne le moins de lignes de boîte aux lettres) jusqu'à ce que l'image réponde aux exigences.

Une chose que vous voudrez peut-être considérer est la qualité d'une image qui est passée de 16: 9 à 5: 4 - je me souviens encore des cowboys incroyablement grands et minces que je regardais dans ma jeunesse à la télévision avant l'introduction de la boxe aux lettres. Vous feriez peut-être mieux d'avoir une image différente par rapport d'aspect et de redimensionner simplement la bonne pour les dimensions réelles de l'écran avant de l'envoyer sur le fil.


1
C'était la première réponse que je pensais donner, mais je craignais que cela ne renvoie pas de résultats utiles à son composant tiers si sa fenêtre était dimensionnée à quelque chose comme 1021x711, par exemple.
Nosredna

2
Cela ressemble à une exagération. Et cela ne fonctionne pas pour les cas mentionnés par Nosredna. J'ai une solution basée sur l'approximation.
Chetan S

1
Mon client m'a dit qu'il avait besoin du rapport hauteur / largeur du spectateur. C'est un service pour une imprimerie. C'est pour les statistiques, je pense
Nathan

1
cas de test: 728x90-> 364:45je ne suis pas sûr que ce soit le résultat recherché
Rafael Herscovici

@Dementic, qui est la forme la plus simple de la fraction, d' où le rapport d'aspect correct, et 158 autres personnes (y compris l'OP) semblent être d'accord :-). Si vous avez une autre idée de ce qui serait mieux, faites-le moi savoir et j'essaierai d'ajuster la réponse.
paxdiablo

56
aspectRatio = width / height

si c'est ce que vous recherchez. Vous pouvez ensuite le multiplier par l'une des dimensions de l'espace cible pour découvrir l'autre (qui maintient le rapport) par exemple

widthT = heightT * aspectRatio
heightT = widthT / aspectRatio

13

La réponse de paxdiablo est excellente, mais il existe de nombreuses résolutions communes qui n'ont que quelques pixels de plus ou moins dans une direction donnée, et la plus grande approche de diviseur commun leur donne des résultats horribles.

Prenons par exemple la résolution bien comportée de 1360x765 qui donne un joli rapport 16: 9 en utilisant l'approche gcd. Selon Steam, cette résolution n'est utilisée que par 0,01% de ses utilisateurs, tandis que 1366x768 est utilisée par 18,9%. Voyons ce que nous obtenons en utilisant l'approche gcd:

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

Nous voudrions arrondir ce rapport de 683: 384 au rapport 16: 9 le plus proche.

J'ai écrit un script python qui analyse un fichier texte avec des nombres collés à partir de la page d'enquête sur le matériel Steam, et imprime toutes les résolutions et les ratios connus les plus proches, ainsi que la prévalence de chaque ratio (ce qui était mon objectif lorsque j'ai commencé cela):

# 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]) + '%')

Pour les curieux, voici la prévalence des ratios d'écran parmi les utilisateurs de Steam (en octobre 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%

11

Je suppose que vous voulez décider lequel des 4: 3 et 16: 9 est le meilleur ajustement.

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

1
Bien que votre solution soit correcte pour 4x3 et 16x9, cela ne semble pas prendre en charge tous les formats d'image possibles (bien que ce ne soit peut-être pas important pour l'OP). Le rapport pour la plupart des moniteurs à écran large, par exemple, est de 16x10 (1920x1200, 1600x1000)?
Falaina

Nous n'avons vraiment pas assez d'informations pour bien répondre à la question. :-)
Nosredna

4

Voici une version du meilleur algorithme d'approximation rationnelle de James Farey avec un niveau de flou ajustable porté en javascript à partir du code de calcul du rapport hauteur / largeur écrit à l'origine en python.

La méthode prend un float ( width/height) et une limite supérieure pour le numérateur / dénominateur de fraction.

Dans l'exemple ci-dessous, je fixe une limite supérieure de 50parce que j'ai besoin de 1035x582(1,77835051546) pour être traité comme 16:9(1,77777777778) plutôt que 345:194ce que vous obtenez avec l' gcdalgorithme simple répertorié dans d'autres réponses.

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

Le résultat:

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

3

Juste au cas où vous êtes un maniaque de la performance ...

Le moyen le plus rapide (en JavaScript) de calculer un rapport rectangle consiste à utiliser un véritable algorithme binaire Great Common Divisor.

(Tous les tests de vitesse et de chronométrage ont été effectués par d'autres, vous pouvez vérifier un benchmark ici: https://lemire.me/blog/2013/12/26/fastest-way-to-compute-the-greatest-common-divisor / )

Le voici:

/* 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]
*/


2

Voici ma solution, elle est assez simple car tout ce qui me préoccupe n'est pas nécessairement GCD ou même des ratios précis: parce que vous obtenez alors des choses étranges comme 345/113 qui ne sont pas compréhensibles par l'homme.

J'ai essentiellement défini des ratios de paysage ou de portrait acceptables et leur «valeur» en tant que flottant ... Je compare ensuite ma version flottante du rapport à chacun et celui qui a jamais la différence de valeur absolue la plus faible est le rapport le plus proche de l'élément. De cette façon, lorsque l'utilisateur fait 16: 9 mais supprime ensuite 10 pixels du bas, cela compte toujours pour 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

1

Comme solution alternative à la recherche GCD, je vous suggère de vérifier par rapport à un ensemble de valeurs standard. Vous pouvez trouver une liste sur Wikipedia .


1

Je suppose que vous parlez de vidéo ici, auquel cas vous devrez peut-être également vous soucier du rapport hauteur / largeur en pixels de la vidéo source. Par exemple.

PAL DV est disponible dans une résolution de 720x576. Ce qui ressemblerait à son 4: 3. Maintenant, en fonction du rapport hauteur / largeur des pixels (PAR), le rapport d'écran peut être de 4: 3 ou 16: 9.

Pour plus d'informations, regardez ici http://en.wikipedia.org/wiki/Pixel_aspect_ratio

Vous pouvez obtenir le rapport hauteur / largeur des pixels carrés, et beaucoup de vidéos sur le Web, mais vous voudrez peut-être faire attention aux autres cas.

J'espère que cela t'aides

marque


1

Sur la base des autres réponses, voici comment j'ai obtenu les nombres dont j'avais besoin en 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')}

0

Je crois que le rapport hauteur / largeur est la largeur divisée par la hauteur.

 r = w/h


0

Cet algorithme en Python vous permet de faire une partie du chemin.


Dites-moi ce qui se passe si les fenêtres sont d'une taille amusante.

Peut-être que vous devriez avoir une liste de tous les ratios acceptables (pour le composant tiers). Ensuite, trouvez la correspondance la plus proche de votre fenêtre et renvoyez ce ratio à partir de la liste.


0

une façon un peu étrange de faire cela, mais utilisez la résolution comme aspect. PAR EXEMPLE

1024: 768

ou tu peux essayer

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

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

0

dans mon cas, je veux quelque chose comme

[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 ]


0

Vous pouvez toujours commencer par créer une table de consultation basée sur des proportions communes. Vérifiez https://en.wikipedia.org/wiki/Display_aspect_ratio Ensuite, vous pouvez simplement faire la division

Pour les problèmes de la vie réelle, vous pouvez faire quelque chose comme ci-dessous

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

Ensuite, vous donnez simplement les dimensions dans n'importe quel ordre

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

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.