En boy oranını hesaplayan algoritma nedir?


89

Bir görüntüyü tüm pencereye sığacak şekilde kırpmak için JavaScript ile kullanmayı planlıyorum.

Düzenleme : Yalnızca en boy oranını şu biçimde kabul eden bir 3. taraf bileşeni kullanacağım: 4:3, 16:9.


Görünüşe göre bu soruda eksik bir parça var. Kaynak en boy oranını zaten biliyorsanız ... q'nun başlığı bana mantıklı gelmiyor.
Gishu

"Pencere" derken, "ekran" mı demek istiyorsun?
Nosredna

Aslında ihtiyacım olan şey: görüntüyü pencereye sığdırmak, ajax aracılığıyla veritabanına en boy oranını göndermek.
Nathan

Peki, pencereler herhangi bir korkak boyutta olabilir, değil mi? Pencereyi çoğunlukla dikey hale getirebilirler.
Nosredna

Benim hatam, yani görüntüyü ekrana sığdırmak. (Kullanıcı bunu duvar kağıdı olarak kullanacak)
Nathan

Yanıtlar:


204

Anladığım kadarıyla bir çözümden çok kullanışlı bir en boy oranı integer:integerçözümü arıyorsunuz .16:9float:11.77778:1

Öyleyse, yapmanız gereken en büyük ortak böleni (OBEB) bulmak ve her iki değeri de buna bölmektir. OBEB, her iki sayıyı da eşit olarak bölen en yüksek sayıdır. Yani 6 ve 10 için OBEB 2, 44 ve 99 için OBEB 11'dir.

Örneğin, 1024x768'lik bir monitörün GCD'si 256'dır. Her iki değeri de buna böldüğünüzde 4x3 veya 4: 3 elde edersiniz.

A (yinelemeli) GCD algoritması:

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

C'de:

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

Ve işte, ekran boyutunu algılamanın ve bundan en boy oranını hesaplamanın bir yolunu gösteren bazı eksiksiz HTML / Javascript. FF3 Bu eserler, ben diğer tarayıcıları destekleyen için ne emin değilim screen.widthve 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>

Çıktılar (garip geniş ekran monitörümde):

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

Bunu test ettiğim diğerleri:

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

Keşke sonuncusu evde olsaydı ama hayır, maalesef bu bir iş makinesi.

En boy oranının grafiği yeniden boyutlandırma aracınız tarafından desteklenmediğini fark ederseniz ne yapacağınız başka bir konudur. Sanırım en iyi bahis, mektup kutusu satırları eklemek olacaktır (eski TV'nizin üstünde ve altında geniş ekranlı bir film izlerken gördüğünüz gibi). Resim gereksinimleri karşılayana kadar bunları üst / alt veya yan taraflara (hangisi en az mektup kutusu satırıyla sonuçlanırsa) eklerdim.

Göz önünde bulundurmak isteyebileceğiniz bir şey, 16: 9'dan 5: 4'e değiştirilmiş bir resmin kalitesidir - gençliğimde mektup boksunun tanıtılmasından önce televizyonda izlediğim inanılmaz derecede uzun, ince kovboyları hala hatırlıyorum. En boy oranı başına bir farklı görüntüye sahip olmanız daha iyi olabilir ve kabloyu göndermeden önce gerçek ekran boyutları için doğru olanı yeniden boyutlandırabilirsiniz.


1
Vermeyi düşündüğüm ilk cevap buydu, ancak örneğin penceresi 1021x711 gibi boyutlandırılırsa 3. taraf bileşeni için yararlı sonuçlar döndürmeyeceğinden endişeleniyordum.
Nosredna

2
Aşırılık gibi görünüyor. Ve Nosredna'nın bahsettiği durumlarda işe yaramıyor. Yaklaşıma dayalı bir çözümüm var.
Chetan S

1
Müvekkilim bana izleyicinin en boy oranına ihtiyacı olduğunu söyledi. Bir matbaa hizmeti. İstatistikler için bence
Nathan

1
test vakası: 728x90-> 364:45istenen sonucun bu olduğundan emin değilim
Rafael Herscovici

@Dementic, bir fraksiyonu, dolayısıyla doğru en boy oranı ve (OP dahil) 158 başkalarıyla en basit biçimi :-) kabul görünmektedir. Neyin daha iyi olacağına dair başka bir fikriniz varsa , lütfen bana bildirin, ben de cevabı ayarlamaya bakayım.
paxdiablo

56
aspectRatio = width / height

eğer peşinde olduğun buysa. Daha sonra diğerini (oranı koruyan) bulmak için onu hedef alanın boyutlarından biriyle çarpabilirsiniz.

widthT = heightT * aspectRatio
heightT = widthT / aspectRatio

13

paxdiablo'nun cevabı harika, ancak belirli bir yönde az veya çok piksele sahip birçok ortak çözünürlük var ve en büyük ortak bölme yaklaşımı onlara korkunç sonuçlar veriyor.

Örneğin, gcd yaklaşımını kullanarak güzel bir 16: 9 oranı veren 1360x765'lik iyi davranılmış çözünürlüğü ele alalım. Steam'e göre, bu çözünürlük kullanıcılarının yalnızca% 0,01'i tarafından kullanılırken, 1366x768% 18,9'u tarafından kullanılıyor. Gcd yaklaşımını kullanarak ne elde ettiğimize bakalım:

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

