将颜色值从float 0..1转换为byte 0..255

时间:2009-12-16 11:33:51

标签: c language-agnostic colors

将颜色值从float转换为byte的正确方法是什么?起初我认为b=f*255.0应该这样做,但现在我想,在这种情况下,只有确切的1.0才会转换为255,但0.9999已经是254,这可能不是我想要的......

似乎b=f*256.0会更好,除非在精确256的情况下会产生1.0的不必要的情况。

最后我正在使用它:

#define F2B(f) ((f) >= 1.0 ? 255 : (int)((f)*256.0))

10 个答案:

答案 0 :(得分:22)

1.0是唯一可能出错的情况,因此单独处理该案例:

b = floor(f >= 1.0 ? 255 : f * 256.0)

此外,值得强制的是f确实为0< = f< = 1以避免由于舍入错误导致的错误行为(例如f = 1.0000001)。

f2 = max(0.0, min(1.0, f))
b = floor(f2 == 1.0 ? 255 : f2 * 256.0)

替代安全解决方案:

b = (f >= 1.0 ? 255 : (f <= 0.0 ? 0 : (int)floor(f * 256.0)))

b = max(0, min(255, (int)floor(f * 256.0)))

答案 1 :(得分:8)

我一直都在做round(f * 255.0)

不需要进行测试(1的特殊情况)和/或其他答案中的钳位。这是否适合您的目的取决于您的目标是尽可能地匹配输入值[我的公式],还是将每个组件划分为256个相等的间隔[其他公式]。

我公式的可能缺点是0和255区间只有其他区间宽度的一半。经过多年的使用,我还没有看到任何视觉证据表明这是不好的。相反,我发现在输入非常接近之前,最好不要触及任何一个极端 - 但这是一个品味问题。

可能的好处是[我相信]对于更广泛的输入值,R-G-B组件的相对值(稍微)更精确。
虽然我没有试图证明这一点,但这是我的直觉,因为对于每个组件我都会得到最接近的可用整数。 (例如,我相信如果颜色有G~ = 2 x R,这个公式通常会保持接近该比率;虽然差异非常小,但256公式有更多其他颜色可以做得更好所以这可能是洗漱。)

在实践中,基于256255的方法似乎都能提供良好的效果。

评估 255 vs 256的另一种方法是检查其他方向 -
从0..255字节转换为0.0..1.0浮点数。

将0..255个整数值转换为0.0..1.0范围内等间距值的公式为:

f = b / 255.0

朝着这个方向前进,毫无疑问是使用255还是256:上面的公式产生等间距结果的公式。注意它使用255

要理解两个方向上255公式之间的关系,请考虑此图,如果您只有2位,则值为整数值0..3:

使用3表示两位,类似于255表示8位。转换可以是从上到下,也可以从下到上:

0 --|-- 1 --|-- 2 --|-- 3  
0 --|--1/3--|--2/3--|-- 0
   1/6     1/2     5/6

|是4个范围之间的界限。观察内部,浮点值和整数值位于其范围的中点。观察所有值之间的间距在两种表示中都是常量。

如果你掌握了这些图表,你就会理解为什么我赞成基于255的公式优于基于256的公式。

声明:如果您将字节转到浮动状态时使用/ 255.0,但在转到<时不要使用round(f * 255.0) em> to 来自float的字节,然后是&#34;平均往返&#34;错误增加。详情如下。

这最容易通过从float开始,到byte,然后再到float来测量。要进行简单分析,请使用2位&#34; 0..3&#34;图。

从大量浮点值开始,均匀间隔0.0到1.0。 往返会将所有这些值分组为4值 该图有6个半间隔长度范围:
0..1 / 6,1 / 6..1 / 3,..,5 / 6..1
对于每个范围,平均往返误差是范围的一半,因此1/12(最小误差为零,最大误差为1/6,均匀分布)。
所有范围都给出了相同的误差; 1/12是往返时的总体平均误差。

