Який алгоритм обчислення пропорції?


87

Я планую використовувати його з JavaScript для обрізання зображення відповідно до всього вікна.

Редагувати : я буду використовувати сторонній компонент, який приймає співвідношення сторін лише у такому форматі як: 4:3, 16:9.


Схоже, у цьому питанні відсутній шматок. Якщо ви вже знаєте співвідношення сторін джерела ... назва q для мене не має сенсу.
Gishu

Коли ви говорите "вікно", ви маєте на увазі "екран?"
Носредна

Власне, мені потрібно: зробити зображення відповідно до вікна, надіслати через ajax пропорцію до бази даних.
Натан,

Ну, вікна можуть бути будь-якого забавного розміру, так? Вони могли зробити вікно переважно вертикальним.
Носредна

Погано, я маю на увазі зробити зображення відповідним екрану. (Користувач використовуватиме його як шпалери)
Натан,

Відповіді:


203

Мені здається, ви шукаєте корисне integer:integerрішення зі співвідношенням сторін, 16:9а не таке float:1рішення 1.77778:1.

Якщо так, то вам потрібно знайти найбільший спільний дільник (GCD) і розділити обидва значення на це. GCD - це найбільше число, яке рівномірно ділить обидва числа. Отже, GCD для 6 і 10 дорівнює 2, GCD для 44 і 99 дорівнює 11.

Наприклад, монітор 1024x768 має GCD 256. Коли ви ділите обидва значення, ви отримуєте 4x3 або 4: 3.

(Рекурсивний) алгоритм GCD:

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

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

І ось деякий повний HTML / Javascript, який показує один із способів виявити розмір екрану та розрахувати співвідношення сторін з нього. Це працює в FF3, я не впевнений, яку підтримку підтримують інші браузери screen.widthта 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>

Він виводить (на моєму дивному широкоформатному моніторі):

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

Інші, на яких я тестував це:

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

Я хотів би, щоб у мене був останній вдома, але, ні, це робоча машина, на жаль.

Інша справа, що ви робите, якщо виявите, що формат зображення не підтримується вашим графічним інструментом зміни розміру. Я підозрюю, що найкращим варіантом було б додати рядки для поштового боксу (наприклад, ті, які ви отримуєте вгорі та внизу вашого старого телевізора, коли ви дивитесь на ньому широкоформатний фільм). Я додав би їх зверху / знизу або з боків (залежно від того, який із них призводить до найменшої кількості ліній для поштових ящиків), поки зображення не відповідає вимогам.

Одне, що ви можете розглянути, - це якість картинки, яку було змінено з 16: 9 на 5: 4 - Я все ще пам’ятаю неймовірно високих худорлявих ковбоїв, яких я дивився в молодості на телебаченні до того, як був представлений бокс для листів. Можливо, вам краще мати одне інше зображення на співвідношення сторін і просто змінити розмір правильного для фактичних розмірів екрана, перш ніж відправити його по дроту.


1
Це була перша відповідь, яку я думав дати, але я переживав, що це не поверне корисних результатів для його сторонніх компонентів, якщо його вікно розміром приблизно до 1021x711, наприклад.
Nosredna

2
Здається надмірним. І це не працює для випадків, про які згадала Носредна. У мене є рішення на основі наближення.
Chetan S

1
Мій клієнт сказав мені, що йому потрібно співвідношення сторін глядача. Це послуга для друкарні. Це для статистики, я думаю
Натан

1
тестовий випадок: 728x90-> 364:45я не впевнений, що це
бажаний

@Dementic, це найпростіша форма дробу, отже, правильне співвідношення сторін, і 158 інших людей (включаючи ОП), схоже, погоджуються :-). Якщо у вас є якесь інше уявлення про те, що було б краще, повідомте мені, і я подивлюсь на коригування відповіді.
paxdiablo

56
aspectRatio = width / height

якщо це те, що ви шукаєте. Потім ви можете помножити його на один із вимірів цільового простору, щоб з’ясувати інший (який підтримує співвідношення), наприклад

widthT = heightT * aspectRatio
heightT = widthT / aspectRatio

13

Відповідь paxdiablo чудова, але є багато загальних резолюцій, які мають лише кілька більш-менш пікселів у певному напрямку, і найбільший загальний підхід до дільників дає їм жахливі результати.

Візьмемо для прикладу добре виховану роздільну здатність 1360x765, яка дає приємне співвідношення 16: 9, використовуючи підхід gcd. За даними Steam, цю роздільну здатність використовують лише 0,01% її користувачів, тоді як 1366x768 використовують колосальні 18,9%. Давайте подивимося, що ми отримуємо, використовуючи підхід gcd:

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

