use indexmap::IndexSet;
use rgb::RGBA8;
use crate::{
colors::{BitDepth, ColorType},
headers::IhdrData,
png::{PngImage, scan_lines::ScanLine},
};
#[must_use]
pub fn reduced_palette(png: &PngImage, optimize_alpha: bool) -> Option<PngImage> {
if png.ihdr.bit_depth != BitDepth::Eight {
return None;
}
let ColorType::Indexed { palette } = &png.ihdr.color_type else {
return None;
};
let mut used = [false; 256];
for &byte in &png.data {
used[byte as usize] = true;
}
let black = RGBA8::new(0, 0, 0, 255);
let mut condensed = IndexSet::with_capacity(palette.len());
let mut byte_map = [0; 256];
let mut did_change = false;
for (i, used) in used.iter().enumerate() {
if !used {
continue;
}
let color = *palette.get(i).unwrap_or(&black);
byte_map[i] = add_color_to_set(color, &mut condensed, optimize_alpha);
if byte_map[i] as usize != i {
did_change = true;
}
}
let data = if did_change {
png.data.iter().map(|b| byte_map[*b as usize]).collect()
} else if condensed.len() != palette.len() {
png.data.clone()
} else {
return None;
};
let palette: Vec<_> = condensed.into_iter().collect();
Some(PngImage {
ihdr: IhdrData {
color_type: ColorType::Indexed { palette },
..png.ihdr
},
data,
})
}
fn add_color_to_set(mut color: RGBA8, set: &mut IndexSet<RGBA8>, optimize_alpha: bool) -> u8 {
if optimize_alpha && color.a == 0 {
color.r = 0;
color.g = 0;
color.b = 0;
}
let (idx, _) = set.insert_full(color);
idx as u8
}
#[must_use]
pub fn sorted_palette(png: &PngImage) -> Option<PngImage> {
if png.ihdr.bit_depth != BitDepth::Eight {
return None;
}
let palette = match &png.ihdr.color_type {
ColorType::Indexed { palette } if palette.len() > 1 => palette,
_ => return None,
};
let mut enumerated: Vec<_> = palette.iter().enumerate().collect();
let keep_first = most_popular_edge_color(palette.len(), png);
let first = keep_first.map(|f| enumerated.remove(f));
enumerated.sort_by(|a, b| {
let color_val = |color: &RGBA8| {
let a = i32::from(color.a);
((a & 0xFE) << 18) + (a & 0x01)
- i32::from(color.r) * 299
- i32::from(color.g) * 587
- i32::from(color.b) * 114
};
color_val(a.1).cmp(&color_val(b.1))
});
if let Some(first) = first {
enumerated.insert(0, first);
}
let (remapping, palette): (Vec<_>, Vec<RGBA8>) = enumerated.into_iter().unzip();
if remapping.iter().enumerate().all(|(a, b)| a == *b) {
return None;
}
let mut byte_map = [0; 256];
for (i, &v) in remapping.iter().enumerate() {
byte_map[v] = i as u8;
}
let data = png.data.iter().map(|&b| byte_map[b as usize]).collect();
Some(PngImage {
ihdr: IhdrData {
color_type: ColorType::Indexed { palette },
..png.ihdr
},
data,
})
}
#[must_use]
pub fn sorted_palette_mzeng(png: &PngImage) -> Option<PngImage> {
if png.ihdr.bit_depth != BitDepth::Eight || png.ihdr.interlaced {
return None;
}
let palette = match &png.ihdr.color_type {
ColorType::Indexed { palette } if palette.len() > 2 => palette,
_ => return None,
};
let matrix = co_occurrence_matrix(palette.len(), png);
let edges = weighted_edges(&matrix);
let mut remapping = mzeng_reindex(palette.len(), edges, &matrix);
apply_most_popular_color(png, &mut remapping);
apply_palette_reorder(png, &remapping)
}
#[must_use]
pub fn sorted_palette_battiato(png: &PngImage) -> Option<PngImage> {
if png.ihdr.bit_depth != BitDepth::Eight || png.ihdr.interlaced {
return None;
}
let palette = match &png.ihdr.color_type {
ColorType::Indexed { palette } if palette.len() > 2 => palette,
_ => return None,
};
let matrix = co_occurrence_matrix(palette.len(), png);
let edges = weighted_edges(&matrix);
let mut remapping = battiato_reindex(palette.len(), edges);
apply_most_popular_color(png, &mut remapping);
apply_palette_reorder(png, &remapping)
}
fn apply_palette_reorder(png: &PngImage, remapping: &[usize]) -> Option<PngImage> {
let ColorType::Indexed { palette } = &png.ihdr.color_type else {
return None;
};
if remapping.iter().enumerate().all(|(a, b)| a == *b) {
return None;
}
let mut new_palette = Vec::new();
let mut byte_map = [0; 256];
for (i, &v) in remapping.iter().enumerate() {
new_palette.push(palette[v]);
byte_map[v] = i as u8;
}
let data = png.data.iter().map(|&b| byte_map[b as usize]).collect();
Some(PngImage {
ihdr: IhdrData {
color_type: ColorType::Indexed {
palette: new_palette,
},
..png.ihdr
},
data,
})
}
fn most_popular_edge_color(num_colors: usize, png: &PngImage) -> Option<usize> {
let mut counts = [0_u32; 256];
for line in png.scan_lines(false) {
if let &[first, .., last] = line.data {
counts[first as usize] += 1;
counts[last as usize] += 1;
}
}
let max = counts
.iter()
.take(num_colors)
.enumerate()
.max_by_key(|&(_, v)| v)
.unwrap();
let max_equal = counts.iter().filter(|&v| v == max.1).count();
if max_equal > 1 {
return None;
}
Some(max.0)
}
fn most_popular_color(num_colors: usize, png: &PngImage) -> (usize, u32) {
let mut counts = [0_u32; 256];
for &val in &png.data {
counts[val as usize] += 1;
}
counts
.iter()
.copied()
.take(num_colors)
.enumerate()
.max_by_key(|&(_, v)| v)
.unwrap_or_default()
}
fn apply_most_popular_color(png: &PngImage, remapping: &mut [usize]) {
let most_popular = most_popular_color(remapping.len(), png);
if most_popular.1 < png.data.len() as u32 * 3 / 20 {
return;
}
let first_idx = remapping.iter().position(|&i| i == most_popular.0).unwrap();
if first_idx >= remapping.len() / 2 {
remapping.reverse();
remapping.rotate_right(first_idx + 1);
} else {
remapping.rotate_left(first_idx);
}
}
fn co_occurrence_matrix(num_colors: usize, png: &PngImage) -> Vec<Vec<u32>> {
let mut matrix = vec![vec![0_u32; num_colors]; num_colors];
let mut prev: Option<ScanLine> = None;
let mut prev_val = None;
for line in png.scan_lines(false) {
for i in 0..line.data.len() {
let val = line.data[i] as usize;
if val > num_colors {
continue;
}
if let Some(prev_val) = prev_val.replace(val) {
matrix[prev_val][val] += 1;
matrix[val][prev_val] += 1;
}
if let Some(prev) = &prev {
let prev_val = prev.data[i] as usize;
if prev_val > num_colors {
continue;
}
matrix[prev_val][val] += 1;
matrix[val][prev_val] += 1;
}
}
prev = Some(line);
}
matrix
}
fn weighted_edges(matrix: &[Vec<u32>]) -> Vec<(usize, usize)> {
let mut edges = Vec::new();
for (i, m_row) in matrix.iter().enumerate() {
for (j, val) in m_row.iter().enumerate().take(i) {
edges.push(((j, i), val));
}
}
edges.sort_by(|(_, w1), (_, w2)| w2.cmp(w1));
edges.into_iter().map(|(e, _)| e).collect()
}
fn mzeng_reindex(num_colors: usize, edges: Vec<(usize, usize)>, matrix: &[Vec<u32>]) -> Vec<usize> {
let mut remapping = vec![edges[0].0, edges[0].1];
let mut sums = Vec::new();
let mut best_sum_pos = 0;
let mut best_sum = (0, 0);
for (i, m_row) in matrix.iter().enumerate() {
if i == remapping[0] || i == remapping[1] {
continue;
}
let sum = (i, m_row[remapping[0]] + m_row[remapping[1]]);
if sum.1 > best_sum.1 {
best_sum_pos = sums.len();
best_sum = sum;
}
sums.push(sum);
}
while !sums.is_empty() {
let best_index = best_sum.0;
let mut delta: isize = 0;
let n = (num_colors - sums.len()) as isize;
for (i, &index) in remapping.iter().enumerate() {
delta += (n - 1 - 2 * i as isize) * matrix[best_index][index] as isize;
}
if delta > 0 {
remapping.insert(0, best_index);
} else {
remapping.push(best_index);
}
sums.swap_remove(best_sum_pos);
if !sums.is_empty() {
best_sum_pos = 0;
best_sum = (0, 0);
for (i, sum) in sums.iter_mut().enumerate() {
sum.1 += matrix[best_index][sum.0];
if sum.1 > best_sum.1 {
best_sum_pos = i;
best_sum = *sum;
}
}
}
}
remapping
}
fn battiato_reindex(num_colors: usize, edges: Vec<(usize, usize)>) -> Vec<usize> {
let mut chains = Vec::new();
let mut vx = vec![(0, 0); num_colors];
for (i, j) in edges {
let vi = vx[i];
let vj = vx[j];
if vi.0 == 0 && vj.0 == 0 {
vx[i].0 = 1;
vx[i].1 = chains.len();
vx[j].0 = 1;
vx[j].1 = chains.len();
chains.push(vec![i, j]);
} else if vi.0 == 0 && vj.0 == 1 {
vx[i].0 = 1;
vx[i].1 = vj.1;
vx[j].0 = 2;
let chain = &mut chains[vj.1];
if chain[0] == j {
chain.insert(0, i);
} else {
chain.push(i);
}
} else if vi.0 == 1 && vj.0 == 0 {
vx[j].0 = 1;
vx[j].1 = vi.1;
vx[i].0 = 2;
let chain = &mut chains[vi.1];
if chain[0] == i {
chain.insert(0, j);
} else {
chain.push(j);
}
} else if vi.0 == 1 && vj.0 == 1 && vi.1 != vj.1 {
vx[i].0 = 2;
vx[j].0 = 2;
let (a, b) = if vi.1 < vj.1 { (i, j) } else { (j, i) };
let ca = vx[a].1;
let cb = vx[b].1;
let chainb = std::mem::take(&mut chains[cb]);
for &v in &chainb {
vx[v].1 = ca;
}
let chaina = &mut chains[ca];
if chaina[0] == a && chainb[0] == b {
for v in chainb {
chaina.insert(0, v);
}
} else if chaina[0] == a {
chaina.splice(0..0, chainb);
} else if chainb[0] == b {
chaina.extend(chainb);
} else {
let pos = chaina.len();
for v in chainb {
chaina.insert(pos, v);
}
}
}
if chains[0].len() == num_colors {
break;
}
}
chains.swap_remove(0)
}