add Acorn Moving Blocks HQ decoder
[nihav.git] / nihav-core / src / scale / palette / mod.rs
CommitLineData
4b459d0b
KS
1use crate::formats::*;
2use super::*;
3use super::kernel::Kernel;
4
5#[derive(Default,Clone,Copy,PartialEq,Debug)]
6pub struct Pixel {
7 pub r: u8,
8 pub g: u8,
9 pub b: u8,
10}
11
12impl Pixel {
13 #[allow(dead_code)]
14 fn new(src: &[u8]) -> Self {
15 Self { r: src[0], g: src[1], b: src[2] }
16 }
6f263099 17 fn to_rgb(self) -> [u8; 3] {
4b459d0b
KS
18 [self.r, self.g, self.b]
19 }
20 fn dist(&self, pix: Pixel) -> u32 {
21 let dr = i32::from(self.r) - i32::from(pix.r);
22 let dg = i32::from(self.g) - i32::from(pix.g);
23 let db = i32::from(self.b) - i32::from(pix.b);
24 (dr * dr + dg * dg + db * db) as u32
25 }
26 fn min(&self, pix: Pixel) -> Pixel {
27 Pixel { r: self.r.min(pix.r), g: self.g.min(pix.g), b: self.b.min(pix.b) }
28 }
29 fn max(&self, pix: Pixel) -> Pixel {
30 Pixel { r: self.r.max(pix.r), g: self.g.max(pix.g), b: self.b.max(pix.b) }
31 }
32}
33
34#[allow(dead_code)]
35fn avg_u8(a: u8, b: u8) -> u8 {
36 (a & b) + ((a ^ b) >> 1)
37}
38
39mod elbg;
40mod mediancut;
41mod neuquant;
42mod palettise;
43
44//use elbg::ELBG;
45//use mediancut::quantise_median_cut;
46//use neuquant::NeuQuantQuantiser;
47
e6aaad5c 48#[derive(Clone,Copy,Debug,PartialEq,Default)]
4b459d0b
KS
49/// Palette quantisation algorithms.
50pub enum QuantisationMode {
51 /// Median cut approach proposed by Paul Heckbert.
52 ///
53 /// This is moderately fast and moderately good.
e6aaad5c 54 #[default]
4b459d0b
KS
55 MedianCut,
56 /// Enhanced LBG algorithm proposed by Giuseppe Patane and Marco Russo.
57 ///
58 /// This is slow but good method.
59 ELBG,
60 /// NeuQuant algorithm proposed by Anthony Dekker.
61 ///
62 /// It may operate on randomly subsampled image with subsampling factors 1-30.
63 /// This algorithm is fast especially with high subsampling factor but output palette may be far from optimal one.
64 NeuQuant(u8),
65}
66
e6aaad5c 67#[derive(Clone,Copy,Debug,PartialEq,Default)]
4b459d0b
KS
68/// Algorithms for seaching an appropriate palette entry for a given pixel.
69pub enum PaletteSearchMode {
70 /// Full search (slowest).
71 Full,
72 /// Local search (faster but may be not so good).
e6aaad5c 73 #[default]
4b459d0b
KS
74 Local,
75 /// k-d tree based one (the fastest but not so accurate).
76 KDTree,
77}
78
4b459d0b
KS
79use crate::scale::palette::elbg::ELBG;
80use crate::scale::palette::mediancut::quantise_median_cut;
81use crate::scale::palette::neuquant::NeuQuantQuantiser;
82use crate::scale::palette::palettise::*;
83
84fn palettise_frame_internal(pic_in: &NABufferType, pic_out: &mut NABufferType, qmode: QuantisationMode, palmode: PaletteSearchMode, pixels: &mut Vec<Pixel>) -> ScaleResult<()> {
85 if let (Some(ref sbuf), Some(ref mut dbuf)) = (pic_in.get_vbuf(), pic_out.get_vbuf()) {
86 let ioff = sbuf.get_offset(0);
87 let (w, h) = sbuf.get_dimensions(0);
88 let istride = sbuf.get_stride(0);
89 let ifmt = sbuf.get_info().get_format();
90 let sdata1 = sbuf.get_data();
91 let sdata = &sdata1[ioff..];
92
93 let doff = dbuf.get_offset(0);
94 let paloff = dbuf.get_offset(1);
95 let dstride = dbuf.get_stride(0);
96 let ofmt = dbuf.get_info().get_format();
97 let dst = dbuf.get_data_mut().unwrap();
98
b191eef3 99 pixels.clear();
4b459d0b
KS
100 if !ifmt.is_unpacked() {
101 let esize = ifmt.elem_size as usize;
102 let coffs = [ifmt.comp_info[0].unwrap().comp_offs as usize, ifmt.comp_info[1].unwrap().comp_offs as usize, ifmt.comp_info[2].unwrap().comp_offs as usize];
103 for src in sdata.chunks(istride).take(h) {
104 for chunk in src.chunks_exact(esize).take(w) {
105 let pixel = Pixel{ r: chunk[coffs[0]], g: chunk[coffs[1]], b: chunk[coffs[2]] };
106 pixels.push(pixel);
107 }
108 }
109 } else {
110 let mut roff = ioff;
111 let mut goff = sbuf.get_offset(1);
112 let mut boff = sbuf.get_offset(2);
113 let rstride = istride;
114 let gstride = sbuf.get_stride(1);
115 let bstride = sbuf.get_stride(2);
116 for _ in 0..h {
117 for x in 0..w {
118 let pixel = Pixel{ r: sdata[roff + x], g: sdata[goff + x], b: sdata[boff + x] };
119 pixels.push(pixel);
120 }
121 roff += rstride;
122 goff += gstride;
123 boff += bstride;
124 }
125 }
126 let mut pal = [[0u8; 3]; 256];
127 match qmode {
128 QuantisationMode::ELBG => {
129 let mut elbg = ELBG::new_random();
130 elbg.quantise(pixels.as_slice(), &mut pal);
131 },
132 QuantisationMode::MedianCut => {
133 quantise_median_cut(pixels.as_slice(), &mut pal);
134 },
135 QuantisationMode::NeuQuant(fact) => {
136 let mut nq = NeuQuantQuantiser::new(fact as usize);
137 nq.learn(pixels.as_slice());
138 nq.make_pal(&mut pal);
139 },
140 };
141 let esize = ofmt.elem_size as usize;
142 let coffs = [ofmt.comp_info[0].unwrap().comp_offs as usize, ofmt.comp_info[1].unwrap().comp_offs as usize, ofmt.comp_info[2].unwrap().comp_offs as usize];
e6aaad5c 143 for (dpal, spal) in dst[paloff..].chunks_mut(esize).zip(pal.iter()) {
4b459d0b
KS
144 dpal[coffs[0]] = spal[0];
145 dpal[coffs[1]] = spal[1];
146 dpal[coffs[2]] = spal[2];
147 }
148
149 let dst = &mut dst[doff..];
150 match palmode {
151 PaletteSearchMode::Full => {
152 for (dline, sline) in dst.chunks_mut(dstride).take(h).zip(pixels.chunks(w)) {
153 for (didx, pix) in dline.iter_mut().take(w).zip(sline.iter()) {
154 let rgb = pix.to_rgb();
155 *didx = find_nearest(&rgb, &pal) as u8;
156 }
157 }
158 },
159 PaletteSearchMode::Local => {
160 let ls = LocalSearch::new(&pal);
161 for (dline, sline) in dst.chunks_mut(dstride).take(h).zip(pixels.chunks(w)) {
162 for (didx, pix) in dline.iter_mut().take(w).zip(sline.iter()) {
163 *didx = ls.search(pix.to_rgb()) as u8;
164 }
165 }
166 },
167 PaletteSearchMode::KDTree => {
168 let kdtree = KDTree::new(&pal);
169 for (dline, sline) in dst.chunks_mut(dstride).take(h).zip(pixels.chunks(w)) {
170 for (didx, pix) in dline.iter_mut().take(w).zip(sline.iter()) {
171 *didx = kdtree.search(pix.to_rgb()) as u8;
172 }
173 }
174 },
175 };
176 Ok(())
177 } else {
178 Err(ScaleError::InvalidArgument)
179 }
180}
181
182/// Converts packed RGB frame into palettised one.
183///
184/// This function can operate in several modes of both palette generation and colour substitution with palette indices.
185/// Some may work fast but produce worse output image.
186/// See [`QuantisationMode`] and [`PaletteSearchMode`] for details.
187/// If you are not sure what to use there are `QuantisationMode::default()` and `PaletteSearchMode::default()`.
188///
189/// [`QuantisationMode`]: ./enum.QuantisationMode.html
190/// [`PaletteSearchMode`]: ./enum.PaletteSearchMode.html
191pub fn palettise_frame(pic_in: &NABufferType, pic_out: &mut NABufferType, qmode: QuantisationMode, palmode: PaletteSearchMode) -> ScaleResult<()> {
192 let size;
193 if let Some(ref vbuf) = pic_in.get_vbuf() {
194//todo check format for being packed RGB in and pal out
195 let (w, h) = vbuf.get_dimensions(0);
196 size = w * h;
197 } else {
198 return Err(ScaleError::InvalidArgument);
199 }
200 let mut pixels = Vec::with_capacity(size);
201 palettise_frame_internal(pic_in, pic_out, qmode, palmode, &mut pixels)
202}
203
204
205#[derive(Default)]
206struct PalettiseKernel {
207 pixels: Vec<Pixel>,
208 qmode: QuantisationMode,
209 palmode: PaletteSearchMode,
210}
211
212impl PalettiseKernel {
213 fn new() -> Self { Self::default() }
214}
215
216impl Kernel for PalettiseKernel {
25e0bf9a
KS
217 fn init(&mut self, in_fmt: &ScaleInfo, _dest_fmt: &ScaleInfo, options: &[(String, String)]) -> ScaleResult<NABufferType> {
218 for (name, value) in options.iter() {
219 match name.as_str() {
220 "pal.quant" => {
221 self.qmode = match value.as_str() {
222 "mediancut" => QuantisationMode::MedianCut,
223 "elbg" => QuantisationMode::ELBG,
224 "neuquant" => QuantisationMode::NeuQuant(3),
225 _ => QuantisationMode::default(),
226 };
227 },
228 "pal.search" => {
229 self.palmode = match value.as_str() {
230 "full" => PaletteSearchMode::Full,
231 "local" => PaletteSearchMode::Local,
232 "kdtree" => PaletteSearchMode::KDTree,
233 _ => PaletteSearchMode::default(),
234 };
235 },
236 _ => {},
237 };
238 }
239
4b459d0b
KS
240 self.pixels = Vec::with_capacity(in_fmt.width * in_fmt.height);
241 let res = alloc_video_buffer(NAVideoInfo::new(in_fmt.width, in_fmt.height, false, PAL8_FORMAT), 0);
242 if res.is_err() { return Err(ScaleError::AllocError); }
243 Ok(res.unwrap())
244 }
245 fn process(&mut self, pic_in: &NABufferType, pic_out: &mut NABufferType) {
246 palettise_frame_internal(pic_in, pic_out, self.qmode, self.palmode, &mut self.pixels).unwrap();
247 }
248}
249
250pub fn create_palettise() -> Box<dyn Kernel> {
251 Box::new(PalettiseKernel::new())
252}