Ми хотіли б округлити це співвідношення 683: 384 до найближчого, співвідношення 16: 9.

Я написав сценарій python, який аналізує текстовий файл із вставленими цифрами зі сторінки опитування апаратного забезпечення Steam, і друкує всі роздільні здатності та найближчі відомі коефіцієнти, а також поширеність кожного коефіцієнта (що було моєю метою, коли я починав це):

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

Для цікавих, це поширеність коефіцієнтів екрану серед користувачів Steam (станом на жовтень 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

Я думаю, ви хочете вирішити, який з 4: 3 та 16: 9 найкраще підходить.

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

1
Хоча ваше рішення підходить для 4x3 та 16x9, це, здається, не підтримує всіх можливих співвідношень сторін (хоча, можливо, це не важливо для OP). Наприклад, співвідношення для більшості широкоформатних моніторів становить 16x10 (1920x1200, 1600x1000)?
Falaina

У нас дійсно недостатньо інформації, щоб добре відповісти на питання. :-)
Носредна

4

Ось версія найкращого раціонального алгоритму апроксимації Джеймса Фарі з регульованим рівнем нечіткості, перенесеним на javascript із коду розрахунку співвідношення сторін, спочатку написаного на python.

Метод бере float ( width/height) і верхню межу для чисельника знаменника / знаменника.

У наведеному нижче прикладі я встановлюю верхню межу, 50оскільки мені потрібно 1035x582(1.77835051546) розглядати як 16:9(1.77777777778), а не як 345:194ви отримуєте із звичайним gcdалгоритмом, переліченим в інших відповідях.

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

Результат:

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

3

Про всяк випадок, коли ти виродник продуктивності ...

Найшвидший спосіб (у JavaScript) обчислити співвідношення прямокутників за допомогою справжнього двійкового алгоритму Великого спільного дільника.

(Усі тести швидкості та синхронізації проводили інші, ви можете перевірити один еталон тут: https://lemire.me/blog/2013/12/26/fastest-way-to-compute-the-greatest-common-divisor / )

Ось воно:

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

Ось моє рішення - це досить прямо, оскільки все, що мене цікавить, - це не обов’язково GCD чи навіть точні співвідношення: адже тоді ви отримуєте дивні речі, такі як 345/113, які не зрозумілі людиною.

В основному я встановлюю прийнятні ландшафтні або портретні співвідношення та їх "значення" як плаваючі ... Потім я порівнюю свою плаваючу версію коефіцієнта з кожною, і яка коли-небудь має найнижчу абсолютну різницю значень, це співвідношення, найближче до елемента. Таким чином, коли користувач робить його 16: 9, але потім видаляє 10 пікселів знизу, він все одно вважається 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

Як альтернативне рішення для пошуку GCD, я пропоную вам перевірити набір стандартних значень. Ви можете знайти список у Вікіпедії .


1

Я припускаю, що ви тут говорите про відео, і в цьому випадку вам також може знадобитися піклуватися про пропорцію пікселів вихідного відео. Наприклад.

PAL DV поставляється в роздільній здатності 720x576. Що виглядало б як його 4: 3. Тепер, залежно від пропорції пікселів (PAR), співвідношення екрану може становити 4: 3 або 16: 9.

Для отримання додаткової інформації загляньте тут http://en.wikipedia.org/wiki/Pixel_aspect_ratio

Ви можете отримати співвідношення сторін квадратних пікселів, і це багато веб-відео, але ви, можливо, захочете остерігатися інших випадків.

Сподіваюся, це допомагає

Марка


1

Виходячи з інших відповідей, ось як я отримав потрібні мені числа в 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

Цей алгоритм на Python доставить вам частину шляху.


Скажіть, що станеться, якщо вікна забавного розміру.

Можливо, те, що ви повинні мати, це перелік усіх прийнятних співвідношень (до сторонніх компонентів). Потім знайдіть найближчу відповідність до вашого вікна та поверніть це співвідношення зі списку.


0

трохи дивний спосіб зробити це, але використовувати роздільну здатність як аспект. ЕГ

1024: 768

або ви можете спробувати

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

у моєму випадку я хочу щось подібне

[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

Ви завжди можете почати із створення таблиці пошуку на основі загальних пропорцій. Перевірте https://en.wikipedia.org/wiki/Display_aspect_ratio Тоді ви можете просто зробити поділ

Для реальних життєвих проблем ви можете зробити щось подібне нижче

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

Тоді ви просто вказуєте розміри в будь-якому порядку

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.