Qual è l'algoritmo per calcolare le proporzioni?


89

Ho intenzione di usarlo con JavaScript per ritagliare un'immagine per adattarla all'intera finestra.

Modifica : userò un componente di terze parti che accetta solo le proporzioni nel formato come: 4:3, 16:9.


Sembra che ci sia un pezzo mancante in questa domanda. Se conosci già le proporzioni della sorgente .. il titolo della q non ha senso per me.
Gishu,

Quando dici "finestra", intendi "schermo?"
Nosredna

In realtà, ho bisogno di: adattare l'immagine alla finestra, inviare tramite ajax le proporzioni al database.
Nathan,

Bene, le finestre possono essere di qualsiasi dimensione originale, giusto? Potrebbero rendere la finestra per lo più verticale.
Nosredna

Colpa mia, intendo adattare l'immagine allo schermo. (L'utente lo utilizzerà come sfondo)
Nathan

Risposte:


204

Immagino che tu stia cercando una integer:integersoluzione di proporzioni utilizzabile come 16:9piuttosto che una float:1soluzione come 1.77778:1.

In tal caso, quello che devi fare è trovare il massimo comune divisore (MCD) e dividere entrambi i valori per quello. Il GCD è il numero più alto che divide equamente entrambi i numeri. Quindi il MCD per 6 e 10 è 2, il MCD per 44 e 99 è 11.

Ad esempio, un monitor 1024x768 ha un MCD di 256. Quando dividi entrambi i valori per questo, ottieni 4x3 o 4: 3.

Un algoritmo GCD (ricorsivo):

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

Ed ecco alcuni HTML / Javascript completi che mostrano un modo per rilevare le dimensioni dello schermo e calcolare le proporzioni da quello. Funziona in FF3, non sono sicuro di quale supporto abbiano altri browser per 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>

Emette (sul mio strano monitor widescreen):

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

Altri su cui l'ho testato:

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

Vorrei averlo avuto l'ultima a casa ma, no, purtroppo è una macchina da lavoro.

Quello che fai se scopri che le proporzioni non sono supportate dal tuo strumento di ridimensionamento grafico è un'altra questione. Sospetto che la scommessa migliore sarebbe quella di aggiungere linee di boxe delle lettere (come quelle che ottieni in cima e in fondo alla tua vecchia TV quando guardi un film a grande schermo). Li aggiungerei in alto / in basso o ai lati (a seconda di quale risulta il minor numero di linee di letter boxing) fino a quando l'immagine non soddisfa i requisiti.

Una cosa che potresti voler considerare è la qualità di un'immagine che è stata cambiata da 16: 9 a 5: 4 - Ricordo ancora i cowboy incredibilmente alti e magri che guardavo in gioventù in televisione prima dell'introduzione del letterboxing. Potrebbe essere meglio avere un'immagine diversa per proporzioni e ridimensionare quella corretta per le dimensioni effettive dello schermo prima di inviarla lungo il cavo.


1
Questa è stata la prima risposta che ho pensato di dare, ma ero preoccupato che non avrebbe restituito risultati utili al suo componente di terze parti se la sua finestra fosse dimensionata a qualcosa come 1021x711, per esempio.
Nosredna,

2
Sembra eccessivo. E non funziona per i casi citati da Nosredna. Ho una soluzione basata sull'approssimazione.
Chetan S

1
Il mio cliente mi ha detto che ha bisogno delle proporzioni dello spettatore. È un servizio per una tipografia. È per le statistiche penso
Nathan

1
caso di test: 728x90-> 364:45non sono sicuro che sia il risultato desiderato
Rafael Herscovici

@Dementic, che è la forma più semplice della frazione, da qui le proporzioni corrette, e altre 158 persone (incluso l'OP) sembrano essere d'accordo :-). Se hai qualche altra idea di cosa sarebbe meglio, fammelo sapere e cercherò di modificare la risposta.
paxdiablo

56
aspectRatio = width / height

se è questo che stai cercando. È quindi possibile moltiplicarlo per una delle dimensioni dello spazio target per scoprire l'altra (che mantiene il rapporto) ad es.

widthT = heightT * aspectRatio
heightT = widthT / aspectRatio

13

La risposta di paxdiablo è ottima, ma ci sono molte risoluzioni comuni che hanno solo pochi pixel in più o in meno in una determinata direzione, e l'approccio con il massimo comune divisore dà loro risultati orribili.

Prendiamo ad esempio la risoluzione ben educata di 1360x765 che offre un bel rapporto 16: 9 utilizzando l'approccio gcd. Secondo Steam, questa risoluzione viene utilizzata solo dallo 0,01% dei suoi utenti, mentre 1366x768 viene utilizzata da un 18,9%. Vediamo cosa otteniamo utilizzando l'approccio gcd:

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

