====== Brightness ====== In the simplest terms the "brightness" of a pixel is its luminosity value as perceived by a human eye (making infrared and gamma rays \(0\) "bright"). There are quite a few competing standards and definitions of what the means exactly, but for our purposes we will use the \(L\) component of the HSL color space as our "brightness" value of a pixel. Again, in the simplest terms, increasing or reducing pixel's brightness can be interpreted as adding or subtracting a fixed "gain" value (others may argue that a more natural approach would be to use a multiplier, while others still may bring the "vibrancy" approach into picture). ===== In RGB color space ===== The mathematically correct way to do the brightness is to first convert into HSL color space, adjust the L (luma) component, and then convert back into RGB. That gives the correct result but costs way too many transistors and cycles. So we've got to improvise! ==== Algorithm ==== Here's the algorithm to follow, assuming \(R, G, B \in [0..4095]\) and \(br \in [-1024..+1023]\): - calculate luminosity - adjust the brightness additively and clamp the value to range \([0..4095]\) - figure out the slope for each components based on whether luma is below or above 50% and set chroma components to values that correspond to that 50% luma - figure out if the new luma is going to cross the 50% boundary and if so - "flip the slopes" - recalculate RGB components ==== Calculation reference ==== To calculate luminosity we just find the max and min of the triplet and get a simple average: \[L = \frac{min(R, G, B)+max(R, G, B)}{2}\] Brightness adjustment is a trivial addition, clamping the value to its proper limits: \[ L` = L + br \\ L` \in [0..4095] \] The slope \(k_R\) for the red component calculation depends on whether \(L\) is above or below the middle: \[ k_{R} = \begin{cases} R / L & \text{if} \; L \leq 2047 \\ \frac{R - 4095}{L - 4095} & \text{if} \; L > 2047 \end{cases} \] Finding the "middle point" value also depends on whether the \(L\) is above or below the middle: \[ R = \begin{cases} k_R * 2047 & \text{if} \; L \leq 2047 \\ 4095 - k_R * 2047 & \text{if} \; L > 2047 \end{cases} \] If we are crossing the middle luma boundary as the result of this adjustment - flip the slope: \[ k_R = 2 - k_R \] Applying the new \(L`\) to R component and clamping the result is trivial: \[ R` = k_R * (L` - 2047) + R \\ R` \in [0..4095] \] \(G\) and \(B\) calculations are similar to \(R\). ===== In HSL color space ===== When using the HSL color space the adjustment is as simple as elementary school's arithmetic operation. Namely - just a simple addition. With that the implementation of the Brightness adjustment can be as simple as the following (where the "luma", or brightness component \(\in[0..100]%%\)): // pseudo-code void brightness(/*array of pixels*/image, int _br){ for(const & pixel: image){ pixel.luma = std::clamp(pixel.luma + _br, 0, 100); } }