immeta/formats/
gif.rs

1//! Metadata of GIF images.
2
3use 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/// GIF file version number.
14#[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/// Represents various kinds of blocks which can be used in a GIF image.
31#[derive(Clone, PartialEq, Eq, Debug)]
32pub enum Block {
33    /// An image descriptor (image contents for one frame).
34    ImageDescriptor(ImageDescriptor),
35    /// Graphics control metadata block (e.g. frame delay or transparency).
36    GraphicControlExtension(GraphicControlExtension),
37    /// Plain text block (textual data that can be displayed as an image).
38    PlainTextExtension(PlainTextExtension),
39    /// Application information block (contains information about application which created the
40    /// image).
41    ApplicationExtension(ApplicationExtension),
42    /// Comment block (contains commentary data which is not displayed in the image).
43    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/// Contains information about a color table (global or local).
59#[derive(Clone, PartialEq, Eq, Debug)]
60pub struct ColorTable {
61    /// Color table size, between 2 and 256.
62    pub size: u16,
63    /// Whether the color table is sorted. Quoting from GIF spec:
64    ///
65    /// > If the flag is set, the [..] Color Table is sorted, in order of
66    /// decreasing importance. Typically, the order would be decreasing frequency, with most 
67    /// frequent color first. This assists a decoder, with fewer available colors, in choosing 
68    /// the best subset of colors; the decoder may use an initial segment of the 
69    /// table to render the graphic.
70    pub sorted: bool,
71}
72
73/// Contains metadata about an image block, i.e. a single frame of a GIF image.
74#[derive(Clone, PartialEq, Eq, Debug)]
75pub struct ImageDescriptor {
76    /// Offset of the image data from the left boundary of the logical screen.
77    pub left: u16,
78    /// Offset of the image data from the top boundary of the logical screen.
79    pub top: u16,
80    /// Width of the image data.
81    pub width: u16,
82    /// Height of the image data.
83    pub height: u16,
84
85    /// Information about local color table, if it is present.
86    pub local_color_table: Option<ColorTable>,
87
88    /// Whether the image is interlaced.
89    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/// Contains metadata for a graphic control extension block.
152///
153/// This block usually leads an image descriptor block and contains information on how this
154/// image should be displayed. It is especially important for animated GIF images because
155/// it contains delay and disposal method flags.
156#[derive(Clone, PartialEq, Eq, Debug)]
157pub struct GraphicControlExtension {
158    /// Indicates how the graphic should be treated after it is displayed.
159    ///
160    /// See `DisposalMethod` enum documentation for more information.
161    pub disposal_method: DisposalMethod,
162    /// Whether or not user input is required before continuing.
163    ///
164    /// How this flag is treated depends on a application.
165    pub user_input: bool,
166
167    /// Specifies "transparent" color in a color table, if available.
168    ///
169    /// "Transparent" color makes the decoder ignore a pixel and go on to the next one.
170    pub transparent_color_index: Option<u8>,
171
172    /// Defines the delay before processing the rest of the GIF stream.
173    /// 
174    /// The value is specified in one hundredths of a second. Use `delay_time_ms()` method
175    /// to obtain a more conventional time representation.
176    ///
177    /// If zero, it means that there is no delay time.
178    pub delay_time: u16
179}
180
181impl GraphicControlExtension {
182    /// Returns delay time in milliseconds.
183    ///
184    /// See `delay_time` field description.
185    #[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/// Describes disposal methods used for GIF image frames.
233///
234/// Disposal method defines how the graphic should be treated after being displayed. Descriptions
235/// of enum variants come from GIF spec.
236#[derive(Copy, Clone, PartialEq, Eq, Debug)]
237pub enum DisposalMethod {
238    /// The decoder is not required to take any action.
239    None,
240    /// The graphic is to be left in place.
241    DoNotDispose,
242    /// The area used by the graphic must be restored to the background color.
243    RestoreToBackgroundColor,
244    /// The decoder is required to restore the area overwritten by the graphic with what
245    /// was there prior to rendering the graphic.
246    RestoreToPrevious,
247    /// Unknown disposal method.
248    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/// Contains metadata for a plain text extension block.
265///
266/// Plain text blocks can be used to render texts represented as an actual textual data as
267/// opposed to pre-rendered rasterized text. However, it seems that these blocks are not
268/// well supported by the existing software.
269#[derive(Clone, PartialEq, Eq, Debug)]
270pub struct PlainTextExtension {
271    /// Column number, in pixels, of the left edge of the text grid, with respect to 
272    /// the left edge of the logical screen.
273    pub left: u16,
274    /// Same as above, for the top edges.
275    pub top: u16,
276    /// Width of the text grid in pixels.
277    pub width: u16,
278    /// Height of the text grid in pixels.
279    pub height: u16,
280
281    /// Width in pixels of each cell in the text grid.
282    pub cell_width: u8,
283    /// Height in pixels of each cell in the text grid.
284    pub cell_height: u8,
285
286    /// Index of a foreground color in the global color table.
287    pub foreground_color_index: u8,
288    /// Index of a background color in the global color table.
289    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/// Contains metadata for application extension block.
354///
355/// These blocks usually contain information about the application which was used to create
356/// the image.
357#[derive(Clone, PartialEq, Eq, Debug)]
358pub struct ApplicationExtension {
359    /// Eight ASCII bytes of an application identifier.
360    pub application_identifier: [u8; 8],
361    /// Three bytes of an application authentication code.
362    ///
363    /// Citing the GIF spec:
364    ///
365    /// > Sequence of three bytes used to authenticate the Application Identifier. 
366    /// An Application program may use an algorithm to compute a binary code that uniquely
367    /// identifies it as the application owning the Application Extension.
368    pub authentication_code: [u8; 3]
369}
370
371impl ApplicationExtension {
372    /// Returns application identifier as a UTF-8 string, if possible.
373    ///
374    /// For correct images this method should always return `Some`.
375    pub fn application_identifier_str(&self) -> Option<&str> {
376        str::from_utf8(&self.application_identifier).ok()
377    }
378
379    /// Returns authentication code as a UTF-8 string, if possible.
380    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/// Represents a comment extension block.
410///
411/// Comment block does not contain any metadata, so this struct is used for uniformity
412/// as a placeholder in the enum.
413#[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/// Contains metadata about the whole GIF image.
426#[derive(Clone, PartialEq, Eq, Debug)]
427pub struct Metadata {
428    /// GIF format version from the file header.
429    pub version: Version,
430
431    /// Logical screen dimensions of the image.
432    pub dimensions: Dimensions,
433
434    /// Information about global color table, if it is present.
435    pub global_color_table: Option<ColorTable>,
436
437    /// Number of colors available to the original image.
438    ///
439    /// Quoting the GIF spec:
440    ///
441    /// > Number of bits per primary color available to the original image, minus 1. 
442    /// This value represents the size of the entire palette from which the colors in the 
443    /// graphic were selected, not the number of colors actually used in the graphic. 
444    /// For example, if the value in this field is 3, then the palette of the original image 
445    /// had 4 bits per primary color available to create the image. This value should be set
446    /// to indicate the richness of the original palette, even if not every color from the whole
447    /// palette is available on the source machine.
448    ///
449    /// Note that the value in this structure is the number of *colors*, not the number of *bits*.
450    pub color_resolution: u16,
451    /// Index of the default background color in the global color table.
452    pub background_color_index: u8,
453    /// A factor which defines the aspect ration of a pixel in the original image.
454    ///
455    /// Quoting from the GIF spec:
456    ///
457    /// > Factor used to compute an approximation of the aspect ratio of the pixel in the original 
458    /// image. If the value of the field is not 0, this approximation of the aspect ratio is 
459    /// computed based on the formula: 
460    /// >
461    /// >    Aspect Ratio = (Pixel Aspect Ratio + 15) / 64
462    /// >
463    /// > The Pixel Aspect Ratio is defined to be the quotient of the pixel's width over its
464    /// height. The value range in this field allows specification of the widest pixel of 4:1 to
465    /// the tallest pixel of 1:4 in increments of 1/64th.
466    ///
467    /// If zero, no information about pixel aspect ratio is available.
468    ///
469    /// See also `pixel_aspect_ratio_approx()` method.
470    pub pixel_aspect_ratio: u8,
471
472    /// Metadata for each block in the GIF image.
473    pub blocks: Vec<Block>
474}
475
476impl Metadata {
477    /// Computes pixel aspect ratio approximation, if it is available.
478    ///
479    /// See `pixel_aspect_ration` field documentation.
480    #[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    /// Computes the number of frames, i.e. the number of image descriptor blocks.
490    #[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    /// Returns `true` if the image is animated, `false` otherwise.
499    ///
500    /// This is currently decided based on the number of frames. If there are more than one frames,
501    /// then the image is considered animated.
502    #[inline]
503    pub fn is_animated(&self) -> bool {
504        // TODO: is this right?
505        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}