Vorremmo arrotondare il rapporto 683: 384 al rapporto più vicino, 16: 9.

Ho scritto uno script Python che analizza un file di testo con numeri incollati dalla pagina del sondaggio Steam Hardware e stampa tutte le risoluzioni e i rapporti noti più vicini, nonché la prevalenza di ciascun rapporto (che era il mio obiettivo quando ho iniziato questo):

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

Per i curiosi, queste sono la prevalenza delle proporzioni dello schermo tra gli utenti di Steam (a partire da ottobre 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

Immagino che tu voglia decidere quale tra 4: 3 e 16: 9 è la soluzione migliore.

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

1
Mentre la tua soluzione va bene per 4x3 e 16x9, questo non sembra supportare tutti i rapporti di aspetto possibili (anche se forse non è importante per l'OP). Il rapporto per la maggior parte dei monitor widescreen, ad esempio, è 16x10 (1920x1200, 1600x1000)?
Falaina

Non abbiamo davvero abbastanza informazioni per rispondere bene alla domanda. :-)
Nosredna

5

Ecco una versione del miglior algoritmo di approssimazione razionale di James Farey con livello di sfocatura regolabile portato su javascript dal codice di calcolo delle proporzioni scritto originariamente in python.

Il metodo accetta un float ( width/height) e un limite superiore per il numeratore / denominatore della frazione.

Nell'esempio seguente sto impostando un limite superiore 50perché ho bisogno di 1035x582(1.77835051546) da trattare come 16:9(1.77777777778) piuttosto che 345:194come si ottiene con l' gcdalgoritmo semplice elencato in altre risposte.

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

Il risultato:

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

3

Nel caso tu sia un maniaco delle prestazioni ...

Il modo più veloce (in JavaScript) per calcolare un rapporto rettangolo è utilizzare un vero algoritmo binario Great Common Divisor.

(Tutti i test di velocità e timing sono stati eseguiti da altri, puoi controllare un benchmark qui: https://lemire.me/blog/2013/12/26/fastest-way-to-compute-the-greatest-common-divisor / )

Eccolo:

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

Ecco la mia soluzione che è piuttosto semplice poiché tutto ciò che mi interessa non è necessariamente MCD o anche rapporti accurati: perché allora ottieni cose strane come 345/113 che non sono comprensibili dall'uomo.

Fondamentalmente ho impostato rapporti orizzontali o verticali accettabili e il loro "valore" come float ... quindi confronto la mia versione float del rapporto con ciascuno e che abbia mai la differenza di valore assoluto più basso è il rapporto più vicino all'elemento. In questo modo quando l'utente fa 16: 9 ma poi rimuove 10 pixel dal fondo, conta ancora come 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

Come soluzione alternativa alla ricerca in GCD, ti suggerisco di confrontarti con un insieme di valori standard. Puoi trovare un elenco su Wikipedia .


1

Immagino che tu stia parlando di video qui, nel qual caso potresti anche dover preoccuparti delle proporzioni dei pixel del video sorgente. Per esempio.

PAL DV ha una risoluzione di 720x576. Che sarebbe come il suo 4: 3. Ora, a seconda delle proporzioni dei pixel (PAR), le proporzioni dello schermo possono essere 4: 3 o 16: 9.

Per maggiori informazioni dai un'occhiata qui http://en.wikipedia.org/wiki/Pixel_aspect_ratio

Puoi ottenere il rapporto di aspetto pixel quadrato e molti video web lo sono, ma potresti voler guardare fuori dagli altri casi.

Spero che sia di aiuto

marchio


1

Sulla base delle altre risposte, ecco come ho ottenuto i numeri di cui avevo bisogno 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')}



0

Questo algoritmo in Python ti fa fare parte del percorso.


Dimmi cosa succede se le finestre hanno una dimensione buffa.

Forse quello che dovresti avere è un elenco di tutti i rapporti accettabili (rispetto al componente di terze parti). Quindi, trova la corrispondenza più vicina alla tua finestra e restituisci quel rapporto dall'elenco.


0

un modo un po 'strano per farlo, ma usa la risoluzione come aspetto. PER ESEMPIO

1024: 768

oppure puoi provare

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

nel mio caso voglio qualcosa di simile

[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

Puoi sempre iniziare creando una tabella di ricerca basata su proporzioni comuni. Controlla https://en.wikipedia.org/wiki/Display_aspect_ratio Quindi puoi semplicemente fare la divisione

Per i problemi della vita reale, puoi fare qualcosa come di seguito

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

Quindi dai semplicemente le dimensioni in qualsiasi ordine

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.