Was ist der Algorithmus zur Berechnung des Seitenverhältnisses?


84

Ich habe vor, es mit JavaScript zu verwenden, um ein Bild so zuzuschneiden, dass es in das gesamte Fenster passt.

Bearbeiten : Ich verwende eine Komponente eines Drittanbieters, die nur das Seitenverhältnis im Format wie : 4:3, akzeptiert 16:9.


Es sieht so aus, als ob in dieser Frage ein Teil fehlt. Wenn Sie das Quell-Seitenverhältnis bereits kennen, ergibt der Titel des q für mich keinen Sinn.
Gishu

Wenn Sie "Fenster" sagen, meinen Sie "Bildschirm"?
Nosredna

Eigentlich brauche ich: das Bild in das Fenster einpassen, über Ajax das Seitenverhältnis zur Datenbank senden.
Nathan

Nun, Fenster können jede funky Größe haben, oder? Sie könnten das Fenster größtenteils vertikal machen.
Nosredna

Mein schlechtes, ich meine, machen Sie das Bild passend zum Bildschirm. (Der Benutzer wird es als Hintergrundbild verwenden)
Nathan

Antworten:


200

Ich nehme an, Sie suchen eher nach einer brauchbaren integer:integerLösung mit Seitenverhältnis 16:9als nach einer float:1Lösung wie 1.77778:1.

Wenn ja, müssen Sie den größten gemeinsamen Teiler (GCD) finden und beide Werte dadurch dividieren. Die GCD ist die höchste Zahl, die beide Zahlen gleichmäßig teilt. Die GCD für 6 und 10 ist also 2, die GCD für 44 und 99 ist 11.

Ein 1024x768-Monitor hat beispielsweise eine GCD von 256. Wenn Sie beide Werte durch diese dividieren, erhalten Sie 4x3 oder 4: 3.

Ein (rekursiver) GCD-Algorithmus:

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

Und hier ist ein vollständiges HTML / Javascript, das eine Möglichkeit zeigt, die Bildschirmgröße zu ermitteln und das Seitenverhältnis daraus zu berechnen. Dies funktioniert in FF3. Ich bin mir nicht sicher, welche Unterstützung andere Browser für screen.widthund haben 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>

Es gibt aus (auf meinem seltsamen Breitbildmonitor):

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

Andere, an denen ich das getestet habe:

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

Ich wünschte, ich hätte den letzten zu Hause, aber nein, es ist leider eine Arbeitsmaschine.

Was Sie tun, wenn Sie herausfinden, dass das Seitenverhältnis von Ihrem Grafikgrößestool nicht unterstützt wird, ist eine andere Sache. Ich vermute, die beste Wahl wäre es, Letterboxing-Zeilen hinzuzufügen (wie die, die Sie oben und unten auf Ihrem alten Fernseher erhalten, wenn Sie einen Breitbildfilm darauf ansehen). Ich würde sie oben / unten oder an den Seiten hinzufügen (je nachdem, was die geringste Anzahl von Briefkastenzeilen ergibt), bis das Bild den Anforderungen entspricht.

Eine Sache, die Sie vielleicht berücksichtigen möchten, ist die Qualität eines Bildes, das von 16: 9 auf 5: 4 geändert wurde. Ich erinnere mich noch an die unglaublich großen, dünnen Cowboys, die ich in meiner Jugend im Fernsehen gesehen habe, bevor das Briefboxen eingeführt wurde. Möglicherweise ist es besser, ein anderes Bild pro Seitenverhältnis zu haben und einfach die richtige Größe für die tatsächlichen Bildschirmabmessungen zu ändern, bevor Sie es über das Kabel senden.


1
Dies war die erste Antwort, die ich geben wollte, aber ich befürchtete, dass sie keine nützlichen Ergebnisse für seine Drittanbieter-Komponente liefern würde, wenn sein Fenster beispielsweise eine Größe von etwa 1021 x 711 hat.
Nosredna

2
Scheint wie ein Overkill. Und es funktioniert nicht für Fälle, die Nosredna erwähnt hat. Ich habe eine Lösung, die auf Annäherung basiert.
Chetan S

1
Mein Kunde sagte mir, dass er das Seitenverhältnis des Betrachters benötigt. Es ist ein Service für eine Druckerei. Es ist für Statistiken, denke ich
Nathan

1
Testfall: 728x90-> 364:45bin nicht sicher, ob das das gewünschte Ergebnis ist
Dementic

@Dementic, das ist die einfachste Form der Fraktion, daher das richtige Seitenverhältnis, und 158 andere Personen (einschließlich des OP) scheinen zuzustimmen :-). Wenn Sie eine andere Vorstellung davon haben, was besser wäre, lassen Sie es mich bitte wissen und ich werde versuchen, die Antwort anzupassen.
Paxdiablo

50
aspectRatio = width / height

wenn es das ist, wonach du suchst. Sie können es dann mit einer der Dimensionen des Zielraums multiplizieren, um die andere herauszufinden (die das Verhältnis beibehält), z

widthT = heightT * aspectRatio
heightT = widthT / aspectRatio

14

Die Antwort von paxdiablo ist großartig, aber es gibt viele gängige Auflösungen, die nur wenige mehr oder weniger Pixel in einer bestimmten Richtung haben, und der größte gemeinsame Divisor-Ansatz liefert ihnen schreckliche Ergebnisse.

Nehmen wir zum Beispiel die gut erzogene Auflösung von 1360 x 765, die mit dem GCD-Ansatz ein gutes Verhältnis von 16: 9 ergibt. Laut Steam wird diese Auflösung nur von 0,01% der Benutzer verwendet, während 1366 x 768 von 18,9% verwendet wird. Mal sehen, was wir mit dem gcd-Ansatz bekommen:

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