如果您改为使用* 256* 255.999公式中的任何公式,大多数的往返结果都是相同的,但有一些会移到相邻的范围内。
对其他范围的任何更改都会增加错误;例如,如果先前单个浮点输入的误差小于小于1/6,则返回相邻范围的中心会导致误差更多而不是1/6 。例如。最佳公式中的0.18 =&gt;字节1 =&gt; float 1/3~ = 0.333,错误为| 0.33-0.18| = 0.147;使用256公式=&gt;字节0 =&gt; float 0,表示错误0.18,这是最佳错误0.147的增加。

使用* 4/ 3的图表。转换是从一行到下一行 注意第一行的间距不均匀:0..3 / 8,3 / 8..5 / 8,5 / 8..1。那些距离是3 / 8,2 / 8,3 / 8。 请注意,最后一行的间隔边界与第一行不同。

   0------|--3/8--|--5/8--|------0
         1/4     1/2     3/4
=> 0------|-- 1 --|-- 2 --|------3  

=> 0----|---1/3---|---2/3---|----0
       1/6       1/2       5/6

避免这种增加的错误的唯一方法是在从byte到float时使用一些不同的公式。如果您坚信其中一个256公式,那么我会留给您确定最佳公式。
(每个字节值,它应该返回浮点值的中点,该值变为该字节值。除了0到0,以及3到1.或者可能是0到1 / 8,3到7/8!在上图中,它应该从中线回到顶线。)

但是现在你将遇到难以防御的情况,你已经采用了等间距的字节值,并将它们转换为非等间距的浮点值。

如果您使用除255之外的任何值,那么这些是您的选项,对于整数0..255:平均往返误差增加,或者非均匀间隔值浮动域。

答案 2 :(得分:6)

为什么不尝试类似

的内容
b=f*255.999

删除特例f==1,但0.999仍为255

答案 3 :(得分:1)

当它比较整数时,接受的解决方案失败了。

这段代码工作正常:

float f;
uint8_t i;
//byte to float
f =CLAMP(((float)((i &0x0000ff))) /255.0, 0.0, 1.0);
//float to byte
i =((uint8_t)(255.0f *CLAMP(f, 0.0, 1.0)));

如果您没有CLAMP:

#define CLAMP(value, min, max) (((value) >(max)) ? (max) : (((value) <(min)) ? (min) : (value)))

或者全RGB:

integer_color =((uint8_t)(255.0f *CLAMP(float_color.r, 0.0, 1.0)) <<16) |
               ((uint8_t)(255.0f *CLAMP(float_color.g, 0.0, 1.0)) <<8) |
               ((uint8_t)(255.0f *CLAMP(float_color.b, 0.0, 1.0))) & 0xffffff;

float_color.r =CLAMP(((float)((integer_color &0xff0000) >>16)) /255.0, 0.0, 1.0);
float_color.g =CLAMP(((float)((integer_color &0x00ff00) >>8)) /255.0, 0.0, 1.0);
float_color.b =CLAMP(((float)((integer_color &0x0000ff))) /255.0, 0.0, 1.0);

答案 4 :(得分:0)

我认为正确的是楼层(f * 256),而不是圆形。这会将间隔0..1映射到相等长度的256个区域。

[编辑]并将256视为特例。

答案 5 :(得分:0)

public static void floatToByte(float f)
{
     return (byte)(f * 255 % 256)
}

值&lt; 1准确转换。

转换后,介于255和256之间的值在转换为字节时会浮动到255.

值&gt; 1使用%运算符循环回0。

答案 6 :(得分:0)

