
What every coder should know about gamma - johnnovak
http://blog.johnnovak.net/2016/09/21/what-every-coder-should-know-about-gamma/
======
jacobolus
One thing I hate is that essentially _all_ vector graphics and text rendering
(Cairo, Quartz, MS Windows, Adobe apps, ...) is done with gamma-oblivious
antialiasing, which means that apparent stroke width / text color changes as
you scale text up or down.

This is why if you render vector graphics to a raster image at high resolution
and then scale the image down (using high quality resampling), you get
something that looks substantially thinner/lighter than a vector render.

This causes all kinds of problems with accurately rendering very detailed
vector images full of fine lines and detailed patterns (e.g. zoomed out maps).
It also breaks WYSIWYG between high-resolution printing and screen renders.
(It doesn’t help that the antialiasing in common vector graphics / text
renderers are also fairly inaccurate in general for detailed shapes, leading
to weird seams etc.)

But nobody can afford to fix their gamma handling code for on-screen
rendering, because all the screen fonts we use were designed with the
assumption of wrong gamma treatment, which means most text will look too thin
after the change.

* * *

To see a prototype of a better vector graphics implementation than anything in
current production, and some nice demo images of how broken current
implementations are when they hit complicated graphics, check this 2014 paper:
[http://w3.impa.br/~diego/projects/GanEtAl14/](http://w3.impa.br/~diego/projects/GanEtAl14/)

~~~
panic
_But nobody can afford to fix their gamma handling code for on-screen
rendering, because all the screen fonts we use were designed with the
assumption of wrong gamma treatment, which means most text will look too thin
after the change._

OS X dilates glyphs when using (linear) LCD antialiasing to counteract this
effect. Chrome was having problems getting consistent text rendering in
different contexts because of this dilation:
[https://lists.w3.org/Archives/Public/www-
style/2012Oct/0109....](https://lists.w3.org/Archives/Public/www-
style/2012Oct/0109.html)

------
cscheid
Hey, so gamma is not a logarithmic response. You claim that the delta you use
in Figure 2 is a ratio, but your code,
[https://github.com/johnnovak/johnnovak.site/blob/master/blog...](https://github.com/johnnovak/johnnovak.site/blob/master/blog/files/2016-09-21/src/gammaramp.nim#L64)
uses a fixed power. These are not the same thing.

f(x+eps)/f(x) ~= eps f'(x)/f(x) + 1

f(x) = x^2.2 f'(x) = 2.2x^1.2

f(x+eps)/f(x) ~= 1.2 eps/x + 1

Human response to light is _not_ particularly well-modeled by a logarithmic
response. It's --- no big surprise --- better modeled by a power law.

This stuff is confusing because there's two perceptual "laws" that people like
to cite: Fechner-Weber, and Stephens's. Fechner-Weber is logarithmic;
Stephens's is a generalized power-law response.

~~~
lightedman
"Human response to light is not particularly well-modeled by a logarithmic
response. It's --- no big surprise --- better modeled by a power law.

This stuff is confusing because there's two perceptual "laws" that people like
to cite: Fechner-Weber, and Stephens's. Fechner-Weber is logarithmic;
Stephens's is a generalized power-law response."

Neither would seem that useful, given the very uneven weighting of the eye
towards certain wavelengths, and the fact that women have different color
perception versus men (and that doesn't get into those who have had cataract
surgery and can now see into the UV range, which throws perception off
dramatically from what most people consider 'normal.') Those generalizations
are outdated with any current science from roughly the 1970s on. Steven's
power law was shown to not hold up very well when considering individual
respondents.

~~~
cscheid
You are right, of course, but from the perspective of an engineer, these two
(admittedly broad) laws are _very good things to have around_.

"Essentially, all models are wrong, but some are useful" and all that.

------
inopinatus
Um. Curiously, that first example didn't work for me. Figures 1 & 2, under
"Light emission vs perceptual brightness" are compared thus: "On which image
does the gradiation appear more even? It’s the second one!"

Except that for me it isn't. The first one, graded by emission rather than
perception, appears more evenly graded to me. There is no setting I can find
using the Apple calibration tool (even in expert mode) that does anything but
strengthen this perception.

This raises only questions. Is this discrepancy caused by my Apple Thunderbolt
Display? By my mild myopia? The natural lighting? My high-protein diet? The
jazz on the stereo? The NSA? Or do I really have a different perception of
light intensity?

And is anyone else getting the same?

Note: I have always had trouble with gamma correction during game setup; there
has never been a setting I liked. Typically there'll be a request to adjust
gamma until a character disappears, but however I fiddle things it never does.

~~~
dspeyer
I also had trouble calling either "more even". The first one has greater
division at the darker end, and the second at the lighter, but they about
balance out.

The extreme for me was figure 12. A and B are so similar I can't see the line
between them, but C (the "corrected" square) is a completely different shade.

I'm viewing on a data projector. That's probably the reason. Still, it makes
me skeptical that there's anything display-agnostic you can do for gamma.

~~~
ZenPsycho
The major point the article makes is not about any particular display. It is
about image processing algorithms, such as color blending, anti-aliasing,
resizing, etc.

All these algorithms assume they are performing math on linear scale
measurements of physical light. However, most image data is not encoded as
linear scale samples of light intensity. Instead they are _gamma encoded_.

What the article gets slightly wrong though is that images are not gamma
encoded to deal with the non linear response to intensity of the human eye.
Instead, it's to deal with the non linear response of CRT displays to linear
amounts of voltage, as produced by camera sensors. The gamma encoding adjusts
the image data so that a display will correctly produce linear scales of light
intensity to match the physical light measured from a scene.

You are rightly skeptical that Gamma Encoding can't really deal with the broad
variety of different displays. However, it is still the case that most images
are gamma encoded with roughly gamma 2.2, and that all image _processing_
algorithms on the other hand assume gamma 1.0, and misbehave on data that is
gamma 2.2

It is, of course still the case that by chance, human visual response is
roughly the inverse of gamma 2.2. But bringing this up while trying to make a
point about performing operations on linear gamma data is somewhat
distracting.

------
Negitivefrags
Something that is important to note is that in photoshop the _default_ is
gamma incorrect blending.

If you work on game textures, and especially for effects like particles, it's
important that you change the photoshop option to use gamma correct alpha
blending. If you don't, you will get inconsistent results between your game
engine and what you author in photoshop.

This isn't as important for normal image editing because the resulting image
is just being viewed directly and you just edit until it looks right.

~~~
johnnovak
Yep, that bit about Photoshop is being mentioned in the description of Figure
8.

------
ansgri
Enough has been said about incorrect gamma (this and [0]), now I think it's
high time to bash the software of the world for incorrect downscaling (e.g.
[1]). It has much more visible effects, and has real consequences for computer
vision algorithms.

In the course on computer vision in my university (which I help teaching) we
teach this stuff to make students understand physics, but at the end of the
lecture I'd always note that for vision it's largely irrelevant and isn't
worth the cycles to convert image to the linear scale.

[0]
[http://www.4p8.com/eric.brasseur/gamma.html](http://www.4p8.com/eric.brasseur/gamma.html)

[1] [http://photo.stackexchange.com/questions/53820/why-do-
photos...](http://photo.stackexchange.com/questions/53820/why-do-photos-of-
digital-screens-turn-out-the-way-they-do)

~~~
goatface
The development version of GIMP (the 2.9 series) - which will be come 2.10
uses GEGL which always does scaling with linear light encoding; as well as
premultiplied alpha, regardless of what color "mode" the project is in.
Getting scaling and resampling right in all cases is easier than retroactively
fix the prevalent bad habit of non-linear compositing..

~~~
ansgri
γ-correct scaling is good, but will GIMP support area-downscaling? I.e. if you
downscale three times, you _average_ each three (or 3×3 if both axes) pixels,
not choose any one of them. Currently you get moire patterns, and
"interpolation" is not a correct way to do downscale more than ½.

~~~
mark-r
Pure averaging isn't the best way to go, that's basically a box filter. You
can use the interpolation kernels, but you need to widen them proportionally
to the amount you're downscaling. Instead of your bicubic working with a 4x4
area, for 3x downscaling it needs 12x12. Ideally for ½ scale you should be
using 8x8 already.

~~~
goatface
A box filter for scaling down cubic and linear is much better than using
impulse filters with the interpolated values at the floating point coordinates
in question. If someone wants to improve the code in GEGL (used for rotations,
scaling, perspective transforms, live on image warping as well as other
transforms in the development version of GIMP) the relevant place to put it in
is here:

[https://git.gnome.org/browse/gegl/tree/gegl/buffer/gegl-
samp...](https://git.gnome.org/browse/gegl/tree/gegl/buffer/gegl-sampler-
linear.c#n76)

For the linear case a "tent-filter" instead of a box filter would be really
correct, a similar box filter function exist in cubic. Both of these receive a
reverse jacobian matrix indicating the shape and extent of surrounding area in
source image to sample from.

~~~
mark-r
I'll have to check out that GEGL code when I have more time, thanks for the
link.

Yes, a linear interpolation is the same as a tent filter. See
[http://stackoverflow.com/a/12613415/5987](http://stackoverflow.com/a/12613415/5987)

To eliminate moire artifacts you need to remove all frequencies above the
Nyquist limit. In resizing applications there are _two_ Nyquist limits, one
for the input and one for the output; you need to filter for whichever is
smallest. When upsizing the input limit is always smallest, so the filters can
be constant. When downsizing the output limit is smallest so that's the one
you need to tune your filter for. That's why I suggest widening the
interpolation formula when downsizing.

I've been meaning for years to make a blog post on this subject. I don't think
many people realize that an interpolation formula is also a filter formula,
and that it can be manipulated and analyzed as such.

I have my own implementation of the classic filters that I use for my resizing
tasks. It works in linear gamma space as suggested in the article. I've
implemented lots of different algorithms, and I've settled on Lanczos-5 as the
best overall compromise. One interesting observation, the Catmull-Rom bicubic
interpolation is nearly indistinguishable from Lanczos-2.

------
crazygringo
This is one of the most fascinating articles I've come across on HN, and so
well explained, so thank you.

But I wonder about what the "right" way to blend gradients really is -- the
article shows how linear blending of bright hues results in an arguably more
natural transition.

Yet a linear blending from black to white would actually, perceptually, feel
too light -- exactly what Fig. 1 looks like -- the whole point is that a
black-to-white gradient looks more even if calculated in sRGB, and _not_
linearly.

So for gradients intended to look good to human eyes, or more specifically
that change at a perceptually constant rate, what is the right algorithm when
color is taken into account?

I wonder if relying just on gamma (which maps only brightness) is not enough,
but whether there are equivalent curves for hue and saturation? For example,
looking at any circular HSV color picker, we've very sensitive to changes
around blue, and much less so around green -- is there an equivalent
perceptual "gamma" for hue? Should we take that into an account for even
better gradients, and calculate gradients as linear transitions in HSV rather
than RGB?

~~~
johnnovak
I'm glad you enjoyed reading the article. I completely agree on not really
having a "right" way to blend gradients. For that reason I almost ended up
omitting the gradient example from the article because doing it "correctly" in
linear space does not always yield the most pleasant looking results.
Generating perceptually fine looking gradients is another huge (and very
interesting!) topic in itself, so maybe I'll discuss that in another post in
the future.

Until then, have a look at this online gradient generator tools that can use
five different algorithms (there's some explanation provided as well for each
method):

[http://davidjohnstone.net/pages/lch-lab-colour-gradient-
pick...](http://davidjohnstone.net/pages/lch-lab-colour-gradient-picker)

I also recommend reading the superb 'Subtleties of Color' NASA article series
on the matter:

[http://earthobservatory.nasa.gov/blogs/elegantfigures/2013/0...](http://earthobservatory.nasa.gov/blogs/elegantfigures/2013/08/05/subtleties-
of-color-part-1-of-6/)

PS: I'm slightly confused about your comment on the black to white gradient
though. Are you saying the gradient on Figure 1 looks more even to you than on
Figure 2? In that case, I think your monitor is miscalibrated, so try gamma-
calibrating it first then have another look. Your opinion might change :)

~~~
ars
I think you are supposed to add the colors then divide by two to keep the
brightness constant.

This is not how light behaves (it doesn't divide by two on a wall), but it's
what you need to make a gradient.

~~~
johnnovak
Not divide by two; you have to use the gamma transfer curve.

------
kazinator
I was going to comment snarkily: "Really? Every coder? What if you program
toasters?"

Then it immediately occurred to me that a toaster has some binary enumeration
of the blackness level of the toast, like from 0 to 15, and this corresponds
to a non-linear way to the actual darkness: i.e. yep, you have to know
something about gamma.

~~~
mrob
Bread darkening accelerates with time under heating as the water levels
reduce, so human vision isn't the only non-linearity here. For an ordinary
consumer product I'd just test various breads to find the average time needed
for light/medium/heavy toast, and linearly interpolate between those if I
needed finer granularity, no gamma correction required. Hardly anybody knows
about objective standards for toastedness, and they can always turn it up or
down next time.

Only if it's a fancy commercial toaster with optical feedback then gamma
correction could matter.

~~~
kazinator
The whole combined nonlinearity could be one "toaster gamma" function, mapping
from cooking time values to darkness level.

------
Alexey_Nigin
I tried viewing the article on 4 different monitors. All monitors had default
settings except for brightness. Monitors A & B were on new laptops, monitor C
was on a very old laptop, and monitor D was on a smartphone. Here are the
results:

FIGURES 1 & 2\. On monitor A, all bands of color in figure 1 were easily
discernible. The first four bands of color in figure 2 looked identically.
Figure 1 looked more evenly spaced than figure 2. On monitor B, all bands of
color in figure 1 were easily discernible. The first five bands of color in
figure 2 looked identically. Figure 1 looked more evenly spaced than figure 2.
On monitor C, all bands of color except the last two in figure 1 were easily
discernible. The first three bands of color in figure 2 looked identically.
Figure 1 looked about as evenly spaced as figure 2. The result from monitor D
was the same as the result from monitor A.

FIGURE 12. On monitors A and B, the color of (A) was closer to (B) than to
(C). On monitor C, (A) appeared equally close in color to (B) and (C). On
monitor D, the color of (A) was exactly identical to (B).

CONCLUSION: On monitor C, gamma correction had neutral effect. On all other
monitors, the effects were negative. Unfortunately, I was unable to find a
standalone PC monitor for my comparison. It is entirely possible that a PC
monitor would give a different result. However, since most people use laptops
and tablets nowadays, I doubt the article's premise that "every coder should
know about gamma".

~~~
odbol_
It's kind of silly how he says "everyone needs to know about this; it's so
important" and then the first example he preceded by saying that mobile phone
screens basically can't tell the difference and don't matter.

------
datenwolf
I think the deep underlying problem is not just handling gamma but that to
this day the graphics systems we use make programs output their graphics
output in the color space of the connected display device. If graphics system
coders in the late 1980-ies and early 1990-ies would have bothered to just
think for a moment and look at the existing research then the APIs we're using
today would expect colors in linear contact color space.

Practically all the problems described in the article (which BTW has a few
factual inaccuracies regarding the technical details on the how and why of
gamma) vanish if graphics operations are performed in a linear contact color
space. The most robust choice would have been CIE1931 (aka XYZ1931).

Doing linear operations in CIE Lab also avoids the gamma problems (the L
component is linear as well), however the chroma transformation between XYZ
and the ab component of Lab is nonlinear. However from a image processing and
manipulation point of view doing linear operations also on the ab components
of Lab will actually yield the "expected" results.

The biggest drawback with contact color spaces is, that 8 bits of dynamic
range are insufficient for the L channel; 10 bits is sufficient, but in
general one wants at least 12 bits. In terms of 32 bits per pixel practical
distribution is 12L 10a 10b. Unfortunately current GPUs experience a
performance penality with this kind of alignment. So in practice one is going
to use a 16 bits per channel format.

One must be aware that aside the linear XYZ and Lab color spaces, even if a
contact color space is used images are often stored with a nonlinear mapping.
For example DCI compliant digital cinema package video essence encoding is
specified to be stored as CIE1931 XYZ with D65 whitepoint and a gamma=2.6
mapping applied, using 12 bits per channel.

~~~
contingencies
Wow. May I ask how/where you learned all that?

~~~
jacobolus
If you want to learn about color, the best source on the web is
[http://www.handprint.com/LS/CVS/color.html](http://www.handprint.com/LS/CVS/color.html)

There are some (in my opinion) better explanations in books, but it’s hard to
link people to books. :-)

------
skierscott
I work on algorithms that can be applied to images, and was equally surprised
when I saw a video called "Computer color is broken."

I investigated and wrote a post called "Computer color is only kinda
broken"[1].

This post includes visuals and investigates mixing two colors together in
different colorspaces.

[1]:[http://scottsievert.com/blog/2015/04/23/image-
sqrt/](http://scottsievert.com/blog/2015/04/23/image-sqrt/)

------
tomjakubowski
Hi John!

If you're reading comments, I just thought you should know that the link to
w3.org in the (color) "Gradients" section is broken.

It should point to [https://lists.w3.org/Archives/Public/www-
style/2012Jan/0607....](https://lists.w3.org/Archives/Public/www-
style/2012Jan/0607.html) but there's an extra "o" at the end of the URL in
your page's link.

~~~
johnnovak
Thanks for that, I'll fix it later today!

~~~
tomjakubowski
Another one: "ray racer" is probably meant to be "ray tracer" (that, or my
imagination is far too small) - and links to another broken page here
[http://blog.johnnovak.net/tags/ray%20tracing](http://blog.johnnovak.net/tags/ray%20tracing)

~~~
johnnovak
Thanks, good catch! No matter how many times I proofread my writings, some of
these errors always slip through...

~~~
mctx
Also a couple of typos: "acceleation" and "sofware’s"

~~~
johnnovak
Cheers, corrected!

------
Glyptodon
The thing that seems a bit weird to me is that the constant light intensity
graduation (fig 1) appears much more even/linearly monotonic to me than the
perceptual one (fig 2) which seems really off at the ends, kind of sticking to
really really dark black for too long at the left end, shifting to white too
fast at the right end.

~~~
jusssi
You may want to check your display settings. Possibly too much contrast.

Many TVs have horribly excessive color saturation and contrast settings by
default, in order to look sharp and colorful (specifically, more sharp and
colorful than the next brand) at the store. Maybe that applies to computer
monitors too.

------
elihu
This is very good and useful; I'll have to update my ray-tracer accordingly.

One thing not discussed though is what to do about values that don't fit in
the zero-to-one range? In 3-D rendering, there is no maximum intensity of
light, so what's the ideal strategy to truncate to the needed range?

~~~
jacobolus
> what's the ideal strategy to truncate to the needed range?

This depends on your aesthetic goals. There’s no single right answer here.

There’s been a large amount of academic research into “high dynamic range
imaging”. If you do a google scholar search, you can find hundreds of papers
on the subject, I recommend you start with the most cited ones, and then
follow the citation graph where your interests lead you.

Or start with Wikipedia, [https://en.wikipedia.org/wiki/High-dynamic-
range_imaging](https://en.wikipedia.org/wiki/High-dynamic-range_imaging)

~~~
elihu
Mostly my goal is just "do the simplest thing that doesn't look bad". My naive
approach is to just clamp the values to not exceed 1.0, but it occurs to me
that it might be worth asking if there's something else I should be doing
instead.

I'm more interested in the computational and algorithmic side of ray-tracing,
so I care more about things like constructing optimal bounding volume
hierarchies than getting all the physically-correct-rending details right to
produce the absolute best possible output. I just don't want the output to be
ugly for easily fixable reasons.

~~~
2renderman
"getting all the physically-correct-rending details right"

So, the short answer to your real question is 'tone mapping' which...is kind
of dumb, imho. Clamping is probably fine.

The important thing to remember is that, while ray tracing is cool and fun to
code, it has no basis in physical reality. It's no more or less of a hack than
scanline polygon rendering (which is to say, you could possibly look at them
as approximate solutions to the 'rendering equation' with important things
variably ignored? but that's like saying y=x is a cheap approximation of
y=x^2...)

One cool hack to take a description of a scene graph as a set of polygons and
end up with an image of the scene is to be like "ok what polygon is under this
pixel in the target view? Which way is it facing? Which way are the lights
facing relative to it? What color is this material? Ok multiply that crap
together, make the pixel that color". That's good old fashioned scanline
'computer graphics'. Another cool hack is "well, what if we followed a 'ray'
out from each pixel, did angle-of-incidence-equals-angle-of-reflection, did
some csg for intersecting the rays with surfaces, see if we end up with a
light at the end and then multiply through the light and the material colors
blah blah blah" but its also just a hack.

I mean, it takes some loose inspiration from the real world I guess, but it's
not physically correct at all.

I mention this because I totally get where you are coming from. You might want
to check out some techniques that are physically-based though, because they
also have interesting implementations (mlt, photon mapping, radiosity)....you
might even find it useful to drive your physically-based renderer's sampling
bias from intermediate output of your ray tracer!

~~~
elihu
Plain Whitted-style ray tracing has a lot of shortcomings and in general
doesn't look great compared with what people expect from modern graphics, but
I don't think it's fair to say that ray tracing is "just a hack". Global
illumination methods such as path tracing, Metropolis light transport, and
photon mapping are much more accurate and are all fundamentally based on ray
tracing. (Radiosity is not, but then radiosity is tremendously slow and
doesn't handle non-diffuse surfaces.)

My goal is real-time rendering. I've met with some success; it's definitely
nowhere close to the fastest ray tracers around, but I can manage to pull off
around 30 fps on a well-behaved static scene with maybe a few tens of
thousands of triangles at 720x480 on a dual-socket broadwell Xeon setup with
24 cores. This means that it's fast enough to make simple interactive
simulations and/or games, which is what I mostly care about.

Ray tracing has a lot of advantages when it comes to building applications. I
can define by own geometric primitives that are represented by compact data
structures. I can do CSG. I can create "portals" that teleport any ray that
hits them to another part of the scene and use that to build scenes that
violate conventional geometry. I can trace rays to do visibility calculations,
collision detection, and to tell me what I just clicked on. I can even click
on the reflection of a thing and still be able to identify the object. There
may be ways to do some of these things in scanline renderers, but I find it
satisfying to be able to do them purely in software with a relatively simple
codebase.

I don't have the CPU resources or the skill at optimization to attempt global
illumination in real time, but there are other projects that are working on
that sort of thing. I have done non-real-time photon mapping before in an
earlier incarnation of my ray-tracer; maybe I'll port that forward some day.

(In case anyone is curious, my ray-tracer minus a lot of changes I've made in
the last month or so and haven't bothered to push yet can be found here:
[https://github.com/jimsnow/glome](https://github.com/jimsnow/glome))

------
panic
Nowadays GPUs are able to convert between sRGB and linear automatically when
reading and writing textures. There's no more excuse for incorrect rendering
on modern hardware!

~~~
jblow
Actually no, there are plenty of excuses, because this stuff gets super
confusing and it's easy to make a mistake.

~~~
kvark
How is The Witness doing w.r.t. gamma correctness? Any issues you can remember
as an example of "super confusing" stuff?

~~~
jblow
If we made any mistakes, they are pretty small.

What gets super confusing is that you have a bunch of different stuff flying
around. You have textures in different formats and render targets in different
formats (some are in sRGB, some are in HDR 16-bit floating-point, some are
other random formats somewhere in-between). You need to set up your shader
state to do the right thing for both the input texture and the render target,
and the nuances of how to do this are going to change from system to system.
Sometimes if you make a mistake it is easily spotted; other times it isn't.

And then there are issues of vertex color, etc. Do you put your vertex colors
in sRGB or linear space? Well, there are good reasons for either choice in
different contexts. So maybe your engine provides both options. Well, now
that's another thing for a programmer to accidentally get wrong sometimes.
Maybe you want to introduce typechecked units to your floating-point colors to
try and error-proof this, but we have not tried that and it might be annoying.

All that said, everyone is about to rejigger their engines somewhat in order
to be able to output to HDR TVs (we are in the process of doing this, and
whereas it is not too terrible, it does involve throwing away some old stuff
that doesn't make sense any more, and replace it by stuff that works a new
way).

~~~
mentos
Off topic but curious to know your thoughts on Unreal Engine 4 and if you feel
you could be as expressive in it as you would in your own proprietary engine?

~~~
pfranz
I'd love to hear a direct response, but here's[1] a quote from a few years ago
(specifically mentioned Unity, not UE4, but I assume the sentiment is the
same).

I can't find it, but I was also reading an article a few days ago about why
that person would choose to write their own engine over a third-party one. In
that article (maybe it was the comments), they specifically called out the
game mechanics of the The Witness and Braid as example of things traditional
engines don't do very well.

[1]
[https://www.reddit.com/r/Games/comments/282l0k/jonathan_blow...](https://www.reddit.com/r/Games/comments/282l0k/jonathan_blow_on_using_his_own_engine_i_would/)

~~~
mentos
That article is actually why I chose to use the word 'expressive' in my
question!

"..when I'm making these games, they're not just commercial products. They're
expressive works that we're working very hard to make, and we want them to
have as long of a lifetime as possible. So I would like this game to be
relevant 20 years from now, 40 years from now. How do you do that? Well, you
don't necessarily do that by building it on top of a very complicated system,
that you don't own, that somebody else will cease to support at some point in
the future."

Given that UE4 is open source I wonder if he still has the same sentiment.

As someone that has been working on a game in UE4 for 2.5 years the idea of
rolling and supporting my own engine/tools is dizzying.

------
jadbox
Interesting that Nim (lang) is used in the examples- good readable code too

~~~
qwertyuiop924
Yeah, Nim is interesting, and pretty nice for gamedev (efficient, GCed when
you want it, hand management when you don't, no stop-the-world, etc.).
Personally, I haven't worked with it much yet, as there are just too many
languages, and some of the semantics were immidiate turn-offs (static typing
with _very_ minimal inference, odd multi-assignment semantics, enforced use of
discard (which discourages minimal APIs, and encourages functions with side-
effects to have no return value, which is a Bad Thing), requiring forward
declarations (if you need it for metaprogramming, than why can Scheme and Lisp
avoid it?), not defaulting to bignums, dynamic dispatch chosen by the creator
of the original class, really clumsy macros, no hygene AFAICT (although two
namespaces and a module system make this _less_ of an issue: However, I doubt
the module system was built to protect hygene. Somebody should look into this,
so we can know for sure), really, _really_ clumsy macros (I mean _really_ ,
come on guys: macros aren't new: how did you even mess that up? You can
totally do better than that, even without parens. But if you add parens, it's
so much easier. Clojure even made it socially acceptable!)).

~~~
dom96
I agree with a lot of your points, and I can assure you that many of them are
on our to do list including removal of the need for forward declarations.
However, I very much disagree with your assessment of Nim's macros, what
exactly makes them "really really clumsy?"

I have worked with them a lot and they are very powerful (if a bit verbose),
despite this I was able to write a fairly nice implementation of async await
and just last weekend I made writing both synchronous and asynchronous IO code
much easier using a ``multisync`` macro[1]. This only took me ~100 lines[2].

1: Still a WIP, but already reduces code duplication A LOT:
[https://github.com/nim-
lang/Nim/blob/devel/lib/pure/httpclie...](https://github.com/nim-
lang/Nim/blob/devel/lib/pure/httpclient.nim#L897)

2: [https://github.com/nim-
lang/Nim/blob/devel/lib/pure/asyncmac...](https://github.com/nim-
lang/Nim/blob/devel/lib/pure/asyncmacro.nim#L374-L480)

~~~
qwertyuiop924
Of course they're _powerful_. They're true imperative macros, the very thing
that all Smug Lisp Weenies worship (I don't, because I'm not an SLW, but they
are Pretty Cool). The reason I think the macros are clumsy is pretty simple:
Nim is really, really bad at manipulating its own AST: It shuffles around,
munging data, and by the end, you have no idea what the result should look
like. Unlike Lisp, there are no templates in macros, which make seeing what
the result will be very easy.

This is how a debug macro, a very simple one, is implemented in Nim (taken
from the tutorial):

    
    
      macro debug(n: varargs[expr]): stmt =
        result = newNimNode(nnkStmtList, n)
        for i in 0..n.len-1:
          result.add(newCall("write", newIdentNode("stdout"), toStrLit(n[i])))
          result.add(newCall("write", newIdentNode("stdout"), newStrLitNode(": ")))
          result.add(newCall("writeLine", newIdentNode("stdout"), n[i]))
    
    

What the heck is going on? This is the equivalent Lisp (specifically, Chicken
Scheme, the dialect I am most familiar with, but it should be similar in most
lisps with imperative macros):

    
    
      (define-syntax debug
        (ir-macro-transformer
          (lambda (e i c)
            (let ((stmts (cdr e)))
              (map (lambda (stmt)
                     `(begin (write ,stmt)
                             (display ": ")
                             (newline)) 
                   stmts)))))
    

That's much easier to understand. It might be a bit less concise, but it more
than makes up for it.

I don't blame Nim for being bad at manipulating its own AST. Lisp is pretty
bad at it, too (no, lists are not the lisp AST, not in any lisp suitable for
the Real World. I don't know who told you that, but it's a lie: Most lisp ASTs
are just as complex as Nim's). What I do blame Nim for is not being
potentially homoiconic so there's no easy datastructure to represent code in,
or at least providing a datastructure that's easier to manipulate, or at the
_very_ least provide some mechanisms to make the existing structures easier to
manipulate.

~~~
qwertyuiop924
Okay, I did actually get the Scheme macro slightly wrong. That's why I usually
try to test this stuff in advance...

    
    
      (define-syntax debug
        (ir-macro-transformer
          (lambda (e i c)
            (let ((stmts (cdr e)))
              (cons 'begin (map (lambda (stmt)
                            `(begin (write ,stmt)
                                    (display ": ")
                                    (newline)) 
                            stmts)))))

------
mxfh
Good reminder about these persisting blending issues in the linear
interpretation of RGB values, which was well explained to non-coders as well
here in a quite popular _MinutePhysics_ video:
[https://www.youtube.com/watch?v=LKnqECcg6Gw](https://www.youtube.com/watch?v=LKnqECcg6Gw)

As others commented the gamma scaling issues seem even more relevant.

Just please, don't use RGB color space for generating gradients. In fact, it's
ill fitted for most operations concerning the perception of colors as is.

 _chroma.js_ : [https://vis4.net/blog/posts/mastering-multi-hued-color-
scale...](https://vis4.net/blog/posts/mastering-multi-hued-color-scales/)

 _D3_ :
[https://bl.ocks.org/mbostock/3014589](https://bl.ocks.org/mbostock/3014589)

Interesting excursion: historically the default viewing gammas seem to have
lowered, because broadcasting defaulted to dimly lit rooms, while today's
ubiquitous displays are usually in brighter environments.

[https://www.w3.org/Graphics/Color/sRGB.html](https://www.w3.org/Graphics/Color/sRGB.html)

------
mixmastamyk
The conclusion reminded me of the "unicode sandwich," i.e. decode on data
load, process in a pure form, then encode before writing to disk.

------
qwertyuiop924
I'd already seen most of this in a video (courtesy of Henry, aka
MinutePhysics,
[https://m.youtube.com/watch?v=LKnqECcg6Gw](https://m.youtube.com/watch?v=LKnqECcg6Gw)),
but it was nice to see a programmer-oriented explanation, nonetheless.

------
kristofferR
I get that this is an article about gamma, but it should have mentioned that
sRGB is on the way out. People who need to think about gamma also need to
think about wider color spaces like DCI-P3, which the Apple ecosystem is
moving to pretty quickly (and others would be dumb to not follow).

~~~
mark-r
Apparently DCI-P3 specifies 2.6 for the gamma, except that Apple sticks with a
gamma of 2.2. Hopefully color management will be able to compensate in all
situations. Either way, you still need to worry about gamma!

~~~
foolrush
Having peeled apart the iMac ICC, Apple is actually using the sRGB in their
EOTFs.

------
willvarfar
I'm divided; I really want the article to be true, and for everyone to realise
what a whole mistake we've been making all along... but, as the legions of us
who don't adjust for gamma demonstrate, ignoring it doesn't make the world
end?!

~~~
zubat
Ignoring the color space is hardly a world ender for most applications, but it
has an effect on quality that can be subtle to drastic depending on what
you're doing. For example, arcade games emulated in MAME often have a washed-
out look by default because the monitor gamma isn't included(but it can be
achieved through user customization).

One thing the article misses is a process or checklist to discover your
requirements for color spaces. Characterizing it as a gamma-only problem isn't
entirely correct since we also have completely different models of color(e.g.
LAB, HSV, YUV) that make tradeoffs for different applications. So, something
like:

1\. Input data: if it contains gamma data or specifies a color space use that,
else assume linear sRGB.

2\. Output device: is gamma correction done for you/can you access information
about the device capabilities and lighting situation? This can inform viewing
conditions. For example, if your smartphone has a light sensor this can be
used to adjust gamma as well as backlighting to achieve a near-linear
perceptual response. Most apps wouldn't consider doing this of course. If the
output is a file or stream determine the most likely use cases and convert as
necessary.

3\. Internal processing. For each image or signal you process, determine its
input color space, and the ideal color space to run it in. Then decide which
approximation is an appropriate trade-off for your application(since many
color space conversions are compute-intense), and implement conversions as
necessary. For many situations, gamma-corrected sRGB is "good enough" hence
its emphasis in the article.

~~~
astrange
> Ignoring the color space is hardly a world ender for most applications, but
> it has an effect on quality that can be subtle to drastic depending on what
> you're doing. For example, arcade games emulated in MAME often have a
> washed-out look by default because the monitor gamma isn't included(but it
> can be achieved through user customization).

Actually, no emulator does gamma-correct scaling, and when I've tried adding
some in the past a lot of games looked quite different and much darker. So
different that I don't think most players, who grew up on emus instead of the
real thing, would actually accept it.

It was hard enough getting them to accept correct aspect ratios, since NES
pixels weren't square either.

~~~
DiabloD3
The problem with gamma-correctness in emulators is you ALSO have to emulate
limited nominal ranges (digitally, 16-235 for Y, 16-240 for UV) on top of
doing NTSC or PAL coloring correctly.

Since doing limited ranges on sRGB looks so weird (mainly, 16 is surprisingly
bright for being black), some people gamma correct for 1.8 instead of 2.2...
which is a hilarious value when you realize that is the gamma value for
classic Macs.

~~~
astrange
I feel pretty good about the "unfiltered" display on an emulator being the
original RGB. Seems like adding that 16-235 NTSC YUV conversion on top of
5-bit-per-channel SNES RGB would just make banding even worse, and not really
look different?

I mean, I don't like running with the accurately rainbowy and scanliney
composite cable mode filters myself, and I thought I cared.

~~~
DiabloD3
It won't make the banding worse.

To do this naively: Mathematically, 5 bit is 32 values. Multiply each value
step by 8, and you get 0, 8 .. 256. Multiply each step by 7, and you get 0, 7
... 224. Offset each step by 16, you get 16, 17 ... 240.

Also, some emulators _do_ offer chroma subsampling effects just to make
certain things look more accurate.

That said, yes, all my emulation days involved "incorrect" SNES rendering: 5
bit per channel SNES RGB, with each step being +32, with no NTSC adjustment.

------
spunker540
I don't think this is something every coder should know about-- maybe every
graphics coder

~~~
johnnovak
Ah, the title again... I'll promise I'll choose my title more carefully next
time, so many people are hung up on this...

------
kevin_thibedeau
What is usually little mentioned is that the transfer function for LCDs is a
sigmoid rather than an exponential. The latter is simulated for desktop
displays to maintain compatibility with CRTs. Embedded LCDs don't usually have
this luxury.

~~~
johnnovak
That's some very good info and it explains why the checkerboard pattern
example looks completely off on all phones!

------
chmike
Does it mean that when doing a conversion from sRGB encoding to physical
intensity encoding we have to extend the number of bits to encode the physical
intensity values to avoid rounding errors in the sRGB encoding ?

I guess that that the required number of bits to encode physical intensity
values depends on the operations. performed. The author suggest using floats,
but this means 3x4 bytes and 4x4 bytes with the alpha channel. Would 16 bit
unsigned integer be enough ? Floats are ok when using graphic cards, but not
ok when using the processor.

~~~
elbows
16-bit integers are good enough for most image-processing algorithms. But you
have to code pretty careful to avoid overflow or loss of precision in your
intermediate values.

Floating point is much easier to work with, and sometimes makes a difference
with quality. On modern CPUs it's also about as fast as integer processing, if
not a bit faster sometimes.

On GPUs, it might actually be the other way around -- image processing tasks
tend to be limited by memory bandwidth, so you might get better performance
with 16-bit integers. But I haven't tried it.

------
emcq
Meh gamma is a simplistic nonlinearity to model the world; if you care about
perception use a CIE colorspace, if you care about gaming they have developed
more sophisticated nonlinearities for HDR.

------
nichochar
The design of your website, and it's readability, is great! Good job

~~~
johnnovak
Thanks man, I'm a bit obsessed with these things and have spent way too much
time on it. I'm glad someone notices and appreciates these details! :)

~~~
ShinyCyril
I absolutely loved the design too. Simple, readable, but still striking.

------
reduxive
This article could really benefit from an image DIFF widget. Even animated
flashing GIF images would be an improvement.

It needs something that not only permits comparable overlays, but (perhaps
with a third diff layer) also highlights the ugly/wrong pixels with a high-
contrast paint.

A handful of images are only somewhat obviously problematic, but for most of
the images, I really had to struggle to find undesirable artifacts.

If it's that difficult to discern inconsistent image artifacts, one can
understand why so little attention is often paid to this situation.

------
slacka
> The graphics libraries of my operating system handle gamma correctly. (Only
> if your operating system is Mac OS X 10.6 or higher)

Not just OS X. The majority of Linux games from the past 2 decades including
all SDL and id tech 1-3 games relied on X server's gamma function. An X.Org
Server update broken it about 6 years ago. It was fixed a few weeks ago.

[https://bugs.freedesktop.org/show_bug.cgi?id=27222](https://bugs.freedesktop.org/show_bug.cgi?id=27222)

~~~
johnnovak
My bad, I'm not really using Linux. I'll update the article, thanks! :)

------
j2kun
Is this why my computer screen's brightness controls always seem to have a
huge jump between the lowest two settings (off and dimmest-before-off)?

~~~
Symbiote
That's probably a sign of a cheap monitor.

You can try the calibration instructions here, which does a decent job of
calibrating the monitor, and showing you whether yours is good or not:
[http://www.lagom.nl/lcd-test/](http://www.lagom.nl/lcd-test/)

------
daredevildave
And if you want to use a WebGL engine with gamma correct rendering...
[https://playcanvas.com](https://playcanvas.com) ;-)

[http://developer.playcanvas.com/en/user-
manual/designer/sett...](http://developer.playcanvas.com/en/user-
manual/designer/settings/#gamma-correction)

------
amelius
> sRGB is a colour space that is the de-facto standard for consumer electronic
> devices nowadays, including monitors, digital cameras, scanners, printers
> and handheld devices. It is also the standard colour space for images on the
> Internet.

Ok, does that mean that the device performs the gamma-transformation for me,
and I don't need to worry about gamma?

(and if not, why not?)

------
kevinwang
On my iPhone, for the checkerboard resizing, the srgb-space resizing (b) is
almost an exact match, while C appears much whiter.

~~~
j4_james
I had the same problem on my laptop which, like many phones, has a high
density display. When a website hasn't explicitly provided support for high
DPI displays, the browser is forced to scale up the source image (typically by
200%). To see the expected effect, you just need to lower the browser zoom
level a couple of times to get the image back down to its native size. You'll
know you've got it right when the A block suddenly appears lighter and matches
the C block rather than B block (which will now look too dark).

~~~
dspeyer
I zoomed the image in and out a bunch -- no change. When I zoomed in enough, I
could see the checkerboarding in A, but the overall color stayed the same as
B.

~~~
j4_james
This technique may not work if you've got a weird DPI that's causing scaling
to something like 150%. In that case you'd need to set your browser's zoom
level to 66.666% which is probably not one of the levels it supports. My
display scales by 200% so I just needed to zoom out to 50% which has worked on
every browser I tried.

Another option is to save the image from the webpage to disk and then open
that image in a basic image editor (making sure the editor is not zoomed in at
all). I'm not sure how feasible this is if you're on a phone though.

What would have been ideal is if the author of the article had included srcset
alternatives for these images to cater for some of the more common high DPI
devices. Would then have just work automatically for most people and caused a
lot less confusion.

~~~
johnnovak
Thanks for the suggestion, I'll look into the scrset thing!

------
sriku
When viewing this on a macbook air, the discussion around the two images in
the section "Light emission vs perceptual brightness" appears weird. To me,
the first image _appears_ linearly spaced and in the second image I can hardly
make out the difference between the first few bars of black.

~~~
hjnilsson
Imagine there is a light source farthest to the right. Which bar would
accurately reflect how you expect the light to decrease from the source? It
should be the second (linear) bar, that the difference between the last few
levels of black is imperceptible could be attributed to the fact that the MBA
has a quite poor screen.

------
anotheryou
Can anyone recommend a tool/library to output at least full HD image fades to
a video with correct gamma? Preferably even with dithering for finer steps
when fading slowly.

My main problem is that I'm not good at on-the-fly encoding and outputting
frame by frame feels a bit excessive.

------
notlisted
Beautiful description and great examples. One thing confuses me. I'm actually
using PS CS5 (supposedly the last 'correct' one?) and resizing figure 11 to
50% actually results in B, not C. Is there an option/setting I can use to fix
this?

~~~
ondross
I think it's oddly worded - it's wrong as of CS6, meaning at the time of CS6
it is wrong, and it got fixed after that?

~~~
johnnovak
It's wrong in CS6, and that the only version of Photoshop I could test it
with.

------
Retric
Did anyone else think the first set of bars was linear not the second? I could
not notice any difference between the leftmost three bars on the bottom
section. Or does this relate to how iPad renders images or something? ed: Same
issue on PC.

~~~
johnnovak
It's most likely an iPad thing. Try viewing it on a real desktop monitor!

~~~
Retric
Nope, tried PC got same issue.

~~~
johnnovak
You need to calibrate your monitor then.

------
wfunction
Can anyone get IrfanView to output the correct image? I'm trying the latest
version I can find and it still gives me full gray.

------
catpolice
As a pedantic writer, it annoys me that the article starts by mentioning a
quiz and making a big deal about answering yes or no to the questions... but
there aren't actually any questions. The "quiz" is a list of statements. Each
one can be understood by context to imply a question about whether you agree
with the statement, but it's distracting because you can't answer yes to
something that isn't a question.

------
pilooch
I do AI, I let my CNN eat the gammas :)

------
platz
please dont use condescending titles barking what all coders should or
shouldnt know (invariably the topic is a niche that the author wants to cajole
others into caring about too)

~~~
BetterThanYou
You've been downvoted or whatever that is called here. But you're right. I
know all about that shit. I know more than most people have forgotten. But
it's not something everyone needs to know. I can't even use my skills in a
world where everyone needs a "full stack developer" but people keep pretending
they're the person who does the interviews and (not pretending) treats them as
an autistic "better than you" contest.

~~~
johnnovak
Sorry if you found the title condescending. In the intro section I also admit
that I was ignorant about the whole gamma topic a few weeks ago. But I think
it's an important topic, hence the title. Of course, 10 different people have
10 different opinions about what other people "should know". But this is a
personal blog, and this is my personal opinion :) I guess you can't please
everyone.

------
optimuspaul
Lost me when he said the fig 2 appeared to have a more even gradation than fig
1, and not just because of the spelling error. The fig 1 looked more even to
me, but I am colorblind.

~~~
optimuspaul
Ok, I went further and became even more annoyed. I think in all of the
examples the linear images looked way better. Could be due to the gamma
correction my Mac does, or maybe the authors opinions are vastly different
than mine.

~~~
johnnovak
It's not an opinion, check the references section for further info.

------
twothamendment
I know and love my gamma. She makes the best cookies!

------
jnordwick
Gamma?
[http://www.investopedia.com/terms/g/gamma.asp](http://www.investopedia.com/terms/g/gamma.asp)

