core/scale: add options support
[nihav.git] / nihav-core / src / scale / mod.rs
1 //! Image conversion functionality.
2
3 //! # Examples
4 //!
5 //! Convert input image into YUV one and scale down two times.
6 //! ```no_run
7 //! use nihav_core::scale::*;
8 //! use nihav_core::formats::{RGB24_FORMAT, YUV420_FORMAT};
9 //! use nihav_core::frame::{alloc_video_buffer, NAVideoInfo};
10 //!
11 //! let mut in_pic = alloc_video_buffer(NAVideoInfo::new(640, 480, false, RGB24_FORMAT), 4).unwrap();
12 //! let mut out_pic = alloc_video_buffer(NAVideoInfo::new(320, 240, false, YUV420_FORMAT), 4).unwrap();
13 //! let in_fmt = get_scale_fmt_from_pic(&in_pic);
14 //! let out_fmt = get_scale_fmt_from_pic(&out_pic);
15 //! let mut scaler = NAScale::new(in_fmt, out_fmt).unwrap();
16 //! scaler.convert(&in_pic, &mut out_pic).unwrap();
17 //! ```
18 use crate::frame::*;
19
20 mod kernel;
21
22 mod colorcvt;
23 mod repack;
24 #[allow(clippy::module_inception)]
25 mod scale;
26
27 mod palette;
28
29 pub use crate::scale::palette::{palettise_frame, QuantisationMode, PaletteSearchMode};
30
31 /// Image format information used by the converter.
32 #[derive(Clone,Copy,PartialEq)]
33 pub struct ScaleInfo {
34 /// Pixel format description.
35 pub fmt: NAPixelFormaton,
36 /// Image width.
37 pub width: usize,
38 /// Image height.
39 pub height: usize,
40 }
41
42 impl std::fmt::Display for ScaleInfo {
43 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
44 write!(f, "({}x{}, {})", self.width, self.height, self.fmt)
45 }
46 }
47
48 /// A list specifying general image conversion errors.
49 #[derive(Debug,Clone,Copy,PartialEq)]
50 #[allow(dead_code)]
51 pub enum ScaleError {
52 /// Input or output buffer contains no image data.
53 NoFrame,
54 /// Allocation failed.
55 AllocError,
56 /// Invalid argument.
57 InvalidArgument,
58 /// Feature is not implemented.
59 NotImplemented,
60 /// Internal implementation bug.
61 Bug,
62 }
63
64 /// A specialised `Result` type for image conversion operations.
65 pub type ScaleResult<T> = Result<T, ScaleError>;
66
67 /*trait Kernel {
68 fn init(&mut self, in_fmt: &ScaleInfo, dest_fmt: &ScaleInfo) -> ScaleResult<NABufferType>;
69 fn process(&mut self, pic_in: &NABufferType, pic_out: &mut NABufferType);
70 }*/
71
72 struct KernelDesc {
73 name: &'static str,
74 create: fn () -> Box<dyn kernel::Kernel>,
75 }
76
77 impl KernelDesc {
78 fn find(name: &str) -> ScaleResult<Box<dyn kernel::Kernel>> {
79 for kern in KERNELS.iter() {
80 if kern.name == name {
81 return Ok((kern.create)());
82 }
83 }
84 Err(ScaleError::InvalidArgument)
85 }
86 }
87
88 const KERNELS: &[KernelDesc] = &[
89 KernelDesc { name: "pack", create: repack::create_pack },
90 KernelDesc { name: "unpack", create: repack::create_unpack },
91 KernelDesc { name: "depal", create: repack::create_depal },
92 KernelDesc { name: "palette", create: palette::create_palettise },
93 KernelDesc { name: "scale", create: scale::create_scale },
94 KernelDesc { name: "rgb_to_yuv", create: colorcvt::create_rgb2yuv },
95 KernelDesc { name: "yuv_to_rgb", create: colorcvt::create_yuv2rgb },
96 ];
97
98 struct Stage {
99 fmt_out: ScaleInfo,
100 tmp_pic: NABufferType,
101 next: Option<Box<Stage>>,
102 worker: Box<dyn kernel::Kernel>,
103 }
104
105 /// Converts input picture information into format used by scaler.
106 pub fn get_scale_fmt_from_pic(pic: &NABufferType) -> ScaleInfo {
107 let info = pic.get_video_info().unwrap();
108 ScaleInfo { fmt: info.get_format(), width: info.get_width(), height: info.get_height() }
109 }
110
111 impl Stage {
112 fn new(name: &str, in_fmt: &ScaleInfo, dest_fmt: &ScaleInfo, options: &[(String, String)]) -> ScaleResult<Self> {
113 let mut worker = KernelDesc::find(name)?;
114 let tmp_pic = worker.init(in_fmt, dest_fmt, options)?;
115 let fmt_out = get_scale_fmt_from_pic(&tmp_pic);
116 Ok(Self { fmt_out, tmp_pic, next: None, worker })
117 }
118 fn add(&mut self, new: Stage) {
119 if let Some(ref mut next) = self.next {
120 next.add(new);
121 } else {
122 self.next = Some(Box::new(new));
123 }
124 }
125 fn process(&mut self, pic_in: &NABufferType, pic_out: &mut NABufferType) -> ScaleResult<()> {
126 if let Some(ref mut nextstage) = self.next {
127 self.worker.process(pic_in, &mut self.tmp_pic);
128 nextstage.process(&self.tmp_pic, pic_out)?;
129 } else {
130 self.worker.process(pic_in, pic_out);
131 }
132 Ok(())
133 }
134 fn drop_last_tmp(&mut self) {
135 if let Some(ref mut nextstage) = self.next {
136 nextstage.drop_last_tmp();
137 } else {
138 self.tmp_pic = NABufferType::None;
139 }
140 }
141 }
142
143 /// Image format converter.
144 pub struct NAScale {
145 fmt_in: ScaleInfo,
146 fmt_out: ScaleInfo,
147 just_convert: bool,
148 pipeline: Option<Stage>,
149 }
150
151 fn check_format(in_fmt: NAVideoInfo, ref_fmt: &ScaleInfo, just_convert: bool) -> ScaleResult<()> {
152 if in_fmt.get_format() != ref_fmt.fmt { return Err(ScaleError::InvalidArgument); }
153 if !just_convert && (in_fmt.get_width() != ref_fmt.width || in_fmt.get_height() != ref_fmt.height) {
154 return Err(ScaleError::InvalidArgument);
155 }
156 Ok(())
157 }
158
159 fn copy(pic_in: &NABufferType, pic_out: &mut NABufferType)
160 {
161 if let (Some(ref sbuf), Some(ref mut dbuf)) = (pic_in.get_vbuf(), pic_out.get_vbuf()) {
162 let mut same = true;
163 let num_components = sbuf.get_info().get_format().get_num_comp();
164 for i in 0..num_components {
165 if sbuf.get_stride(i) != dbuf.get_stride(i) {
166 same = false;
167 break;
168 }
169 if sbuf.get_offset(i) != dbuf.get_offset(i) {
170 same = false;
171 break;
172 }
173 }
174 if same {
175 let sdata = sbuf.get_data();
176 let ddata = dbuf.get_data_mut().unwrap();
177 ddata.copy_from_slice(&sdata[0..]);
178 } else {
179 let sdata = sbuf.get_data();
180 for comp in 0..num_components {
181 let (_, h) = sbuf.get_dimensions(comp);
182 let src = &sdata[sbuf.get_offset(comp)..];
183 let sstride = sbuf.get_stride(comp);
184 let doff = dbuf.get_offset(comp);
185 let dstride = dbuf.get_stride(comp);
186 let ddata = dbuf.get_data_mut().unwrap();
187 let dst = &mut ddata[doff..];
188 let copy_size = sstride.min(dstride);
189 for (dline, sline) in dst.chunks_exact_mut(dstride).take(h).zip(src.chunks_exact(sstride)) {
190 (&mut dline[..copy_size]).copy_from_slice(&sline[..copy_size]);
191 }
192 }
193 }
194 } else if let (Some(ref sbuf), Some(ref mut dbuf)) = (pic_in.get_vbuf16(), pic_out.get_vbuf16()) {
195 let mut same = true;
196 let num_components = sbuf.get_info().get_format().get_num_comp();
197 for i in 0..num_components {
198 if sbuf.get_stride(i) != dbuf.get_stride(i) {
199 same = false;
200 break;
201 }
202 if sbuf.get_offset(i) != dbuf.get_offset(i) {
203 same = false;
204 break;
205 }
206 }
207 if same {
208 let sdata = sbuf.get_data();
209 let ddata = dbuf.get_data_mut().unwrap();
210 ddata.copy_from_slice(&sdata[0..]);
211 } else {
212 let sdata = sbuf.get_data();
213 for comp in 0..num_components {
214 let (_, h) = sbuf.get_dimensions(comp);
215 let src = &sdata[sbuf.get_offset(comp)..];
216 let sstride = sbuf.get_stride(comp);
217 let doff = dbuf.get_offset(comp);
218 let dstride = dbuf.get_stride(comp);
219 let ddata = dbuf.get_data_mut().unwrap();
220 let dst = &mut ddata[doff..];
221 let copy_size = sstride.min(dstride);
222 for (dline, sline) in dst.chunks_exact_mut(dstride).take(h).zip(src.chunks_exact(sstride)) {
223 (&mut dline[..copy_size]).copy_from_slice(&sline[..copy_size]);
224 }
225 }
226 }
227 } else {
228 unimplemented!();
229 }
230 }
231
232 macro_rules! add_stage {
233 ($head:expr, $new:expr) => {
234 if let Some(ref mut h) = $head {
235 h.add($new);
236 } else {
237 $head = Some($new);
238 }
239 };
240 }
241 fn is_better_fmt(a: &ScaleInfo, b: &ScaleInfo) -> bool {
242 if (a.width >= b.width) && (a.height >= b.height) {
243 return true;
244 }
245 if a.fmt.get_max_depth() > b.fmt.get_max_depth() {
246 return true;
247 }
248 if a.fmt.get_max_subsampling() < b.fmt.get_max_subsampling() {
249 return true;
250 }
251 false
252 }
253 fn fmt_needs_scale(ifmt: &NAPixelFormaton, ofmt: &NAPixelFormaton) -> bool {
254 for (ichr, ochr) in ifmt.comp_info.iter().zip(ofmt.comp_info.iter()) {
255 if let (Some(ic), Some(oc)) = (ichr, ochr) {
256 if ic.h_ss != oc.h_ss || ic.v_ss != oc.v_ss {
257 return true;
258 }
259 }
260 }
261 false
262 }
263 fn build_pipeline(ifmt: &ScaleInfo, ofmt: &ScaleInfo, just_convert: bool, options: &[(String, String)]) -> ScaleResult<Option<Stage>> {
264 let mut debug = false;
265 for (name, value) in options.iter() {
266 if name == "debug" && (value == "" || value == "true") {
267 debug = true;
268 break;
269 }
270 }
271
272 let inname = ifmt.fmt.get_model().get_short_name();
273 let outname = ofmt.fmt.get_model().get_short_name();
274
275 if debug {
276 println!("convert {} -> {}", ifmt, ofmt);
277 }
278 let needs_scale = if fmt_needs_scale(&ifmt.fmt, &ofmt.fmt) {
279 true
280 } else {
281 !just_convert
282 };
283 let needs_unpack = !ifmt.fmt.is_unpacked();
284 let needs_pack = !ofmt.fmt.is_unpacked();
285 let needs_convert = inname != outname;
286 let scale_before_cvt = is_better_fmt(&ifmt, &ofmt) && needs_convert
287 && (ofmt.fmt.get_max_subsampling() == 0);
288 let needs_palettise = ofmt.fmt.palette;
289 //todo stages for model and gamma conversion
290
291 let mut stages: Option<Stage> = None;
292 let mut cur_fmt = *ifmt;
293
294 if needs_unpack {
295 if debug {
296 println!("[adding unpack]");
297 }
298 let new_stage = if !cur_fmt.fmt.is_paletted() {
299 Stage::new("unpack", &cur_fmt, &ofmt, options)?
300 } else {
301 Stage::new("depal", &cur_fmt, &ofmt, options)?
302 };
303 cur_fmt = new_stage.fmt_out;
304 add_stage!(stages, new_stage);
305 }
306 if needs_scale && scale_before_cvt {
307 if debug {
308 println!("[adding scale]");
309 }
310 let new_stage = Stage::new("scale", &cur_fmt, &ofmt, options)?;
311 cur_fmt = new_stage.fmt_out;
312 add_stage!(stages, new_stage);
313 }
314 if needs_convert {
315 if debug {
316 println!("[adding convert]");
317 }
318 let cvtname = format!("{}_to_{}", inname, outname);
319 if debug {
320 println!("[{}]", cvtname);
321 }
322 let new_stage = Stage::new(&cvtname, &cur_fmt, &ofmt, options)?;
323 //todo if fails try converting via RGB or YUV
324 cur_fmt = new_stage.fmt_out;
325 add_stage!(stages, new_stage);
326 //todo alpha plane copy/add
327 }
328 if needs_scale && !scale_before_cvt {
329 if debug {
330 println!("[adding scale]");
331 }
332 let new_stage = Stage::new("scale", &cur_fmt, &ofmt, options)?;
333 cur_fmt = new_stage.fmt_out;
334 add_stage!(stages, new_stage);
335 }
336 if needs_pack && !needs_palettise {
337 if debug {
338 println!("[adding pack]");
339 }
340 let new_stage = Stage::new("pack", &cur_fmt, &ofmt, options)?;
341 //cur_fmt = new_stage.fmt_out;
342 add_stage!(stages, new_stage);
343 }
344 if needs_palettise {
345 if debug {
346 println!("[adding palettise]");
347 }
348 let new_stage = Stage::new("palette", &cur_fmt, &ofmt, options)?;
349 //cur_fmt = new_stage.fmt_out;
350 add_stage!(stages, new_stage);
351 }
352
353 if let Some(ref mut head) = stages {
354 head.drop_last_tmp();
355 }
356
357 Ok(stages)
358 }
359
360 fn swap_plane<T:Copy>(data: &mut [T], stride: usize, h: usize, line0: &mut [T], line1: &mut [T]) {
361 let mut doff0 = 0;
362 let mut doff1 = stride * (h - 1);
363 for _ in 0..h/2 {
364 line0.copy_from_slice(&data[doff0..][..stride]);
365 line1.copy_from_slice(&data[doff1..][..stride]);
366 (&mut data[doff1..][..stride]).copy_from_slice(line0);
367 (&mut data[doff0..][..stride]).copy_from_slice(line1);
368 doff0 += stride;
369 doff1 -= stride;
370 }
371 }
372
373 /// Flips the picture contents.
374 pub fn flip_picture(pic: &mut NABufferType) -> ScaleResult<()> {
375 match pic {
376 NABufferType::Video(ref mut vb) => {
377 let ncomp = vb.get_num_components();
378 for comp in 0..ncomp {
379 let off = vb.get_offset(comp);
380 let stride = vb.get_stride(comp);
381 let (_, h) = vb.get_dimensions(comp);
382 let data = vb.get_data_mut().unwrap();
383 let mut line0 = vec![0; stride];
384 let mut line1 = vec![0; stride];
385 swap_plane(&mut data[off..], stride, h, line0.as_mut_slice(), line1.as_mut_slice());
386 }
387 },
388 NABufferType::Video16(ref mut vb) => {
389 let ncomp = vb.get_num_components().max(1);
390 for comp in 0..ncomp {
391 let off = vb.get_offset(comp);
392 let stride = vb.get_stride(comp);
393 let (_, h) = vb.get_dimensions(comp);
394 let data = vb.get_data_mut().unwrap();
395 let mut line0 = vec![0; stride];
396 let mut line1 = vec![0; stride];
397 swap_plane(&mut data[off..], stride, h, line0.as_mut_slice(), line1.as_mut_slice());
398 }
399 },
400 NABufferType::Video32(ref mut vb) => {
401 let ncomp = vb.get_num_components().max(1);
402 for comp in 0..ncomp {
403 let off = vb.get_offset(comp);
404 let stride = vb.get_stride(comp);
405 let (_, h) = vb.get_dimensions(comp);
406 let data = vb.get_data_mut().unwrap();
407 let mut line0 = vec![0; stride];
408 let mut line1 = vec![0; stride];
409 swap_plane(&mut data[off..], stride, h, line0.as_mut_slice(), line1.as_mut_slice());
410 }
411 },
412 NABufferType::VideoPacked(ref mut vb) => {
413 let ncomp = vb.get_num_components();
414 for comp in 0..ncomp {
415 let off = vb.get_offset(comp);
416 let stride = vb.get_stride(comp);
417 let (_, h) = vb.get_dimensions(comp);
418 let data = vb.get_data_mut().unwrap();
419 let mut line0 = vec![0; stride];
420 let mut line1 = vec![0; stride];
421 swap_plane(&mut data[off..], stride, h, line0.as_mut_slice(), line1.as_mut_slice());
422 }
423 if ncomp == 0 && vb.get_stride(0) != 0 {
424 let off = vb.get_offset(0);
425 let stride = vb.get_stride(0);
426 let (_, h) = vb.get_dimensions(0);
427 let data = vb.get_data_mut().unwrap();
428 let mut line0 = vec![0; stride];
429 let mut line1 = vec![0; stride];
430 swap_plane(&mut data[off..], stride, h, line0.as_mut_slice(), line1.as_mut_slice());
431 }
432 },
433 _ => { return Err(ScaleError::InvalidArgument); },
434 };
435 Ok(())
436 }
437
438 impl NAScale {
439 /// Constructs a new `NAScale` instance.
440 pub fn new(fmt_in: ScaleInfo, fmt_out: ScaleInfo) -> ScaleResult<Self> {
441 let pipeline;
442 let just_convert = (fmt_in.width == fmt_out.width) && (fmt_in.height == fmt_out.height);
443 if fmt_in != fmt_out {
444 pipeline = build_pipeline(&fmt_in, &fmt_out, just_convert, &[])?;
445 } else {
446 pipeline = None;
447 }
448 Ok(Self { fmt_in, fmt_out, just_convert, pipeline })
449 }
450 /// Constructs a new `NAScale` instance taking into account provided options.
451 pub fn new_with_options(fmt_in: ScaleInfo, fmt_out: ScaleInfo, options: &[(String, String)]) -> ScaleResult<Self> {
452 let pipeline;
453 let just_convert = (fmt_in.width == fmt_out.width) && (fmt_in.height == fmt_out.height);
454 if fmt_in != fmt_out {
455 pipeline = build_pipeline(&fmt_in, &fmt_out, just_convert, options)?;
456 } else {
457 pipeline = None;
458 }
459 Ok(Self { fmt_in, fmt_out, just_convert, pipeline })
460 }
461 /// Checks whether requested conversion operation is needed at all.
462 pub fn needs_processing(&self) -> bool { self.pipeline.is_some() }
463 /// Returns the input image format.
464 pub fn get_in_fmt(&self) -> ScaleInfo { self.fmt_in }
465 /// Returns the output image format.
466 pub fn get_out_fmt(&self) -> ScaleInfo { self.fmt_out }
467 /// Performs the image format conversion.
468 pub fn convert(&mut self, pic_in: &NABufferType, pic_out: &mut NABufferType) -> ScaleResult<()> {
469 let in_info = pic_in.get_video_info();
470 let out_info = pic_out.get_video_info();
471 if in_info.is_none() || out_info.is_none() { return Err(ScaleError::InvalidArgument); }
472 let in_info = in_info.unwrap();
473 let out_info = out_info.unwrap();
474 if self.just_convert &&
475 (in_info.get_width() != out_info.get_width() || in_info.get_height() != out_info.get_height()) {
476 return Err(ScaleError::InvalidArgument);
477 }
478 let needs_flip = in_info.is_flipped() ^ out_info.is_flipped();
479 check_format(in_info, &self.fmt_in, self.just_convert)?;
480 check_format(out_info, &self.fmt_out, self.just_convert)?;
481 let ret = if let Some(ref mut pipe) = self.pipeline {
482 pipe.process(pic_in, pic_out)
483 } else {
484 copy(pic_in, pic_out);
485 Ok(())
486 };
487 if ret.is_ok() && needs_flip {
488 flip_picture(pic_out)?;
489 }
490 ret
491 }
492 }
493
494 #[cfg(test)]
495 mod test {
496 use super::*;
497
498 fn fill_pic(pic: &mut NABufferType, val: u8) {
499 if let Some(ref mut buf) = pic.get_vbuf() {
500 let data = buf.get_data_mut().unwrap();
501 for el in data.iter_mut() { *el = val; }
502 } else if let Some(ref mut buf) = pic.get_vbuf16() {
503 let data = buf.get_data_mut().unwrap();
504 for el in data.iter_mut() { *el = val as u16; }
505 } else if let Some(ref mut buf) = pic.get_vbuf32() {
506 let data = buf.get_data_mut().unwrap();
507 for el in data.iter_mut() { *el = (val as u32) * 0x01010101; }
508 }
509 }
510 #[test]
511 fn test_convert() {
512 let mut in_pic = alloc_video_buffer(NAVideoInfo::new(1, 1, false, RGB565_FORMAT), 3).unwrap();
513 fill_pic(&mut in_pic, 42);
514 let mut out_pic = alloc_video_buffer(NAVideoInfo::new(1, 1, false, RGB24_FORMAT), 3).unwrap();
515 fill_pic(&mut out_pic, 0);
516 let ifmt = get_scale_fmt_from_pic(&in_pic);
517 let ofmt = get_scale_fmt_from_pic(&out_pic);
518 let mut scaler = NAScale::new(ifmt, ofmt).unwrap();
519 scaler.convert(&in_pic, &mut out_pic).unwrap();
520 let obuf = out_pic.get_vbuf().unwrap();
521 let odata = obuf.get_data();
522 assert_eq!(odata[0], 0x0);
523 assert_eq!(odata[1], 0x4);
524 assert_eq!(odata[2], 0x52);
525
526 let mut in_pic = alloc_video_buffer(NAVideoInfo::new(4, 4, false, RGB24_FORMAT), 3).unwrap();
527 fill_pic(&mut in_pic, 42);
528 let mut out_pic = alloc_video_buffer(NAVideoInfo::new(4, 4, false, YUV420_FORMAT), 3).unwrap();
529 fill_pic(&mut out_pic, 0);
530 let ifmt = get_scale_fmt_from_pic(&in_pic);
531 let ofmt = get_scale_fmt_from_pic(&out_pic);
532 let mut scaler = NAScale::new(ifmt, ofmt).unwrap();
533 scaler.convert(&in_pic, &mut out_pic).unwrap();
534 let obuf = out_pic.get_vbuf().unwrap();
535 let yoff = obuf.get_offset(0);
536 let uoff = obuf.get_offset(1);
537 let voff = obuf.get_offset(2);
538 let odata = obuf.get_data();
539 assert_eq!(odata[yoff], 42);
540 assert!(((odata[uoff] ^ 0x80) as i8).abs() <= 1);
541 assert!(((odata[voff] ^ 0x80) as i8).abs() <= 1);
542 let mut scaler = NAScale::new(ofmt, ifmt).unwrap();
543 scaler.convert(&out_pic, &mut in_pic).unwrap();
544 let obuf = in_pic.get_vbuf().unwrap();
545 let odata = obuf.get_data();
546 assert_eq!(odata[0], 42);
547 }
548 #[test]
549 fn test_scale() {
550 let mut in_pic = alloc_video_buffer(NAVideoInfo::new(2, 2, false, RGB565_FORMAT), 3).unwrap();
551 fill_pic(&mut in_pic, 42);
552 let mut out_pic = alloc_video_buffer(NAVideoInfo::new(3, 3, false, RGB565_FORMAT), 3).unwrap();
553 fill_pic(&mut out_pic, 0);
554 let ifmt = get_scale_fmt_from_pic(&in_pic);
555 let ofmt = get_scale_fmt_from_pic(&out_pic);
556 let mut scaler = NAScale::new(ifmt, ofmt).unwrap();
557 scaler.convert(&in_pic, &mut out_pic).unwrap();
558 let obuf = out_pic.get_vbuf16().unwrap();
559 let odata = obuf.get_data();
560 assert_eq!(odata[0], 42);
561 }
562 #[test]
563 fn test_scale_and_convert() {
564 let mut in_pic = alloc_video_buffer(NAVideoInfo::new(7, 3, false, RGB565_FORMAT), 3).unwrap();
565 fill_pic(&mut in_pic, 42);
566 let mut out_pic = alloc_video_buffer(NAVideoInfo::new(4, 4, false, YUV420_FORMAT), 3).unwrap();
567 fill_pic(&mut out_pic, 0);
568 let ifmt = get_scale_fmt_from_pic(&in_pic);
569 let ofmt = get_scale_fmt_from_pic(&out_pic);
570 let mut scaler = NAScale::new(ifmt, ofmt).unwrap();
571 scaler.convert(&in_pic, &mut out_pic).unwrap();
572 let obuf = out_pic.get_vbuf().unwrap();
573 let yoff = obuf.get_offset(0);
574 let uoff = obuf.get_offset(1);
575 let voff = obuf.get_offset(2);
576 let odata = obuf.get_data();
577 assert_eq!(odata[yoff], 11);
578 assert_eq!(odata[uoff], 162);
579 assert_eq!(odata[voff], 118);
580 }
581 #[test]
582 fn test_scale_and_convert_to_pal() {
583 let mut in_pic = alloc_video_buffer(NAVideoInfo::new(7, 3, false, YUV420_FORMAT), 3).unwrap();
584 fill_pic(&mut in_pic, 142);
585 let mut out_pic = alloc_video_buffer(NAVideoInfo::new(4, 4, false, PAL8_FORMAT), 0).unwrap();
586 fill_pic(&mut out_pic, 0);
587 let ifmt = get_scale_fmt_from_pic(&in_pic);
588 let ofmt = get_scale_fmt_from_pic(&out_pic);
589 let mut scaler = NAScale::new(ifmt, ofmt).unwrap();
590 scaler.convert(&in_pic, &mut out_pic).unwrap();
591 let obuf = out_pic.get_vbuf().unwrap();
592 let dataoff = obuf.get_offset(0);
593 let paloff = obuf.get_offset(1);
594 let odata = obuf.get_data();
595 assert_eq!(odata[dataoff], 0);
596 assert_eq!(odata[paloff], 157);
597 assert_eq!(odata[paloff + 1], 129);
598 assert_eq!(odata[paloff + 2], 170);
599 }
600 }