Qual é o algoritmo para calcular a proporção de aspecto?


87

Pretendo usá-lo com JavaScript para cortar uma imagem para caber na janela inteira.

Editar : estarei usando um componente de terceiros que aceita apenas a proporção no formato: 4:3, 16:9.


Parece que falta uma peça nesta questão. Se você já sabe a relação de aspecto da fonte .. o título do q não faz sentido pra mim.
Gishu

Quando você diz "janela", quer dizer "tela"?
Nosredna

Na verdade, eu preciso: fazer a imagem caber na janela, enviar via ajax a relação de aspecto para o banco de dados.
Nathan

Bem, as janelas podem ter qualquer tamanho original, certo? Eles poderiam deixar a janela quase toda vertical.
Nosredna

Meu mal, quero dizer fazer a imagem caber na tela. (O usuário usará como papel de parede)
Nathan

Respostas:


203

Percebi que você está procurando uma integer:integersolução de proporção de aspecto utilizável, em 16:9vez de uma float:1solução como 1.77778:1.

Nesse caso, o que você precisa fazer é encontrar o maior divisor comum (GCD) e dividir os dois valores por ele. O GCD é o número mais alto que divide os dois números igualmente. Portanto, o GCD para 6 e 10 é 2, o GCD para 44 e 99 é 11.

Por exemplo, um monitor de 1024x768 tem um GCD de 256. Quando você divide os dois valores por isso, obtém 4x3 ou 4: 3.

Um algoritmo GCD (recursivo):

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

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

E aqui está um HTML / Javascript completo que mostra uma maneira de detectar o tamanho da tela e calcular a proporção a partir disso. Isso funciona no FF3, não tenho certeza de qual suporte outros navegadores têm para screen.widthe 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>

Ele produz (no meu monitor widescreen):

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

Outros em que testei:

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

Eu gostaria de ter esse último em casa, mas, não, é uma máquina de trabalho infelizmente.

O que você faz se descobrir que a proporção de aspecto não é compatível com sua ferramenta de redimensionamento gráfico é outra questão. Suspeito que a melhor aposta seria adicionar linhas de caixa de correio (como as que aparecem na parte superior e inferior da sua velha TV quando você está assistindo a um filme em tela ampla). Eu os adicionaria na parte superior / inferior ou nas laterais (o que resultar no menor número de linhas do letter-box) até que a imagem atenda aos requisitos.

Uma coisa que você pode considerar é a qualidade de uma imagem que foi alterada de 16: 9 para 5: 4 - ainda me lembro dos cowboys incrivelmente altos e magros que costumava assistir na minha juventude na televisão antes do lançamento do boxe de correio. Talvez seja melhor você ter uma imagem diferente por proporção de aspecto e apenas redimensionar a imagem correta para as dimensões reais da tela antes de enviá-la pelo fio.


1
Esta foi a primeira resposta que pensei em dar, mas estava preocupado que não retornasse resultados úteis para seu componente de terceiros se sua janela fosse dimensionada para algo como 1021x711, por exemplo.
Nosredna

2
Parece um exagero. E não funciona para os casos mencionados por Nosredna. Tenho uma solução baseada em aproximação.
Chetan S

1
Meu cliente me disse que precisa da proporção do visualizador. É um serviço para uma gráfica. Acho que é para estatísticas
Nathan

1
caso de teste: 728x90-> 364:45não tenho certeza se é o resultado desejado
Rafael Herscovici

@Dementic, que é a forma mais simples da fração, portanto, a proporção correta, e 158 outras pessoas (incluindo o OP) parecem concordar :-). Se você tiver alguma outra ideia do que seria melhor, por favor me avise e eu irei ajustar a resposta.
paxdiablo

56
aspectRatio = width / height

se é isso que você quer. Você pode então multiplicá-lo por uma das dimensões do espaço alvo para descobrir o outro (que mantém a proporção), por exemplo

widthT = heightT * aspectRatio
heightT = widthT / aspectRatio

13

A resposta do paxdiablo é ótima, mas há muitas resoluções comuns que têm apenas alguns pixels a mais ou a menos em uma determinada direção, e a abordagem do maior divisor comum dá resultados horríveis a elas.

Pegue por exemplo a resolução bem comportada de 1360x765 que dá uma boa proporção de 16: 9 usando a abordagem mdc. De acordo com o Steam, esta resolução é usada apenas por 0,01% dos usuários, enquanto 1366x768 é usada por 18,9%. Vamos ver o que conseguimos usando a abordagem gcd:

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