将颜色值从float转换为字节的正确方法是什么意思?您是说,如果您从[0,1[范围内选择统一的随机实数,它们将在2560的{​​{1}}个bin中唯一地分布?

为了使事情变得简单,我们假设我们有一个实数而不是255,而不是float我们要转换为两位整数,类似于int -完全由两位组成的整数表示。这意味着我们的uint_2可以具有值unit2_t00b01b10b(b表示我们这里有一个二进制数。也称为Intel约定)。然后,我们必须提出一个想法,即哪些实数区间应映射到哪些整数值。如果要将11b映射到[0,0.25[,将0映射到[0.25,0.5[,将1映射到[0.5,0.75[,将2映射到{{1 }},可以通过[0.75,1.0]完成转换(底数仅取数字的整数部分,而忽略小数部分)。这适用于除3以外的所有数字。对b = std::floor(f * 4.0)的简单更改可以解决此问题。该方程式确保间隔等距分布。

如果您假设我们的实际值是单精度IEEE 754浮点数,则在f=1区间内将有有限数量的可能的浮点表示形式。您必须确定那些实数的表示形式属于哪个整数表示形式。然后,您可以拿出一些源代码,将您的浮点数转换为整数,并检查其是否适合您的映射。也许b = floor(f >= 1.0 ? 255 : f * 256.0)对您来说是正确的事,或者也许[0,1]是对的。这取决于您要映射到哪种整数表示形式。

看看下面的程序。它说明了不同的转换会执行不同的操作:

int ig = int(255.99 * g);

您可以使用这个小型试验台进行实验:

b = floor(f >= 1.0 ? 255 : f * 256.0)

最后,我建议不要从float转换为integer。将图片存储为高动态范围数据,然后选择将数据转换为低动态范围的工具(例如http://djv.sourceforge.net/)。音调映射是一个自己的研究领域,有些工具具有不错的用户界面,可为您提供各种音调映射操作符。

答案 7 :(得分:0)

如果您希望拥有大小完全相等的块,则以下方法将是最佳解决方案。 它将[0,1]的范围转换为[0,256[

#include <cstdint>
#include <limits>

// Greatest double predecessor of 256:
constexpr double MAXCOLOR = 256.0 - std::numeric_limits<double>::epsilon() * 128;

inline uint32_t float_to_int_color(const double color){
  return static_cast<uint32_t>(color * MAXCOLOR);
}

答案 8 :(得分:0)

clamp(round(f * 256 - 0.5), 0, 255)

min(max(round(f * 256 - 0.5), 0), 255)

clamp(floor(f * 256), 0, 255)(从零舍入)

min(max(floor(f * 256), 0), 255)(从零舍入)

上面的公式将浮点数 [0..1] 转换为浮点数 [-0.5..255.5] 到字节 [0..255]。但是检查 Direct3D 数据转换规则,它们的作用不同。相当于:

floor(clamp(f, 0, 1) * 255 + 0.5)(从零舍入)

floor(min(max(f, 0), 1) * 255 + 0.5)(从零舍入)

此公式将浮点数 [-0.00196..1.00196] 转换为浮点数 [-0.5..255.5] 到字节 [0..255]。

类似的方法是:

round(clamp(f, 0, 1) * 255)

round(min(max(f, 0), 1) * 255)

答案 9 :(得分:0)

每种方法的优缺点:

  1. (f * 256).clip(0, 255)
    • ✓ 统一大小的间隔。
    • ✗ 添加小噪声项时无法正确恢复原始图像。
  2. (f * 255.999)
    • ✓ 统一大小的区间(误差 <0.0004%)。
    • ✗ 添加小噪声项时无法正确恢复原始图像。
    • ✓ 最快。
  3. (f * 255).round()
    • ✗ 在范围 [1, 254] 内统一调整区间大小,但对端点 0 和 255 使用这些区间大小的一半。
    • ✓ 正确恢复原始图像。

建议:

  • 如果 f 是“随机变量”或不是来自未经处理的图像,则使用方法 1。
  • 如果您想要快速而简单的东西,请使用方法 2。
  • 如果您想稳健地恢复生成 f 的原始图像,请使用方法 3。

测试

>>> x = np.arange(256)
[0, 1, 2, ..., 253, 254, 255]

>>> f = x / 255
[0.000, 0.004, 0.008, ..., 0.992, 0.996, 1.000]

>>> def test(func, eps=1e-3):
...     print(
...         (x == func(f - eps)).all(),
...         (x == func(f)).all(),
...         (x == func(f + eps)).all(),
...     )

我们现在测试哪种方法最能从 x 恢复原始 f 值:

>>> test(lambda f: (f * 256).clip(0, 255).astype(np.uint8))
False True False

>>> test(lambda f: (f * 255.999).astype(np.uint8))
False True False

>>> test(lambda f: (f * 255).round().astype(np.uint8))
True True True