1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
use std::io::{BufRead, Cursor, Seek, SeekFrom, BufReader};
use std::fs::File;
use std::path::Path;
use std::result;

use types::{Result, Dimensions};
use traits::LoadableMetadata;
use formats::{jpeg, png, gif, webp};
use generic::markers::MetadataMarker;

/// Contains metadata marker types.
///
/// Metadata markers is a convenient way to access metadata loading functions for particular
/// image types. They are also integrated with `GenericMetadata`, providing a convenient
/// syntax to downcast a `GenericMetadata` value to a specific metadata type.
///
/// Metadata marker types can be used directly, for example:
/// ```ignore
/// use immeta::markers::Jpeg;
///
/// let metadata = Jpeg::load_from_file("kitty.jpg").unwrap();
/// ```
///
/// They can also be used together with `GenericMetadata`:
/// ```ignore
/// use immeta::markers::Jpeg;
///
/// let gmd = immeta::load_from_file("kitty.jpg").unwrap();
/// let jpeg_metadata: Jpeg::Metadata = gmd.into::<Jpeg>().unwrap();
/// ```
///
/// Alternatively, you can use `as_ref()`:
/// ```ignore
/// let jpeg_metadata: &Jpeg::Metadata = gmd.as_ref::<Jpeg>().unwrap();
/// ```
///
/// `MetadataMarker::Metadata` associated type always points to concrete metadata type
/// from one of `immeta::formats` submodules.
pub mod markers {
    use std::io::{BufRead, Seek};
    use std::path::Path;
    use std::result;

    use generic::GenericMetadata;
    use types::Result;
    use formats::{jpeg, png, gif, webp};

    /// A marker trait for a specific metadata type.
    pub trait MetadataMarker {
        type Metadata;

        /// Tries to convert the given `GenericMetadata` instance into a concrete metadata type.
        ///
        /// If the generic value really contains the associated metadata type, then `Ok` variant
        /// is returned; otherwise `Err` variant containing the original value is returned.
        ///
        /// # Examples
        ///
        /// ```no_run
        /// use immeta::markers::Jpeg;
        /// use immeta::formats::jpeg;
        /// use immeta::GenericMetadata;
        ///
        /// let generic = immeta::load_from_file("kitty.jpg").unwrap();
        /// let concrete: Result<jpeg::Metadata, GenericMetadata> = generic.into::<Jpeg>();
        /// assert!(concrete.is_ok());
        /// ```
        /// 
        /// ```no_run
        /// use immeta::markers::Jpeg;
        /// use immeta::formats::jpeg;
        /// use immeta::GenericMetadata;
        ///
        /// let generic = immeta::load_from_file("kitty.png").unwrap();
        /// let concrete: Result<jpeg::Metadata, GenericMetadata> = generic.into::<Jpeg>();
        /// assert!(concrete.is_err());
        /// ```
        fn from_generic(gmd: GenericMetadata) -> result::Result<Self::Metadata, GenericMetadata>;

        /// Tries to extract a reference to a concrete metadata type from the given
        /// `GenericMetadata` reference.
        ///
        /// Behaves similarly to `from_generic()`, except using references instead of immediate
        /// values.
        ///
        /// # Examples
        ///
        /// ```no_run
        /// use immeta::markers::Jpeg;
        /// use immeta::formats::jpeg;
        ///
        /// let generic = immeta::load_from_file("kitty.jpg").unwrap();
        /// let concrete: Option<&jpeg::Metadata> = generic.as_ref::<Jpeg>();
        /// assert!(concrete.is_some());
        /// ```
        ///
        /// ```no_run
        /// use immeta::markers::Jpeg;
        /// use immeta::formats::jpeg;
        ///
        /// let generic = immeta::load_from_file("kitty.png").unwrap();
        /// let concrete: Option<&jpeg::Metadata> = generic.as_ref::<Jpeg>();
        /// assert!(concrete.is_none());
        /// ```
        fn from_generic_ref(gmd: &GenericMetadata) -> Option<&Self::Metadata>;

