first commit
This commit is contained in:
parent
ba33aba577
commit
ec636f160b
8 changed files with 711 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
/target
|
||||
/.DS_Store
|
||||
/.vscode
|
||||
331
Cargo.lock
generated
Normal file
331
Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,331 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "adler32"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439989e6b8c38d1b6570a384ef1e49c8848128f5a97f3914baef02920842712f"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "color_quant"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "738c290dfaea84fc1ca15ad9c168d083b05a714e1efddd8edaab678dc28d2836"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
"lazy_static",
|
||||
"memoffset",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deflate"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174"
|
||||
dependencies = [
|
||||
"adler32",
|
||||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
||||
|
||||
[[package]]
|
||||
name = "gif"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3a7187e78088aead22ceedeee99779455b23fc231fe13ec443f99bb71694e5b"
|
||||
dependencies = [
|
||||
"color_quant",
|
||||
"weezl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.23.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder",
|
||||
"color_quant",
|
||||
"gif",
|
||||
"jpeg-decoder",
|
||||
"num-iter",
|
||||
"num-rational",
|
||||
"num-traits",
|
||||
"png",
|
||||
"scoped_threadpool",
|
||||
"tiff",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jpeg-decoder"
|
||||
version = "0.1.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2"
|
||||
dependencies = [
|
||||
"rayon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.112"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435"
|
||||
dependencies = [
|
||||
"adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
|
||||
dependencies = [
|
||||
"adler",
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-iter"
|
||||
version = "0.1.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "octo"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"image",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.16.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crc32fast",
|
||||
"deflate",
|
||||
"miniz_oxide 0.3.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"crossbeam-deque",
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
"lazy_static",
|
||||
"num_cpus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scoped_threadpool"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "tiff"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437"
|
||||
dependencies = [
|
||||
"jpeg-decoder",
|
||||
"miniz_oxide 0.4.4",
|
||||
"weezl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "weezl"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8b77fdfd5a253be4ab714e4ffa3c49caf146b4de743e97510c0656cf90f1e8e"
|
||||
12
Cargo.toml
Normal file
12
Cargo.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "octo"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
image="0.23.14"
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
||||
6
README.md
Normal file
6
README.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
Does quantization in pure rust on Rgba image, using https://crates.io/crates/image
|
||||
|
||||
algorithm:
|
||||
https://observablehq.com/@tmcw/octree-color-quantization
|
||||
|
||||
inspired by to https://github.com/objectProfessionals/movieMaps
|
||||
359
src/lib.rs
Normal file
359
src/lib.rs
Normal file
|
|
@ -0,0 +1,359 @@
|
|||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use image::{Rgba, RgbaImage};
|
||||
|
||||
const MAX_LEVEL: usize = 5;
|
||||
|
||||
pub fn quantize(image: &RgbaImage, num_colors:usize) -> RgbaImage{
|
||||
let mut quantizer=OctTreeQuantizer::new(num_colors);
|
||||
quantizer.quantize(image)
|
||||
}
|
||||
|
||||
struct OctTreeQuantizer {
|
||||
root: Rc<RefCell<OctTreeNode>>,
|
||||
reduce_colors: usize,
|
||||
maximum_colors: usize,
|
||||
colors: usize,
|
||||
color_list: Vec<Vec<Option<Rc<RefCell<OctTreeNode>>>>>,
|
||||
}
|
||||
|
||||
impl OctTreeQuantizer {
|
||||
fn new(num_colors: usize) -> Self {
|
||||
let mut new_quantizer = Self {
|
||||
root: Rc::new(RefCell::new(OctTreeNode::new())),
|
||||
reduce_colors: usize::max(512, num_colors * 2),
|
||||
maximum_colors: num_colors,
|
||||
color_list: vec![],
|
||||
colors: 0,
|
||||
};
|
||||
|
||||
for _ in 0..=MAX_LEVEL {
|
||||
new_quantizer.color_list.push(Vec::new());
|
||||
}
|
||||
new_quantizer
|
||||
}
|
||||
|
||||
pub fn quantize(&mut self, image: &RgbaImage) -> RgbaImage {
|
||||
for y in 0..image.height() {
|
||||
for x in 0..image.width() {
|
||||
self.insert_color(image.get_pixel(x, y), Rc::clone(&self.root));
|
||||
|
||||
if self.colors > self.reduce_colors {
|
||||
self.reduce_tree(self.reduce_colors);
|
||||
//reduce sets to None and the code below actually removes nodes from the list
|
||||
for level in &mut self.color_list {
|
||||
level.retain(|c| c.is_some());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let table = self.build_color_table();
|
||||
|
||||
let mut out = RgbaImage::new(image.width(), image.height());
|
||||
for y in 0..image.height() {
|
||||
for x in 0..image.width() {
|
||||
let pixel = image.get_pixel(x, y);
|
||||
if let Some(index) = self.get_index_for_color(pixel, &self.root) {
|
||||
let color = table.get(index).unwrap();
|
||||
if let Some(color) = color {
|
||||
out.put_pixel(x, y, *color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn get_index_for_color(
|
||||
&self,
|
||||
color: &Rgba<u8>,
|
||||
node: &Rc<RefCell<OctTreeNode>>,
|
||||
) -> Option<usize> {
|
||||
fn get_index_for_color(
|
||||
quantizer: &OctTreeQuantizer,
|
||||
color: &Rgba<u8>,
|
||||
level: usize,
|
||||
node: &Rc<RefCell<OctTreeNode>>,
|
||||
) -> Option<usize> {
|
||||
if level > MAX_LEVEL {
|
||||
return None;
|
||||
}
|
||||
let node = Rc::clone(node);
|
||||
let index = get_bitmask(color, &level);
|
||||
|
||||
let node_b = node.borrow();
|
||||
let child = &node_b.leaf[index];
|
||||
|
||||
if let Some(child) = child {
|
||||
let child_b = child.borrow();
|
||||
if child_b.is_leaf {
|
||||
return Some(child_b.index);
|
||||
} else {
|
||||
return get_index_for_color(quantizer, color, level + 1, child);
|
||||
}
|
||||
} else {
|
||||
return Some(node_b.index);
|
||||
}
|
||||
}
|
||||
|
||||
get_index_for_color(&self, color, 0, node)
|
||||
}
|
||||
|
||||
fn build_color_table(&mut self) -> Vec<Option<Rgba<u8>>> {
|
||||
//nested function that is called recursively
|
||||
fn build_color_table(
|
||||
quantizer: &mut OctTreeQuantizer,
|
||||
node: &Rc<RefCell<OctTreeNode>>,
|
||||
table: &mut Vec<Option<Rgba<u8>>>,
|
||||
index: usize,
|
||||
) -> usize {
|
||||
if quantizer.colors > quantizer.maximum_colors {
|
||||
quantizer.reduce_tree(quantizer.maximum_colors);
|
||||
}
|
||||
if node.borrow().is_leaf {
|
||||
{
|
||||
let node = node.borrow();
|
||||
let count = node.count;
|
||||
table[index] = Some(Rgba::from([
|
||||
(node.total_red / count as u32) as u8,
|
||||
(node.total_green / count as u32) as u8,
|
||||
(node.total_blue / count as u32) as u8,
|
||||
0xFF,
|
||||
]));
|
||||
}
|
||||
node.borrow_mut().index = index;
|
||||
|
||||
index + 1
|
||||
} else {
|
||||
let mut result = index;
|
||||
for i in 0..8 {
|
||||
// cannot iterate leaf, because that widens the scope of the borrow (of node)
|
||||
let mut node = node.borrow_mut();
|
||||
if let Some(leaf) = &node.leaf[i] {
|
||||
//could be immutable borrow
|
||||
let new_index = build_color_table(quantizer, leaf, table, result);
|
||||
node.index = index; //but also need mutable borrow here
|
||||
result = new_index;
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
let mut table: Vec<Option<Rgba<u8>>> = vec![None; self.colors];
|
||||
let node = Rc::clone(&self.root);
|
||||
build_color_table(self, &node, &mut table, 0);
|
||||
table
|
||||
}
|
||||
|
||||
fn insert_color(&mut self, rgb: &Rgba<u8>, node: Rc<RefCell<OctTreeNode>>) {
|
||||
//nested function that is called recursively
|
||||
fn insert_color(
|
||||
quantizer: &mut OctTreeQuantizer,
|
||||
color: &Rgba<u8>,
|
||||
level: usize,
|
||||
node: Rc<RefCell<OctTreeNode>>,
|
||||
) {
|
||||
if level > MAX_LEVEL {
|
||||
return;
|
||||
}
|
||||
|
||||
let index = get_bitmask(color, &level);
|
||||
|
||||
if node.borrow().leaf[index].is_none() {
|
||||
let mut child = OctTreeNode::new();
|
||||
child.parent = Some(Rc::clone(&node));
|
||||
child.p_index = quantizer.color_list[level].len();
|
||||
|
||||
if level == MAX_LEVEL {
|
||||
child.is_leaf = true;
|
||||
child.count = 1;
|
||||
child.total_red = color[0] as u32;
|
||||
child.total_green = color[1] as u32;
|
||||
child.total_blue = color[2] as u32;
|
||||
child.level = level;
|
||||
quantizer.colors += 1;
|
||||
}
|
||||
|
||||
let child = Rc::new(RefCell::new(child));
|
||||
quantizer.color_list[level].push(Some(Rc::clone(&child)));
|
||||
let clone = Rc::clone(&child);
|
||||
|
||||
{
|
||||
let mut mutnode = node.borrow_mut();
|
||||
mutnode.children += 1;
|
||||
mutnode.is_leaf = false;
|
||||
mutnode.leaf[index] = Some(child);
|
||||
}
|
||||
|
||||
if level < MAX_LEVEL {
|
||||
insert_color(quantizer, color, level + 1, clone);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if node
|
||||
.borrow()
|
||||
.leaf
|
||||
.get(index)
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.borrow()
|
||||
.is_leaf
|
||||
{
|
||||
let mut node = node.borrow_mut();
|
||||
let mut child = node
|
||||
.leaf
|
||||
.get_mut(index)
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.borrow_mut();
|
||||
child.count += 1;
|
||||
child.total_red += color[0] as u32;
|
||||
child.total_green += color[1] as u32;
|
||||
child.total_blue += color[2] as u32;
|
||||
return;
|
||||
} else {
|
||||
insert_color(
|
||||
quantizer,
|
||||
color,
|
||||
level + 1,
|
||||
Rc::clone(&(node.borrow().leaf[index]).as_ref().unwrap()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
insert_color(self, rgb, 0, node);
|
||||
}
|
||||
|
||||
fn reduce_tree(&mut self, num_colors: usize) {
|
||||
// Nested function that is called recursively
|
||||
fn reduce_tree(quantizer: &mut OctTreeQuantizer, num_colors: usize, level: isize) {
|
||||
if level < 0 {
|
||||
return;
|
||||
} else {
|
||||
let mut removals = Vec::new();
|
||||
let list = &quantizer.color_list[level as usize];
|
||||
for node in list {
|
||||
if let Some(node) = node {
|
||||
if node.borrow().children > 0 {
|
||||
for i in 0..8 {
|
||||
let mut color: Option<(usize, u32, u32, u32, usize)> = None;
|
||||
|
||||
if let Some(child) = node.borrow().leaf.get(i) {
|
||||
if let Some(child) = child {
|
||||
let child = child.borrow();
|
||||
color = Some((
|
||||
child.count,
|
||||
child.total_red,
|
||||
child.total_green,
|
||||
child.total_blue,
|
||||
child.p_index,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// need to mutate node, which conflicts with previous borrow to retrieve the child
|
||||
if let Some(color) = color {
|
||||
let mut node = node.borrow_mut();
|
||||
node.count += color.0;
|
||||
node.total_red += color.1;
|
||||
node.total_green += color.2;
|
||||
node.total_blue += color.3;
|
||||
node.leaf[i] = None;
|
||||
node.children -= 1;
|
||||
quantizer.colors -= 1;
|
||||
|
||||
removals.push(color.4); //save for further processing outside loop (and borrow of colorlist)
|
||||
}
|
||||
}
|
||||
node.borrow_mut().is_leaf = true;
|
||||
quantizer.colors += 1;
|
||||
if quantizer.colors <= num_colors {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let color_list = &mut quantizer.color_list[(level as usize + 1)];
|
||||
for index in removals {
|
||||
color_list[index] = None; //set to None here, Option removed later
|
||||
}
|
||||
|
||||
reduce_tree(quantizer, num_colors, level - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// call to nested function
|
||||
reduce_tree(self, num_colors, (MAX_LEVEL - 1) as isize);
|
||||
}
|
||||
}
|
||||
|
||||
struct OctTreeNode {
|
||||
children: usize,
|
||||
level: usize,
|
||||
parent: Option<Rc<RefCell<OctTreeNode>>>,
|
||||
leaf: Vec<Option<Rc<RefCell<OctTreeNode>>>>,
|
||||
is_leaf: bool,
|
||||
count: usize,
|
||||
total_red: u32,
|
||||
total_green: u32,
|
||||
total_blue: u32,
|
||||
index: usize,
|
||||
p_index: usize,
|
||||
}
|
||||
|
||||
impl OctTreeNode {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
children: 0,
|
||||
level: 0,
|
||||
parent: None,
|
||||
leaf: vec![None; 8],
|
||||
is_leaf: false,
|
||||
count: 0,
|
||||
total_red: 0,
|
||||
total_green: 0,
|
||||
total_blue: 0,
|
||||
index: 0,
|
||||
p_index: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_bitmask(color: &Rgba<u8>, level: &usize) -> usize {
|
||||
let bit = 0x80 >> level;
|
||||
|
||||
let mut index = 0;
|
||||
if (color[0] & bit) != 0 {
|
||||
index += 4;
|
||||
}
|
||||
if (color[1] & bit) != 0 {
|
||||
index += 2;
|
||||
}
|
||||
if (color[2] & bit) != 0 {
|
||||
index += 1;
|
||||
}
|
||||
index
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
use image::ImageError;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_big_image() -> Result<(), ImageError> {
|
||||
let src: RgbaImage = image::open("testdata/input.jpg").unwrap().into_rgba8();
|
||||
|
||||
let out = quantize(&src, 256);
|
||||
out.save_with_format("output.jpg", image::ImageFormat::Jpeg)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
BIN
testdata/img.png
vendored
Normal file
BIN
testdata/img.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.1 MiB |
BIN
testdata/input.jpg
vendored
Normal file
BIN
testdata/input.jpg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 MiB |
BIN
testdata/test.jpg
vendored
Normal file
BIN
testdata/test.jpg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
Loading…
Add table
Reference in a new issue