1use std::io::BufRead;
4use std::borrow::Cow;
5use std::str;
6
7use byteorder::{ReadBytesExt, LittleEndian};
8
9use types::{Result, Dimensions};
10use traits::LoadableMetadata;
11use utils::BufReadExt;
12
13#[derive(Copy, Clone, PartialEq, Eq, Debug)]
15pub enum Version {
16 V87a,
17 V89a
18}
19
20impl Version {
21 fn from_bytes(b: &[u8]) -> Option<Version> {
22 match b {
23 b"87a" => Some(Version::V87a),
24 b"89a" => Some(Version::V89a),
25 _ => None
26 }
27 }
28}
29
30#[derive(Clone, PartialEq, Eq, Debug)]
32pub enum Block {
33 ImageDescriptor(ImageDescriptor),
35 GraphicControlExtension(GraphicControlExtension),
37 PlainTextExtension(PlainTextExtension),
39 ApplicationExtension(ApplicationExtension),
42 CommentExtension(CommentExtension)
44}
45
46fn skip_blocks<R: ?Sized + BufRead, F>(r: &mut R, on_eof: F) -> Result<()>
47 where F: Fn() -> Cow<'static, str>
48{
49 loop {
50 let n = try_if_eof!(r.read_u8(), on_eof()) as u64;
51 if n == 0 { return Ok(()); }
52 if try!(r.skip_exact(n)) != n {
53 return Err(unexpected_eof!(on_eof()));
54 }
55 }
56}
57
58#[derive(Clone, PartialEq, Eq, Debug)]
60pub struct ColorTable {
61 pub size: u16,
63 pub sorted: bool,
71}
72
73#[derive(Clone, PartialEq, Eq, Debug)]
75pub struct ImageDescriptor {
76 pub left: u16,
78 pub top: u16,
80 pub width: u16,
82 pub height: u16,
84
85 pub local_color_table: Option<ColorTable>,
87
88 pub interlace: bool
90}
91
92impl ImageDescriptor {
93 fn load<R: ?Sized + BufRead>(index: usize, r: &mut R) -> Result<ImageDescriptor> {
94 let left = try_if_eof!(
95 r.read_u16::<LittleEndian>(),
96 "when reading left offset of image block {}", index
97 );
98 let top = try_if_eof!(
99 r.read_u16::<LittleEndian>(),
100 "when reading top offset of image block {}", index
101 );
102 let width = try_if_eof!(
103 r.read_u16::<LittleEndian>(),
104 "when reading width of image block {}", index
105 );
106 let height = try_if_eof!(
107 r.read_u16::<LittleEndian>(),
108 "when reading height of image block {}", index
109 );
110
111 let packed_flags = try_if_eof!(r.read_u8(), "when reading flags of image block {}", index);
112 let local_color_table = (0b10000000 & packed_flags) > 0;
113 let interlace = (0b01000000 & packed_flags) > 0;
114 let local_color_table_sorted = (0b00100000 & packed_flags) > 0;
115 let local_color_table_size_p = (0b00000111 & packed_flags) >> 0;
116
117 let local_color_table_size = if local_color_table {
118 1u16 << (local_color_table_size_p+1)
119 } else {
120 0
121 };
122
123 if local_color_table {
124 let skip_size = local_color_table_size as u64 * 3;
125 if try!(r.skip_exact(skip_size)) != skip_size {
126 return Err(unexpected_eof!("when reading color table of image block {}", index));
127 }
128 }
129
130 let _ = try_if_eof!(r.read_u8(), "when reading LZW minimum code size of image block {}", index);
131 try!(skip_blocks(r, || format!("when reading image data of image block {}", index).into()));
132
133 Ok(ImageDescriptor {
134 left: left,
135 top: top,
136 width: width,
137 height: height,
138
139 local_color_table: if local_color_table {
140 Some(ColorTable {
141 size: local_color_table_size,
142 sorted: local_color_table_sorted
143 })
144 } else { None },
145
146 interlace: interlace
147 })
148 }
149}
150
151#[derive(Clone, PartialEq, Eq, Debug)]
157pub struct GraphicControlExtension {
158 pub disposal_method: DisposalMethod,
162 pub user_input: bool,
166
167 pub transparent_color_index: Option<u8>,
171
172 pub delay_time: u16
179}
180
181impl GraphicControlExtension {
182 #[inline]
186 pub fn delay_time_ms(&self) -> u32 {
187 self.delay_time as u32 * 10
188 }
189
190 fn load<R: ?Sized + BufRead>(index: usize, r: &mut R) -> Result<GraphicControlExtension> {
191 const NAME: &'static str = "graphics control extension block";
192
193 let block_size = try_if_eof!(r.read_u8(), "when reading block size of {} {}", NAME, index);
194 if block_size != 0x04 {
195 return Err(invalid_format!("invalid block size in {} {}: {}", NAME, index, block_size));
196 }
197
198 let packed_flags = try_if_eof!(r.read_u8(), "when reading flags of {} {}", NAME, index);
199 let disposal_method = (0b00011100 & packed_flags) >> 2;
200 let user_input = (0b00000010 & packed_flags) > 0;
201 let transparent_color = (0b00000001 & packed_flags) > 0;
202
203 let delay_time = try_if_eof!(
204 r.read_u16::<LittleEndian>(),
205 "when reading delay time of {} {}", NAME, index
206 );
207
208 let transparent_color_index = try_if_eof!(
209 r.read_u8(),
210 "when reading transparent color index of {} {}", NAME, index
211 );
212
213 try!(skip_blocks(r, || format!("when reading block terminator of {} {}", NAME, index).into()));
214
215 Ok(GraphicControlExtension {
216 disposal_method: try!(
217 DisposalMethod::from_u8(disposal_method)
218 .ok_or(invalid_format!("invalid disposal method in {} {}: {}",
219 NAME, index, disposal_method))
220 ),
221 user_input: user_input,
222 transparent_color_index: if transparent_color {
223 Some(transparent_color_index)
224 } else {
225 None
226 },
227 delay_time: delay_time
228 })
229 }
230}
231
232#[derive(Copy, Clone, PartialEq, Eq, Debug)]
237pub enum DisposalMethod {
238 None,
240 DoNotDispose,
242 RestoreToBackgroundColor,
244 RestoreToPrevious,
247 Unknown(u8)
249}
250
251impl DisposalMethod {
252 fn from_u8(n: u8) -> Option<DisposalMethod> {
253 match n {
254 0 => Some(DisposalMethod::None),
255 1 => Some(DisposalMethod::DoNotDispose),
256 2 => Some(DisposalMethod::RestoreToBackgroundColor),
257 3 => Some(DisposalMethod::RestoreToPrevious),
258 n if n < 8 => Some(DisposalMethod::Unknown(n)),
259 _ => None
260 }
261 }
262}
263
264#[derive(Clone, PartialEq, Eq, Debug)]
270pub struct PlainTextExtension {
271 pub left: u16,
274 pub top: u16,
276 pub width: u16,
278 pub height: u16,
280
281 pub cell_width: u8,
283 pub cell_height: u8,
285
286 pub foreground_color_index: u8,
288 pub background_color_index: u8
290}
291
292impl PlainTextExtension {
293 fn load<R: ?Sized + BufRead>(index: usize, r: &mut R) -> Result<PlainTextExtension> {
294 const NAME: &'static str = "plain text extension block";
295
296 let block_size = try_if_eof!(r.read_u8(), "when reading block size of {} {}", NAME, index);
297 if block_size != 0x0C {
298 return Err(invalid_format!("invalid block size in {} {}: {}", NAME, index, block_size));
299 }
300
301 let left = try_if_eof!(
302 r.read_u16::<LittleEndian>(),
303 "when reading left offset of {} {}", NAME, index
304 );
305 let top = try_if_eof!(
306 r.read_u16::<LittleEndian>(),
307 "when reading top offset of {} {}", NAME, index
308 );
309 let width = try_if_eof!(
310 r.read_u16::<LittleEndian>(),
311 "when reading width of {} {}", NAME, index
312 );
313 let height = try_if_eof!(
314 r.read_u16::<LittleEndian>(),
315 "when reading height of {} {}", NAME, index
316 );
317
318 let cell_width = try_if_eof!(
319 r.read_u8(),
320 "when reading character cell width of {} {}", NAME, index
321 );
322 let cell_height = try_if_eof!(
323 r.read_u8(),
324 "when reading character cell height of {} {}", NAME, index
325 );
326
327 let foreground_color_index = try_if_eof!(
328 r.read_u8(),
329 "when reading foreground color index of {} {}", NAME, index
330 );
331 let background_color_index = try_if_eof!(
332 r.read_u8(),
333 "when reading background color index of {} {}", NAME, index
334 );
335
336 try!(skip_blocks(r, || format!("when reading text data of {} {}", NAME, index).into()));
337
338 Ok(PlainTextExtension {
339 left: left,
340 top: top,
341 width: width,
342 height: height,
343
344 cell_width: cell_width,
345 cell_height: cell_height,
346
347 foreground_color_index: foreground_color_index,
348 background_color_index: background_color_index
349 })
350 }
351}
352
353#[derive(Clone, PartialEq, Eq, Debug)]
358pub struct ApplicationExtension {
359 pub application_identifier: [u8; 8],
361 pub authentication_code: [u8; 3]
369}
370
371impl ApplicationExtension {
372 pub fn application_identifier_str(&self) -> Option<&str> {
376 str::from_utf8(&self.application_identifier).ok()
377 }
378
379 pub fn authentication_code_str(&self) -> Option<&str> {
381 str::from_utf8(&self.authentication_code).ok()
382 }
383
384 fn load<R: ?Sized + BufRead>(index: usize, r: &mut R) -> Result<ApplicationExtension> {
385 const NAME: &'static str = "application extension block";
386
387 let block_size = try_if_eof!(r.read_u8(), "when reading block size of {} {}", NAME, index);
388 if block_size != 0x0B {
389 return Err(invalid_format!("invalid block size in {} {}: {}", NAME, index, block_size));
390 }
391
392 let mut application_identifier = [0u8; 8];
393 try!(r.read_exact(&mut application_identifier)
394 .map_err(if_eof!(std, "while reading application identifier in {} {}", NAME, index)));
395
396 let mut authentication_code = [0u8; 3];
397 try!(r.read_exact(&mut authentication_code)
398 .map_err(if_eof!(std, "while reading authentication code in {} {}", NAME, index)));
399
400 try!(skip_blocks(r, || format!("when reading application data of {} {}", NAME, index).into()));
401
402 Ok(ApplicationExtension {
403 application_identifier: application_identifier,
404 authentication_code: authentication_code
405 })
406 }
407}
408
409#[derive(Clone, PartialEq, Eq, Debug)]
414pub struct CommentExtension;
415
416impl CommentExtension {
417 fn load<R: ?Sized + BufRead>(index: usize, r: &mut R) -> Result<CommentExtension> {
418 const NAME: &'static str = "comments extension block";
419 try!(skip_blocks(r, || format!("when reading comment data of {} {}", NAME, index).into()));
420
421 Ok(CommentExtension)
422 }
423}
424
425#[derive(Clone, PartialEq, Eq, Debug)]
427pub struct Metadata {
428 pub version: Version,
430
431 pub dimensions: Dimensions,
433
434 pub global_color_table: Option<ColorTable>,
436
437 pub color_resolution: u16,
451 pub background_color_index: u8,
453 pub pixel_aspect_ratio: u8,
471
472 pub blocks: Vec<Block>
474}
475
476impl Metadata {
477 #[inline]
481 pub fn pixel_aspect_ratio_approx(&self) -> Option<f64> {
482 if self.pixel_aspect_ratio == 0 {
483 None
484 } else {
485 Some((self.pixel_aspect_ratio as f64 + 15.0)/64.0)
486 }
487 }
488
489 #[inline]
491 pub fn frames_number(&self) -> usize {
492 self.blocks.iter().filter(|b| match **b {
493 Block::ImageDescriptor(_) => true,
494 _ => false
495 }).count()
496 }
497
498 #[inline]
503 pub fn is_animated(&self) -> bool {
504 self.frames_number() > 1
506 }
507}
508
509impl LoadableMetadata for Metadata {
510 fn load<R: ?Sized + BufRead>(r: &mut R) -> Result<Metadata> {
511 let mut signature = [0u8; 6];
512 try!(r.read_exact(&mut signature).map_err(if_eof!(std, "when reading GIF signature")));
513
514 let version = try!(Version::from_bytes(&signature[3..])
515 .ok_or(invalid_format!("invalid GIF version: {:?}", &signature[3..])));
516
517 let width = try_if_eof!(r.read_u16::<LittleEndian>(), "when reading logical width");
518 let height = try_if_eof!(r.read_u16::<LittleEndian>(), "when reading logical height");
519
520 let packed_flags = try_if_eof!(r.read_u8(), "when reading global flags");
521
522 let global_color_table = (packed_flags & 0b10000000) > 0;
523 let color_resolution = (packed_flags & 0b01110000) >> 4;
524 let global_color_table_sorted = (packed_flags & 0b00001000) > 0;
525 let global_color_table_size_p = (packed_flags & 0b00000111) >> 0;
526
527 let global_color_table_size = if global_color_table {
528 1u16 << (global_color_table_size_p + 1)
529 } else {
530 0
531 };
532 let background_color_index = try_if_eof!(r.read_u8(), "when reading background color index");
533 let pixel_aspect_ratio = try_if_eof!(r.read_u8(), "when reading pixel aspect ration");
534
535 if global_color_table {
536 let skip_size = global_color_table_size as u64 * 3;
537 if try!(r.skip_exact(skip_size)) != skip_size {
538 return Err(unexpected_eof!("when reading global color table"));
539 }
540 }
541
542 let mut blocks = Vec::new();
543 let mut index = 0usize;
544 loop {
545 let separator = try_if_eof!(r.read_u8(), "when reading separator of block {}", index);
546 let block = match separator {
547 0x2c => Block::ImageDescriptor(try!(ImageDescriptor::load(index, r))),
548 0x21 => {
549 let label = try_if_eof!(r.read_u8(), "when reading label of block {}", index);
550 match label {
551 0x01 => Block::PlainTextExtension(try!(PlainTextExtension::load(index, r))),
552 0xf9 => Block::GraphicControlExtension(try!(GraphicControlExtension::load(index, r))),
553 0xfe => Block::CommentExtension(try!(CommentExtension::load(index, r))),
554 0xff => Block::ApplicationExtension(try!(ApplicationExtension::load(index, r))),
555 _ => return Err(invalid_format!("unknown extension type of block {}: 0x{:X}", index, label))
556 }
557 },
558 0x3b => break,
559 _ => return Err(invalid_format!("unknown block type of block {}: 0x{:X}", index, separator))
560 };
561 blocks.push(block);
562 index += 1;
563 }
564
565 Ok(Metadata {
566 version: version,
567
568 dimensions: (width, height).into(),
569
570 global_color_table: if global_color_table {
571 Some(ColorTable {
572 size: global_color_table_size,
573 sorted: global_color_table_sorted
574 })
575 } else {
576 None
577 },
578
579 color_resolution: 1u16 << (color_resolution + 1),
580
581 background_color_index: background_color_index,
582 pixel_aspect_ratio: pixel_aspect_ratio,
583
584 blocks: blocks
585 })
586 }
587}