Hacker News new | past | comments | ask | show | jobs | submit login
What every coder should know about gamma (johnnovak.net)
560 points by johnnovak on Sept 21, 2016 | hide | past | web | favorite | 183 comments

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/

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....

Ah, this line width problem with vector graphics has been bothering me for ages... Now it makes perfect sense! I suspected most fonts are designed for incorrect gamma handling, after all, designers just look at the gamme-incorrect output and then tweak it until it looks right.

By the way, as I mentioned in my article, Photoshop has an option to use a custom gamma for text antialiasing, which is set to 1.42 by default (check out the antialiasing section). Vector graphics programs could adopt a similar workaround and things would be mostly fine.

>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.

Be sure to check you're not using gamma-incorrect scaling algorithms, they'd have that effect as well.

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... 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.

Your exactly right, my bad... I'll correct the article later today (and I'll also remove the word 'logarithmic' from a few places). Thanks!

"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.

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.

A power law plotted on a logarithmic scale will be a straight line. log(x^2.2) == 2.2*log(x).

You need log transformations on both axes for this to work.

For those whose algebra is rusty:

Let y = ax^n

Then log(y) = log(ax^n) = log(a)+log(x^n) = log(a) + log(x)*n

i.e. and equation for a line when plotting log(y) vs. log(x).

Yeah OK, after a little reflection I see where my mistake was. I wish there was a way to delete my comment.

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.

I highly doubt you perceive light intensity radically differently to your fellow humans... :) Most likely your monitor is miscalibrated.

Try a few different gamma calibration images from other sources (Google Images -> "gamma calibration") and if they consistently indicate that your monitor is miscalibrated, then you have your answer.

Well he never said he was a human. For all we know he could be The Terminator trying to find John Connor in Google Maps.

Instant upvote :)

How do you know GP is male?

I have the same problem as the GP, on four monitors. All four, bought at different times in different countries, are miscalibrated according to these calibration images. Is that plausible? If not, what might be going on?

And if it _is_ plausible that four random monitors are all miscalibrated in the same way, why should we optimize for well-calibrated monitors?

If you read the article carefully, you'll realise that this is not just an optimisation (check for example the sections on colour blending and rendering; those artifacts are gamma calibration independent.)

I'm on a Retina Macbook Pro, and the white/black checkerboard square (A in Figure 12) matches the (128,128,128) gray (B), not the lighter one (C).

Looking closer, it seems like my computer is, like, anti-aliasing the image itself. In Digital Color Meter, the white and black pixels are both grays. See screenshot below, the magnified area is from square A in the browser. When I downloaded the image and opened it in Preview it is black and white like it's supposed to be.

Screenshot: https://cl.ly/2T2U2J0A3v31 (If you're on a Mac maybe try opening the image in an image viewer)

Anyone know what's going on? I also can't distinguish between the first few black bars in Figure 2.

When I disobey the don't-zoom directive ("These examples will only work if your browser doesn’t do any rescaling on the images below") I see what your screenshot has. Try pressing Cmd+0 (command zero) to reset your browser zoom to Actual Size and then the pattern should match C instead of B.

EDIT: Sorry, the above applies to my non-retina external display that I have hooked up to my retina macbook. When I view the test images on my internal retina display, I do see the issue you describe (pattern matches B). If I press cmd+- (command minus) a few times until I'm at a 50% zoom level, the issue is resolved and the pattern matches C! Makes some sense actually, since showing a normal dpi photo at 50% on a retina makes for a 1:1 pixel mapping :) Showing an image at 100% on a retina makes for a 1:2 pixel mapping (each pixel from the image ends up being 2x2=4 physical pixels), which disobeys the don't-rescale directive.

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.

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.

If the resolution you're sending into the projector matches the native/optimal/recommended resolution of the projector (in at least the long dimension, and it will letterbox the other dimension), and you set the digital keystone to neutral/zero, you will achieve a 1:1 pixel mapping that is required to be in compliance with the author's statement that "These examples will only work if your [browser|projector|etc]* doesn’t do any rescaling on the images below."

* Author only said browser, but actually everything in the chain matters. If you're not at a 1:1 pixel mapping you're resampling, and resampling breaks the checkerboard example. Digital keystone (but not optical keystone with tilting lenses) included.

More even = an even gradation from black to white.

I thought the same, but then in the rest of the post the gamma adjusted images looked better.

Hey - I'm viewing the page on a macbook air and see what you see.

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.

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

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

