core/scale: add options support
[nihav.git] / nihav-core / src / scale / colorcvt.rs
CommitLineData
03accf76
KS
1use super::*;
2use super::kernel::Kernel;
3
dd6800c5
KS
4const DEFAULT_YUV: usize = 4;
5
03accf76
KS
6const YUV_PARAMS: &[[f32; 2]] = &[
7 [ 0.333, 0.333 ], // RGB
8 [ 0.2126, 0.0722 ], // ITU-R BT709
9 [ 0.333, 0.333 ], // unspecified
10 [ 0.333, 0.333 ], // reserved
11 [ 0.299, 0.114 ], // ITU-R BT601
12 [ 0.299, 0.114 ], // ITU-R BT470
13 [ 0.299, 0.114 ], // SMPTE 170M
14 [ 0.212, 0.087 ], // SMPTE 240M
15 [ 0.333, 0.333 ], // YCoCg
16 [ 0.2627, 0.0593 ], // ITU-R BT2020
17 [ 0.2627, 0.0593 ], // ITU-R BT2020
18];
19
25e0bf9a
KS
20fn parse_yuv_mat(name: &str) -> usize {
21 match name {
22 "rgb" => 0,
23 "bt709" => 1,
24 "bt601" => 4,
25 "bt470" => 5,
26 "smpte170m" => 6,
27 "smpte240m" => 7,
28 "ycocg" => 8,
29 "bt2020" => 9,
30 _ => 2,
31 }
32}
33
34/*fn get_yuv_mat(id: usize) -> &'static str {
35 match id {
36 1 => "bt709",
37 4 => "bt601",
38 5 => "bt470",
39 6 => "smpte170m",
40 7 => "smpte240m",
41 8 => "ycocg",
42 9 => "bt2020",
43 _ => "rgb",
44 }
45}*/
46
03accf76
KS
47const BT_PAL_COEFFS: [f32; 2] = [ 0.493, 0.877 ];
48
49const SMPTE_NTSC_COEFFS: &[f32; 4] = &[ -0.268, 0.7358, 0.4127, 0.4778 ];
50
51/*const RGB2YCOCG: [[f32; 3]; 3] = [
52 [ 0.25, 0.5, 0.25 ],
53 [ -0.25, 0.5, -0.25 ],
54 [ 0.5, 0.0, -0.5 ]
55];
56const YCOCG2RGB: [[f32; 3]; 3] = [
57 [ 1.0, -1.0, 1.0 ],
58 [ 1.0, 1.0, 0.0 ],
59 [ 1.0, -1.0, -1.0 ]
60];
61
62const XYZ2RGB: [[f32; 3]; 3] = [
63 [ 0.49, 0.31, 0.2 ],
64 [ 0.17697, 0.8124, 0.01063 ],
65 [ 0.0, 0.01, 0.99 ]
66];
67const RGB2XYZ: [[f32; 3]; 3] = [
68 [ 2.364613, -0.89654, -0.46807 ],
69 [ -0.515167, 1.42641, 0.08876 ],
70 [ 0.0052, -0.01441, 1.00920 ]
71];*/
72
73fn make_rgb2yuv(kr: f32, kb: f32, mat: &mut [[f32; 3]; 3]) {
74 // Y
75 mat[0][0] = kr;
76 mat[0][1] = 1.0 - kr - kb;
77 mat[0][2] = kb;
78 // Cb
79 mat[1][0] = -mat[0][0] * 0.5 / (1.0 - kb);
80 mat[1][1] = -mat[0][1] * 0.5 / (1.0 - kb);
81 mat[1][2] = 0.5;
82 // Cr
83 mat[2][0] = 0.5;
84 mat[2][1] = -mat[0][1] * 0.5 / (1.0 - kr);
85 mat[2][2] = -mat[0][2] * 0.5 / (1.0 - kr);
86}
87
88fn make_yuv2rgb(kr: f32, kb: f32, mat: &mut [[f32; 3]; 3]) {
89 let kg = 1.0 - kr - kb;
90
91 // R
92 mat[0][0] = 1.0;
93 mat[0][1] = 0.0;
94 mat[0][2] = 2.0 * (1.0 - kr);
95 // G
96 mat[1][0] = 1.0;
97 mat[1][1] = -kb * 2.0 * (1.0 - kb) / kg;
98 mat[1][2] = -kr * 2.0 * (1.0 - kr) / kg;
99 // B
100 mat[2][0] = 1.0;
101 mat[2][1] = 2.0 * (1.0 - kb);
102 mat[2][2] = 0.0;
103}
104
105fn apply_pal_rgb2yuv(eu: f32, ev: f32, mat: &mut [[f32; 3]; 3]) {
106 let ufac = 2.0 * (1.0 - mat[0][2]) * eu;
107 let vfac = 2.0 * (1.0 - mat[0][0]) * ev;
108
109 // U
110 mat[1][0] *= ufac;
111 mat[1][1] *= ufac;
112 mat[1][2] = eu * (1.0 - mat[0][2]);
113 // V
114 mat[2][0] = ev * (1.0 - mat[0][0]);
115 mat[2][1] *= vfac;
116 mat[2][2] *= vfac;
117}
118
119fn apply_pal_yuv2rgb(eu: f32, ev: f32, mat: &mut [[f32; 3]; 3]) {
120 let ufac = 1.0 / (mat[2][1] * eu);
121 let vfac = 1.0 / (mat[0][2] * ev);
122
123 // R
124 mat[0][2] *= vfac;
125 // G
126 mat[1][1] *= ufac;
127 mat[1][2] *= vfac;
128 // B
129 mat[2][1] *= ufac;
130}
131
132fn apply_ntsc_rgb2yiq(params: &[f32; 4], mat: &mut [[f32; 3]; 3]) {
133 let ufac = 2.0 * (1.0 - mat[0][2]);
134 let vfac = 2.0 * (1.0 - mat[0][0]);
135 let mut tmp: [[f32; 3]; 2] = [[0.0; 3]; 2];
136
137 for i in 0..3 {
138 tmp[0][i] = mat[1][i] * ufac;
139 tmp[1][i] = mat[2][i] * vfac;
140 }
141 for i in 0..3 {
142 mat[1][i] = params[0] * tmp[0][i] + params[1] * tmp[1][i];
143 mat[2][i] = params[2] * tmp[0][i] + params[3] * tmp[1][i];
144 }
145}
146
147fn subm_det(mat: &[[f32; 3]; 3], col: usize, row: usize) -> f32 {
148 let row0 = if row == 0 { 1 } else { 0 };
149 let row1 = if (row == 1) || (row0 == 1) { 2 } else { 1 };
150 let col0 = if col == 0 { 1 } else { 0 };
151 let col1 = if (col == 1) || (col0 == 1) { 2 } else { 1 };
152
153 let det = mat[row0][col0] * mat[row1][col1] - mat[row0][col1] * mat[row1][col0];
154 if ((col ^ row) & 1) == 0 {
155 det
156 } else {
157 -det
158 }
159}
160
161fn invert_matrix(mat: &mut [[f32; 3]; 3]) {
162 let d00 = subm_det(mat, 0, 0);
163 let d01 = subm_det(mat, 0, 1);
164 let d02 = subm_det(mat, 0, 2);
165 let d10 = subm_det(mat, 1, 0);
166 let d11 = subm_det(mat, 1, 1);
167 let d12 = subm_det(mat, 1, 2);
168 let d20 = subm_det(mat, 2, 0);
169 let d21 = subm_det(mat, 2, 1);
170 let d22 = subm_det(mat, 2, 2);
171 let det = 1.0 / (mat[0][0] * d00 + mat[0][1] * d10 + mat[0][2] * d20).abs();
172
173 mat[0][0] = det * d00;
174 mat[0][1] = det * d01;
175 mat[0][2] = det * d02;
176 mat[1][0] = det * d10;
177 mat[1][1] = det * d11;
178 mat[1][2] = det * d12;
179 mat[2][0] = det * d20;
180 mat[2][1] = det * d21;
181 mat[2][2] = det * d22;
182}
183
184fn matrix_mul(mat: &[[f32; 3]; 3], a: f32, b: f32, c: f32) -> (f32, f32, f32) {
185 (a * mat[0][0] + b * mat[0][1] + c * mat[0][2],
186 a * mat[1][0] + b * mat[1][1] + c * mat[1][2],
187 a * mat[2][0] + b * mat[2][1] + c * mat[2][2] )
188}
189
190#[derive(Default)]
191struct RgbToYuv {
192 matrix: [[f32; 3]; 3],
25e0bf9a 193 mode: usize,
03accf76
KS
194}
195
196impl RgbToYuv {
197 fn new() -> Self { Self::default() }
198}
199
e243ceb4 200#[allow(clippy::many_single_char_names)]
03accf76 201impl Kernel for RgbToYuv {
25e0bf9a
KS
202 fn init(&mut self, in_fmt: &ScaleInfo, dest_fmt: &ScaleInfo, options: &[(String, String)]) -> ScaleResult<NABufferType> {
203 let mut debug = false;
204 let mut mode = DEFAULT_YUV;
205 for (name, value) in options.iter() {
206 match (name.as_str(), value.as_str()) {
207 ("debug", "") => { debug = true; },
208 ("debug", "true") => { debug = true; },
209 ("rgb2yuv.mode", ymode) => {
210 mode = parse_yuv_mat(ymode);
211 },
212 _ => {},
213 }
214 }
215 self.mode = mode;
216
03accf76 217 let mut df = dest_fmt.fmt;
25e0bf9a 218 make_rgb2yuv(YUV_PARAMS[mode][0], YUV_PARAMS[mode][1], &mut self.matrix);
03accf76
KS
219 if let ColorModel::YUV(yuvsm) = df.get_model() {
220 match yuvsm {
221 YUVSubmodel::YCbCr => {},
222 YUVSubmodel::YIQ => { apply_ntsc_rgb2yiq(SMPTE_NTSC_COEFFS, &mut self.matrix); },
223 YUVSubmodel::YUVJ => { apply_pal_rgb2yuv(BT_PAL_COEFFS[0], BT_PAL_COEFFS[1], &mut self.matrix); },
224 };
225 } else {
226 return Err(ScaleError::InvalidArgument);
227 }
228 for i in 0..MAX_CHROMATONS {
229 if let Some(ref mut chr) = df.comp_info[i] {
230 chr.packed = false;
231 chr.comp_offs = i as u8;
232 chr.h_ss = 0;
233 chr.v_ss = 0;
234 }
235 }
25e0bf9a
KS
236 if debug {
237 println!(" [intermediate format {}]", df);
238 }
03accf76
KS
239 let res = alloc_video_buffer(NAVideoInfo::new(in_fmt.width, in_fmt.height, false, df), 3);
240 if res.is_err() { return Err(ScaleError::AllocError); }
241 Ok(res.unwrap())
242 }
243 fn process(&mut self, pic_in: &NABufferType, pic_out: &mut NABufferType) {
244 if let (Some(ref sbuf), Some(ref mut dbuf)) = (pic_in.get_vbuf(), pic_out.get_vbuf()) {
93790967
KS
245 if dbuf.get_info().get_format().get_num_comp() < 3 {
246 return self.process_grayscale(sbuf, dbuf);
247 }
03accf76
KS
248 let istrides = [sbuf.get_stride(0), sbuf.get_stride(1), sbuf.get_stride(2)];
249 let dstrides = [dbuf.get_stride(0), dbuf.get_stride(1), dbuf.get_stride(2)];
250 let (w, h) = sbuf.get_dimensions(0);
251
252 let mut roff = sbuf.get_offset(0);
253 let mut goff = sbuf.get_offset(1);
254 let mut boff = sbuf.get_offset(2);
255 let mut yoff = dbuf.get_offset(0);
256 let mut uoff = dbuf.get_offset(1);
257 let mut voff = dbuf.get_offset(2);
258 let src = sbuf.get_data();
259 let dst = dbuf.get_data_mut().unwrap();
260 for _y in 0..h {
261 for x in 0..w {
e243ceb4
KS
262 let r = f32::from(src[roff + x]);
263 let g = f32::from(src[goff + x]);
264 let b = f32::from(src[boff + x]);
03accf76
KS
265 let (y, u, v) = matrix_mul(&self.matrix, r, g, b);
266
267 dst[yoff + x] = (y as i16).max(0).min(255) as u8;
268 dst[uoff + x] = ((u as i16).max(-128).min(128) + 128) as u8;
269 dst[voff + x] = ((v as i16).max(-128).min(128) + 128) as u8;
270 }
271 roff += istrides[0];
272 goff += istrides[1];
273 boff += istrides[2];
274 yoff += dstrides[0];
275 uoff += dstrides[1];
276 voff += dstrides[2];
277 }
278 }
279 }
280}
281
93790967
KS
282impl RgbToYuv {
283 fn process_grayscale(&self, sbuf: &NAVideoBuffer<u8>, dbuf: &mut NAVideoBuffer<u8>) {
284 let istrides = [sbuf.get_stride(0), sbuf.get_stride(1), sbuf.get_stride(2)];
285 let ystride = dbuf.get_stride(0);
286 let (w, h) = sbuf.get_dimensions(0);
287
288 let mut roff = sbuf.get_offset(0);
289 let mut goff = sbuf.get_offset(1);
290 let mut boff = sbuf.get_offset(2);
291 let mut yoff = dbuf.get_offset(0);
292 let src = sbuf.get_data();
293 let dst = dbuf.get_data_mut().unwrap();
294 for _y in 0..h {
295 for x in 0..w {
296 let r = f32::from(src[roff + x]);
297 let g = f32::from(src[goff + x]);
298 let b = f32::from(src[boff + x]);
299 let (y, _u, _v) = matrix_mul(&self.matrix, r, g, b);
300
301 dst[yoff + x] = (y as i16).max(0).min(255) as u8;
302 }
303 roff += istrides[0];
304 goff += istrides[1];
305 boff += istrides[2];
306 yoff += ystride;
307 }
308 }
309}
310
6011e201 311pub fn create_rgb2yuv() -> Box<dyn Kernel> {
03accf76
KS
312 Box::new(RgbToYuv::new())
313}
314
315#[derive(Default)]
316struct YuvToRgb {
317 matrix: [[f32; 3]; 3],
25e0bf9a 318 mode: usize,
2fe24f6f
KS
319 yscale: Vec<i16>,
320 r_chr: Vec<i16>,
321 g_u: Vec<i16>,
322 g_v: Vec<i16>,
323 b_chr: Vec<i16>,
03accf76
KS
324}
325
326impl YuvToRgb {
327 fn new() -> Self { Self::default() }
328}
329
e243ceb4 330#[allow(clippy::many_single_char_names)]
03accf76 331impl Kernel for YuvToRgb {
25e0bf9a
KS
332 fn init(&mut self, in_fmt: &ScaleInfo, dest_fmt: &ScaleInfo, options: &[(String, String)]) -> ScaleResult<NABufferType> {
333 let mut debug = false;
334 let mut mode = DEFAULT_YUV;
335 for (name, value) in options.iter() {
336 match (name.as_str(), value.as_str()) {
337 ("debug", "") => { debug = true; },
338 ("debug", "true") => { debug = true; },
339 ("yuv2rgb.mode", ymode) => {
340 mode = parse_yuv_mat(ymode);
341 },
342 _ => {},
343 }
344 }
345 self.mode = mode;
346
03accf76 347 let mut df = dest_fmt.fmt;
4b459d0b 348 df.palette = false;
575c0b27
KS
349 if !df.is_unpacked() || df.get_max_depth() != 8 || df.get_total_depth() != df.get_num_comp() as u8 * 8 {
350 df = NAPixelFormaton {
351 model: ColorModel::RGB(RGBSubmodel::RGB), components: 3,
352 comp_info: [
353 Some(NAPixelChromaton{ h_ss: 0, v_ss: 0, packed: false, depth: 8, shift: 0, comp_offs: 0, next_elem: 1 }),
354 Some(NAPixelChromaton{ h_ss: 0, v_ss: 0, packed: false, depth: 8, shift: 0, comp_offs: 1, next_elem: 1 }),
355 Some(NAPixelChromaton{ h_ss: 0, v_ss: 0, packed: false, depth: 8, shift: 0, comp_offs: 2, next_elem: 1 }),
356 None, None],
357 elem_size: 3, be: false, alpha: false, palette: false };
358 if in_fmt.fmt.alpha && dest_fmt.fmt.alpha {
359 df.alpha = true;
360 df.components = 4;
361 df.comp_info[3] = Some(NAPixelChromaton{ h_ss: 0, v_ss: 0, packed: false, depth: 8, shift: 0, comp_offs: 3, next_elem: 1 });
362 }
363 }
25e0bf9a 364 make_yuv2rgb(YUV_PARAMS[mode][0], YUV_PARAMS[mode][1], &mut self.matrix);
03accf76
KS
365 if let ColorModel::YUV(yuvsm) = in_fmt.fmt.get_model() {
366 match yuvsm {
367 YUVSubmodel::YCbCr => {},
368 YUVSubmodel::YIQ => {
dd6800c5 369 make_rgb2yuv(YUV_PARAMS[DEFAULT_YUV][0], YUV_PARAMS[DEFAULT_YUV][1], &mut self.matrix);
03accf76
KS
370 apply_ntsc_rgb2yiq(SMPTE_NTSC_COEFFS, &mut self.matrix);
371 invert_matrix(&mut self.matrix);
372 },
373 YUVSubmodel::YUVJ => {
374 apply_pal_yuv2rgb(BT_PAL_COEFFS[0], BT_PAL_COEFFS[1], &mut self.matrix);
375 },
376 };
2fe24f6f
KS
377 if yuvsm != YUVSubmodel::YIQ {
378 self.yscale = Vec::with_capacity(256);
379 self.r_chr = Vec::with_capacity(256);
380 self.g_u = Vec::with_capacity(256);
381 self.g_v = Vec::with_capacity(256);
382 self.b_chr = Vec::with_capacity(256);
383 for i in 0..256 {
384 let yval = i as i16; // todo limited range as well
385 self.yscale.push(yval);
386 let rval = (((i as f32) - 128.0) * self.matrix[0][2]) as i16;
387 self.r_chr.push(rval);
388 let uval = (((i as f32) - 128.0) * self.matrix[1][1]) as i16;
389 self.g_u.push(uval);
390 let vval = (((i as f32) - 128.0) * self.matrix[1][2]) as i16;
391 self.g_v.push(vval);
392 let bval = (((i as f32) - 128.0) * self.matrix[2][1]) as i16;
393 self.b_chr.push(bval);
394 }
395 }
03accf76
KS
396 } else {
397 return Err(ScaleError::InvalidArgument);
398 }
399 for i in 0..MAX_CHROMATONS {
400 if let Some(ref mut chr) = df.comp_info[i] {
401 chr.packed = false;
402 chr.comp_offs = i as u8;
403 }
404 }
25e0bf9a
KS
405 if debug {
406 println!(" [intermediate format {}]", df);
407 }
03accf76
KS
408 let res = alloc_video_buffer(NAVideoInfo::new(in_fmt.width, in_fmt.height, false, df), 3);
409 if res.is_err() { return Err(ScaleError::AllocError); }
410 Ok(res.unwrap())
411 }
412 fn process(&mut self, pic_in: &NABufferType, pic_out: &mut NABufferType) {
413 if let (Some(ref sbuf), Some(ref mut dbuf)) = (pic_in.get_vbuf(), pic_out.get_vbuf()) {
414 let istrides = [sbuf.get_stride(0), sbuf.get_stride(1), sbuf.get_stride(2)];
415 let dstrides = [dbuf.get_stride(0), dbuf.get_stride(1), dbuf.get_stride(2)];
416 let (w, h) = sbuf.get_dimensions(0);
93790967
KS
417 if sbuf.get_info().get_format().get_num_comp() < 3 {
418 return self.process_grayscale(sbuf, dbuf);
419 }
03accf76
KS
420 let (sv0, sh0) = sbuf.get_info().get_format().get_chromaton(1).unwrap().get_subsampling();
421 let (sv1, sh1) = sbuf.get_info().get_format().get_chromaton(2).unwrap().get_subsampling();
422
423 let uhmask = (1 << sh0) - 1;
424 let vhmask = (1 << sh1) - 1;
425 let mut roff = dbuf.get_offset(0);
426 let mut goff = dbuf.get_offset(1);
427 let mut boff = dbuf.get_offset(2);
428 let mut yoff = sbuf.get_offset(0);
429 let mut uoff = sbuf.get_offset(1);
430 let mut voff = sbuf.get_offset(2);
431 let src = sbuf.get_data();
432 let dst = dbuf.get_data_mut().unwrap();
b36f412c 433 if !self.yscale.is_empty() {
2fe24f6f
KS
434 for y in 0..h {
435 for x in 0..w {
436 let y = self.yscale[src[yoff + x] as usize];
437 let u = src[uoff + (x >> sv0)] as usize;
438 let v = src[voff + (x >> sv1)] as usize;
439 let r = y + self.r_chr[v];
440 let g = y + self.g_u[u] + self.g_v[v];
441 let b = y + self.b_chr[u];
442 dst[roff + x] = r.max(0).min(255) as u8;
443 dst[goff + x] = g.max(0).min(255) as u8;
444 dst[boff + x] = b.max(0).min(255) as u8;
445 }
446 roff += dstrides[0];
447 goff += dstrides[1];
448 boff += dstrides[2];
449 yoff += istrides[0];
450 if (y & uhmask) == uhmask {
451 uoff += istrides[1];
452 }
453 if (y & vhmask) == vhmask {
454 voff += istrides[2];
455 }
456 }
457 return;
458 }
03accf76
KS
459 for y in 0..h {
460 for x in 0..w {
e243ceb4
KS
461 let y = f32::from(src[yoff + x]);
462 let u = f32::from(i16::from(src[uoff + (x >> sv0)]) - 128);
463 let v = f32::from(i16::from(src[voff + (x >> sv1)]) - 128);
03accf76
KS
464
465 let (r, g, b) = matrix_mul(&self.matrix, y, u, v);
466 dst[roff + x] = (r as i16).max(0).min(255) as u8;
467 dst[goff + x] = (g as i16).max(0).min(255) as u8;
468 dst[boff + x] = (b as i16).max(0).min(255) as u8;
469 }
470 roff += dstrides[0];
471 goff += dstrides[1];
472 boff += dstrides[2];
473 yoff += istrides[0];
474 if (y & uhmask) == uhmask {
475 uoff += istrides[1];
476 }
477 if (y & vhmask) == vhmask {
478 voff += istrides[2];
479 }
480 }
481 }
482 }
483}
484
93790967
KS
485impl YuvToRgb {
486 fn process_grayscale(&self, sbuf: &NAVideoBuffer<u8>, dbuf: &mut NAVideoBuffer<u8>) {
487 let ystride = sbuf.get_stride(0);
488 let dstrides = [dbuf.get_stride(0), dbuf.get_stride(1), dbuf.get_stride(2)];
489 let (w, h) = sbuf.get_dimensions(0);
490 let mut roff = dbuf.get_offset(0);
491 let mut goff = dbuf.get_offset(1);
492 let mut boff = dbuf.get_offset(2);
493 let mut yoff = sbuf.get_offset(0);
494 let src = sbuf.get_data();
495 let dst = dbuf.get_data_mut().unwrap();
b36f412c 496 if !self.yscale.is_empty() {
93790967
KS
497 for _y in 0..h {
498 for x in 0..w {
499 let y = self.yscale[src[yoff + x] as usize];
500 let r = y + self.r_chr[128];
501 let g = y + self.g_u[128] + self.g_v[128];
502 let b = y + self.b_chr[128];
503 dst[roff + x] = r.max(0).min(255) as u8;
504 dst[goff + x] = g.max(0).min(255) as u8;
505 dst[boff + x] = b.max(0).min(255) as u8;
506 }
507 roff += dstrides[0];
508 goff += dstrides[1];
509 boff += dstrides[2];
510 yoff += ystride;
511 }
512 } else {
513 for _y in 0..h {
514 for x in 0..w {
515 let y = f32::from(src[yoff + x]);
516 let (r, g, b) = matrix_mul(&self.matrix, y, 0.0, 0.0);
517 dst[roff + x] = (r as i16).max(0).min(255) as u8;
518 dst[goff + x] = (g as i16).max(0).min(255) as u8;
519 dst[boff + x] = (b as i16).max(0).min(255) as u8;
520 }
521 roff += dstrides[0];
522 goff += dstrides[1];
523 boff += dstrides[2];
524 yoff += ystride;
525 }
526 }
527 }
528}
529
6011e201 530pub fn create_yuv2rgb() -> Box<dyn Kernel> {
03accf76
KS
531 Box::new(YuvToRgb::new())
532}