Wir möchten dieses Verhältnis von 683: 384 auf das nächste Verhältnis von 16: 9 aufrunden.

Ich habe ein Python-Skript geschrieben, das eine Textdatei mit eingefügten Zahlen von der Steam Hardware-Umfrageseite analysiert und alle Auflösungen und nächsten bekannten Verhältnisse sowie die Häufigkeit der einzelnen Verhältnisse druckt (was mein Ziel war, als ich damit anfing):

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

Für Neugierige ist dies die Prävalenz der Bildschirmverhältnisse bei Steam-Nutzern (Stand Oktober 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

Ich denke, Sie möchten entscheiden, welches von 4: 3 und 16: 9 am besten passt.

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

1
Während Ihre Lösung für 4x3 und 16x9 in Ordnung ist, scheint dies nicht alle möglichen Seitenverhältnisse zu unterstützen (obwohl dies für das OP möglicherweise nicht wichtig ist). Das Verhältnis für die meisten Breitbildmonitore beträgt beispielsweise 16 x 10 (1920 x 1200, 1600 x 1000).
Falaina

Wir haben wirklich nicht genug Informationen, um die Frage gut zu beantworten. :-)
Nosredna

3

Hier ist eine Version von James Fareys bestem Algorithmus für rationale Approximation mit einstellbarem Grad an Unschärfe, der aus dem ursprünglich in Python geschriebenen Berechnungscode für das Seitenverhältnis auf Javascript portiert wurde .

Die Methode verwendet ein float ( width/height) und eine Obergrenze für den Bruchzähler / Nenner.

Im folgenden Beispiel setze ich eine Obergrenze von, 50weil ich 1035x582(1.77835051546) als 16:9(1.77777777778) behandeln muss, anstatt 345:194mit dem gcdin anderen Antworten aufgeführten einfachen Algorithmus.

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

Das Ergebnis:

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

3

Nur für den Fall, dass Sie ein Performance-Freak sind ...

Der schnellste Weg (in JavaScript), um ein Rechteckverhältnis zu berechnen, ist die Verwendung eines echten binären Great Common Divisor-Algorithmus.

(Alle Geschwindigkeits- und Timing-Tests wurden von anderen durchgeführt. Sie können einen Benchmark hier überprüfen: https://lemire.me/blog/2013/12/26/fastest-way-to-compute-the-greatest-common-divisor / )

Hier ist es:

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

Hier ist meine Lösung, es ist ziemlich einfach, da alles, was mich interessiert, nicht unbedingt GCD oder sogar genaue Verhältnisse sind: denn dann bekommt man seltsame Dinge wie 345/113, die für den Menschen nicht verständlich sind.

Grundsätzlich setze ich akzeptable Landschafts- oder Hochformatverhältnisse und deren "Wert" als Float ... Ich vergleiche dann meine Float-Version des Verhältnisses mit jedem und wer jemals die niedrigste absolute Wertdifferenz aufweist, ist das Verhältnis, das dem Objekt am nächsten liegt. Auf diese Weise zählt es immer noch als 16: 9, wenn der Benutzer es 16: 9 macht, dann aber 10 Pixel von der Unterseite entfernt ...

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

Ich glaube, dass das Seitenverhältnis die Breite geteilt durch die Höhe ist.

 r = w/h


1

Dieser Algorithmus in Python bringt Sie auf dem Weg dorthin.


Sag mir, was passiert, wenn die Fenster eine lustige Größe haben.

Vielleicht sollten Sie eine Liste aller akzeptablen Verhältnisse (zur Komponente eines Drittanbieters) haben. Suchen Sie dann die Übereinstimmung, die Ihrem Fenster am nächsten kommt, und geben Sie dieses Verhältnis aus der Liste zurück.


1

Als alternative Lösung zur GCD-Suche empfehle ich Ihnen, anhand einer Reihe von Standardwerten zu prüfen. Sie finden eine Liste auf Wikipedia .


1

Ich gehe davon aus, dass Sie hier über Video sprechen. In diesem Fall müssen Sie sich möglicherweise auch um das Pixel-Seitenverhältnis des Quellvideos kümmern. Beispielsweise.

PAL DV ist in einer Auflösung von 720 x 576 erhältlich. Welches würde wie sein 4: 3 aussehen. Abhängig vom Pixel-Seitenverhältnis (PAR) kann das Bildschirmverhältnis nun entweder 4: 3 oder 16: 9 betragen.

Weitere Informationen finden Sie hier http://en.wikipedia.org/wiki/Pixel_aspect_ratio

Sie können ein quadratisches Pixel-Seitenverhältnis erhalten, und viele Webvideos sind das, aber Sie möchten vielleicht auch die anderen Fälle betrachten.

Hoffe das hilft

Kennzeichen


1

Basierend auf den anderen Antworten habe ich hier die Zahlen erhalten, die ich in Python benötigt habe.

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

Ein bisschen seltsam, aber verwenden Sie die Auflösung als Aspekt. Z.B

1024: 768

oder du kannst es versuchen

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

in meinem Fall möchte ich so etwas wie

[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

Sie können jederzeit eine Nachschlagetabelle erstellen, die auf allgemeinen Seitenverhältnissen basiert. Überprüfen Sie https://en.wikipedia.org/wiki/Display_aspect_ratio. Dann können Sie einfach die Teilung durchführen

Bei Problemen im wirklichen Leben können Sie wie folgt vorgehen

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

Dann geben Sie einfach die Maße in beliebiger Reihenfolge an

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

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.