[1] http://photo.stackexchange.com/questions/53820/why-do-photos...

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..

γ-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 ½.

It already does, in git master GEGL/GIMP does averaging of pixels contributing in the area when scaling down. The previous development releases in the 2.9 series would only do proper downscaling for the nohalo and lohalo samplers. But since may of this year GEGL also does an area/averaging box filter for cubic and linear samplers.

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.

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:


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.

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

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.

ugh, yes. smart objects in photoshop downscale way uglier than pure pixel images (even when rasterizing the smart objects after the transform), no Idea why.

The resizing algorithm set in the photoshop preferences (bicubic, etc) is applied when resizing smart objects. I have found that if I am unhappy with how one algorithm looks, I switch to another, free transform the smart object (but make no sizing adjustment) and hit enter. The object will be rerendered with the newly selected algorithm.

I found the settings, but somehow it's just still not the same. I now do the smart object stuff until it's all settled, than resize to 100%, rasterize and resize back to what I had set.

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?

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):


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


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 :)

It took me a while, but I finally figured out why your color gradients were different in nature to the grayscale gradient.

All of your color gradients use a constant light output. You transition from one fully saturated primary to another, or two fully saturated primaries to two others. For that to look natural, the interpolated values should also produce a constant light output.

The grayscale ramp is not a constant light output, and the light intensity needs to follow a perceptual curve to look natural.

On the greyscale gradient- the other way around: Figure 2 looks more even, being a perceptually linear gradient, ie. calculated "gamma incorrectly". Which is to say it's not what a real light would look like (if a light had linear falloff in illumination of this surface?).

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.

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

As I read it, for "physical correctness" gamma is the most important consideration.

When selecting your initial color palette, though, it is true hue and saturation do have nonlinear perception.

There are (somewhat complicated) color spaces designed to deal with that: https://en.wikipedia.org/wiki/Lab_color_space

http://www.husl-colors.org/ lets you play with a compromise color space.

Maybe gradients should be computed in a more perceptually oriented colorspace like LAB, YCbCr, YIQ, XYZ, etc.

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.

Speaking of non-traditional applications of Gamma...

In the 3d printing world, we have a filament of PLA (corn based plastic) with wood dust mixed in the plastic, of around 20%. This gives a filament that feels, machines, and colors like wood.

And if you extrude at varying temps, you can give the part lighter or darker colors (darkness dependant on higher temp).

I can almost guarantee that there's no gamma correction, let alone darkness calculation, being done for filaments like that. And it would follow an inverse power distribution - the more heat, the higher the power and the lower the final energy result (burnt = lower potential energy in wood).

So yeah, even if you don't do graphics, it still applies. Damn...

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.

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

As arguably vision is the most important sensory channel in human-computer interaction, I'd say it doesn't hurt for all coders to have at least a vague understanding of gamma. I think it should be part of the "basic education". Of course, it's just my opinion :)

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".

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.

Looks like you missed all the other artifacts I gave examples for, which are all calibration independent (colour blending, compositing, rendering etc.)

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.

> If graphics system coders in the late 1980-ies and early 1990-ies would have bothered to just think [...] the APIs we're using today would expect colors in linear contact color space.

Nope. As you point out, if you use 8-bit integers to represent colors, you absolutely want to use a gamma-encoded color space. Otherwise you’re wasting most of your bits and your images will look like crap. In the 80s/90s, extra bits per pixel were expensive.

Linear encoding only starts to be reasonable with 12+ bit integers or a floating point representation.

> The most robust choice would have been CIE1931

RGB or XYZ doesn’t make any difference to “robustness”, if we’re just adding colors together. These are just linear transformations of each other.

> (the L component is linear as well) [...] 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.

This is not correct.

It is true that the errors you get from taking affine combinations of colors in CIELAB space are not quite as outrageous as the errors you get from doing the same in gamma-encoded RGB space.

> RGB or XYZ doesn’t make any difference to “robustness”, if we’re just adding colors together. These are just linear transformations of each other.

What I meant was, that there are so many different RGB color spaces, that just converting to "RGB" is not enough. One picture may have been encoded in Adobe RGB, another one in sRGB. And even after linearization they're not exactly the same. Yes, one can certainly bring them into a common RGB. But then you can as well transform into a well defined contact color space like XYZ.

> It is true that the errors you get from taking affine combinations of colors in CIELAB space are not quite as outrageous as the errors you get from doing the same in gamma-encoded RGB space.

