I believe I converted between sRGB and linear RGB correctly, so why do the results look worse for dark colors?

huangapple go评论72阅读模式
英文:

I believe I converted between sRGB and linear RGB correctly, so why do the results look worse for dark colors?

问题

在研究了sRGB的维基百科条目之后,我实现了一组函数来帮助进行颜色转换:

import "math"

// https://en.wikipedia.org/wiki/SRGB#Transformation

var byteDecoded [256]float32 = func() (floats [256]float32) {
	for i := 0; i < 256; i++ {
		floats[i] = float32(i) / 255
	}
	return floats
}()

// Standard函数将线性RGB范围[0.0-1.0]内的v转换为sRGB颜色空间范围[0.0-1.0]内的值。
func Standard(v float32) float32 {
	if v <= 0.0031308 {
		return v * 12.92
	}
	return float32(1.055*math.Pow(float64(v), 1.0/2.4) - 0.055)
}

// Standardb函数将线性RGB范围[0.0-1.0]内的v转换为sRGB颜色空间范围[0-255]内的值。
func Standardb(v float32) uint8 {
	if v >= 1 {
		return 255
	}
	if v <= 0 {
		return 0
	}
	return uint8(Standard(v)*255 + 0.5)
}

// Linear函数将sRGB范围[0.0-1.0]内的v转换为线性RGB颜色空间范围[0.0-1.0]内的值。
func Linear(v float32) float32 {
	if v <= 0.04045 {
		return v * (1.0 / 12.92)
	}
	return float32(math.Pow((float64(v)+0.055)/1.055, 2.4))
}

// Linearb函数将sRGB范围[0-255]内的b转换为线性RGB颜色空间范围[0.0-1.0]内的值。
func Linearb(b uint8) float32 {
	return Linear(byteDecoded[b])
}

然后我进行了一些实验。

log.Printf("Half of sRGB 255 calculated in linear RGB is %d", Standardb(Linearb(255)/2))
输出 Half of sRGB 255 calculated in linear RGB is 188

然后我做了这个实验:

I believe I converted between sRGB and linear RGB correctly, so why do the results look worse for dark colors?

上半部分:交错排列的红色像素(255, 0, 0)和绿色像素(0, 255, 0)。
左下角:通过除以2进行简单混合得到的像素(128, 128, 0)。
右下角:(188, 188, 0)

下半部分展示了两种不同的尝试,即将上半部分在两个轴上缩小50%。由于上半部分是交错排列的全绿色和全红色像素,缩小后需要将一半的红色和一半的绿色相加,之前计算的值就是这个结果(188)。

当我用裸眼观察时,右下角的图像与上半部分非常接近,所以整个转换过程似乎是正确的。

但是对于较暗的颜色呢?

log.Printf("Half of sRGB 64 calculated in linear RGB is %d", Standardb(Linearb(64)/2))
输出 Half of sRGB 64 calculated in linear RGB is 44

我做了与之前相同的实验:

I believe I converted between sRGB and linear RGB correctly, so why do the results look worse for dark colors?

上半部分:交错排列的深红色像素(64, 0, 0)和深绿色像素(0, 64, 0)。
左下角:通过除以2进行简单混合得到的像素(32, 32, 0)。
右下角:(44, 44, 0)

这次,在我的显示器上,简单混合的方法与上半部分几乎完美匹配,而我费力计算的右下角的值看起来太亮了。

我犯了一个错误吗?还是这只是在消费者显示设备上可以预期的误差范围内?

英文:

After a study of the Wikipedia entry on sRGB I implemented a set of functions to help with color conversions:

import &quot;math&quot;

// https://en.wikipedia.org/wiki/SRGB#Transformation

var byteDecoded [256]float32 = func() (floats [256]float32) {
	for i := 0; i &lt; 256; i++ {
		floats[i] = float32(i) / 255
	}
	return floats
}()

// Standard returns the sRGB color space value in range [0.0-1.0] for v, assuming v is in linear RGB in range [0.0-1.0].
func Standard(v float32) float32 {
	if v &lt;= 0.0031308 {
		return v * 12.92
	}
	return float32(1.055*math.Pow(float64(v), 1.0/2.4) - 0.055)
}

// Standardb returns the sRGB color space value in range [0-255] for v, assuming v is in linear RGB in range [0.0-1.0].
func Standardb(v float32) uint8 {
	if v &gt;= 1 {
		return 255
	}
	if v &lt;= 0 {
		return 0
	}
	return uint8(Standard(v)*255 + 0.5)
}

