The correct way to scale a number from one range to another is ((value - min) / (max - min)) * output_range + output_min and in this way the standard colour normalization should be (value -0)/(255 - 0) * 1 + 0 = value /255
Now the question becomes whether an 8bit pixel value is a correct representation of a colour, or should it quantize to a half value. To answer this, I aggressively point to the above equation. It is fine. There are 256 well considered values, and any system with higher resolutions/precision should also map from 0 to 1 per channel.
It should be obvious to the reader that 0.5/256 is not zero, and therefore not black. The 256 method is also not idempotent, applying it once destructively alters the data.
And the argument that 256 has higher resolution: so would 354256, but there’s no sensible place that matters.
The correct way to scale a number from one range to another is …
Citation needed.
It is just as valid to quantize the following way:
q = (x+0.5)/256
The inverse would be:
x = min(truncate((q*256)), 255)
In fact, you could argue that this method is technically more correct. The only issue being the exact value of 1.0, which we have to handle explicitly with min(..., 255).
This method is idempotent. Try it with x=90 for example. It does destroy data, but that is true for all quantization methods.
We have to remember that quantization is the process of representing a bigger set of numbers (usually infinite) with a smaller one. We sort a range of values from the bigger set into a single bin in the smaller set.
The argument of the article is not about “resolution”. The argument is about using the 8bits available as efficiently as possible.
As the article points out, if you follow the naive approach of:
q = x/255
x = round(q * 255)
You are losing a tiny bit of the 8bit range. Since the ranges represented by 0 and 255 are half as big as the other ones. The naive approach is really quantizing in the range of [0-0.5/255, 1+0.5/255) instead of [0,1). This is because round() maps approaching values to x from both sides, so some negative values map to 0, but there are no negative values in our set.
The conclusion at the end of the article is mostly the correct one though. If there is an industry standard, it is best to follow that standard. Since mixing a quantization function from one method with the dequantization function from another is just wrong. You can only choose your own method if both your input and your output is the small range.
EDIT:
The “naive” approach described in the article does x = truncate(q * 255 + 0.5) which is equivalent to the x = round(q*255)
My idempotent claim was too broad and wrong, up to aliasing that is reversible.
I think I’ve been talking about this differently than you, when you get 8bit into, 255 in/out is correct.
When generating data I do it in the natural [0,1] space, then map to whatever domain I’m quantizing for.
In that case, for N bins you divide by N+1 (and round however you want via correction) so 256. I was probably wrong earlier around this, 256 is correct for quantizing that way.
The correct way to scale a number from one range to another is
((value - min) / (max - min)) * output_range + output_minand in this way the standard colour normalization should be(value - 0)/(255 - 0) * 1 + 0 = value / 255Now the question becomes whether an 8bit pixel value is a correct representation of a colour, or should it quantize to a half value. To answer this, I aggressively point to the above equation. It is fine. There are 256 well considered values, and any system with higher resolutions/precision should also map from 0 to 1 per channel.
It should be obvious to the reader that 0.5/256 is not zero, and therefore not black. The 256 method is also not idempotent, applying it once destructively alters the data.
And the argument that 256 has higher resolution: so would 354256, but there’s no sensible place that matters.
Citation needed.
It is just as valid to quantize the following way:
q = (x+0.5)/256The inverse would be:
x = min(truncate((q*256)), 255)In fact, you could argue that this method is technically more correct. The only issue being the exact value of 1.0, which we have to handle explicitly with
min(..., 255).This method is idempotent. Try it with
x=90for example. It does destroy data, but that is true for all quantization methods.We have to remember that quantization is the process of representing a bigger set of numbers (usually infinite) with a smaller one. We sort a range of values from the bigger set into a single bin in the smaller set.
The argument of the article is not about “resolution”. The argument is about using the 8bits available as efficiently as possible.
As the article points out, if you follow the naive approach of:
q = x/255x = round(q * 255)You are losing a tiny bit of the 8bit range. Since the ranges represented by 0 and 255 are half as big as the other ones. The naive approach is really quantizing in the range of
[0-0.5/255, 1+0.5/255)instead of[0,1). This is becauseround()maps approaching values to x from both sides, so some negative values map to 0, but there are no negative values in our set.The conclusion at the end of the article is mostly the correct one though. If there is an industry standard, it is best to follow that standard. Since mixing a quantization function from one method with the dequantization function from another is just wrong. You can only choose your own method if both your input and your output is the small range.
EDIT:
The “naive” approach described in the article does
x = truncate(q * 255 + 0.5)which is equivalent to thex = round(q*255)My idempotent claim was too broad and wrong, up to aliasing that is reversible.
I think I’ve been talking about this differently than you, when you get 8bit into, 255 in/out is correct.
When generating data I do it in the natural [0,1] space, then map to whatever domain I’m quantizing for.
In that case, for N bins you divide by N+1 (and round however you want via correction) so 256. I was probably wrong earlier around this, 256 is correct for quantizing that way.
This is the way