将 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})");
}
}
是的,虽然并不完美,但传播更高的位结果更接近人们的预期:)
有些人 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})");
}
}
是的,虽然并不完美,但传播更高的位结果更接近人们的预期:)