Jaki jest algorytm obliczania współczynnika proporcji?


89

Planuję użyć go z JavaScriptem, aby przyciąć obraz tak, aby pasował do całego okna.

Edycja : będę używać komponentu innej firmy, który akceptuje tylko proporcje w formacie: 4:3, 16:9.


Wygląda na to, że w tym pytaniu brakuje jakiegoś elementu. Jeśli znasz już źródłowe proporcje obrazu ... tytuł q nie ma dla mnie sensu.
Gishu

Kiedy mówisz „okno”, masz na myśli „ekran”?
Nosredna

Właściwie to potrzebuję: dopasuj obraz do okna, wyślij przez Ajax proporcje do bazy danych.
Nathan,

Cóż, okna mogą mieć dowolny rozmiar, prawda? Mogli sprawić, że okno będzie w większości pionowe.
Nosredna

Mój błąd, mam na myśli dopasowanie obrazu do ekranu. (Użytkownik użyje go jako tapety)
Nathan

Odpowiedzi:


204

Rozumiem, że szukasz użytecznego integer:integerrozwiązania ze współczynnikiem proporcji , 16:9a nie takiego float:1rozwiązania 1.77778:1.

Jeśli tak, to co musisz zrobić, to znaleźć największy wspólny dzielnik (GCD) i podzielić przez niego obie wartości. GCD to najwyższa liczba, która równo dzieli obie liczby. Więc GCD dla 6 i 10 to 2, GCD dla 44 i 99 to 11.

Na przykład monitor 1024x768 ma GCD 256. Dzieląc obie wartości przez, otrzymujemy 4x3 lub 4: 3.

Algorytm (rekurencyjny) GCD:

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

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

A oto kompletny kod HTML / JavaScript, który pokazuje jeden ze sposobów wykrywania rozmiaru ekranu i obliczania współczynnika proporcji z tego. Działa to w FF3, nie jestem pewien, do czego obsługują inne przeglądarki screen.widthi 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>

Wyprowadza (na moim dziwnym monitorze szerokoekranowym):

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

Inne, na których to przetestowałem:

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

Żałuję, że nie mam tego ostatniego w domu, ale nie, to niestety maszyna robocza.

To, co zrobisz, jeśli okaże się, że współczynnik proporcji nie jest obsługiwany przez narzędzie do zmiany rozmiaru grafiki, to inna sprawa. Podejrzewam, że najlepszym rozwiązaniem byłoby dodanie linii z literami (jak te, które dostajesz na górze i na dole starego telewizora, gdy oglądasz na nim film szerokoekranowy). Dodawałbym je u góry / dołu lub po bokach (w zależności od tego, który z nich powoduje najmniejszą liczbę linii z czarnymi pasami), aż obraz spełni wymagania.

Jedną rzeczą, którą warto rozważyć, jest jakość obrazu, który został zmieniony z 16: 9 na 5: 4 - wciąż pamiętam niesamowicie wysokich, chudych kowbojów, których oglądałem w telewizji w młodości, zanim wprowadzono skrzynkę pocztową. Lepiej byłoby mieć jeden inny obraz na współczynnik proporcji i po prostu zmienić rozmiar na właściwy dla rzeczywistych wymiarów ekranu przed wysłaniem go dalej.


1
To była pierwsza odpowiedź, jakiej chciałem udzielić, ale martwiłem się, że nie zwróci wyników przydatnych dla jego komponentu innej firmy, jeśli jego okno ma rozmiar na przykład 1021x711.
Nosredna

2
Wydaje się, że to przesada. I to nie działa w przypadkach, o których wspomniał Nosredna. Mam rozwiązanie oparte na przybliżeniu.
Chetan S

1
Mój klient powiedział mi, że potrzebuje proporcji widza. To usługa dla drukarni. Myślę, że to dla statystyk
Nathan,

1
przypadek testowy: 728x90-> 364:45nie jestem pewien, czy jest to pożądany wynik
Rafael Herscovici

@Dementic, to jest najprostsza postać frakcji, a więc o właściwych proporcjach i 158 ludzi (w tym OP) wydają się zgadzać :-). Jeśli masz inny pomysł, co byłoby lepsze, daj mi znać, a ja spróbuję dostosować odpowiedź.
paxdiablo

56
aspectRatio = width / height

jeśli o to ci chodzi. Możesz następnie pomnożyć go przez jeden z wymiarów przestrzeni docelowej, aby znaleźć drugi (który zachowuje stosunek) np.

widthT = heightT * aspectRatio
heightT = widthT / aspectRatio

13

Odpowiedź paxdiablo jest świetna, ale istnieje wiele typowych rozdzielczości, które mają tylko kilka mniej lub więcej pikseli w danym kierunku, a zastosowanie największego wspólnego dzielnika daje im okropne wyniki.

Weźmy na przykład dobrze zachowującą się rozdzielczość 1360x765, która daje ładny współczynnik 16: 9 przy zastosowaniu podejścia gcd. Według Steam z tej rozdzielczości korzysta tylko 0,01% użytkowników, a 1366x768 - aż 18,9%. Zobaczmy, co otrzymamy stosując podejście gcd:

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