Gostaríamos de arredondar essa proporção de 683: 384 para a proporção mais próxima, de 16: 9.

Eu escrevi um script python que analisa um arquivo de texto com números colados da página de pesquisa do Steam Hardware e imprime todas as resoluções e proporções conhecidas mais próximas, bem como a prevalência de cada proporção (que era meu objetivo quando comecei isso):

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

Para os curiosos, essas são as prevalências das proporções de tela entre os usuários do Steam (em outubro de 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

Acho que você quer decidir qual dos 4: 3 e 16: 9 é o mais adequado.

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

1
Embora sua solução seja boa para 4x3 e 16x9, isso não parece que suportaria todas as proporções possíveis (embora talvez isso não seja importante para o OP). A proporção para a maioria dos monitores de tela ampla, por exemplo, é 16x10 (1920x1200, 1600x1000)?
Falaina

Na verdade, não temos informações suficientes para responder bem à pergunta. :-)
Nosredna

4

Aqui está uma versão do melhor algoritmo de aproximação racional de James Farey com nível ajustável de imprecisão portado para javascript a partir do código de cálculo de proporção de aspecto originalmente escrito em python.

O método leva um float ( width/height) e um limite superior para o numerador / denominador da fração.

No exemplo abaixo, estou definindo um limite superior de 50porque preciso 1035x582(1.77835051546) ser tratado como 16:9(1.777777778) em vez do algoritmo 345:194simples gcdlistado em outras respostas.

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

O resultado:

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

3

Apenas no caso de você ser um fanático por desempenho ...

A maneira mais rápida (em JavaScript) de calcular a proporção de um retângulo é usar um verdadeiro algoritmo binário do Grande Divisor Comum.

(Todos os testes de velocidade e tempo foram feitos por outros, você pode verificar um benchmark aqui: https://lemire.me/blog/2013/12/26/fastest-way-to-compute-the-greatest-common-divisor / )

Aqui está:

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

Aqui está a minha solução, é bastante simples, pois tudo que me interessa não é necessariamente o GCD ou mesmo proporções precisas: porque então você obtém coisas estranhas como 345/113 que não são compreensíveis para humanos.

Basicamente, defino proporções de paisagem ou retrato aceitáveis ​​e seu "valor" como um float ... Em seguida, comparo minha versão de float da proporção com cada um e o que tiver a menor diferença de valor absoluto é a proporção mais próxima do item. Dessa forma, quando o usuário torna 16: 9, mas remove 10 pixels da parte inferior, ainda conta como 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

Como uma solução alternativa para a pesquisa de GCD, sugiro que você compare com um conjunto de valores padrão. Você pode encontrar uma lista na Wikipedia .


1

Suponho que você esteja falando sobre vídeo aqui, caso em que você também pode precisar se preocupar com a proporção de pixel do vídeo de origem. Por exemplo.

PAL DV vem com uma resolução de 720x576. Que seria semelhante a 4: 3. Agora, dependendo da proporção do pixel (PAR), a proporção da tela pode ser 4: 3 ou 16: 9.

Para obter mais informações, dê uma olhada aqui http://en.wikipedia.org/wiki/Pixel_aspect_ratio

Você pode obter Razão de aspecto de pixel quadrado, e muitos vídeos da web são isso, mas você pode querer assistir nos outros casos.

Espero que isto ajude

Marca


1

Com base nas outras respostas, aqui está como obtive os números de que precisava no 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

Este algoritmo em Python mostra parte do caminho até lá.


Diga-me o que acontece se as janelas tiverem um tamanho estranho.

Talvez o que você deva ter é uma lista de todas as proporções aceitáveis ​​(para o componente de terceiros). Em seguida, encontre a correspondência mais próxima à sua janela e retorne essa proporção da lista.


0

é uma maneira estranha de fazer isso, mas use a resolução como aspecto. POR EXEMPLO

1024: 768

ou você pode tentar

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

no meu caso eu quero algo como

[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

Você sempre pode começar criando uma tabela de pesquisa baseada em proporções comuns. Verifique https://en.wikipedia.org/wiki/Display_aspect_ratio Então você pode simplesmente fazer a divisão

Para problemas da vida real, você pode fazer algo como abaixo

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

Então você simplesmente dá as dimensões em qualquer ordem

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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.