Saturday, June 27, 2020

Visualizing Color Space in 2D

I present here an optimized visualization of a perceptually uniform 3D color gamut in 2D space, or "color map", and show a few ways it can be used. It is designed to provide persistent awareness of position within color space, so that it can be navigated, learned, and utilized more effectively by practitioners of digital color, such as painters, photographers, illustrators, etc.

This is a follow-up to my previous "Color Navigation Map" article, with the same goals but an alternate approach. In my view, this approach fully supersedes the previous one, although no single "best" visualization exists. Like a map projection, it represents a balance of competing tradeoffs.



5.0 JND, 541 colors

4.0 JND, 1,052 colors

3.0 JND, 2,509 colors




2.0 JND, 8,434 colors


1.0 JND, 67,455 colors

Technical Summary

The method used to generate these color maps is as follows:
  1. Generate a preliminary 5-bit color gamut in RGB space.
  2. Convert the gamut to Lab space under the standard D65 illuminant.
  3. Calculate the boundary of the gamut point cloud, obtaining an irregular concave polyhedron.
  4. Generate a final color gamut within the polyhedron, using a fixed ΔE resolution for perceptual uniformity.
  5. Section the final gamut at each L value.
  6. Convert each section to an RGB image, with exactly one pixel for each color.
  7. Arrange the sections laterally, with a axes horizontal, b axes vertical, and L axes aligned vertically.


By design, the final color map is extremely compact and efficient:




Why Map Color?

To begin, it bears mentioning why color mapping is worthwhile in the first place. In essence, it does for color what a geographical map does for global positioning. It tells the navigator where they are in space, what else is around, and in which direction to travel to reach their destination. Imagine how difficult navigation would be without maps. We are effectively "mapless" when it comes to color, which makes it harder than necessary to use and understand. Solving this problem is my goal.

Which Gamut To Map?

Now, some discussion on scope. For practical reasons, I attempt to visualize only the set of colors capable of being displayed by standard computer/phone screens. These screens generally show color in the RGB (red-green-blue) format with 8-bit resolution for each channel. Altogether, this paradigm provides 256^3 = 2^8^3 = 2^24 = 16.7 million unique colors. While RGB color is linear with respect to colored light, human perception of color is highly irregular and nonlinear. In other words, RGB is a good color model for screens, but not for people. It is therefore natural to introduce the Lab color space which by design is perceptually uniform.

Perceptual uniformity means that physical distance between color corresponds 1:1 with their perceived color distance (at least in theory - there are limitations). It's important to realize that color measurement can always be interpreted as geometry, e.g. a pixel's color can be plotted as a point in 3D space, and a gamut (set of colors) can be plotted as a point cloud in 3D space. Euclidean (straight-line) distance in Lab space is a very good measure of perceived color distance, and is conventionally denoted as ΔE, with the letter "E" tracing back to the original German empfindung for "sensation". It is also helpful to think of color distance in terms of JNDs, or just noticeable differences, commonly defined as 2.3 ΔE.

Filling the RGB gamut in Lab space with uniformly-distributed colors at ΔE = 2.3 = 1.0 JND generates about 67,000 unique colors. Compare this to the 16.7 million colors in the RGB gamut, and you'll see that perceptually speaking, we can do with many fewer colors. In fact, a gamut about 1/250 the size of the standard 8-bit RGB gamut is perceptually "complete", give or take. Of course, there are good practical reasons why a larger gamut is used. But for my purposes they are not relevant, so I use a smaller, perceptually uniform gamut instead.

As for the "best" ΔE value to use, it depends on the application. In some cases, a lower resolution is actually preferable, and a good example of this can be found in hardware stores paint swatches. It is actually helpful for there to be fewer choices, up to a point, because they are more differentiable, and more consideration can be given to each. In other cases, where very accurate matching is desired, a higher resolution will be preferable. For my purposes, primarily as an oil painter, I prefer a ΔE of 4.6, or 2.0 JND.


