#![forbid(unsafe_code)]
use crate::c_types::{GifByteType, GifColorType};
use core::ffi::c_int;
const COLOR_ARRAY_SIZE: usize = 32768;
const BITS_PER_PRIM_COLOR: usize = 5;
const MAX_PRIM_COLOR: u8 = 0x1f;
#[derive(Clone)]
struct QuantizedColor {
rgb: [u8; 3],
new_color_index: u8,
count: i64,
}
struct ColorSubdiv {
rgb_min: [u8; 3],
rgb_width: [u8; 3],
num_entries: usize,
count: i64,
color_indices: Vec<usize>,
}
pub fn gif_quantize_buffer(
width: u32,
height: u32,
color_map_size: &mut c_int,
red_input: &[GifByteType],
green_input: &[GifByteType],
blue_input: &[GifByteType],
output_buffer: &mut [GifByteType],
output_color_map: &mut [GifColorType],
) -> bool {
let num_pixels = (width as usize) * (height as usize);
let mut color_array = Vec::with_capacity(COLOR_ARRAY_SIZE);
for i in 0..COLOR_ARRAY_SIZE {
color_array.push(QuantizedColor {
rgb: [
(i >> (2 * BITS_PER_PRIM_COLOR)) as u8,
((i >> BITS_PER_PRIM_COLOR) & MAX_PRIM_COLOR as usize) as u8,
(i & MAX_PRIM_COLOR as usize) as u8,
],
new_color_index: 0,
count: 0,
});
}
for i in 0..num_pixels {
let index = ((red_input[i] >> (8 - BITS_PER_PRIM_COLOR)) as usize)
<< (2 * BITS_PER_PRIM_COLOR)
| ((green_input[i] >> (8 - BITS_PER_PRIM_COLOR)) as usize) << BITS_PER_PRIM_COLOR
| (blue_input[i] >> (8 - BITS_PER_PRIM_COLOR)) as usize;
color_array[index].count += 1;
}
let mut subdivs: Vec<ColorSubdiv> = Vec::with_capacity(256);
for _ in 0..256 {
subdivs.push(ColorSubdiv {
rgb_min: [0, 0, 0],
rgb_width: [255, 255, 255],
num_entries: 0,
count: 0,
color_indices: Vec::new(),
});
}
let non_empty: Vec<usize> =
(0..COLOR_ARRAY_SIZE).filter(|&i| color_array[i].count > 0).collect();
if non_empty.is_empty() {
*color_map_size = 0;
return true;
}
subdivs[0].color_indices = non_empty;
subdivs[0].num_entries = subdivs[0].color_indices.len();
subdivs[0].count = (width as i64) * (height as i64);
let mut new_color_map_size: usize = 1;
if !subdiv_color_map(
&mut subdivs,
*color_map_size as usize,
&mut new_color_map_size,
&mut color_array,
) {
return false;
}
if new_color_map_size < *color_map_size as usize {
for entry in
output_color_map.iter_mut().take(*color_map_size as usize).skip(new_color_map_size)
{
entry.Red = 0;
entry.Green = 0;
entry.Blue = 0;
}
}
for i in 0..new_color_map_size {
let j = subdivs[i].num_entries;
if j > 0 {
let mut red: i64 = 0;
let mut green: i64 = 0;
let mut blue: i64 = 0;
for &ci in &subdivs[i].color_indices {
color_array[ci].new_color_index = i as u8;
red += color_array[ci].rgb[0] as i64;
green += color_array[ci].rgb[1] as i64;
blue += color_array[ci].rgb[2] as i64;
}
output_color_map[i].Red = ((red << (8 - BITS_PER_PRIM_COLOR)) / j as i64) as u8;
output_color_map[i].Green = ((green << (8 - BITS_PER_PRIM_COLOR)) / j as i64) as u8;
output_color_map[i].Blue = ((blue << (8 - BITS_PER_PRIM_COLOR)) / j as i64) as u8;
}
}
for i in 0..num_pixels {
let index = ((red_input[i] >> (8 - BITS_PER_PRIM_COLOR)) as usize)
<< (2 * BITS_PER_PRIM_COLOR)
| ((green_input[i] >> (8 - BITS_PER_PRIM_COLOR)) as usize) << BITS_PER_PRIM_COLOR
| (blue_input[i] >> (8 - BITS_PER_PRIM_COLOR)) as usize;
output_buffer[i] = color_array[index].new_color_index;
}
*color_map_size = new_color_map_size as c_int;
true
}
fn subdiv_color_map(
subdivs: &mut Vec<ColorSubdiv>,
color_map_size: usize,
new_color_map_size: &mut usize,
color_array: &mut [QuantizedColor],
) -> bool {
while color_map_size > *new_color_map_size {
let mut max_size: i32 = -1;
let mut index: usize = 0;
let mut sort_rgb_axis: usize = 0;
for i in 0..*new_color_map_size {
for j in 0..3 {
if (subdivs[i].rgb_width[j] as i32) > max_size && subdivs[i].num_entries > 1 {
max_size = subdivs[i].rgb_width[j] as i32;
index = i;
sort_rgb_axis = j;
}
}
}
if max_size == -1 {
return true;
}
let mut sort_array: Vec<usize> = subdivs[index].color_indices.clone();
sort_array.sort_by(|&a, &b| {
let ca = &color_array[a];
let cb = &color_array[b];
let hash_a = (ca.rgb[sort_rgb_axis] as i32) * 256 * 256
+ (ca.rgb[(sort_rgb_axis + 1) % 3] as i32) * 256
+ (ca.rgb[(sort_rgb_axis + 2) % 3] as i32);
let hash_b = (cb.rgb[sort_rgb_axis] as i32) * 256 * 256
+ (cb.rgb[(sort_rgb_axis + 1) % 3] as i32) * 256
+ (cb.rgb[(sort_rgb_axis + 2) % 3] as i32);
hash_a.cmp(&hash_b)
});
subdivs[index].color_indices = sort_array;
let half_count = subdivs[index].count / 2;
let mut sum = half_count - color_array[subdivs[index].color_indices[0]].count;
let mut num_entries: usize = 1;
let mut count: i64 = color_array[subdivs[index].color_indices[0]].count;
let total_entries = subdivs[index].num_entries;
let mut split_pos = 0;
for pos in 0..total_entries - 1 {
let next_idx = subdivs[index].color_indices[pos + 1];
if sum - color_array[next_idx].count < 0 {
split_pos = pos;
break;
}
if pos + 2 >= total_entries {
split_pos = pos;
break;
}
sum -= color_array[next_idx].count;
let cur_idx = subdivs[index].color_indices[pos + 1];
num_entries += 1;
count += color_array[cur_idx].count;
split_pos = pos + 1;
}
let max_color_val = color_array[subdivs[index].color_indices[split_pos]].rgb[sort_rgb_axis];
let min_color_val =
color_array[subdivs[index].color_indices[split_pos + 1]].rgb[sort_rgb_axis];
let max_color = (max_color_val as u32) << (8 - BITS_PER_PRIM_COLOR);
let min_color = (min_color_val as u32) << (8 - BITS_PER_PRIM_COLOR);
let second_half: Vec<usize> = subdivs[index].color_indices[split_pos + 1..].to_vec();
subdivs[index].color_indices.truncate(split_pos + 1);
let new_idx = *new_color_map_size;
let parent_rgb_min = subdivs[index].rgb_min;
let parent_rgb_width = subdivs[index].rgb_width;
subdivs[new_idx].color_indices = second_half;
subdivs[new_idx].count = subdivs[index].count - count;
subdivs[index].count = count;
subdivs[new_idx].num_entries = subdivs[index].num_entries - num_entries;
subdivs[index].num_entries = num_entries;
for j in 0..3 {
subdivs[new_idx].rgb_min[j] = parent_rgb_min[j];
subdivs[new_idx].rgb_width[j] = parent_rgb_width[j];
}
subdivs[new_idx].rgb_width[sort_rgb_axis] = (subdivs[new_idx].rgb_min[sort_rgb_axis] as u32
+ subdivs[new_idx].rgb_width[sort_rgb_axis] as u32
- min_color) as u8;
subdivs[new_idx].rgb_min[sort_rgb_axis] = min_color as u8;
subdivs[index].rgb_width[sort_rgb_axis] =
(max_color - subdivs[index].rgb_min[sort_rgb_axis] as u32) as u8;
*new_color_map_size += 1;
}
true
}