That's what I meant with "expected" results. In general Lab is a very convenient to work with color space. I was wrong though about L being linear. It's nonlinear as well, but not in such a nasty way as sRGB is.

Care to elaborate on the technical inaccuracies you spotted in the article? I would like to correct them, that's why I'm asking.

The explanation of why there this kind of gamma mapping was introduced in the first place is inaccurate. An often cited explanation is the nonlinear sensual response of human vision; if you wanted to accurately model that you'd need a logarithmic mapping. The true reason for the need of gamma correction is the nonlinear behaviour of certain kinds of image sensors and display devices. CRT displays, be the very physics they are based on have an inherent nonlinearity that's approximated by a gamma=2.2. When LCDs got introduced they of course were made to approximate the behaviour of CRT displays, so that you could substitute them without further ado.

Another important aspect to consider is, that using just gamma is not the most efficient way to distribute the bits. You want a logarithmic mapping for that; which also has the nice side effect, that a power law gamma value ends up as a constant scaling factor to the logarithmic values.

Now, it's also important to understand that these days the bread-and-butter colorspace is sRGB and that complicates things. sRGB has the somewhat inconvenient property that for the lower range of values its actually _linear_ and only after a certain threshold it continues (differentiable) with a power law curve. That's kind of annoying, because with that you no longer can remap logarithmically. And of course converting from and to sRGB can be a bit annoying because of that threshold value; you certainly can no longer write it as a convenient one-liner in a GPU shader for example. That's why modern OpenGL profiles also have special sRGB framebuffer and image formats and reading from and writing to them will perform the right linearization-mapping.

However either way what the explanation for gamma is, the important takeaway is, that to properly do image processing the values have to be converted into a linear color space for things to work nicely. Ideally a linear contact color space.

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

If you want to learn about color, the best source on the web is 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. :-)

The L component of CIE Lab is not linear, it has a similar non-linearity to sRGB due to one of the aims of the design of the CIE Lab space is to be perceptually uniform.

Yes, but you can certainly linearize that for image operations and much easier than sRGB (which has a nasty change of mapping functions midway).

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.


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.... but there's an extra "o" at the end of the URL in your page's link.

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

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

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

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

Cheers, corrected!

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.

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.

Your display is severely miscalibrated. Do a gamma calibration first then try again, things should look very different then!

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?

> 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

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.

"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!

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)

I'd say scanline rendering is "more of a hack" than ray tracing, but both can produce useful looking results, of course. They are both crude approximations of reality, but ray tracing is a closer model and it makes non-local phenomena easier to model (especially if you do things in a physically-correct way, respecting the law of conservation of energy etc.) Arguably, you would end up writing a "Universe simulator" if you wanted to accurately model absolutely everything that's happening in the real world :)

By the way, I've written about this exact same topic in my initial ray tracing post:


One way is to spread out the too-high value across neighbouring pixels, so-called bloom. This helps in creating a sensation of brightness even if of course no pixel actually has a higher value than 1.0.

The easiest way is to make a copy of the image, subtract 1.0 from the copy, blur that a bit and then add it on top of the original. This should of course be done before you go into gamma space, at which point you do clamp to 1.0.

Just clamping at 1.0 is fine. HDR tonemapping trying to emulate human vision is mostly bullshit and generally looks awful anyway.

As long as you're rendering in a linear space (which means simply that you've gamma-corrected all your inputs) and displaying with a display gamma applied, then you're fine.

Beyond that you might choose to emulate a "film look" by applying an S-curve to your image.

EDIT: also, you really don't want to be clamping at all when saving images - just write out a floating-point OpenEXR file.

> HDR tonemapping trying to emulate human vision is mostly bullshit and generally looks awful anyway.

This is just because most of the academic researchers in the field (basically mathematicians/programmers) have horrible aesthetic taste and poor understanding of human vision, not because the concept of local contrast control is inherently bad.

HDR “tonemapping” when well done doesn’t call attention to itself, and you won’t notice it per se.

This is a problem with image processing in general. Researchers are looking for something to demo at SIGGRAPH to non-artists. They want something very obvious and fancy on a few hand-picked images for a 5 minute talk. They aren’t necessarily trying to build general-purpose tools for artists.

Real photographers use all kinds of trickery to control contrast and dynamic range. Ansel Adams tweaked the hell out of his photographs at every stage from capture to print, using pre-exposed negatives, non-standard chemistry timings, contrast-adjusting masks sandwiched with negatives, tons of dodging and burning, etc.

