///|
/// Palette selection mode for quantized frames.
pub(all) enum DitherMode {
NoDither
Ordered2x2
FloydSteinberg
} derive(Eq, Debug)
///|
pub fn Palette::nearest(self : Palette, color : Rgba) -> Int {
nearest_palette_index(color, self.colors)
}
///|
pub fn Palette::contains(self : Palette, color : Rgba) -> Bool {
let mut found = false
for value in self.colors {
if value == color {
found = true
}
}
found
}
///|
/// Serialize an RGB color table without GIF sub-block framing.
pub fn Palette::to_rgb_bytes(self : Palette, table_size~ : Int) -> Bytes {
let size = if table_size < self.colors.length() {
self.colors.length()
} else {
table_size
}
let bytes : Array[Byte] = []
for i in 0.. {
bytes.push(0)
bytes.push(0)
bytes.push(0)
}
Some(color) => {
bytes.push(color.red())
bytes.push(color.green())
bytes.push(color.blue())
}
}
}
Bytes::from_array(bytes[:])
}
///|
fn dithered_color(color : Rgba, x : Int, y : Int, mode : DitherMode) -> Rgba {
match mode {
NoDither | FloydSteinberg => color
Ordered2x2 => {
let matrix = [[0, 2], [3, 1]]
let amount = (matrix[y % 2][x % 2] - 1) * 16
rgba(
clamp_byte_int(color.red().to_int() + amount),
clamp_byte_int(color.green().to_int() + amount),
clamp_byte_int(color.blue().to_int() + amount),
color.alpha(),
)
}
}
}
///|
fn add_error(
values : Array[Int],
index : Int,
error : Int,
numerator : Int,
) -> Unit {
values[index] += error * numerator / 16
}
///|
fn quantize_floyd(
canvas : Canvas,
base : QuantizedFrame,
transparent : Rgba?,
) -> QuantizedFrame {
let palette = base.palette()
let indices : Array[Byte] = Array::make(canvas.width * canvas.height, b'\x00')
let mut current_r = Array::make(canvas.width + 2, 0)
let mut current_g = Array::make(canvas.width + 2, 0)
let mut current_b = Array::make(canvas.width + 2, 0)
let mut next_r = Array::make(canvas.width + 2, 0)
let mut next_g = Array::make(canvas.width + 2, 0)
let mut next_b = Array::make(canvas.width + 2, 0)
for y in 0.. ()
Some(index) => indices[x + y * canvas.width] = index.to_byte()
}
} else {
let adjusted = rgba(
clamp_byte_int(color.red().to_int() + current_r[x + 1]),
clamp_byte_int(color.green().to_int() + current_g[x + 1]),
clamp_byte_int(color.blue().to_int() + current_b[x + 1]),
color.alpha(),
)
let index = palette.nearest(adjusted)
indices[x + y * canvas.width] = index.to_byte()
let chosen = palette.colors[index]
let error_r = adjusted.red().to_int() - chosen.red().to_int()
let error_g = adjusted.green().to_int() - chosen.green().to_int()
let error_b = adjusted.blue().to_int() - chosen.blue().to_int()
add_error(current_r, x + 2, error_r, 7)
add_error(current_g, x + 2, error_g, 7)
add_error(current_b, x + 2, error_b, 7)
add_error(next_r, x, error_r, 3)
add_error(next_g, x, error_g, 3)
add_error(next_b, x, error_b, 3)
add_error(next_r, x + 1, error_r, 5)
add_error(next_g, x + 1, error_g, 5)
add_error(next_b, x + 1, error_b, 5)
add_error(next_r, x + 2, error_r, 1)
add_error(next_g, x + 2, error_g, 1)
add_error(next_b, x + 2, error_b, 1)
}
}
let old_r = current_r
let old_g = current_g
let old_b = current_b
current_r = next_r
current_g = next_g
current_b = next_b
next_r = old_r
next_g = old_g
next_b = old_b
for i in 0.. QuantizedFrame {
let base = match transparent {
None => quantize(canvas, max_colors~)
Some(color) => quantize(canvas, max_colors~, transparent=color)
}
match mode {
NoDither => base
Ordered2x2 => {
let palette = base.palette()
let indices : Array[Byte] = []
for y in 0..
indices.push(palette.transparent_index().unwrap_or(0).to_byte())
_ =>
indices.push(
palette.nearest(dithered_color(color, x, y, mode)).to_byte(),
)
}
}
}
QuantizedFrame::{ palette, indices }
}
FloydSteinberg => quantize_floyd(canvas, base, transparent)
}
}