///|
/// Gamma correction through a 256-entry lookup table:
/// `out = 255 * (c / 255) ^ (1 / value)`. `value > 1` lifts midtones,
/// `0 < value < 1` deepens them, and `1.0` is an exact identity. A
/// non-positive `value` falls back to the identity.
pub fn Image::gamma(self : Image, value : Double) -> Image {
let g = if value <= 0.0 { 1.0 } else { value }
let inv = 1.0 / g
let lut = FixedArray::make(256, 0)
for i in 0..<256 {
let normalized = i.to_double() / 255.0
lut[i] = (@math.pow(normalized, inv) * 255.0 + 0.5).to_int()
}
self.map_rgb(fn(r, g, b) { (lut[r], lut[g], lut[b]) })
}
///|
/// Vignette: darkens pixels toward the corners while leaving the geometric
/// center untouched. `strength` in `[0, 1]` is the fraction of light removed
/// at the far corners; the falloff follows the squared distance from center.
pub fn Image::vignette(self : Image, strength : Double) -> Image {
let s = if strength < 0.0 {
0.0
} else if strength > 1.0 {
1.0
} else {
strength
}
let out = self.copy()
let cx = (self.width - 1).to_double() / 2.0
let cy = (self.height - 1).to_double() / 2.0
let max_d2 = cx * cx + cy * cy
if max_d2 <= 0.0 {
return out
}
for y in 0..