Apa algoritma untuk menghitung rasio aspek?


89

Saya berencana menggunakannya dengan JavaScript untuk memotong gambar agar sesuai dengan seluruh jendela.

Edit : Saya akan menggunakan komponen pihak ke-3 yang hanya menerima rasio aspek dalam format seperti: 4:3, 16:9.


Sepertinya ada bagian yang hilang dalam pertanyaan ini. Jika Anda sudah mengetahui rasio aspek sumber .. judul q tidak masuk akal bagi saya.
Gishu

Saat Anda mengucapkan "jendela", yang Anda maksud adalah "layar"?
Nosredna

Sebenarnya, saya perlu: buat gambar sesuai dengan jendela, kirim melalui ajax rasio aspek ke database.
Nathan

Nah, jendela bisa berukuran apapun yang funky, bukan? Mereka bisa membuat jendela sebagian besar vertikal.
Nosredna

Saya buruk, maksud saya membuat gambar sesuai dengan layar. (Pengguna akan menggunakannya sebagai wallpaper)
Nathan

Jawaban:


204

Saya rasa Anda sedang mencari integer:integersolusi rasio aspek yang dapat digunakan seperti 16:9daripada float:1solusi seperti 1.77778:1.

Jika ya, yang perlu Anda lakukan adalah mencari pembagi persekutuan terbesar (PBT) dan membagi kedua nilai tersebut dengan itu. PBT adalah angka tertinggi yang membagi kedua angka secara merata. Jadi PBT untuk 6 dan 10 adalah 2, PBT 44 dan 99 adalah 11.

Misalnya, monitor 1024x768 memiliki GCD 256. Jika Anda membagi kedua nilai tersebut, Anda akan mendapatkan 4x3 atau 4: 3.

Algoritme GCD (rekursif):

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

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

Dan inilah beberapa HTML / Javascript lengkap yang menunjukkan satu cara untuk mendeteksi ukuran layar dan menghitung rasio aspek dari itu. Ini berfungsi di FF3, saya tidak yakin dukungan apa yang dimiliki browser lain screen.widthdan 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>

Ini menghasilkan (pada monitor layar lebar aneh saya):

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

Orang lain yang saya uji ini:

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

Saya berharap saya memiliki yang terakhir di rumah tetapi, tidak, sayangnya ini adalah mesin kerja.

Apa yang Anda lakukan jika Anda menemukan rasio aspek tidak didukung oleh alat pengubah ukuran grafik adalah masalah lain. Saya curiga cara terbaik adalah menambahkan baris letter-boxing (seperti yang Anda dapatkan di bagian atas dan bawah TV lama Anda saat Anda menonton film layar lebar di atasnya). Saya akan menambahkannya di bagian atas / bawah atau samping (mana saja yang menghasilkan paling sedikit garis kotak surat) sampai gambar memenuhi persyaratan.

Satu hal yang mungkin ingin Anda pertimbangkan adalah kualitas gambar yang telah diubah dari 16: 9 menjadi 5: 4 - Saya masih ingat koboi kurus dan tinggi yang biasa saya tonton di masa muda saya di televisi sebelum letter-boxing diperkenalkan. Anda mungkin lebih baik memiliki satu gambar berbeda per rasio aspek dan hanya mengubah ukuran yang benar untuk dimensi layar yang sebenarnya sebelum mengirimkannya ke kabel.


1
Ini adalah jawaban pertama yang saya pikirkan, tetapi saya khawatir itu tidak akan mengembalikan hasil yang berguna untuk komponen pihak ketiganya jika jendelanya berukuran seperti 1021x711, misalnya.
Nosredna

2
Sepertinya berlebihan. Dan itu tidak berhasil untuk kasus yang disebutkan Nosredna. Saya punya solusi berdasarkan perkiraan.
Chetan S

1
Klien saya memberi tahu saya bahwa dia membutuhkan rasio aspek penonton. Ini adalah layanan untuk toko cetak. Ini untuk statistik saya pikir
Nathan

1
kasus uji: 728x90-> 364:45saya tidak yakin itu adalah hasil yang diinginkan
Rafael Herscovici

@Dementic, itu adalah bentuk pecahan yang paling sederhana, oleh karena itu rasio aspek yang benar, dan 158 orang lainnya (termasuk OP) tampaknya setuju :-). Jika Anda memiliki ide lain tentang apa yang akan lebih baik, beri tahu saya dan saya akan melihat menyesuaikan jawabannya.
paxdiablo

56
aspectRatio = width / height

jika itu yang Anda cari. Anda kemudian dapat mengalikannya dengan salah satu dimensi ruang target untuk mengetahui yang lain (yang mempertahankan rasio), mis.

widthT = heightT * aspectRatio
heightT = widthT / aspectRatio

13

Jawaban paxdiablo sangat bagus, tetapi ada banyak resolusi umum yang hanya memiliki sedikit lebih banyak atau lebih sedikit piksel ke arah tertentu, dan pendekatan pembagi umum terbesar memberikan hasil yang mengerikan bagi mereka.

Ambil contoh resolusi berperilaku baik 1360x765 yang memberikan rasio 16: 9 yang bagus menggunakan pendekatan gcd. Menurut Steam, resolusi ini hanya digunakan oleh 0,01% penggunanya, sementara 1366x768 digunakan oleh 18,9%. Mari kita lihat apa yang kita dapatkan menggunakan pendekatan gcd:

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

