Каков алгоритм расчета соотношения сторон?

Я планирую использовать его с JavaScript, чтобы обрезать изображение, чтобы оно соответствовало всему окну.

Изменить: я буду использовать сторонний компонент, который принимает соотношение сторон только в формате, например: 4:3, 16:9.

~ 12-летнее редактирование: такой вопрос довольно интересен! Здесь что-то есть? Абсолютно!


person Nathan    schedule 27.07.2009    source источник
comment
Похоже, в этом вопросе отсутствует часть. Если вы уже знаете исходное соотношение сторон... название вопроса не имеет для меня смысла.   -  person Gishu    schedule 27.07.2009
comment
Когда вы говорите окно, вы имеете в виду экран?   -  person Nosredna    schedule 27.07.2009
comment
Собственно, мне нужно: сделать так, чтобы изображение подходило к окну, отправить через ajax соотношение сторон в базу данных.   -  person Nathan    schedule 27.07.2009
comment
Ну, окна могут быть любого причудливого размера, верно? Они могли сделать окно в основном вертикальным.   -  person Nosredna    schedule 27.07.2009
comment
Мой плохой, я имею в виду, чтобы изображение соответствовало размеру экрана. (Пользователь будет использовать его в качестве обоев)   -  person Nathan    schedule 27.07.2009
comment
Ну, вы можете получить фактические размеры экрана. Или, по крайней мере, вы можете в Firefox. Я только что видел это в DOM, когда копался в Firebug.   -  person Nosredna    schedule 27.07.2009


Ответы (17)


Насколько я понимаю, вы ищете подходящее решение соотношения сторон integer:integer, такое как 16:9, а не решение float:1, такое как 1.77778:1.

Если да, то вам нужно найти наибольший общий делитель (НОД) и разделить на него оба значения. НОД — это наибольшее число, на которое оба числа делятся без остатка. Таким образом, НОД для чисел 6 и 10 равен 2, НОД для чисел 44 и 99 равен 11.

Например, НОД монитора 1024x768 равен 256. Если разделить оба значения на это, получится 4x3 или 4:3.

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

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

И вот некоторый полный 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 — я до сих пор помню невероятно высоких, худых ковбоев, которых я смотрел в юности по телевизору до того, как был введен почтовый ящик. Возможно, вам будет лучше иметь одно другое изображение для каждого соотношения сторон и просто изменить размер правильного изображения в соответствии с фактическими размерами экрана, прежде чем отправлять его по сети.

person paxdiablo    schedule 27.07.2009
comment
Это был первый ответ, который я подумал дать, но я беспокоился, что он не вернет результаты, полезные для его стороннего компонента, если его окно имеет размер, например, 1021x711. - person Nosredna; 27.07.2009
comment
Похоже на перебор. И это не работает для случаев, упомянутых Носредной. У меня есть решение, основанное на приближении. - person Chetan S; 27.07.2009
comment
Мой клиент сказал мне, что ему нужно соотношение сторон вьювера. Это услуга для типографии. это для статистики я думаю - person Nathan; 27.07.2009
comment
тестовый пример: 728x90 -> 364:45 я не уверен, что это желаемый результат - person Rafael Herscovici; 27.06.2018
comment
@Dementic, это это самая простая форма дроби, следовательно, правильное соотношение сторон, и 158 других людей (включая OP), похоже, согласны :-). Если у вас есть другое представление о том, что было бы лучше, дайте мне знать, и я посмотрю, как скорректировать ответ. - person paxdiablo; 27.06.2018
comment
@Dementic Интересно, какое устройство будет иметь такое соотношение сторон? Ржу не могу. Но даже если и есть, разработчикам не нужно убивать себя, пытаясь угодить всем. Если мой алгоритм или логика могут работать в 80% и более раз без каких-либо проблем, что еще мне нужно? Короче говоря, я тоже поддерживаю принятый ответ. (Где эта стрелка "за"...?) - person maswerdna; 03.12.2019
comment
@maswerdna Какая разница, какое это устройство? Расчет соотношения сторон является алогритмом и не зависит от устройства. - person Rafael Herscovici; 04.12.2019
comment
Ой. Я имел в виду ваше гипотетическое устройство измерения 728x90, как будто я не видел экран с этим измерением, только рекламные баннеры. Но OP работает с устройствами, которые могут отображать полноразмерные изображения. - person maswerdna; 05.12.2019
comment
@maswerdna, я действительно видел странные разрешения на неполноэкранных виртуальных машинах, где у самого гостя размер экрана минус границы, занимаемой окном хоста. Например, экран 1920x1080 может дать гостю только 1900x972. В настоящее время мы также работаем над встроенным оборудованием, где у нас есть экран 1920x178, предназначенный для отображения динамической информации о поездке в поездах. - person paxdiablo; 06.12.2019

