找到三个不同数字的倍数的连续最小和,javascript

时间:2017-01-16 02:49:38

标签: javascript algorithm rgb luminance diophantine

this post我找到了一种算法来确定RGB颜色的亮度:

  

亮度(某些色彩空间的标准):( 0.2126 * R + 0.7152 * G + 0.0722 * B)

我想使用这个等式,从rgb(0,0,0)开始,按照从最低到最高亮度的顺序生成所有RGB颜色,然后将它们绘制到4096x4096画布。

我的问题是,有1670万种不同的组合,我无法生成所有这些组合,然后对它们进行排序,而不会使浏览器崩溃或花费多天时间来完成渲染。所以我想找到一种方法来找到每个数字的倍数,这些数字将汇总到下一个最低数字。

例如,从0,0,0的和rgb开始,亮度为0(0.2126*0 + 0.7152*0 + 0.0722*0 = 0),下一个最小的发光rgb值为0,0,1,因为0.2126*0 + 0.7152*0 + 0.0722*1 = .0722,并且没有可以汇总到较小数字的多个倍数。

前19个连续亮度值如下(我可能错过了一两个,因为我手动计算它们,但希望它有助于说明这一点):

RGB       =>     Luminence

0,0,0     =>     0
0,0,1     =>     .0722
0,0,2     =>     .1444
1,0,0     =>     .2126
0,0,3     =>     .2166
1,0,1     =>     .2848
0,0,4     =>     .2888
1,0,2     =>     .357
0,0,5     =>     .361
2,0,0     =>     .4252
1,0,3     =>     .4292
0,0,6     =>     .4332
2,0,1     =>     .4974
1,0,4     =>     .5014
0,0,7     =>     .5054
2,0,2     =>     .5696
1,0,5     =>     .5736
0,0,8     =>     .5776
3,0,0     =>     .6378

我似乎无法找到任何模式,所以我希望可能有一个等式或编码技巧可以让我找到最小的和,高于前一个和的倍数三个数字,没有强制它并检查每个可能的值。

编辑:我做了一些额外的研究,看起来解决方案可能在于使用线性丢番图方程。如果我取每个小数并乘以1000,得到2126, 7152, & 722。然后将1-by-1计算到2,550,0002126*255 + 7152*255 + 722*255),我可以检查每个数字,看看它是否是等式2126r + 7152g + 722b = n的解,其中n是当前数字计算到,和r,g,& b是未知数。如果我能做到这一点,我可以在下一个连续的亮度值中找出所有可能的rgb值,甚至不必将重复亮度值的任何值加倍,而且我只需要进行255万次计算而不是16.77+次计算百万(每种颜色一个)。如果有人知道如何编码这个等式,或者如果有人有任何更好的解决方案,我将非常感激。谢谢!

4 个答案:

答案 0 :(得分:1)

您可以利用的一个事实是,序列中的每个三元组的R,G或B值只比已经输出的三元组大一个。

因此,您可以维护一个BinaryHeap(按亮度排序),其中包含R,G或B中比已经输出的三元组大1的所有三元组,并在循环中执行此操作:< / p>

  • 从堆中删除最小元素(r,g,b)
  • 输出
  • 将(r + 1,g,b),(r,g + 1,b)和(r,g,b + 1)添加到堆中,但前提是它们是有效的三元组(所有值小于或等于255),并且只有它们不在堆中。如果可以生成的替代三元组(在r,g或b中,在允许的范围内少1)具有比(r,g,b)更高的亮度,则三元组将不会在堆中。 / LI>

例如,如果(r + 1,g-1,b)的亮度高于(r,g,b)或(r + 1,g-1,则仅添加(r + 1,g,b), b)无效。由于基于r,g,b计算亮度的因子是固定的,(r + 1,g-1,b)总是具有较低的亮度,你应该只添加(r + 1,g,b)if( r + 1,g-1,b)无效,即g为0时。

在伪代码中,规则是这样的:

function addTriplets(r, g, b)
{
    if(g < 255)
       pushTripletToHeap(r, g + 1, b);
    if((g == 0) && (r < 255))
       pushTripletToHeap(r + 1, g, b);
    if((g == 0) && (r == 0) && (b < 255))
       pushTripletToHeap(r, g, b + 1);
}

