【问题标题】:What's the algorithm to calculate aspect ratio?计算纵横比的算法是什么?
【发布时间】:2010-11-14 06:11:59
【问题描述】:

我打算将它与 JavaScript 一起使用来裁剪图像以适应整个窗口。

编辑:我将使用仅接受以下格式的纵横比的第 3 方组件:4:316:9

~12岁编辑:这种问题比较有趣!这里有东西对吧? Absolutely!

【问题讨论】:

  • 这个问题似乎缺少了一部分。如果您已经知道源纵横比.. q 的标题对我来说没有意义。
  • 当您说“窗口”时,您的意思是“屏幕”吗?
  • 其实我需要:让图片适合窗口,通过ajax发送长宽比到数据库。
  • 好吧,窗户可以是任何时髦的尺寸,对吧?他们可以使窗口大部分是垂直的。
  • 我的错,我的意思是让图像适合屏幕。 (用户将其用作壁纸)

标签: javascript algorithm math crop aspect-ratio


【解决方案1】:

我猜您正在寻找可用的纵横比 integer:integer 解决方案,例如 16:9,而不是 float:1 解决方案,例如 1.77778:1

如果是这样,您需要做的是找到最大公约数 (GCD) 并将两个值除以它。 GCD 是两个数均分的最高数。所以 6 和 10 的 GCD 是 2, 44 和 99 的 GCD 是 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.widthscreen.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 - 我仍然记得我年轻时在电视上看到的令人难以置信的高瘦牛仔被介绍了。您最好为每个纵横比制作一张不同的图片,然后在发送之前根据实际屏幕尺寸调整正确的图片大小。

【讨论】:

  • 这是我想到的第一个答案,但我担心如果他的窗口大小设置为 1021x711 之类的东西,它不会返回对他的第 3 方组件有用的结果。跨度>
  • 看起来有点矫枉过正。它不适用于 Nosredna 提到的案例。我有一个基于近似的解决方案。
  • 我的客户告诉我他需要查看器的纵横比。它是为印刷店提供的服务。我认为它用于统计
  • 测试用例:728x90 -> 364:45 我不确定这是想要的结果
  • @Dementic,分数的最简单形式,因此是正确的纵横比,其他 158 人(包括 OP)似乎同意 :-)。如果您对什么会更好有其他的想法,请告诉我,我会考虑调整答案。
【解决方案2】:
aspectRatio = width / height

如果那是你所追求的。然后,您可以将其乘以目标空间的维度之一以找出另一个(保持比率) 例如

widthT = heightT * aspectRatio
heightT = widthT / aspectRatio

【讨论】:

    【解决方案3】:

    paxdiablo 的答案很棒,但是有很多常见的分辨率在给定方向上只有几个或多或少的像素,而最大公约数方法会给它们带来可怕的结果。

    以表现良好的 1360x765 分辨率为例,它使用 gcd 方法提供了很好的 16:9 比例。根据 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 年 10 月):

    16:9 -> 58.9%
    16:10 -> 24.0%
    5:4 -> 9.57%
    4:3 -> 6.38%
    5:3 -> 0.84%
    17:9 -> 0.11%
    

    【讨论】:

      【解决方案4】:

      我猜你想决定 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';
      }
      

      【讨论】:

      • 虽然您的解决方案适用于 4x3 和 16x9,但这似乎并不支持所有可能的纵横比(尽管这对 OP 来说可能并不重要)。例如,大多数宽屏显示器的比例是 16x10(1920x1200、1600x1000)?
      • 我们确实没有足够的信息来很好地回答这个问题。 :-)
      【解决方案5】:

      James Farey 的最佳有理逼近算法,具有可调节的模糊度,从最初用 python 编写的 aspect ratio calculation code 移植到 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));

      【讨论】:

        【解决方案6】:

        以防万一你是个表演狂……

        使用真正的二进制大公约数算法来计算矩形比率的最快方法(在 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]
        */

        【讨论】:

          【解决方案7】:

          这是我的解决方案,它非常简单,因为我所关心的不一定是 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
          

          【讨论】:

            【解决方案8】:

            您始终可以从根据常见纵横比制作查找表开始。检查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
            

            【讨论】:

              【解决方案9】:

              作为 GCD 搜索的替代解决方案,我建议您检查一组标准值。你可以在Wikipedia找到一个列表。

              【讨论】:

                【解决方案10】:

                我假设您在这里谈论视频,在这种情况下,您可能还需要担心源视频的像素纵横比。例如。

                PAL DV 的分辨率为 720x576。这看起来像它的 4:3。现在根据像素纵横比 (PAR),屏幕比例可以是 4:3 或 16:9。

                更多信息请看这里http://en.wikipedia.org/wiki/Pixel_aspect_ratio

                您可以获得方形像素纵横比,很多网络视频都是这样,但您可能希望注意其他情况。

                希望对你有帮助

                标记

                【讨论】:

                  【解决方案11】:

                  根据其他答案,这是我在 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')}
                  

                  【讨论】:

                    【解决方案12】:
                    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);
                    

                    【讨论】:

                      【解决方案13】:

                      我认为这可以满足您的要求:

                      webdeveloper.com - decimal to fraction

                      宽度/高度得到一个小数,用“:”代替“/”转换为分数,得到一个“比率”。

                      【讨论】:

                        【解决方案14】:

                        This algorithm in Python 助您一臂之力。


                        告诉我如果窗户的尺寸很奇怪会发生什么。

                        也许您应该拥有一份所有可接受比率的列表(对于第 3 方组件)。然后,找到与您的窗口最接近的匹配项并从列表中返回该比率。

                        【讨论】:

                          【解决方案15】:

                          这样做有点奇怪,但使用分辨率作为方面。 例如

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

                          【讨论】:

                            【解决方案16】:

                            在我的情况下,我想要类似的东西

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

                            【讨论】:

                              【解决方案17】:

                              我相信纵横比是宽度除以高度。

                               r = w/h
                              

                              【讨论】:

                                【解决方案18】:
                                Width / Height
                                

                                ?

                                【讨论】:

                                • 我将使用只接受纵横比的组件,例如:4:3、16:9
                                猜你喜欢
                                • 2015-03-12
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 2013-08-26
                                • 1970-01-01
                                • 2013-06-13
                                • 1970-01-01
                                • 2011-11-18
                                相关资源
                                最近更新 更多