///|
/// Additional production filters built from the core integer raster API.
pub(all) enum ToneMap {
Linear
Filmic
Reinhard
HighlightRollOff
} derive(Eq, Debug)
///|
fn tone_byte(value : Int) -> Byte {
(if value < 0 { 0 } else if value > 255 { 255 } else { value }).to_byte()
}
///|
/// Apply a tone map to RGB channels while preserving alpha.
pub fn Canvas::tone_map(
self : Canvas,
mode : ToneMap,
exposure : Int,
) -> Canvas {
let result = self.copy()
let safe_exposure = if exposure < 0 {
0
} else if exposure > 4000 {
4000
} else {
exposure
}
for i in 0.. Byte {
let scaled = value.to_int() * (1000 + safe_exposure) / 1000
let output = match mode {
Linear => scaled
Filmic => scaled * 1000 / (1000 + scaled)
Reinhard => scaled * 1200 / (1200 + scaled)
HighlightRollOff =>
if scaled > 180 {
180 + (scaled - 180) / 4
} else {
scaled
}
}
tone_byte(output)
}
result.pixels[i] = rgba(
map(source.red()),
map(source.green()),
map(source.blue()),
source.alpha(),
)
}
result
}
///|
/// Smooth a canvas using a separable three-tap kernel.
pub fn Canvas::smooth3(self : Canvas) -> Canvas {
let horizontal = Canvas::new(
width=self.width,
height=self.height,
fill=rgba(0, 0, 0, 0),
)
for y in 0..= self.width { x } else { x + 1 }
let left = self.pixels[left_x + y * self.width]
let center = self.pixels[x + y * self.width]
let right = self.pixels[right_x + y * self.width]
horizontal.pixels[x + y * self.width] = rgba(
((
left.red().to_int() + 2 * center.red().to_int() + right.red().to_int()
) /
4).to_byte(),
((
left.green().to_int() +
2 * center.green().to_int() +
right.green().to_int()
) /
4).to_byte(),
((
left.blue().to_int() +
2 * center.blue().to_int() +
right.blue().to_int()
) /
4).to_byte(),
((
left.alpha().to_int() +
2 * center.alpha().to_int() +
right.alpha().to_int()
) /
4).to_byte(),
)
}
}
let result = Canvas::new(
width=self.width,
height=self.height,
fill=rgba(0, 0, 0, 0),
)
for y in 0..= self.height { y } else { y + 1 }
let top = horizontal.pixels[x + top_y * self.width]
let center = horizontal.pixels[x + y * self.width]
let bottom = horizontal.pixels[x + bottom_y * self.width]
result.pixels[x + y * self.width] = rgba(
((
top.red().to_int() + 2 * center.red().to_int() + bottom.red().to_int()
) /
4).to_byte(),
((
top.green().to_int() +
2 * center.green().to_int() +
bottom.green().to_int()
) /
4).to_byte(),
((
top.blue().to_int() +
2 * center.blue().to_int() +
bottom.blue().to_int()
) /
4).to_byte(),
((
top.alpha().to_int() +
2 * center.alpha().to_int() +
bottom.alpha().to_int()
) /
4).to_byte(),
)
}
}
result
}
///|
/// Unsharp-mask a canvas using the smooth3 pass.
pub fn Canvas::unsharp3(self : Canvas, amount : Int) -> Canvas {
let safe_amount = if amount < 0 {
0
} else if amount > 1000 {
1000
} else {
amount
}
let blurred = self.smooth3()
let result = self.copy()
for i in 0.. Canvas {
let posterized = self.posterize(levels)
if smooth {
posterized.smooth3()
} else {
posterized
}
}
///|
/// Detect high-contrast pixels into an alpha mask.
pub fn Canvas::contrast_mask(self : Canvas, threshold : Int) -> Canvas {
let result = Canvas::new(
width=self.width,
height=self.height,
fill=rgba(0, 0, 0, 0),
)
for y in 0..= threshold {
rgba(255, 255, 255, distance.to_byte())
} else {
rgba(0, 0, 0, 0)
}
}
}
result
}