Right, but what you are talking about is a very involved, creative process (what I would call color grading), not an automatic tone-mapping operator which you would apply on an image and hope it doesn't look like crap.

I think “color grading”, “color correction”, “photo retouching”, “image editing”, etc. are all pretty terrible names. Unfortunately there isn’t a great alternative name.

The old term was “printing”, but to a layman that now connotes pressing a button to get an inkjet or whatever.

* * *

You can certainly come up with automatic operators which look better than “hard clip everything brighter than 1.0”.

I agree though that there should be more work put into making usable tools for artists instead of “let’s magically fix the picture in one click”.

Maybe you're referring to "The HDR Look", the one with halos and high saturation.

Check out these photos - https://imgur.com/a/cisJY - this was shot with a DSLR with 14 stops of dynamic range. My eyes couldn't see much in the dark spots when I was looking at the bright buildings.

These same RAW photos look horrible (and I mean really, unbearably bad) with "linear mapping".

Edit using a scene referred application and select a decent display referred transform.

ACES has a decent enough transform.

Please read up on scene referred to display referred transforms. You can read about them at http://cinematiccolor.com, which has a Visual Effects Society endorsed PDF available.

Ultimately it depends on what aesthetic you are aiming for and the context you are displaying it.

You don't truncate, you map.

That PDF looks excellent, thanks! I will link to it in the further reading section.

Check out these couple of articles for some nice tone-mapping functions: http://filmicgames.com/archives/category/tonemapping

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!

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

Do everything in linear light, and convert to the proper gamma curve afterwards. It's almost the only way to be sure.

If you want to be really sure you're correct do everything in the XYZ colour space and then finally convert to the correct RGB space. Which is pretty much unknown for most displays by the way, if you're lucky it's something similar to sRGB.

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

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).

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?

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...

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.

I don't work on The Witness, but here is an example of an issue I noticed just today on the game I work in.

Look at the particles inside the pit at the bottom of the screen. http://imgur.com/BQjJD25

Can you see what is wrong?

Now take a look at what it used to look like: http://imgur.com/9z9b1cq

After seeing the correct version the problem is really obvious.

The particular problem here is that at some point in the pipeline the colour of the particles were stored linearly instead of in sRGB. That results in the colour banding.

A programmer who was working on the engine managed to accidentally break this, and the thing that really sucks about it is that the issue is really subtle and nobody noticed it for quite some time. Thankfully it didn't get to production before I noticed it, but that was complete chance. It could easily have gone out like that.

I love Path of Exile! Thanks for working hard at making it great!

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

Yeah, Nim is my favourite language currently! The expressivity and succinctness of Python with the speed of C, plus useful metaprogramming constructs. I recommend everyone to give it a go!

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!)).

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...

