将 RGB555 转换为 RGB888 的正确方法是什么?

What is the correct way to convert RGB555 to RGB888?

有些人 suggest 将 RGB555 转换为 RGB888,将最高位向下传播,然而,即使这种方法保留了整个范围(与左拉 3 不同,后者不保留),这方法从最高位引入噪声。

我自己,我使用公式 x * 255 / 31,它保留了整个范围并且不会从最高位引入噪声。

这个小测试显示了两种方法之间的区别:

using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class ColorTest
{
    public TestContext TestContext { get; set; }

    [TestMethod]
    public void Test1()
    {
        for (var i = 0; i < 32; i++)
        {
            var j = i * 255 / 31;
            var k = (i << 3) | ((i >> 2) & 0b111);
            TestContext.WriteLine($"{j,3} -> {k,3}, difference: {k - j}");
        }
    }
}

结果:

TestContext Messages:
  0 ->   0, difference: 0
  8 ->   8, difference: 0
 16 ->  16, difference: 0
 24 ->  24, difference: 0
 32 ->  33, difference: 1
 41 ->  41, difference: 0
 49 ->  49, difference: 0
 57 ->  57, difference: 0
 65 ->  66, difference: 1
 74 ->  74, difference: 0
 82 ->  82, difference: 0
 90 ->  90, difference: 0
 98 ->  99, difference: 1
106 -> 107, difference: 1
115 -> 115, difference: 0
123 -> 123, difference: 0
131 -> 132, difference: 1
139 -> 140, difference: 1
148 -> 148, difference: 0
156 -> 156, difference: 0
164 -> 165, difference: 1
172 -> 173, difference: 1
180 -> 181, difference: 1
189 -> 189, difference: 0
197 -> 198, difference: 1
205 -> 206, difference: 1
213 -> 214, difference: 1
222 -> 222, difference: 0
230 -> 231, difference: 1
238 -> 239, difference: 1
246 -> 247, difference: 1
255 -> 255, difference: 0

问题:

哪种方法最终是正确的?

我设计了一个小测试,结果至少可以说是相当令人惊讶!

事实证明,传播更高的位在等于使用 floating-point 计算的值时获得最佳百分比,四舍五入并转换回整数:

图例:

i: 5-bit index
j: N * 255 / 31
k: (N << 3) | ((N >> 2) & 0b111)
l: (N * 539087) >> 16
m: N * 255.0d / 31.0d
n: (int)Math.Round(N * 255.0d / 31.0d)

结果:

i:   0, j:   0, k:   0, l:   0, m:   0.00, n:   0.00
i:   1, j:   8, k:   8, l:   8, m:   8.23, n:   8.00
i:   2, j:  16, k:  16, l:  16, m:  16.45, n:  16.00
i:   3, j:  24, k:  24, l:  24, m:  24.68, n:  25.00
i:   4, j:  32, k:  33, l:  32, m:  32.90, n:  33.00
i:   5, j:  41, k:  41, l:  41, m:  41.13, n:  41.00
i:   6, j:  49, k:  49, l:  49, m:  49.35, n:  49.00
i:   7, j:  57, k:  57, l:  57, m:  57.58, n:  58.00
i:   8, j:  65, k:  66, l:  65, m:  65.81, n:  66.00
i:   9, j:  74, k:  74, l:  74, m:  74.03, n:  74.00
i:  10, j:  82, k:  82, l:  82, m:  82.26, n:  82.00
i:  11, j:  90, k:  90, l:  90, m:  90.48, n:  90.00
i:  12, j:  98, k:  99, l:  98, m:  98.71, n:  99.00
i:  13, j: 106, k: 107, l: 106, m: 106.94, n: 107.00
i:  14, j: 115, k: 115, l: 115, m: 115.16, n: 115.00
i:  15, j: 123, k: 123, l: 123, m: 123.39, n: 123.00
i:  16, j: 131, k: 132, l: 131, m: 131.61, n: 132.00
i:  17, j: 139, k: 140, l: 139, m: 139.84, n: 140.00
i:  18, j: 148, k: 148, l: 148, m: 148.06, n: 148.00
i:  19, j: 156, k: 156, l: 156, m: 156.29, n: 156.00
i:  20, j: 164, k: 165, l: 164, m: 164.52, n: 165.00
i:  21, j: 172, k: 173, l: 172, m: 172.74, n: 173.00
i:  22, j: 180, k: 181, l: 180, m: 180.97, n: 181.00
i:  23, j: 189, k: 189, l: 189, m: 189.19, n: 189.00
i:  24, j: 197, k: 198, l: 197, m: 197.42, n: 197.00
i:  25, j: 205, k: 206, l: 205, m: 205.65, n: 206.00
i:  26, j: 213, k: 214, l: 213, m: 213.87, n: 214.00
i:  27, j: 222, k: 222, l: 222, m: 222.10, n: 222.00
i:  28, j: 230, k: 231, l: 230, m: 230.32, n: 230.00
i:  29, j: 238, k: 239, l: 238, m: 238.55, n: 239.00
i:  30, j: 246, k: 247, l: 246, m: 246.77, n: 247.00
i:  31, j: 255, k: 255, l: 255, m: 255.00, n: 255.00

Total hits -> j: 17 (53.12%), k: 28 (87.50%), l: 17 (53.12%)

代码:

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class ColorTest
{
    public TestContext TestContext { get; set; }

    [TestMethod]
    public void Test1()
    {
        var (item1, item2, item3) = (0, 0, 0);

        TestContext.WriteLine("i: 5-bit index");
        TestContext.WriteLine("j: N * 255 / 31");
        TestContext.WriteLine("k: (N << 3) | ((N >> 2) & 0b111)");
        TestContext.WriteLine("l: (N * 539087) >> 16");
        TestContext.WriteLine("m: N * 255.0d / 31.0d");
        TestContext.WriteLine("n: (int)Math.Round(N * 255.0d / 31.0d)");
        TestContext.WriteLine(string.Empty);

        for (var i = 0; i < 32; i++)
        {
            var j = i * 255 / 31;
            var k = (i << 3) | ((i >> 2) & 0b111);
            var l = (i * 539087) >> 16;
            var m = i * 255.0d / 31.0d;
            var n = (int)Math.Round(m);

            TestContext.WriteLine(
                $"{nameof(i)}: {i,3}, " +
                $"{nameof(j)}: {j,3}, " +
                $"{nameof(k)}: {k,3}, " +
                $"{nameof(l)}: {l,3}, " +
                $"{nameof(m)}: {m,6:F}, " +
                $"{nameof(n)}: {n,6:F}");

            if (j == n)
            {
                item1++;
            }

            if (k == n)
            {
                item2++;
            }

            if (l == n)
            {
                item3++;
            }
        }

        TestContext.WriteLine($"\r\nTotal hits -> j: {item1} ({item1 / 32f:P}), k: {item2} ({item2 / 32f:P}), l: {item3} ({item3 / 32f:P})");
    }
}

是的,虽然并不完美,但传播更高的位结果更接近人们的预期:)