在开始循环之前将(0,0,0)三元组推入堆,并在堆为空时停止循环。

答案 1 :(得分:1)

这是一个针对您的问题的算法(已经忘记了它的名字): 该算法可以列出按某种顺序排序的所有颜色元组{R,G,B}。在你的情况下,亮度上升:color1&lt; color2&lt; ==&gt; f(color1)&lt; f(color2),其中f(颜色)= 0.2126 * R + 0.7152 * G + 0.0722 * B

  • 初始化:arr = [{r:0,g:0,b:0}](最小颜色)
  • 重复:
    • 选择min(iR):a [iR] = {rR&lt; 255,gR,bR}和cR = {rR + 1,gR,bR}&gt; arr [i]为每一个我。 (在arr中选择第一种颜色,这样如果我们在其r组件中加1,我们会得到一个比当前在arr中的每种颜色都要大的新颜色)
    • 类似于iG和iB =&gt;也得到cG = {rG,gG + 1,bG}和cB = {rB,gB,bB + 1}
    • 在cR中,cG和cB选择最小颜色c
    • 将c附加到数组arr

当没有找到这样的iR,iG或iB时,算法停止。

注意:

  • arr 始终处于排序(升序)顺序,因为每次将新颜色附加到 arr 时,它总是大于当前中的每个元素ARR 即可。
  • 因为 arr 按升序排列,我们只需要将cR / cG / cB与 arr 的最后一个元素进行比较,以检查它是否大于 arr
  • 的每个元素
  • iR,iG和iB通过算法增加
  • 复杂度为O(N),N为颜色数(2 ^ 24)~16M。使用基于堆的算法,复杂性约为O(NlogN)。

这是我的实现(在nodejs 6中测试)

//  use integer to avoid floating point inaccuracy
const lumixOf = {r: 2126, g: 7152, b: 722};

const maxValue = 256;
const components = ['r', 'g', 'b'];

class Color {
  constructor(r, g, b, lum) { 
    this.r = r;
    this.g = g;
    this.b = b;
    this.lum = lum;
  }

  add(component) { 
    const ans = new Color(this.r, this.g, this.b, this.lum);
    if (++ans[component] >= maxValue) return null; // exceed 255
    ans.lum += lumixOf[component];
    return ans;
  }

  greater(color2) {
    // return this.lum > color2.lum;
    if (this.lum !== color2.lum) return this.lum > color2.lum;
    if (this.r !== color2.r) return this.r > color2.r;
    if (this.g !== color2.g) return this.g > color2.g;
    return this.b > color2.b;        
  }
}

let a = [new Color(0, 0, 0, 0)];  //  R, G, B, lumix
let index = {r: 0, g: 0, b: 0};

console.log('#0:', a[0]);
// Test: print the first 100 colors
for (let count = 1; count < 100; ++count) {
  let nextColor = null;
  const len = a.length;
  const currentColor = a[len - 1];
  components.forEach(component => {
    let cIndex = index[component];
    for (; cIndex < len; ++cIndex) {
      const newColor = a[cIndex].add(component);
      if (!newColor || !newColor.greater(currentColor)) continue;

      //  find the minimum next color
      if (nextColor == null || nextColor.greater(newColor)) {
        nextColor = newColor;
      }
      break;
    }
    index[component] = cIndex;
  });

  if (!nextColor) break;  //  done. No more color
  a.push(nextColor);
  console.log('#' + count + ':', nextColor);
}
console.log(a.length);

此实现列出了所有2 ^ 24 = 16777216种颜色(一旦删除主循环中的count < 100条件,但您不想打印出这么多行)。如果某些颜色具有相同的亮度值,则按照它们的R值,然后是G值,然后是B值对它们进行排序。如果您只需要为每个亮度值使用一种颜色,请取消注释greater()函数中的第一行 - 然后您将获得具有明显亮度的1207615种颜色

答案 2 :(得分:0)

很抱歉,但我不得不说你做了一些浪费的工作。