// Linear returns the linear RGB color space value in range [0.0-1.0] for v, assuming v is in sRGB in range [0.0-1.0].
func Linear(v float32) float32 {
	if v &lt;= 0.04045 {
		return v * (1.0 / 12.92)
	}
	return float32(math.Pow((float64(v)+0.055)/1.055, 2.4))
}

// Linearb returns the linear RGB color space value in range [0.0-1.0] for b, assuming b is in sRGB in range [0-255].
func Linearb(b uint8) float32 {
	return Linear(byteDecoded[b])
}

I then played with some results.

log.Printf(&quot;Half of sRGB 255 calculated in linear RGB is %d&quot;, Standardb(Linearb(255)/2))
prints Half of sRGB 255 calculated in linear RGB is 188.

I then made this:

I believe I converted between sRGB and linear RGB correctly, so why do the results look worse for dark colors?

Top half: checkerboarded red (255, 0, 0) and green (0, 255, 0) pixels.
Lower left: naive mixdown by division with 2 (128, 128, 0).
Lower right: (188, 188, 0)

The lower half shows two different attempts at what the top half could look like when scaled down by 50% on both axes. Since the top half is interleaved full green and full red pixels, a downscale would have to add half red and half green together, the value for which is what I calculated earlier (188).

The lower right matches the top half quite exactly on my plain consumer display monitor when crossing my eyes, so it seems like this whole conversion math is working out.

But what about darker colors?

log.Printf(&quot;Half of sRGB 64 calculated in linear RGB is %d&quot;, Standardb(Linearb(64)/2))
prints Half of sRGB 64 calculated in linear RGB is 44.

I do the same as before:

I believe I converted between sRGB and linear RGB correctly, so why do the results look worse for dark colors?

Top half: checkerboarded dark red (64, 0, 0) and dark green (0, 64, 0) pixels.
Lower left: naive mixdown by division with 2 (32, 32, 0).
Lower right: (44, 44, 0)

This time, on my display, the naive (incorrect) method matches the upper half almost perfectly, while the value that I went through the effort to calculate in the lower right looks way too bright.

Did I make a mistake? Or is this just the extent of error to expect on consumer display devices?

答案1

得分: 0

我翻译好了以下内容:

> 我犯了一个错误吗?

是和否。你的代码是正确的,但是你的测试方法有一个疏忽:

> 这只是在消费者显示设备上可以预期的错误程度吗?

是的。特别是,这很可能是由显示面板的控制驱动程序引起的。在这里有人提出了类似的观察:https://electronics.stackexchange.com/questions/401617/lcd-pixels-how-chess-board-pixel-fill-patterns-are-called

通过从棋盘格像素模式转换为交替着色的水平像素线,可以减少问题的强度。数学计算结果是相同的,但是结果可能看起来完全不同。

第一张带有水平线的图片:

I believe I converted between sRGB and linear RGB correctly, so why do the results look worse for dark colors?

第二张带有水平线的图片:

I believe I converted between sRGB and linear RGB correctly, so why do the results look worse for dark colors?

额外奖励:第二张带有垂直线的图片(试着眯起眼睛看;对我来说看起来更准确):

I believe I converted between sRGB and linear RGB correctly, so why do the results look worse for dark colors?

还要注意,“普通的消费者显示器”已经是一个范围很广的概念,涉及到显示的准确性。如果我把这个 Stackoverflow 窗口拖到我更便宜的第二个显示器上,由于其颜色输出的极不准确,我完全无法确认你所提出的任何断言。

英文:

> Did I make a mistake?

Yes and no. Your code is correct, but your testing methodology has an oversight:

> is this just the extent of error to expect on consumer display devices?

Yes. In particular, this likely is caused by the display panel's control driver. See someone making a similar observation here: https://electronics.stackexchange.com/questions/401617/lcd-pixels-how-chess-board-pixel-fill-patterns-are-called

The intensity of the problem can be reduced by going from a checkerboard pixel pattern to alternatingly colored horizontal pixel lines. The math comes out the same, but the results can look wildly different.

First image with horizontal lines:

I believe I converted between sRGB and linear RGB correctly, so why do the results look worse for dark colors?

Second image with horizontal lines:

I believe I converted between sRGB and linear RGB correctly, so why do the results look worse for dark colors?

Bonus: second image with vertical lines (try squinting; it looks even more accurate for me):

I believe I converted between sRGB and linear RGB correctly, so why do the results look worse for dark colors?

Also note that "plain consumer monitor" is already a wide range in terms of the display's ability to be accurate. If I drag this here very Stackoverflow window over to my cheaper second monitor I am completely unable to confirm any of the assertions you are making due to how wildly inaccurate its color output is.

huangapple
  • 本文由 发表于 2022年9月29日 22:09:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/73896966.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定