Chcielibyśmy zaokrąglić ten stosunek 683: 384 do najbliższego, 16: 9.

Napisałem skrypt w języku Python, który analizuje plik tekstowy z wklejonymi liczbami ze strony ankiety dotyczącej sprzętu Steam i drukuje wszystkie rozdzielczości i najbliższe znane współczynniki, a także rozpowszechnienie każdego współczynnika (co było moim celem, gdy zaczynałam):

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

Dla ciekawskich są to rozpowszechnienie współczynników ekranu wśród użytkowników Steam (stan na październik 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

Chyba chcesz zdecydować, która z opcji 4: 3 i 16: 9 jest najlepsza.

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

1
Chociaż twoje rozwiązanie jest dobre dla 4x3 i 16x9, nie wydaje się, aby obsługiwało wszystkie możliwe proporcje (choć może to nie jest ważne dla OP). Na przykład współczynnik dla większości monitorów szerokoekranowych wynosi 16x10 (1920x1200, 1600x1000)?
Falaina

Naprawdę nie mamy wystarczających informacji, aby dobrze odpowiedzieć na pytanie. :-)
Nosredna

5

Oto wersja najlepszego racjonalnego algorytmu przybliżenia Jamesa Fareya z regulowanym poziomem nieostrości, przeniesiona do javascript z kodu obliczania współczynnika kształtu pierwotnie napisanego w Pythonie.

Metoda przyjmuje zmiennoprzecinkową ( width/height) i górną granicę dla licznika / mianownika ułamka.

W poniższym przykładzie ustawiam górną granicę, 50ponieważ potrzebuję 1035x582(1.77835051546), aby być traktowanym jako 16:9(1.77777777778), a nie 345:194za pomocą zwykłego gcdalgorytmu wymienionego w innych odpowiedziach.

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

Wynik:

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

3

Na wypadek, gdybyś był maniakiem wydajności ...

Najszybszy sposób (w JavaScript) na obliczenie współczynnika prostokąta przy użyciu prawdziwego binarnego algorytmu Great Common Divisor.

(Wszystkie testy szybkości i czasu zostały wykonane przez innych, jeden test porównawczy można sprawdzić tutaj: https://lemire.me/blog/2013/12/26/fastest-way-to-compute-the-greatest-common-divisor / )

Tutaj jest to:

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

Oto moje rozwiązanie, które jest dość proste, ponieważ wszystko, na czym mi zależy, to niekoniecznie GCD lub nawet dokładne proporcje: ponieważ wtedy otrzymujesz dziwne rzeczy, takie jak 345/113, które nie są zrozumiałe dla człowieka.

Zasadniczo ustawiam akceptowalne proporcje krajobrazu lub portretu i ich „wartość” jako zmienną ... Następnie porównuję moją zmienną wersję współczynnika z każdym i ten, który kiedykolwiek ma najmniejszą bezwzględną różnicę wartości, jest stosunkiem najbliższym pozycji. W ten sposób, gdy użytkownik zrobi 16: 9, ale potem usunie 10 pikseli od dołu, nadal liczy się jako 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

Jako alternatywne rozwiązanie do wyszukiwania GCD proponuję sprawdzić zestaw wartości standardowych. Możesz znaleźć listę w Wikipedii .


1

Zakładam, że mówisz tutaj o wideo, w takim przypadku możesz również martwić się o proporcje pikseli źródłowego wideo. Na przykład.

PAL DV jest dostępny w rozdzielczości 720x576. Który wyglądałby jak 4: 3. Teraz, w zależności od współczynnika proporcji pikseli (PAR), współczynnik ekranu może wynosić 4: 3 lub 16: 9.

Aby uzyskać więcej informacji, zajrzyj tutaj http://en.wikipedia.org/wiki/Pixel_aspect_ratio

Możesz uzyskać współczynnik proporcji pikseli kwadratowych i jest to dużo wideo w Internecie, ale możesz chcieć uważać na inne przypadki.

Mam nadzieję że to pomoże

znak


1

W oparciu o inne odpowiedzi, oto jak uzyskałem liczby, których potrzebowałem w Pythonie;

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

Ten algorytm w Pythonie prowadzi cię do tego celu.


Powiedz mi, co się stanie, jeśli okna mają zabawny rozmiar.

Może to, co powinieneś mieć, to lista wszystkich akceptowalnych współczynników (do składnika innej firmy). Następnie znajdź najbliższe dopasowanie do swojego okna i zwróć ten współczynnik z listy.


0

trochę dziwny sposób, ale użyj rozdzielczości jako aspektu. NA PRZYKŁAD

1024: 768

lub możesz spróbować

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

w moim przypadku chcę coś takiego

[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

Zawsze możesz zacząć od utworzenia tabeli odnośników na podstawie typowych współczynników proporcji. Sprawdź https://en.wikipedia.org/wiki/Display_aspect_ratio Następnie możesz po prostu dokonać podziału

W przypadku problemów z życia codziennego możesz zrobić coś takiego jak poniżej

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

Następnie po prostu podajesz wymiary w dowolnej kolejności

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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.