RGB Color Space & Gamma Correction

RGB Color Model

The RGB color model is an additive color model in which red, green, and blue light are added together in various ways to reproduce a broad array of colors.

Representation

Each color component (Red, Green, Blue) is typically represented by an 8-bit integer, ranging from 0 to 255.

  • Black: $(0, 0, 0)$
  • White: $(255, 255, 255)$
  • Red: $(255, 0, 0)$
  • Green: $(0, 255, 0)$
  • Blue: $(0, 0, 255)$

Gamma Correction

Gamma correction is a non-linear operation used to encode and decode luminance or tristimulus values in video or still image systems. It's crucial for displaying colors accurately on different devices.

Why Gamma?

Human perception of brightness is non-linear. We are more sensitive to changes in darker tones than in brighter tones. Displays also have a non-linear response to input voltage. Gamma correction compensates for this.

Gamma Encoding (Linear to sRGB)

The sRGB (standard Red Green Blue) color space uses a specific gamma curve. For linear light values $C_{linear}$ (ranging from 0 to 1), the sRGB component $C_{sRGB}$ is calculated as:

$$ C_{sRGB} = \begin{cases} 12.92 \times C_{linear} & \text{if } C_{linear} \leq 0.0031308 \ 1.055 \times (C_{linear})^{1/2.4} - 0.055 & \text{otherwise} \end{cases} $$

Gamma Decoding (sRGB to Linear)

To convert sRGB values back to linear light values $C_{linear}$:

$$ C_{linear} = \begin{cases} \frac{C_{sRGB}}{12.92} & \text{if } C_{sRGB} \leq 0.04045 \ \left(\frac{C_{sRGB} + 0.055}{1.055}\right)^{2.4} & \text{otherwise} \end{cases} $$


Practical Implementation

Python

 1import numpy as np
 2
 3def linear_to_srgb(c_linear):
 4    """Convert linear RGB to sRGB (per channel)"""
 5    c_linear = np.asarray(c_linear)
 6    return np.where(
 7        c_linear <= 0.0031308,
 8        12.92 * c_linear,
 9        1.055 * np.power(c_linear, 1/2.4) - 0.055
10    )
11
12def srgb_to_linear(c_srgb):
13    """Convert sRGB to linear RGB (per channel)"""
14    c_srgb = np.asarray(c_srgb)
15    return np.where(
16        c_srgb <= 0.04045,
17        c_srgb / 12.92,
18        np.power((c_srgb + 0.055) / 1.055, 2.4)
19    )
20
21# Example usage
22linear_val = 0.5  # 50% linear light
23srgb_val = linear_to_srgb(linear_val)
24print(f"Linear {linear_val:.4f} -> sRGB {srgb_val:.4f}")
25
26linear_decoded = srgb_to_linear(srgb_val)
27print(f"sRGB {srgb_val:.4f} -> Linear {linear_decoded:.4f}")

Go

 1package color
 2
 3import "math"
 4
 5func LinearToSRGB(c float64) float64 {
 6    if c <= 0.0031308 {
 7        return 12.92 * c
 8    }
 9    return 1.055*math.Pow(c, 1.0/2.4) - 0.055
10}
11
12func SRGBToLinear(c float64) float64 {
13    if c <= 0.04045 {
14        return c / 12.92
15    }
16    return math.Pow((c+0.055)/1.055, 2.4)
17}

Common Pitfalls

  1. Forgetting to linearize before blending: Always convert to linear RGB before doing math operations like blending, scaling, or filtering.
  2. Applying gamma twice: Don't gamma-correct already gamma-corrected values.
  3. Integer precision loss: Use floating-point for intermediate calculations.

When to Use

  • Always linearize before: image resizing, blending, lighting calculations, color space conversions
  • Keep in sRGB for: storage, display, transmission

Further Reading

Related Snippets