        /// Attempts to load metadata for an image of a concrete type from the provided reader.
        ///
        /// Invokes `LoadableMetadata::load()` for the associated metadata type. Use this
        /// method instead of calling `load()` on the metadata type directly.
        ///
        /// # Examples
        ///
        /// ```no_run
        /// use std::io::{self, BufReader};
        /// use immeta::markers::{MetadataMarker, Jpeg};
        ///
        /// let data = io::stdin();
        /// let metadata = Jpeg::load(&mut data.lock());
        /// ```
        fn load<R: ?Sized + BufRead>(r: &mut R) -> Result<Self::Metadata>;

        /// Attempts to load metadata for an image of a concrete type from the provided
        /// seekable reader.
        ///
        /// Invokes `LoadableMetadata::load_from_seek()` for the associated metadata type. Use
        /// this method instead of calling `load_from_seek()` on the metadata type directly.
        ///
        /// # Examples
        ///
        /// ```no_run
        /// use std::io::{Read, BufReader, Cursor};
        /// use immeta::markers::{MetadataMarker, Jpeg};
        ///
        /// # fn obtain_image() -> Vec<u8> { unimplemented!() }
        ///
        /// let data: Vec<u8> = obtain_image();
        /// let metadata = Jpeg::load(&mut BufReader::new(Cursor::new(data)));
        /// ```
        fn load_from_seek<R: ?Sized + BufRead + Seek>(r: &mut R) -> Result<Self::Metadata>;

        /// Attempts to load metadata for an image of a concrete type from a file identified
        /// by the provided path.
        ///
        /// Invokes `LoadableMetadata::load_from_file()` for the associated metadata type. Use this
        /// method instead of calling `load_from_file()` on the metadata type directly.
        ///
        /// # Examples
        ///
        /// ```no_run
        /// use immeta::markers::{MetadataMarker, Jpeg};
        ///
        /// let metadata = Jpeg::load_from_file("kitty.jpg");
        /// ```
        fn load_from_file<P: AsRef<Path>>(p: P) -> Result<Self::Metadata>;

        /// Attempts to load metadata for an image of a concrete type from the provided byte
        /// buffer.
        ///
        /// Invokes `LoadableMetadata::load_from_buf()` for the associated metadata type. Use this
        /// method instead of calling `load_from_buf()` on the metadata type directly.
        ///
        /// # Examples
        /// 
        /// ```no_run
        /// use immeta::markers::{MetadataMarker, Jpeg};
        ///
        /// let buf: &[u8] = &[1, 2, 3, 4];   // pretend that this is an actual image
        /// let metadata = Jpeg::load_from_buf(buf);
        /// ```
        fn load_from_buf(b: &[u8]) -> Result<Self::Metadata>;
    }

    macro_rules! impl_metadata_marker {
        ($name:ident, $gvar:ident, $mtpe:ty) => {
            pub enum $name {}

            impl MetadataMarker for $name {
                type Metadata = $mtpe;
        
                #[inline]
                fn from_generic(gmd: GenericMetadata) -> result::Result<$mtpe, GenericMetadata> {
                    match gmd {
                        $crate::generic::GenericMetadata::$gvar(md) => Ok(md),
                        gmd => Err(gmd)
                    }
                }

                #[inline]
                fn from_generic_ref(gmd: &GenericMetadata) -> Option<&$mtpe> {
                    match *gmd {
                        $crate::generic::GenericMetadata::$gvar(ref md) => Some(md),
                        _ => None
                    }
                }

                #[inline]
                fn load<R: ?Sized + BufRead>(r: &mut R) -> Result<$mtpe> {
                    $crate::traits::LoadableMetadata::load(r)
                }

                #[inline]
                fn load_from_seek<R: ?Sized + BufRead + Seek>(r: &mut R) -> Result<$mtpe> {
                    $crate::traits::LoadableMetadata::load_from_seek(r)
                }

                #[inline]
                fn load_from_file<P: AsRef<Path>>(p: P) -> Result<$mtpe> {
                    $crate::traits::LoadableMetadata::load_from_file(p)
                }

                #[inline]
                fn load_from_buf(b: &[u8]) -> Result<$mtpe> {
                    $crate::traits::LoadableMetadata::load_from_buf(b)
                }
            }
        }
    }