Hardware store paint swatches: a good example of the benefits of low resolution (Bill's True Value)

In short, the gamut I choose to map represents a perceptually uniform subset of all colors capable of being displayed by modern screens, with the resolution tailored to fit the application.

Visualization Goals

With the gamut established, the next task is deciding how to best visualize it. I stipulate that it be in 2D, i.e. that it be a simple image, so that a user can see all colors all at once without having to rotate, scroll, or otherwise interact. I also want it to be maximally continuous, consistent, and intuitive. By this I mean that colors should be near their closest neighbors, placed using the same set of "rules", and understood with minimal explanation.

Given the 2D constraint, it is not possible to be maximally continuous, consistent, and intuitive, all at once. In other words, it is not possible to meaningfully reduce the rank of color from 3D to 2D, in any general sense, without irreversible loss of information. This would be a true breakthrough, so it is not surprising that it has stubbornly resisted all attempts to "crack" it, from me and all previous color theorists.

After considering many visualization approaches, some of which are captured in the Appendix, I settled on a simple arrangement of cross-sections. This inherently introduces discontinuities (between cross-sections), but their quantity is minimized; otherwise this visualization is extremely consistent and intuitive.

Map Design

Given the cross-section approach, the next question is how to section, and how to arrange the sections. To minimize discontinuity, the natural choice is to take the sections along the axis with the smallest range. At 1.0 JND, the RGB gamut in Lab space has the following domains:

  • L: 2.3 to 98.9, 42 JND
  • a: -83.8 to 95.5, 78 JND
  • b: -105.6 to 92.2, 86 JND
Note that the size of the domains vary significantly by axis. Because of the perceptual uniformity of Lab space, position and direction are irrelevant to color distance - only the Euclidean distance matters. Therefore, it's completely valid to compare ΔE values regardless of which axis/axes they occur along.

The interpretation of this domain data is interesting: it is saying that L, or value/brightness, is the least perceptually consequential component of color. What we think of as hue (color name) and saturation (color intensity) are about twice as perceptually important. Hue and saturation are defined in Lab space as angle and distance about the L axis, which is also the "neutral" axis because all purely gray shades occur along it. The LCh color space uses these more intuitive coordinates, but it's fundamentally describing the same data. This is the same as how a point in 3D space can be described in Cartesian or spherical coordinates, and still refer to the same point.

RGB color gamut in Lab space, polyhedral boundary shown as gray mesh, uniformly filled

This indicates that the optimal section plane is a-b, i.e. perpendicular to / along the L axis, which has the smallest domain. Further, it suggests that the gamut is not exactly square, and that b is generally larger in magnitude than a, which turns out to be true. Therefore, to minimize the size of the color map image, the sections should be arranged with the b axis vertical, for better packing. This also lends itself nicely to having a horizontal and b vertical, which is more intuitive than vice versa due to the alphabetical Cartesian x-y convention. I arrange the sections such that their neutral axes are all aligned, they are as close together as possible without overlapping, with a and b positive corresponding to right and up, and with L increasing across sections from left to right.

Map Properties

To validate the method and build intuition about the visualization, it's worthwhile looking at the color map on a channel-by-channel basis.

 Lab hue channel

Lab hue corresponds to angle about the L/neutral axis, with an artificial appearance of a seam where 0° meets 360°. In reality, the two angles are equivalent, as in a circle.

 Lab saturation channel

Lab saturation corresponds to normal distance from the L/neutral axis, so each section has a circular appearance. Note that highly saturated colors occur more at higher values.

Lab value channel

Lab value is constant within sections, so there is a simple linear relationship across sections from left to right, progressing from darkest (black) to lightest (white).

Lab a channel

a is the horizontal distance within the section, corresponding to a theoretical green-magenta axis.

Lab b channel


b is the vertical distance within the section, corresponding to a theoretical blue-yellow axis. It is loosely accurate to think of it as a color temperature axis, from cool to warm.

Map Usage

The next question that should rightfully be asked is: so what? To answer that, I show three cases for how this color map can be used for interacting with color.

Case 1: which color should I pick?

I am not a fan of conventional color pickers such as those found in Photoshop, GIMP, etc., because they are fundamentally rooted in 2D and are disorientating to use.


Picking a color in GIMP in HSV space

Here, the color map has the advantage of keeping all colors visible and intuitively arranged at all times. The user can pick freely, find neighbors easily, and visualize how their selection fits within the broader gamut.


Demo of picking arbitrary colors from the color map

Case 2: where is this color within space?

Because of how relativistic and cognitive color perception is, it is often very challenging to actually identify "what" a color actually is. Its context can have a shockingly significant effect on how it is perceived, as many well-known optical illusions neatly show. It may be desirable to gauge or counteract these effects by showing where an arbitrary color, e.g. from an image, occurs within the color map.


Checker shadow illusion: A and B are the exact same color!

Because the color map has many fewer unique colors than images do, locating an arbitrary color on the color map is done by error minimization. It is approximate, not exact. In many cases, such as mine, exact matching is not important.


Demo: locating arbitrary image colors within the color map

Note that when the color is displayed as a standalone swatch and dissociated from its context, its appearance can change dramatically. For example, note that the cloud shadows are actually beige-brown, but appear gray in the context of the image.


Here, the dissociation shows that the grassy field, which "reads" as beige-brown, is actually better described as a strong orange.

Case 3: how is this image working?

A user may wish to better understand how a image is working, i.e. visualize how it is using the color gamut to achieve its effect(s). This is rarely trivial, so another case where visualization can provide insight.

The error minimization approach described above is fine for a single color, but for a whole image it is prohibitively slow. To work around this, I define a Lab precision in units of ΔE, round both image and map to the precision, then simply match exactly between the image and map. This is more approximate but operates dramatically faster. I set ΔE such that at least 95% of image colors are matched to map colors, which is typically around ΔE = 10. A small number of image colors are not matched and not shown on the map.

Here are some sample images, with the corresponding "usage" map beneath each, and some insights that can be gained from each. Any map color that is not used at least once is shown as "grayed out". Showing unused colors is important for a couple reasons: which colors are not used can be just as insightful as which colors are used, and the unused colors help visually position the used colors within the section.


  • Good value range - uses the gamut relatively fully without saturating limits
  • Saturated darks and highs, neutral mids
  • Color contrast primarily coming from b (blue-yellow or vertical) axis, minimal usage of a (green-magenta) axis for color differentiation



  • Nearly saturated on low end of value
  • Very warm palette - almost all values for b are positive
  • High saturation throughout value range


  • Value nearly saturated on both ends of range
  • Fairly constant green-yellow hue over value range
  • More saturation along b than a
  • Very warm temperature - all b values are positive, except in darks which are cooler


  • Very low saturation throughout image, clustering around neutral axis
  • Low end of value range is saturated
  • Image primarily based on value contrast
  • Higher saturation at higher value (sky region)


Results

Below are native-resolution (1 pixel per color) versions of my color map at various color resolutions for download. They are rendered in PNG format with a transparent background. These differ from the color maps shown in the introduction, which were upsampled for web display.


5.0 JND, 541 colors


4.0 JND, 1,052 colors


3.0 JND, 2,509 colors


2.0 JND, 8,434 colors


1.0 JND, 67,455 colors

Appendix: Alternate Sub-Optimal Visualizations



Along the way to developing this approach, I explored many alternate ideas, some of which are shown and briefly discussed here in chronological order.



Generated by sorting rows and columns according to various color properties. Depending on the sort parameters, some pleasant results are possible but not very useful. The common pitfall is noise/discontinuity and trying to fit the irregular gamut into a regular square.



A histogram approach, with the x axis representing hue. This looks good, but doesn't hold up under closer inspection, as within a single hue "bin", there is no good method of sorting value and saturation along a single dimension. Again, noise is a problem, though it is better hidden here.



A Mercator projection of the Lab gamut idealized on a spherical shell. The problem here is that saturation gets "left behind" by the geometric transforms, so it appears throughout the map as discontinuous sparse gray patches.



A projection of the gamut point cloud onto the a-b plane using a particle simulation to represent a projecting, divergent force. It works well initially, but falls apart as it works outward due to "leftover" colors with highly discontinuous values.




A compromise using a globe projection, wherein the color gamut is partitioned into "high" and "low" saturation hemispherical subsets which intersect at at the equator, exploiting the singularity at pure black for hemispherical continuity. This enables continuous encoding of hue and saturation, with 1-bit encoding of saturation. This approach could be extended to N "hemispheres" (here, N=2), but at the cost of sacrificing the intuitive globe framework.



A "pinwheel" model wherein N circular sectors have similar hue, and within a sector, radius corresponds to saturation and angle corresponds to value. This is not a bad visualization, but is fairly discontinuous and non-uniform.



Another particle simulation with attractive, repulsive, and cohesive forces simulated based on physical and color distance between particles. This produces interesting animations and results, but the result ultimately is not that good, and the axes essentially have no meaning.



A test of using a Sobel edge detection filter as a cost function to be minimized by random pixel swaps. Again, this method encodes no color information with the axes, merely arranges colors, and the cost function is prone to exploitation.



A few variations on the "rainbow smoke" algorithm which starts with a sparsely seeded image, then grows it outward pixel by pixel using color difference minimization at the border. I experimented with various seeding approaches and was somewhat successful at encoding hue with angle, but again this merely amounts to color arrangement, which may look nice but doesn't encode meaningful information. It is also very computationally expensive.

1 comment:

  1. Wow! what a great summary. Thank you for your research! :)

    ReplyDelete