aspectRatio = width / height

если это то, что вам нужно. Затем вы можете умножить его на одно из измерений целевого пространства, чтобы узнать другое (которое поддерживает соотношение), например.

widthT = heightT * aspectRatio
heightT = widthT / aspectRatio
person Gishu    schedule 27.07.2009

Ответ 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%
person Petrucio    schedule 20.11.2012

Я думаю, вы хотите решить, какой из форматов 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';
}
person Chetan S    schedule 27.07.2009
comment
Хотя ваше решение подходит для 4x3 и 16x9, не похоже, что оно будет поддерживать все возможные соотношения сторон (хотя, возможно, это не важно для OP). Соотношение для большинства широкоформатных мониторов, например, 16х10 (1920х1200, 1600х1000)? - person Falaina; 27.07.2009
comment
У нас действительно недостаточно информации, чтобы хорошо ответить на вопрос. :-) - person Nosredna; 27.07.2009

Лучший алгоритм рациональной аппроксимации Джеймса Фейри с регулируемым уровнем нечеткости, перенесенный на Javascript из кода расчета соотношения сторон изначально написан на питоне.

Метод принимает число с плавающей запятой (width/height) и верхний предел для числителя/знаменателя дроби.

В приведенном ниже примере я устанавливаю верхний предел 50, потому что мне нужно, чтобы 1035x582 (1,77835051546) рассматривалось как 16:9 (1,77777777778), а не 345:194, которое вы получаете с помощью простого алгоритма gcd, указанного в других ответах.

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

console.log(aspect_ratio(801/600, 50));
console.log(aspect_ratio(1035/582, 50));
console.log(aspect_ratio(2560/1441, 50));

person ccpizza    schedule 25.03.2017

На всякий случай, если вы помешаны на производительности...

Самый быстрый способ (в JavaScript) вычислить отношение прямоугольников — использовать настоящий двоичный алгоритм Великого общего делителя.

(Все тесты скорости и времени были выполнены другими, вы можете проверить один тест здесь: https://lemire.me/blog/2013/12/26/fastest-way-to-compute-the-наибольший-общий-делитель/ )

Вот:

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

person Arthur da Paz    schedule 09.05.2018

Вот мое решение, оно довольно простое, поскольку все, что меня волнует, это не обязательно НОД или даже точные отношения: потому что тогда вы получаете странные вещи, такие как 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
person Zargold    schedule 16.03.2017

Вы всегда можете начать с создания справочной таблицы на основе распространенных соотношений сторон. Проверьте 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
person Pithikos    schedule 01.05.2020
comment
Это должен быть ответ, спасибо! - person wtsiamruk; 03.03.2021

В качестве альтернативы поиску НОД я предлагаю вам свериться с набором стандартных значений. Список можно найти в Википедии.

person mouviciel    schedule 27.07.2009

Я предполагаю, что вы говорите здесь о видео, и в этом случае вам также может понадобиться беспокоиться о соотношении сторон пикселя исходного видео. Например.

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

Для получения дополнительной информации см. здесь http://en.wikipedia.org/wiki/Pixel_aspect_ratio.

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

Надеюсь это поможет

отметка

person Mark Lakewood    schedule 27.07.2009

Основываясь на других ответах, вот как я получил нужные мне числа в 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')}
person sleepycal    schedule 04.06.2013

Я думаю, что это делает то, о чем вы просите:

webdeveloper.com — десятичная дробь

Ширина/высота дает вам десятичную дробь, преобразованную в дробь с помощью «:» вместо «/», дает вам «отношение».

person Tobias Cohen    schedule 27.07.2009

Этот алгоритм на Python частично поможет вам в этом.


Скажи мне, что произойдет, если окна смешного размера.

Возможно, у вас должен быть список всех приемлемых соотношений (для стороннего компонента). Затем найдите ближайшее соответствие вашему окну и верните это соотношение из списка.

person Nosredna    schedule 27.07.2009

немного странный способ сделать это, но использовать разрешение в качестве аспекта. НАПРИМЕР.

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);
  }
}
person Aron    schedule 17.04.2012

в моем случае я хочу что-то вроде

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

person Renish Gotecha    schedule 05.09.2019

Я считаю, что соотношение сторон - это ширина, деленная на высоту.

 r = w/h
person Sean A.O. Harney    schedule 27.07.2009

person    schedule
comment
Я буду использовать компонент, который принимает только соотношение сторон, например: 4:3, 16:9. - person Nathan; 27.07.2009