2: https://github.com/nim-lang/Nim/blob/devel/lib/pure/asyncmac...

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
      (lambda (e i c)
        (let ((stmts (cdr e)))
          (map (lambda (stmt)
                 `(begin (write ,stmt)
                         (display ": ")
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.

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
      (lambda (e i c)
        (let ((stmts (cdr e)))
          (cons 'begin (map (lambda (stmt)
                        `(begin (write ,stmt)
                                (display ": ")

Could you expand on the lack of hygiene? I know Nim has hygienic templates by default, with an opt for 'injecting' variables into the enclosing scope (which comes in handy when (for example) you have a DSL for parsing command line options like the docopt.nim or commandeer packages).

If you mean that whatever AST is generated by calling macro gets put in the enclosing scope - then yes, AFAIK you're right, but this can be fixed by wrapping a macro in a proc (function).

I'll agree that forward declaration is really clumsy, I hope that that is fixed before 1.0.

I meant that ASTs were non-hygenic, with all the problems therein, and nary a gensym to be seem, AFAICT.

That's pretty bad.

I also fail to see how wrapping a macro in a proc would help, unless you mean the call to the macro at the expansion site, which is a pretty clumsy thing to do, and actually doesn't fix most of the problems.

> ... and nary a gensym to be seem ...

You mean this? http://nim-lang.org/docs/macros.html#genSym,NimSymKind,strin...

Ah, there it is. Thanks.

That makes things a little bit better, but still.

Defaulting to bignums would turn every + in the program from O(1) to O(N). That makes secure programming pretty difficult - this attack's already bad enough:


It's that, or have every + risk integer overflow.

It's a small thing, but it's nice to do. Or at least detect overflows and move to bignum in the default numerical implementation. It's not end-of-the-world if you don't, but it helps avoid a lot of bugs...

I really recommend having the program trap on overflow. There are some proposals like as-if-infinitely-ranged, which is like bignums but without having to actually store a bignum.

It's really problematic to use them, though. Every integer would turn into a pointer, and the O(N) thing really is a problem. If you're doing secure coding you need careful control of data dependencies, since they create side-channel leaks, and nobody who makes "safe" languages appreciates this.

Plus most people don't need numbers that big. I think it was even a mistake to make size_t 64-bit.

C actually makes it really hard to trap on overflow. It's one of the biggest reasons C kinda sucks.

The way a lot of HLLs do it is to detect a potential overflow, and convert to bignum if needed.

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

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...

D3: 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.


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.

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

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).

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!

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

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?!

Probably most people working in an industry related to graphics have no idea about this and things mostly work. If you get a dark band around something you can tweak it until it looks right or is good enough.

I'm troubleshooting a problem right now where two separate applications blend images together and the result is different. Both results have been shipped to clients for years and fixing it is more of "tidying things up."

But doing it right the first time or being aware helps things look right more often and can enable you to do more complex things without constantly having to tweak the results.

The world wouldn't end without computers either, or in fact, without humans, so this is a moot point... :)

As I mentioned in the article, you can get away with working directly in sRGB in some cases (general image processing, non-photorealistic rendering), but in some other cases it's a must (physically accurate photorealistic rendering). But ignoring it will always produce wrong results. You actually might like those results, but the important point I was trying to get through was to be aware of these potential issues, and then you may ignore them at your own peril.

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.

> 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.

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.

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.

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.

I didn't really miss it because the article wasn't about colour spaces, just gamma :) It was meant as an introductory article for coders who have never heard about gamma correction. For those people, knowing to convert from sRGB to linear before processing and then back is sufficient in 90+% of the cases. But those are some good points you listed in your comment, but I'd say those are more specialised areas that don't really fall into the everyday image processing category.

Nope, world doesn't end, but scaled images look wrong, some more than others. For more examples see http://www.4p8.com/eric.brasseur/gamma.html

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

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

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.

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

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.

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.

Yes, we need greater precision, ideally floats, but maybe 16 bits would be enough (haven't tested this or thought much about it.) But for multiple passes of processing floats are definitely the way to go due to rounding errors. I don't think you'd get worse performance when using floats on current hardware.

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.

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

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! :)

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

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.

> 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.


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

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)?

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/

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


> 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?)

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

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).

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.

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.

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

Yep, for me too on a 4k HiDPI screen. The reason is that the browser scales the original image, using the naive incorrect algorithm. Amazing full-circle confirmation of the author's point, eh?

Which is why he recommends not viewing the examples on a phone. Too bright is less noticeable than too dark, so I guess you're lucky it's off in that direction.

It's also possible that the image file is a PNG with a gAMA chunk, which sometimes gets rendered incorrectly by browsers.

I get a similar behaviour on my Android phone. As I mentioned in the article, that example doesn't work on most phone LCD screens. Try it on a real desktop monitor!

> Try viewing it on a real desktop monitor!

You keep saying this, but many people don't have a real desktop monitor. Laptop sales exceed desktop sales and both are dwarfed even by tablets alone.

So whether it's technically the correct approach is irrelevant for a large proportion of potential end-users, for whom it will look broken.

Sorry, I can't magically fix all the mobile device screens of the world so they will start displaying images in a gamma-correct way... The fact is, the owners of such devices will never see gamma correct images on their screens, at least until the manufacturers fix this. It sucks but I can't help there.

Anyway, I think you have bigger problems than gamma with those devices anyway (glare, reflections etc.), so that's probably the least of your concerns.

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.

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.

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.

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?

Thanks, I'm glad you enjoyed the article. The only way I could get Photoshop to resize correctly was to convert the image to 32-bit first, do the resizing, then convert back to 8/16-bit (there might other simpler ways, though, but this is guaranteed to work). The image description actually describes this process.

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?

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

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.

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

Nope, tried PC got same issue.

You need to calibrate your monitor then.

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.

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.

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

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)

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.

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.

If you don't code around gamma then don't bother reading? I mean it says in the title, right there "about gamma", I don't see how that's drawing anyone else but the intended audience. I don't even deal in the article's subject matter but still found it interesting, informative, and well written; I think the condescending intent is coming entirely from you.

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.

It's always refreshing to meet people who never make spelling errors! :) Other than that, try calibrating your monitor!

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.

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

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

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact