Reputation: 16652
Some people suggest that to convert RGB555 to RGB888, one propagates the highest bits down, however, even though this method preserves full range (as opposed to left-shit by 3 which does not), this approach introduces noise from the highest bits.
Myself, I use the formula x * 255 / 31
which preserves full range and does not introduce noise from the highest bits.
This small test shows the difference between the two approaches:
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}");
}
}
}
Result:
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
Question:
Which approach is correct in definitive?
Upvotes: 2
Views: 1234
Reputation: 16652
I crafted a small test and the results are quite surprising to say the least!
In turns out that propagating higher bits gets the best percentage when it comes to be equal to the value being calculated using floating-point, rounded and casted back to an integer:
Legend:
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)
Results:
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%)
Code:
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})");
}
}
Yes, even though not perfect, propagating higher bits turns out to be closer to what one expects :)
Upvotes: 1