RGB的8位量化将在256个亮度级别(包括黑色和白色)下产生16.7M色彩但是你还没有足够的像素在4K显示器上显示它们就像3840 x 2160 = 8294400 4K电视标准上的像素或像4K电影标准上的4096 x 2160 = 8847360。除了1像素颜色样本对眼睛的意义,特别是在4K显示器上。?

我建议你使用7位量化而不是8位。这将给你2 ^ 21 =&gt; 2097152个颜色样本,它们将被映射为高清监视器/电视上的单个像素和4K监视器/电视上的2x2像素。美丽。

代码如下;

&#13;
&#13;
"use strict";
var allColors = Array(Math.pow(2,21)),           // All 2^21 colors
         cgbl = Array(128).fill().map(e => []);  // Colors gropuped by luminance
for (var i = 0, len = allColors.length; i < len; i++) allColors[i] = [i>>14, (i&16256)>>7, i&127];
allColors.reduce((g,c) => (g[Math.round(c[0]*0.2126 + c[1]*0.7152 + c[2]*0.0722)].push(c),g), cgbl);
cgbl.forEach((y,i) => console.log(y.length,"Colors at luminance level:",i));
&#13;
&#13;
&#13;

但请记住,您的RGB值现在采用7位量化。由于我们已经将它们分组为128个亮度级别,我还建议您通过在显示它们之前将它们的值保持为1位(r << 1; g << 1; b << 1;)来将亮度组(子阵列)中的每个RGB值映射回8位。通过使用.map()仿函数,这是一项微不足道的工作。

答案 3 :(得分:0)

因为我原来的答案已经很长了,所以我正在做出这个答案来澄清算法,正如OP要求的那样

让我们考虑类似的问题(但更容易推理):

  • 设A是一组数字,按升序排列,没有其他素数因子而不是2,3和5 (Ai = 2 ^ x * 3 ^ y * 5 ^ z)
  • 找到第n个数字

当然A1 = 1 = 2 ^ 0 * 3 ^ 0 * 5 ^ 0

让我们假设在某个步骤,我们计算了A1..An,我们需要找到A [n + 1]

  • 如果A [n + 1]可被2整除,则A [n + 1] = A [i2] * 2,其中1 <= i2 <= n
  • 如果A [n + 1]可被3整除,则A [n + 1] = A [i3] * 3,其中1 <= i3 <= n
  • 如果A [n + 1]可被5整除,则A [n + 1] = A [i5] * 5,其中1 <= i5 <= n

(显然A [n + 1]可被其中至少一个整除)

证明:A[n+1] = 2^x * 3^y * 5^z。如果A [n + 1]可被2整除,则x> 1。 0,所以B = A[n+1]/2 = 2^(x-1) * 3^y * 5^z必须在A中。并且因为B&lt; A [n + 1],它必须在A中的A [n + 1]之前,因此B = A [i2],其中1 <= i2 <= n。

所以要找到A [n + 1],我们可以:

  • 找到A [i2] * 2&gt;的最小i2。 A [n]的
  • 类似于i3和i5
  • ==&GT; A [n + 1] = min(A [i2] * 2,A [i3] * 3,A [i5] * 5)

当A1 = 1时,运行这些步骤(n - 1)次,我们找到第n个数字

现在,如果在每次迭代中找到A [n + 1],我们使用3作为从1到n的循环来计算i2,i3和i5,时间复杂度将是O(N ^ 2)。但是你可以看到每次迭代的i2,i3和i5永远不会减少(分别小于前一次迭代的那些值)。所以我们可以保存那些i2,i3和i5值,并且在每次迭代时我们只需要:

while (A[i2]*2 <= A[n]) ++i2;
while (A[i3]*3 <= A[n]) ++i3;
while (A[i5]*5 <= A[n]) ++i5;

现在时间复杂度变为O(N):虽然while循环仍然嵌套在主for 1->n循环中,但只有4个变量从1增加 - &gt; n,可以认为是4个独立的循环。您可以使用时钟验证O(N)属性,并测量不同N s

的运行时间

将此算法应用于您的问题:

  • 2,3和5变为R,G和B
  • 整数比较成为您定义的颜色比较功能
  • 如果您需要列出所有2 ^ 24种颜色,请定义比较功能,使2种不同的颜色“相等”(如果C1和C2是2种不同颜色,则C1