What Is Gamma?
Gamma describes the relationship between the input signal (what your software specifies) and the output luminance (what the screen displays). It's not a 1:1 relationship.
If you send a 50% gray pixel to your display, it doesn't output 50% brightness. With gamma 2.2, it outputs only about 22% brightness. This is intentional—human vision is more sensitive to dark tones, so gamma "expands" the dark range.
The Lookup Table (LUT)
Rather than computing a power function for every pixel, displays use a 256-entry lookup table. Think of it as a conversion chart:
256-ENTRY GAMMA LOOKUP TABLE
═══════════════════════════════════════════════════
Input Index → Output Value
─────────────────────────────────────────────────
0 → 0.000 (black)
32 → 0.012
64 → 0.050
96 → 0.116
128 → 0.216 (mid-gray input ≠ mid output!)
160 → 0.349
192 → 0.521
224 → 0.737
255 → 1.000 (white)
Each RGB channel has its own 256-value table
Total: 256 × 3 = 768 values control your screen
macOS CoreGraphics API
Tap Zap uses two essential CoreGraphics functions:
// Capture the current gamma state
CGGetDisplayTransferByTable(
displayID, // Which screen
256, // Table size
&redTable, // 256 floats for red
&greenTable, // 256 floats for green
&blueTable, // 256 floats for blue
&actualCount // Returns actual count
)
// Apply a new gamma table
CGSetDisplayTransferByTable(
displayID,
256,
modifiedRed,
modifiedGreen,
modifiedBlue
)
Each table entry is a CGGammaValue (Float) from 0.0 to 1.0. By scaling all values down, we darken the screen without touching the backlight.
Dimming via Gamma Scaling
To dim the screen to 50%, Tap Zap multiplies every LUT value by 0.5:
GAMMA TABLE MODIFICATION
═══════════════════════════════════════════════════
ORIGINAL (captured at startup):
Index: 0 64 128 192 255
Value: 0.00 0.05 0.22 0.52 1.00
MODIFIED (brightness = 50%):
Index: 0 64 128 192 255
Value: 0.00 0.025 0.11 0.26 0.50
↑
All values × 0.5
Result: Maximum possible output is now 0.50
Screen appears 50% dimmer
Backlight unchanged → NO FLICKER
Per-Channel Control
Tap Zap applies different multipliers to each channel for color temperature:
COLOR TEMPERATURE VIA CHANNEL SCALING
═══════════════════════════════════════════════════
NEUTRAL (6500K): R × 1.00 G × 1.00 B × 1.00
WARM (4000K): R × 1.00 G × 1.00 B × 0.50
↑
Blue reduced
DEEP WARM (2000K): R × 1.00 G × 0.50 B × 0.00
↑ ↑
Green reduced Blue gone
Combined with brightness factor:
final_value = original × brightness × color_factor
Why Tables Instead of Formulas?
Power Function
- Must compute V^γ for each pixel
- Billions of operations per frame
- Only simple curves possible
Lookup Table
- Just one array lookup per channel
- 768 values, applied by GPU
- Any arbitrary curve possible
The Full Pipeline
PIXEL → SCREEN PIPELINE
═══════════════════════════════════════════════════
┌──────────────┐
│ Application │ Sends RGB value (128, 64, 200)
└──────┬───────┘
↓
┌──────────────┐
│ Gamma LUT │ R: table[128] → 0.11 (dimmed)
│ (Tap Zap) │ G: table[64] → 0.025
│ │ B: table[200] → 0.38
└──────┬───────┘
↓
┌──────────────┐
│ GPU │ Renders with modified values
└──────┬───────┘
↓
┌──────────────┐
│ Display │ Shows dimmed, warm pixel
│ (100% PWM) │ Backlight at full power
└──────────────┘
From 768 Numbers to Zero Flicker
Tap Zap captures your display's gamma on launch, then scales those values in real-time as you adjust brightness and color temperature—all while your backlight stays at 100%.
256 × 3 = Complete control over your screen