Kami ingin membulatkan rasio 683: 384 ke rasio terdekat, 16: 9.

Saya menulis skrip python yang mem-parsing file teks dengan nomor yang ditempel dari halaman survei Perangkat Keras Steam, dan mencetak semua resolusi dan rasio terdekat yang diketahui, serta prevalensi setiap rasio (yang merupakan tujuan saya ketika saya memulai ini):

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

Yang penasaran, berikut adalah prevalensi rasio layar di antara pengguna Steam (per 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

Saya rasa Anda ingin memutuskan mana dari 4: 3 dan 16: 9 yang paling cocok.

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

1
Meskipun solusi Anda baik-baik saja untuk 4x3 dan 16x9, ini sepertinya tidak mendukung semua rasio aspek yang mungkin (meskipun mungkin itu tidak penting untuk OP). Rasio untuk kebanyakan monitor layar lebar, misalnya, adalah 16x10 (1920x1200, 1600x1000)?
Falaina

Kami benar-benar tidak memiliki cukup informasi untuk menjawab pertanyaan dengan baik. :-)
Nosredna

5

Berikut adalah versi algoritma pendekatan rasional terbaik James Farey dengan tingkat ketidakjelasan yang dapat disesuaikan yang ditransfer ke javascript dari kode penghitungan rasio aspek aslinya ditulis dengan python.

Metode ini menggunakan float ( width/height) dan batas atas untuk pembilang / penyebut pecahan.

Dalam contoh di bawah ini saya menetapkan batas atas 50karena saya perlu 1035x582(1.77835051546) diperlakukan sebagai 16:9(1.77777777778) daripada 345:194yang Anda dapatkan dengan polosgcd algoritma yang tercantum dalam jawaban lain.

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

Hasil:

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

3

Kalau-kalau Anda seorang performance freak ...

Cara tercepat (dalam JavaScript) untuk menghitung rasio persegi panjang adalah menggunakan algoritma biner Great Common Divisor sejati.

(Semua tes kecepatan dan waktu telah dilakukan oleh orang lain, Anda dapat memeriksa satu benchmark di sini: https://lemire.me/blog/2013/12/26/fastest-way-to-compute-the-greatest-common-divisor / )

Ini dia:

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

Ini solusi saya. Ini cukup mudah karena yang saya pedulikan belum tentu GCD atau bahkan rasio yang akurat: karena Anda mendapatkan hal-hal aneh seperti 345/113 yang tidak dapat dipahami manusia.

Saya pada dasarnya menetapkan lanskap yang dapat diterima, atau rasio potret dan "nilai" mereka sebagai pelampung ... Saya kemudian membandingkan versi pelampung rasio untuk masing-masing dan yang pernah memiliki perbedaan nilai absolut terendah adalah rasio yang paling dekat dengan item. Dengan cara itu ketika pengguna membuatnya 16: 9 tetapi kemudian menghapus 10 piksel dari bawah masih dihitung sebagai 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

Sebagai solusi alternatif untuk pencarian GCD, saya sarankan Anda untuk memeriksa sekumpulan nilai standar. Anda dapat menemukan daftarnya di Wikipedia .


1

Saya berasumsi Anda berbicara tentang video di sini, dalam hal ini Anda mungkin juga perlu khawatir tentang rasio aspek piksel dari video sumber. Sebagai contoh.

PAL DV hadir dalam resolusi 720x576. Yang akan terlihat seperti 4: 3. Sekarang tergantung pada rasio aspek Pixel (PAR), rasio layar bisa menjadi 4: 3 atau 16: 9.

Untuk info lebih lanjut lihat di sini http://en.wikipedia.org/wiki/Pixel_aspect_ratio

Anda bisa mendapatkan Rasio Aspek piksel Persegi, dan banyak video web adalah itu, tetapi Anda mungkin ingin berhati-hati dari kasus lain.

Semoga ini membantu

Menandai


1

Berdasarkan jawaban lain, berikut adalah cara saya mendapatkan angka yang saya butuhkan dengan 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

Algoritme dengan Python ini membantu Anda mencapai tujuan tersebut.


Ceritakan apa yang terjadi jika jendelanya berukuran lucu.

Mungkin yang harus Anda miliki adalah daftar semua rasio yang dapat diterima (untuk komponen pihak ke-3). Kemudian, temukan yang paling cocok dengan jendela Anda dan kembalikan rasio itu dari daftar.


0

sedikit cara yang aneh untuk melakukan ini tetapi menggunakan resolusi sebagai aspeknya. MISALNYA

1024: 768

atau kamu bisa mencoba

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

dalam kasus saya, saya ingin sesuatu seperti

[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

Anda selalu dapat memulai dengan membuat tabel pencarian berdasarkan rasio aspek umum. Periksa https://en.wikipedia.org/wiki/Display_aspect_ratio Kemudian Anda cukup melakukan pembagian

Untuk masalah kehidupan nyata, Anda bisa melakukan hal seperti di bawah ini

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

Kemudian Anda cukup memberikan dimensi dalam urutan apa pun

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

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.