Bu 683: 384 oranını en yakın 16: 9 oranına yuvarlamak isterdik.

Steam Donanım anket sayfasından yapıştırılan sayılarla bir metin dosyasını ayrıştıran ve tüm çözünürlükleri ve bilinen en yakın oranları ve her oranın yaygınlığını (bunu başlattığımda amacım buydu) yazdıran bir python betiği yazdım:

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

Merak edenler için, Steam kullanıcıları arasında ekran oranlarının yaygınlığı (Ekim 2012 itibariyle):

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

Sanırım 4: 3 ve 16: 9'dan hangisinin en uygun olduğuna karar vermek istiyorsunuz.

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

1
Çözümünüz 4x3 ve 16x9 için iyi olsa da, bu olası tüm en boy oranlarını destekleyecek gibi görünmüyor (belki de bu OP için önemli değil). Örneğin, çoğu geniş ekran monitörün oranı 16x10 (1920x1200, 1600x1000)?
Falaina

Soruyu iyi cevaplamak için yeterli bilgiye sahip değiliz. :-)
Nosredna

5

James Farey'in en iyi rasyonel yaklaşım algoritmasının, orijinal olarak python'da yazılmış en boy oranı hesaplama kodundan javascript'e taşınan ayarlanabilir bulanıklık seviyesine sahip bir versiyonu .

Yöntem bir float ( width/height) ve kesir payı / payda için bir üst limit alır .

Aşağıdaki örnekte , diğer yanıtlarda listelenen düz algoritma ile elde ettiğinizden ziyade (1.77835051546) ' nın (1.77777777778) olarak ele alınmasına 50ihtiyacım olduğu için bir üst sınır 1035x582ayarlıyorum .16:9345:194gcd

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

Sonuç:

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

3

Performans manyağı olmanız durumunda ...

Dikdörtgen oranını hesaplamanın en hızlı yolu (JavaScript'te), gerçek bir ikili Büyük Ortak Bölen algoritması kullanmaktır.

(Tüm hız ve zamanlama testleri başkaları tarafından yapılmıştır, burada bir karşılaştırmayı kontrol edebilirsiniz: https://lemire.me/blog/2013/12/26/fastest-way-to-compute-the-greatest-common-divisor / )

İşte burada:

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

İşte benim çözümüm oldukça açık, çünkü tek umursadığım GCD veya hatta doğru oranlar değil: çünkü o zaman 345/113 gibi insan tarafından anlaşılamayan garip şeyler elde edersiniz.

Temel olarak kabul edilebilir manzara veya portre oranlarını ve bunların "değerini" kayan nokta olarak ayarlıyorum ... Daha sonra oranın kayan versiyonunu her biri ile karşılaştırıyorum ve en düşük mutlak değer farkına sahip olan, öğeye en yakın orandır. Bu şekilde, kullanıcı bunu 16: 9 yapıp daha sonra alttan 10 pikseli kaldırdığında, yine de 16: 9 olarak sayılır ...

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

GCD aramasına alternatif bir çözüm olarak, bir dizi standart değeri kontrol etmenizi öneririm. Wikipedia'da bir liste bulabilirsiniz .


1

Burada video hakkında konuştuğunuzu varsayıyorum, bu durumda kaynak videonun piksel en boy oranı hakkında da endişelenmeniz gerekebilir. Örneğin.

PAL DV 720x576 çözünürlükte gelir. Hangisi 4: 3 gibi görünür. Artık Piksel en boy oranına (PAR) bağlı olarak ekran oranı 4: 3 veya 16: 9 olabilir.

Daha fazla bilgi için buraya bakın http://en.wikipedia.org/wiki/Pixel_aspect_ratio

Kare piksel Boyut Oranı elde edebilirsiniz ve birçok web videosu budur, ancak diğer durumları izlemek isteyebilirsiniz.

Bu yardımcı olur umarım

işaret


1

Diğer cevaplara göre, işte ihtiyacım olan sayıları Python'da nasıl elde ettim;

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

En boy oranının genişliğin yüksekliğe bölünmesi olduğuna inanıyorum.

 r = w/h


0

Python'daki bu algoritma sizi oradaki yolun bir parçası haline getiriyor.


Pencereler komik bir boyuttaysa ne olacağını söyle.

Belki de sahip olmanız gereken, tüm kabul edilebilir oranların bir listesidir (3. taraf bileşenine göre). Ardından, pencerenize en yakın eşleşmeyi bulun ve bu oranı listeden döndürün.


0

Bunu yapmanın biraz garip bir yolu ama çözünürlüğü en boy oranı olarak kullanın. ÖRNEĞİN

1024: 768

ya da deneyebilirsin

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

benim durumumda bir şey istiyorum

[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

Her zaman ortak en boy oranlarına göre bir arama tablosu oluşturarak başlayabilirsiniz. Https://en.wikipedia.org/wiki/Display_aspect_ratio adresini kontrol edin O zaman bölmeyi basitçe yapabilirsiniz

Gerçek hayat problemleri için aşağıdaki gibi bir şey yapabilirsiniz

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

O zaman boyutları herhangi bir sırayla verirsiniz

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

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.