gifenc: support grayscale input directly
[nihav.git] / nihav-commonfmt / src / codecs / gifenc.rs
CommitLineData
fc39649d
KS
1use nihav_core::codecs::*;
2use nihav_core::io::byteio::*;
3use nihav_core::io::bitwriter::*;
4
c455a794
KS
5const GRAY_FORMAT: NAPixelFormaton = NAPixelFormaton {
6 model: ColorModel::YUV(YUVSubmodel::YUVJ),
7 components: 1,
8 comp_info: [Some(NAPixelChromaton{h_ss: 0, v_ss: 0, packed: false, depth: 8, shift: 0, comp_offs: 0, next_elem: 1}), None, None, None, None],
9 elem_size: 1,
10 be: true,
11 alpha: false,
12 palette: false,
13 };
14
fc39649d
KS
15#[derive(Clone,Copy,Default,PartialEq)]
16enum CompressionLevel {
17 None,
18 Fast,
19 #[default]
20 Best
21}
22
23impl std::string::ToString for CompressionLevel {
24 fn to_string(&self) -> String {
25 match *self {
26 CompressionLevel::None => "none".to_string(),
27 CompressionLevel::Fast => "fast".to_string(),
28 CompressionLevel::Best => "best".to_string(),
29 }
30 }
31}
32
33const NO_CODE: u16 = 0;
34
35struct LZWDictionary {
36 cur_size: usize,
37 bit_len: u8,
38 clear_code: u16,
39 end_code: u16,
40 orig_len: u8,
41 trie: Vec<[u16; 257]>,
42}
43
44impl LZWDictionary {
45 fn new() -> Self {
46 Self {
47 trie: Vec::with_capacity(4096),
48 cur_size: 0,
49 bit_len: 0,
50 clear_code: 0,
51 end_code: 0,
52 orig_len: 0,
53 }
54 }
55 fn init(&mut self, bits: u8) {
56 self.cur_size = (1 << bits) + 2;
57 self.bit_len = bits + 1;
58 self.clear_code = 1 << bits;
59 self.end_code = self.clear_code + 1;
60 self.orig_len = self.bit_len;
61
62 self.trie.clear();
63 for _ in 0..self.cur_size {
64 self.trie.push([NO_CODE; 257]);
65 }
66 for (idx, nodes) in self.trie.iter_mut().enumerate() {
67 nodes[256] = idx as u16;
68 }
69 }
70 fn find(&self, src: &[u8]) -> (u16, usize, usize) {
71 let mut idx = usize::from(src[0]);
72 let mut last_len = 0;
73 for (pos, &next) in src.iter().enumerate().skip(1) {
74 let next = usize::from(next);
75 if self.trie[idx][next] != NO_CODE {
76 idx = usize::from(self.trie[idx][next]);
77 } else {
78 return (self.trie[idx][256], pos, idx);
79 }
80 last_len = pos;
81 }
82 (self.trie[idx][256], last_len + 1, idx)
83 }
84 fn add(&mut self, lastidx: usize, next: u8) {
85 if self.cur_size >= (1 << 12) {
86 return;
87 }
88 let next = usize::from(next);
89 if self.trie[lastidx][next] == NO_CODE {
90 let newnode = self.trie.len();
91 self.trie.push([NO_CODE; 257]);
92 self.trie[newnode][256] = self.cur_size as u16;
93 self.trie[lastidx][next] = newnode as u16;
94 }
95 if (self.cur_size & (self.cur_size - 1)) == 0 && self.bit_len < 12 {
96 self.bit_len += 1;
97 }
98 self.cur_size += 1;
99 }
100 fn reset(&mut self) {
101 self.bit_len = self.orig_len;
102 self.cur_size = usize::from(self.end_code) + 1;
103 self.trie.truncate(self.cur_size);
104 for nodes in self.trie.iter_mut() {
105 for el in nodes[..256].iter_mut() {
106 *el = NO_CODE;
107 }
108 }
109 }
110}
111
112struct LZWEncoder {
113 dict: LZWDictionary,
114 level: CompressionLevel,
115 tmp: Vec<u8>,
116}
117
118impl LZWEncoder {
119 fn new() -> Self {
120 Self {
121 dict: LZWDictionary::new(),
122 level: CompressionLevel::default(),
123 tmp: Vec::new(),
124 }
125 }
126 fn compress(&mut self, writer: &mut ByteWriter, src: &[u8]) -> EncoderResult<()> {
127 let clr_bits: u8 = if self.level != CompressionLevel::None {
128 let maxclr = u16::from(src.iter().fold(0u8, |acc, &a| acc.max(a))) + 1;
129 let mut bits = 2;
130 while (1 << bits) < maxclr {
131 bits += 1;
132 }
133 bits
134 } else { 8 };
135
136 self.dict.init(clr_bits);
137
138 self.tmp.clear();
139 let mut tbuf = Vec::new();
140 std::mem::swap(&mut tbuf, &mut self.tmp);
141 let mut bw = BitWriter::new(tbuf, BitWriterMode::LE);
142
143 bw.write(u32::from(self.dict.clear_code), self.dict.bit_len);
144
145 match self.level {
146 CompressionLevel::None => {
8fd97a64 147 let sym_limit = 1 << (clr_bits + 1);
fc39649d 148 for &b in src.iter() {
8fd97a64
KS
149 if self.dict.cur_size >= sym_limit {
150 bw.write(u32::from(self.dict.clear_code), self.dict.bit_len);
151 self.dict.reset();
152 }
fc39649d
KS
153 bw.write(u32::from(b), self.dict.bit_len);
154 self.dict.add(usize::from(b), 0);
155 }
156 },
157 CompressionLevel::Fast => {
158 let mut pos = 0;
159 while pos < src.len() {
160 let (idx, len, trieidx) = self.dict.find(&src[pos..]);
161 bw.write(u32::from(idx), self.dict.bit_len);
162 pos += len;
163 if pos < src.len() {
164 self.dict.add(trieidx, src[pos]);
165 }
166 if self.dict.cur_size == 4096 {
167 bw.write(u32::from(self.dict.clear_code), self.dict.bit_len);
168 self.dict.reset();
169 }
170 }
171 },
172 CompressionLevel::Best => {
173 let mut pos = 0;
174 let mut hist = [0; 16];
175 let mut avg = 0;
176 let mut avg1 = 0;
177 let mut hpos = 0;
178 while pos < src.len() {
179 let (idx, len, trieidx) = self.dict.find(&src[pos..]);
180 bw.write(u32::from(idx), self.dict.bit_len);
181 pos += len;
182 if pos >= src.len() {
183 break;
184 }
185 self.dict.add(trieidx, src[pos]);
186
187 avg1 -= hist[(hpos + 1) & 0xF];
188 avg1 += len;
189 if self.dict.cur_size == 4096 && (avg1 < avg - avg / 8) {
190 bw.write(u32::from(self.dict.clear_code), self.dict.bit_len);
191 self.dict.reset();
192 }
193 avg = avg1;
194 hpos = (hpos + 1) & 0xF;
195 hist[hpos] = len;
196 }
197 },
198 };
199
200 bw.write(u32::from(self.dict.end_code), self.dict.bit_len);
201 tbuf = bw.end();
202 std::mem::swap(&mut tbuf, &mut self.tmp);
203
204 writer.write_byte(clr_bits)?;
205 for chunk in self.tmp.chunks(255) {
206 writer.write_byte(chunk.len() as u8)?;
207 writer.write_buf(chunk)?;
208 }
209 writer.write_byte(0x00)?; // data end marker
210 Ok(())
211 }
212}
213
214struct GIFEncoder {
215 stream: Option<NAStreamRef>,
216 cur_frm: Vec<u8>,
217 prev_frm: Vec<u8>,
218 tmp_buf: Vec<u8>,
219 pal: [u8; 768],
220 pkt: Option<NAPacket>,
221 first: bool,
222 width: usize,
223 height: usize,
c455a794 224 grayscale: bool,
fc39649d
KS
225 lzw: LZWEncoder,
226 p_trans: bool,
227 tr_idx: Option<u8>,
228}
229
230impl GIFEncoder {
231 fn new() -> Self {
232 Self {
233 stream: None,
234 pkt: None,
235 cur_frm: Vec::new(),
236 prev_frm: Vec::new(),
237 pal: [0; 768],
238 tmp_buf: Vec::new(),
239 first: true,
240 width: 0,
241 height: 0,
c455a794 242 grayscale: false,
fc39649d
KS
243 lzw: LZWEncoder::new(),
244 p_trans: false,
245 tr_idx: None,
246 }
247 }
248 fn write_dummy_frame(&mut self, bw: &mut ByteWriter) -> EncoderResult<()> {
249 let mut pix = [self.cur_frm[0]];
250 if let (true, Some(tr_idx)) = (self.p_trans, self.tr_idx) {
251 if tr_idx < pix[0] {
252 pix[0] = tr_idx;
253 }
254 }
255
256 // 1x1 image descriptor
257 bw.write_buf(&[0x2C, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00])?;
258 self.lzw.compress(bw, &pix)?;
259 Ok(())
260 }
261}
262
263impl NAEncoder for GIFEncoder {
264 fn negotiate_format(&self, encinfo: &EncodeParameters) -> EncoderResult<EncodeParameters> {
265 match encinfo.format {
266 NACodecTypeInfo::None => {
267 Ok(EncodeParameters {
268 format: NACodecTypeInfo::Video(NAVideoInfo::new(0, 0, true, YUV420_FORMAT)),
269 ..Default::default()
270 })
271 },
272 NACodecTypeInfo::Audio(_) => Err(EncoderError::FormatError),
273 NACodecTypeInfo::Video(vinfo) => {
c455a794
KS
274 let format = if vinfo.format == GRAY_FORMAT { GRAY_FORMAT } else { PAL8_FORMAT };
275 let outinfo = NAVideoInfo::new(vinfo.width, vinfo.height, false, format);
fc39649d
KS
276 let mut ofmt = *encinfo;
277 ofmt.format = NACodecTypeInfo::Video(outinfo);
278 Ok(ofmt)
279 }
280 }
281 }
282 fn get_capabilities(&self) -> u64 { ENC_CAPS_SKIPFRAME }
283 fn init(&mut self, stream_id: u32, encinfo: EncodeParameters) -> EncoderResult<NAStreamRef> {
284 match encinfo.format {
285 NACodecTypeInfo::None => Err(EncoderError::FormatError),
286 NACodecTypeInfo::Audio(_) => Err(EncoderError::FormatError),
287 NACodecTypeInfo::Video(vinfo) => {
288 if vinfo.width > 65535 || vinfo.height > 65535 {
289 return Err(EncoderError::FormatError);
290 }
c455a794
KS
291 if vinfo.format != PAL8_FORMAT && vinfo.format != GRAY_FORMAT {
292 return Err(EncoderError::FormatError);
293 }
fc39649d
KS
294 self.width = vinfo.width;
295 self.height = vinfo.height;
c455a794 296 self.grayscale = vinfo.format == GRAY_FORMAT;
fc39649d
KS
297
298 let edata = self.tr_idx.map(|val| vec![val]);
299
300 let out_info = NAVideoInfo::new(vinfo.width, vinfo.height, false, PAL8_FORMAT);
301 let info = NACodecInfo::new("gif", NACodecTypeInfo::Video(out_info), edata);
302 let mut stream = NAStream::new(StreamType::Video, stream_id, info, encinfo.tb_num, encinfo.tb_den, 0);
303 stream.set_num(stream_id as usize);
304 let stream = stream.into_ref();
305
306 self.stream = Some(stream.clone());
307
308 self.cur_frm = vec![0; vinfo.width * vinfo.height];
309 self.prev_frm = vec![0; vinfo.width * vinfo.height];
310 self.tmp_buf.clear();
311 self.tmp_buf.reserve(vinfo.width * vinfo.height);
312
313 self.first = true;
314
315 Ok(stream)
316 },
317 }
318 }
319 fn encode(&mut self, frm: &NAFrame) -> EncoderResult<()> {
320 let mut dbuf = Vec::with_capacity(4);
321 let mut gw = GrowableMemoryWriter::new_write(&mut dbuf);
322 let mut bw = ByteWriter::new(&mut gw);
323
324 self.tmp_buf.clear();
325
326 match frm.get_buffer() {
327 NABufferType::Video(ref buf) => {
328 let src = buf.get_data();
329 let stride = buf.get_stride(0);
330 let src = &src[buf.get_offset(0)..];
331
332 for (dline, sline) in self.cur_frm.chunks_exact_mut(self.width)
333 .zip(src.chunks_exact(stride)) {
334 dline.copy_from_slice(&sline[..self.width]);
335 }
336
337 let cur_pal = &src[buf.get_offset(1)..][..768];
338 if self.first {
c455a794
KS
339 if !self.grayscale {
340 self.pal.copy_from_slice(cur_pal);
341 } else {
342 for (i, pal) in self.pal.chunks_exact_mut(3).enumerate() {
343 pal[0] = i as u8;
344 pal[1] = i as u8;
345 pal[2] = i as u8;
346 }
347 }
fc39649d
KS
348 }
349
350 let mut pal_changed = false;
c455a794 351 if !self.first && !self.grayscale {
fc39649d
KS
352 let mut used = [false; 256];
353 for &b in self.cur_frm.iter() {
354 used[usize::from(b)] = true;
355 }
356 for (&used, (pal1, pal2)) in used.iter()
357 .zip(self.pal.chunks_exact(3).zip(cur_pal.chunks_exact(3))) {
358 if used && (pal1 != pal2) {
359 pal_changed = true;
360 break;
361 }
362 }
363 }
364
365 if self.first {
366 bw.write_byte(0x2C)?; // image descriptor
367 bw.write_u16le(0)?; // left
368 bw.write_u16le(0)?; // top
369 bw.write_u16le(self.width as u16)?;
370 bw.write_u16le(self.height as u16)?;
371 bw.write_byte(0)?; // flags
372 self.lzw.compress(&mut bw, &self.cur_frm)?;
373 } else {
374 let mut top = 0;
375 for (y, (line1, line2)) in self.cur_frm.chunks_exact(self.width)
376 .zip(self.prev_frm.chunks_exact(self.width)).enumerate() {
377 if line1 == line2 {
378 top = y;
379 } else {
380 break;
381 }
382 }
383 if top != self.height - 1 {
384 let mut bot = self.height;
385 for (y, (line1, line2)) in self.cur_frm.chunks_exact(self.width)
386 .zip(self.prev_frm.chunks_exact(self.width)).enumerate().rev() {
387 if line1 == line2 {
388 bot = y + 1;
389 } else {
390 break;
391 }
392 }
393 let mut left = self.width - 1;
394 let mut right = 0;
395 for (line1, line2) in self.cur_frm.chunks_exact(self.width)
396 .zip(self.prev_frm.chunks_exact(self.width))
397 .skip(top).take(bot - top) {
398 if left > 0 {
399 let mut cur_l = 0;
400 for (x, (&p1, &p2)) in line1.iter().zip(line2.iter()).enumerate() {
401 if p1 == p2 {
402 cur_l = x + 1;
403 } else {
404 break;
405 }
406 }
407 left = left.min(cur_l);
408 }
409 if right < self.width {
410 let mut cur_r = self.width;
411 for (x, (&p1, &p2)) in line1.iter().zip(line2.iter())
412 .enumerate().rev() {
413 if p1 == p2 {
414 cur_r = x + 1;
415 } else {
416 break;
417 }
418 }
419 right = right.max(cur_r);
420 }
421 }
422 self.tmp_buf.clear();
423 let use_transparency = self.p_trans && self.tr_idx.is_some();
424 let full_frame = right == 0 && top == 0 && left == self.width && bot == self.height;
425
426 let pic = match (use_transparency, full_frame) {
427 (true, _) => {
428 let tr_idx = self.tr_idx.unwrap_or(0);
429 for (cline, pline) in self.cur_frm.chunks_exact(self.width)
430 .zip(self.prev_frm.chunks_exact(self.width))
431 .skip(top).take(bot - top) {
432 for (&cpix, &ppix) in cline[left..right].iter()
433 .zip(pline[left..right].iter()) {
434 self.tmp_buf.push(if cpix == ppix { tr_idx } else { cpix });
435 }
436 }
437 &self.tmp_buf
438 },
439 (false, true) => {
440 &self.cur_frm
441 },
442 (false, false) => {
443 for line in self.cur_frm.chunks_exact(self.width)
444 .skip(top).take(bot - top) {
445 self.tmp_buf.extend_from_slice(&line[left..right]);
446 }
447 &self.tmp_buf
448 },
449 };
450
451 bw.write_byte(0x2C)?; // image descriptor
452 bw.write_u16le(left as u16)?;
453 bw.write_u16le(top as u16)?;
454 bw.write_u16le((right - left) as u16)?;
455 bw.write_u16le((bot - top) as u16)?;
456 if !pal_changed {
457 bw.write_byte(0)?; // flags
458 } else {
459 let maxclr = pic.iter().fold(0u8, |acc, &a| acc.max(a));
460 let clr_bits = if maxclr > 128 {
461 8
462 } else {
463 let mut bits = 1;
464 while (1 << bits) < maxclr {
465 bits += 1;
466 }
467 bits
468 };
469 bw.write_byte(0x80 | (clr_bits - 1))?;
470 bw.write_buf(&cur_pal[..(3 << clr_bits)])?;
471 }
472 self.lzw.compress(&mut bw, pic)?;
473 } else {
474 self.write_dummy_frame(&mut bw)?;
475 }
476 }
477 },
478 NABufferType::None if !self.first => {
479 self.write_dummy_frame(&mut bw)?;
480 },
481 _ => return Err(EncoderError::InvalidParameters),
482 };
483
484 self.pkt = Some(NAPacket::new(self.stream.clone().unwrap(), frm.ts, self.first, dbuf));
485 self.first = false;
486
c455a794
KS
487 if !self.grayscale {
488 if let NABufferType::Video(ref buf) = frm.get_buffer() {
489 let paloff = buf.get_offset(1);
490 let data = buf.get_data();
491 let mut pal = [0; 1024];
492 let srcpal = &data[paloff..][..768];
493 for (dclr, sclr) in pal.chunks_exact_mut(4).zip(srcpal.chunks_exact(3)) {
494 dclr[..3].copy_from_slice(sclr);
495 }
496 if let Some(ref mut pkt) = &mut self.pkt {
497 pkt.side_data.push(NASideData::Palette(true, Arc::new(pal)));
498 }
499 }
500 } else {
fc39649d 501 let mut pal = [0; 1024];
c455a794
KS
502 for (i, quad) in pal.chunks_exact_mut(4).enumerate() {
503 quad[0] = i as u8;
504 quad[1] = i as u8;
505 quad[2] = i as u8;
fc39649d
KS
506 }
507 if let Some(ref mut pkt) = &mut self.pkt {
508 pkt.side_data.push(NASideData::Palette(true, Arc::new(pal)));
509 }
510 }
511
512 std::mem::swap(&mut self.cur_frm, &mut self.prev_frm);
513 Ok(())
514 }
515 fn get_packet(&mut self) -> EncoderResult<Option<NAPacket>> {
516 let mut npkt = None;
517 std::mem::swap(&mut self.pkt, &mut npkt);
518 Ok(npkt)
519 }
520 fn flush(&mut self) -> EncoderResult<()> {
521 Ok(())
522 }
523}
524
525const ENCODER_OPTS: &[NAOptionDefinition] = &[
526 NAOptionDefinition {
527 name: "compr", description: "Compression level",
528 opt_type: NAOptionDefinitionType::String(Some(&["none", "fast", "best"])) },
529 NAOptionDefinition {
530 name: "inter_transparent", description: "Code changed regions with transparency",
531 opt_type: NAOptionDefinitionType::Bool },
532 NAOptionDefinition {
533 name: "transparent_idx", description: "Palette index to use for transparency (on inter frames too if requested)",
534 opt_type: NAOptionDefinitionType::Int(Some(-1), Some(255)) },
535];
536
537impl NAOptionHandler for GIFEncoder {
538 fn get_supported_options(&self) -> &[NAOptionDefinition] { ENCODER_OPTS }
539 fn set_options(&mut self, options: &[NAOption]) {
540 for option in options.iter() {
541 for opt_def in ENCODER_OPTS.iter() {
542 if opt_def.check(option).is_ok() {
543 match option.name {
544 "compr" => {
545 if let NAValue::String(ref strval) = option.value {
546 match strval.as_str() {
547 "none" => self.lzw.level = CompressionLevel::None,
548 "fast" => self.lzw.level = CompressionLevel::Fast,
549 "best" => self.lzw.level = CompressionLevel::Best,
550 _ => {},
551 };
552 }
553 },
554 "inter_transparent" => {
555 if let NAValue::Bool(bval) = option.value {
556 self.p_trans = bval;
557 }
558 },
559 "transparent_idx" => {
560 if let NAValue::Int(ival) = option.value {
561 self.tr_idx = if ival >= 0 { Some(ival as u8) } else { None };
562 }
563 },
564 _ => {},
565 };
566 }
567 }
568 }
569 }
570 fn query_option_value(&self, name: &str) -> Option<NAValue> {
571 match name {
572 "compr" => Some(NAValue::String(self.lzw.level.to_string())),
573 "inter_transparent" => Some(NAValue::Bool(self.p_trans)),
574 "transparent_idx" => Some(NAValue::Int(self.tr_idx.map_or(-1i64, i64::from))),
575 _ => None,
576 }
577 }
578}
579
580pub fn get_encoder() -> Box<dyn NAEncoder + Send> {
581 Box::new(GIFEncoder::new())
582}
583
584#[cfg(test)]
585mod test {
586 use nihav_core::codecs::*;
587 use nihav_core::demuxers::*;
588 use nihav_core::muxers::*;
589 use crate::*;
590 use nihav_codec_support::test::enc_video::*;
591
592 // sample: https://samples.mplayerhq.hu/V-codecs/Uncompressed/8bpp.avi
593 fn test_gif_encoder_single(out_name: &'static str, enc_options: &[NAOption], hash: &[u32; 4]) {
594 let mut dmx_reg = RegisteredDemuxers::new();
595 generic_register_all_demuxers(&mut dmx_reg);
596 let mut dec_reg = RegisteredDecoders::new();
597 generic_register_all_decoders(&mut dec_reg);
598 let mut mux_reg = RegisteredMuxers::new();
599 generic_register_all_muxers(&mut mux_reg);
600 let mut enc_reg = RegisteredEncoders::new();
601 generic_register_all_encoders(&mut enc_reg);
602
603 let dec_config = DecoderTestParams {
604 demuxer: "avi",
605 in_name: "assets/Misc/8bpp.avi",
606 stream_type: StreamType::Video,
607 limit: Some(0),
608 dmx_reg, dec_reg,
609 };
610 let enc_config = EncoderTestParams {
611 muxer: "gif",
612 enc_name: "gif",
613 out_name,
614 mux_reg, enc_reg,
615 };
616 let dst_vinfo = NAVideoInfo {
617 width: 0,
618 height: 0,
619 format: PAL8_FORMAT,
620 flipped: false,
621 bits: 8,
622 };
623 let enc_params = EncodeParameters {
624 format: NACodecTypeInfo::Video(dst_vinfo),
625 quality: 0,
626 bitrate: 0,
627 tb_num: 0,
628 tb_den: 0,
629 flags: 0,
630 };
631 //test_encoding_to_file(&dec_config, &enc_config, enc_params, enc_options);
632 test_encoding_md5(&dec_config, &enc_config, enc_params, enc_options, hash);
633 }
634 // sample: https://samples.mplayerhq.hu/image-samples/GIF/3D.gif
635 fn test_gif_anim(out_name: &'static str, enc_options: &[NAOption], hash: &[u32; 4]) {
636 let mut dmx_reg = RegisteredDemuxers::new();
637 generic_register_all_demuxers(&mut dmx_reg);
638 let mut dec_reg = RegisteredDecoders::new();
639 generic_register_all_decoders(&mut dec_reg);
640 let mut mux_reg = RegisteredMuxers::new();
641 generic_register_all_muxers(&mut mux_reg);
642 let mut enc_reg = RegisteredEncoders::new();
643 generic_register_all_encoders(&mut enc_reg);
644
645 let dec_config = DecoderTestParams {
646 demuxer: "gif",
647 in_name: "assets/Misc/3D.gif",
648 stream_type: StreamType::Video,
649 limit: None,
650 dmx_reg, dec_reg,
651 };
652 let enc_config = EncoderTestParams {
653 muxer: "gif",
654 enc_name: "gif",
655 out_name,
656 mux_reg, enc_reg,
657 };
658 let dst_vinfo = NAVideoInfo {
659 width: 0,
660 height: 0,
661 format: PAL8_FORMAT,
662 flipped: false,
663 bits: 8,
664 };
665 let enc_params = EncodeParameters {
666 format: NACodecTypeInfo::Video(dst_vinfo),
667 quality: 0,
668 bitrate: 0,
669 tb_num: 0,
670 tb_den: 0,
671 flags: 0,
672 };
673 //test_encoding_to_file(&dec_config, &enc_config, enc_params, enc_options);
674 test_encoding_md5(&dec_config, &enc_config, enc_params, enc_options, hash);
675 }
676 #[test]
677 fn test_gif_single_none() {
678 let enc_options = &[
679 NAOption { name: "compr", value: NAValue::String("none".to_string()) },
680 ];
8fd97a64 681 test_gif_encoder_single("none.gif", enc_options, &[0x32900cff, 0xef979bb0, 0x2d0355e8, 0x424bddee]);
fc39649d
KS
682 }
683 #[test]
684 fn test_gif_single_fast() {
685 let enc_options = &[
686 NAOption { name: "compr", value: NAValue::String("fast".to_string()) },
687 ];
688 test_gif_encoder_single("fast.gif", enc_options, &[0x9644f682, 0x497593cd, 0xdabb483d, 0x8fce63f4]);
689 }
690 #[test]
691 fn test_gif_single_best() {
692 let enc_options = &[
693 NAOption { name: "compr", value: NAValue::String("best".to_string()) },
694 ];
695 test_gif_encoder_single("best.gif", enc_options, &[0x9644f682, 0x497593cd, 0xdabb483d, 0x8fce63f4]);
696 }
697 #[test]
698 fn test_gif_anim_opaque() {
699 let enc_options = &[
700 NAOption { name: "compr", value: NAValue::String("fast".to_string()) },
701 ];
702 test_gif_anim("anim-opaque.gif", enc_options, &[0x58489e31, 0x1721d75e, 0xaebf93f2, 0x3fea9c6e]);
703 }
704 #[test]
705 fn test_gif_anim_transparent() {
706 let enc_options = &[
707 NAOption { name: "compr", value: NAValue::String("fast".to_string()) },
708 NAOption { name: "inter_transparent", value: NAValue::Bool(true) },
709 NAOption { name: "transparent_idx", value: NAValue::Int(0x7F) },
710 ];
711 test_gif_anim("anim-transp.gif", enc_options, &[0x62df6232, 0x0c334457, 0x73738404, 0xa8829dcc]);
712 }
713}