    impl_metadata_marker! { Jpeg, Jpeg, jpeg::Metadata }
    impl_metadata_marker! { Png, Png, png::Metadata }
    impl_metadata_marker! { Gif, Gif, gif::Metadata }
    impl_metadata_marker! { Webp, Webp, webp::Metadata }
}

/// Represents metadata loaded from a file whose format was determined automatically.
///
/// Values of this type are obtained via `immeta::load()` function and its derivatives.
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum GenericMetadata {
    Png(png::Metadata),
    Gif(gif::Metadata),
    Jpeg(jpeg::Metadata),
    Webp(webp::Metadata)
}

impl GenericMetadata {
    /// Returns image dimensions from the contained metadata.
    pub fn dimensions(&self) -> Dimensions {
        match *self {
            GenericMetadata::Png(ref md) => md.dimensions,
            GenericMetadata::Gif(ref md) => md.dimensions,
            GenericMetadata::Jpeg(ref md) => md.dimensions,
            GenericMetadata::Webp(ref md) => md.dimensions()
        }
    }

    /// Returns a MIME type string for the image type of the contained metadata.
    pub fn mime_type(&self) -> &'static str {
        match *self {
            GenericMetadata::Png(_) => "image/png",
            GenericMetadata::Gif(_) => "image/gif",
            GenericMetadata::Jpeg(_) => "image/jpeg",
            GenericMetadata::Webp(_) => "image/webp"
        }
    }

    /// Attemts to convert this value to the specific metadata type by value.
    ///
    /// This method is needed only to provide a convenient syntax and it is not necessary
    /// because one may just `match` on the `GenericMetadata` value.
    #[inline]
    pub fn into<T: MetadataMarker>(self) -> result::Result<T::Metadata, GenericMetadata> {
        <T as MetadataMarker>::from_generic(self)
    }

    /// Attempts to convert this value to the sepcific metadata type by reference.
    ///
    /// This method is needed only to provide a convenient syntax and it is not necessary
    /// because one may just `match` on the `GenericMetadata` value.
    #[inline]
    pub fn as_ref<T: MetadataMarker>(&self) -> Option<&T::Metadata> {
        <T as MetadataMarker>::from_generic_ref(self)
    }
}

/// Attempts to load metadata for an image contained in the provided input stream.
///
/// This method automatically determines the format of the contained image. Because it may
/// need to read the stream from the beginning several times, a `Seek` bound is necessary
/// on the input stream. This may cause problems only with network streams as they are
/// naturally not seekable, so one would need to buffer the data from them first.
pub fn load<R: ?Sized + BufRead + Seek>(r: &mut R) -> Result<GenericMetadata> {
    // try png
    try!(r.seek(SeekFrom::Start(0)));
    if let Ok(md) = png::Metadata::load_from_seek(r) {
        return Ok(GenericMetadata::Png(md));
    }

    // try gif
    try!(r.seek(SeekFrom::Start(0)));
    if let Ok(md) = gif::Metadata::load_from_seek(r) {
        return Ok(GenericMetadata::Gif(md));
    }

    // try webp
    try!(r.seek(SeekFrom::Start(0)));
    if let Ok(md) = webp::Metadata::load_from_seek(r) {
        return Ok(GenericMetadata::Webp(md));
    }

    // try jpeg
    try!(r.seek(SeekFrom::Start(0)));
    if let Ok(md) = jpeg::Metadata::load_from_seek(r) {
        return Ok(GenericMetadata::Jpeg(md));
    }

    Err(invalid_format!("unknown or unsupported image type"))
}

/// Attempts to load metadata for an image contained in a file identified by the provided path.
/// 
/// This method delegates to `load()` method and, consequently, also determines the image format
/// automatically.
pub fn load_from_file<P: AsRef<Path>>(p: P) -> Result<GenericMetadata> {
    let mut f = BufReader::new(try!(File::open(p)));
    load(&mut f)
}

/// Attempts to load metadata for an image contained in an in-memory buffer.
///
/// This method delegates to `load()` method and, consequently, also determines the image format
/// automatically.
pub fn load_from_buf(b: &[u8]) -> Result<GenericMetadata> {
    load(&mut Cursor::new(b))
}