Skip to content

Commit

Permalink
Merge pull request #1014 from HeroicKatora/image-reader
Browse files Browse the repository at this point in the history
Image reader
  • Loading branch information
HeroicKatora authored Sep 25, 2019
2 parents 4a7bdb8 + 4f6a82c commit ac93e75
Show file tree
Hide file tree
Showing 6 changed files with 551 additions and 240 deletions.
242 changes: 31 additions & 211 deletions src/dynimage.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
use num_iter;
use std::fs::File;
use std::io;
use std::io::{BufRead, BufReader, BufWriter, Seek, Write};
use std::io::Write;
use std::path::Path;
use std::u32;

#[cfg(feature = "bmp")]
use bmp;
#[cfg(feature = "gif_codec")]
use gif;
#[cfg(feature = "hdr")]
use hdr;
#[cfg(feature = "ico")]
use ico;
#[cfg(feature = "jpeg")]
Expand All @@ -19,12 +16,6 @@ use jpeg;
use png;
#[cfg(feature = "pnm")]
use pnm;
#[cfg(feature = "tga")]
use tga;
#[cfg(feature = "tiff")]
use tiff;
#[cfg(feature = "webp")]
use webp;

use buffer::{
BgrImage, BgraImage, ConvertBuffer, GrayAlphaImage, GrayImage, ImageBuffer, Pixel, RgbImage,
Expand All @@ -36,6 +27,7 @@ use image;
use image::{
GenericImage, GenericImageView, ImageDecoder, ImageFormat, ImageOutputFormat, ImageResult,
};
use io::free_functions;
use imageops;

/// A Dynamic Image
Expand Down Expand Up @@ -730,72 +722,32 @@ fn image_to_bytes(image: &DynamicImage) -> Vec<u8> {

/// Open the image located at the path specified.
/// The image's format is determined from the path's file extension.
///
/// Try [`io::Reader`] for more advanced uses, including guessing the format based on the file's
/// content before its path.
///
/// [`io::Reader`]: io/struct.Reader.html
pub fn open<P>(path: P) -> ImageResult<DynamicImage>
where
P: AsRef<Path>,
{
// thin wrapper function to strip generics before calling open_impl
open_impl(path.as_ref())
}

fn open_impl(path: &Path) -> ImageResult<DynamicImage> {
let fin = match File::open(path) {
Ok(f) => f,
Err(err) => return Err(image::ImageError::IoError(err)),
};
let fin = BufReader::new(fin);

load(fin, ImageFormat::from_path(path)?)
free_functions::open_impl(path.as_ref())
}

/// Read the dimensions of the image located at the specified path.
/// This is faster than fully loading the image and then getting its dimensions.
///
/// Try [`io::Reader`] for more advanced uses, including guessing the format based on the file's
/// content before its path or manually supplying the format.
///
/// [`io::Reader`]: io/struct.Reader.html
pub fn image_dimensions<P>(path: P) -> ImageResult<(u32, u32)>
where
P: AsRef<Path>,
{
// thin wrapper function to strip generics before calling open_impl
image_dimensions_impl(path.as_ref())
}

fn image_dimensions_impl(path: &Path) -> ImageResult<(u32, u32)> {
let fin = File::open(path)?;
let fin = BufReader::new(fin);

#[allow(unreachable_patterns)]
// Default is unreachable if all features are supported.
let (w, h): (u64, u64) = match image::ImageFormat::from_path(path)? {
#[cfg(feature = "jpeg")]
image::ImageFormat::JPEG => jpeg::JPEGDecoder::new(fin)?.dimensions(),
#[cfg(feature = "png_codec")]
image::ImageFormat::PNG => png::PNGDecoder::new(fin)?.dimensions(),
#[cfg(feature = "gif_codec")]
image::ImageFormat::GIF => gif::Decoder::new(fin)?.dimensions(),
#[cfg(feature = "webp")]
image::ImageFormat::WEBP => webp::WebpDecoder::new(fin)?.dimensions(),
#[cfg(feature = "tiff")]
image::ImageFormat::TIFF => tiff::TIFFDecoder::new(fin)?.dimensions(),
#[cfg(feature = "tga")]
image::ImageFormat::TGA => tga::TGADecoder::new(fin)?.dimensions(),
#[cfg(feature = "bmp")]
image::ImageFormat::BMP => bmp::BMPDecoder::new(fin)?.dimensions(),
#[cfg(feature = "ico")]
image::ImageFormat::ICO => ico::ICODecoder::new(fin)?.dimensions(),
#[cfg(feature = "hdr")]
image::ImageFormat::HDR => hdr::HDRAdapter::new(fin)?.dimensions(),
#[cfg(feature = "pnm")]
image::ImageFormat::PNM => pnm::PNMDecoder::new(fin)?.dimensions(),
format => {
return Err(image::ImageError::UnsupportedError(format!(
"Image format image/{:?} is not supported.",
format
)));
}
};
if w >= u64::from(u32::MAX) || h >= u64::from(u32::MAX) {
return Err(image::ImageError::DimensionError);
}
Ok((w as u32, h as u32))
free_functions::image_dimensions_impl(path.as_ref())
}

/// Saves the supplied buffer to a file at the path specified.
Expand All @@ -816,54 +768,7 @@ where
P: AsRef<Path>,
{
// thin wrapper function to strip generics before calling save_buffer_impl
save_buffer_impl(path.as_ref(), buf, width, height, color)
}

fn save_buffer_impl(
path: &Path,
buf: &[u8],
width: u32,
height: u32,
color: color::ColorType,
) -> io::Result<()> {
let fout = &mut BufWriter::new(File::create(path)?);
let ext = path
.extension()
.and_then(|s| s.to_str())
.map_or("".to_string(), |s| s.to_ascii_lowercase());

match &*ext {
#[cfg(feature = "ico")]
"ico" => ico::ICOEncoder::new(fout).encode(buf, width, height, color),
#[cfg(feature = "jpeg")]
"jpg" | "jpeg" => jpeg::JPEGEncoder::new(fout).encode(buf, width, height, color),
#[cfg(feature = "png_codec")]
"png" => png::PNGEncoder::new(fout).encode(buf, width, height, color),
#[cfg(feature = "pnm")]
"pbm" => pnm::PNMEncoder::new(fout)
.with_subtype(pnm::PNMSubtype::Bitmap(pnm::SampleEncoding::Binary))
.encode(buf, width, height, color),
#[cfg(feature = "pnm")]
"pgm" => pnm::PNMEncoder::new(fout)
.with_subtype(pnm::PNMSubtype::Graymap(pnm::SampleEncoding::Binary))
.encode(buf, width, height, color),
#[cfg(feature = "pnm")]
"ppm" => pnm::PNMEncoder::new(fout)
.with_subtype(pnm::PNMSubtype::Pixmap(pnm::SampleEncoding::Binary))
.encode(buf, width, height, color),
#[cfg(feature = "pnm")]
"pam" => pnm::PNMEncoder::new(fout).encode(buf, width, height, color),
#[cfg(feature = "bmp")]
"bmp" => bmp::BMPEncoder::new(fout).encode(buf, width, height, color),
#[cfg(feature = "tiff")]
"tif" | "tiff" => tiff::TiffEncoder::new(fout)
.encode(buf, width, height, color)
.map_err(|e| io::Error::new(io::ErrorKind::Other, Box::new(e))), // FIXME: see https://github.com/image-rs/image/issues/921
format => Err(io::Error::new(
io::ErrorKind::InvalidInput,
&format!("Unsupported image format image/{:?}", format)[..],
)),
}
free_functions::save_buffer_impl(path.as_ref(), buf, width, height, color)
}

/// Saves the supplied buffer to a file at the path specified
Expand All @@ -886,120 +791,35 @@ where
P: AsRef<Path>,
{
// thin wrapper function to strip generics
save_buffer_with_format_impl(path.as_ref(), buf, width, height, color, format)
free_functions::save_buffer_with_format_impl(path.as_ref(), buf, width, height, color, format)
}

fn save_buffer_with_format_impl(
path: &Path,
buf: &[u8],
width: u32,
height: u32,
color: color::ColorType,
format: ImageFormat,
) -> io::Result<()> {
let fout = &mut BufWriter::new(File::create(path)?);

match format {
#[cfg(feature = "ico")]
image::ImageFormat::ICO => ico::ICOEncoder::new(fout).encode(buf, width, height, color),
#[cfg(feature = "jpeg")]
image::ImageFormat::JPEG => jpeg::JPEGEncoder::new(fout).encode(buf, width, height, color),
#[cfg(feature = "png_codec")]
image::ImageFormat::PNG => png::PNGEncoder::new(fout).encode(buf, width, height, color),
#[cfg(feature = "bmp")]
image::ImageFormat::BMP => bmp::BMPEncoder::new(fout).encode(buf, width, height, color),
#[cfg(feature = "tiff")]
image::ImageFormat::TIFF => tiff::TiffEncoder::new(fout)
.encode(buf, width, height, color)
.map_err(|e| io::Error::new(io::ErrorKind::Other, Box::new(e))),
_ => Err(io::Error::new(
io::ErrorKind::InvalidInput,
&format!("Unsupported image format image/{:?}", format)[..],
)),
}
}

/// Create a new image from a Reader
pub fn load<R: BufRead + Seek>(r: R, format: ImageFormat) -> ImageResult<DynamicImage> {
#[allow(deprecated, unreachable_patterns)]
// Default is unreachable if all features are supported.
match format {
#[cfg(feature = "png_codec")]
image::ImageFormat::PNG => decoder_to_image(png::PNGDecoder::new(r)?),
#[cfg(feature = "gif_codec")]
image::ImageFormat::GIF => decoder_to_image(gif::Decoder::new(r)?),
#[cfg(feature = "jpeg")]
image::ImageFormat::JPEG => decoder_to_image(jpeg::JPEGDecoder::new(r)?),
#[cfg(feature = "webp")]
image::ImageFormat::WEBP => decoder_to_image(webp::WebpDecoder::new(r)?),
#[cfg(feature = "tiff")]
image::ImageFormat::TIFF => decoder_to_image(tiff::TIFFDecoder::new(r)?),
#[cfg(feature = "tga")]
image::ImageFormat::TGA => decoder_to_image(tga::TGADecoder::new(r)?),
#[cfg(feature = "bmp")]
image::ImageFormat::BMP => decoder_to_image(bmp::BMPDecoder::new(r)?),
#[cfg(feature = "ico")]
image::ImageFormat::ICO => decoder_to_image(ico::ICODecoder::new(r)?),
#[cfg(feature = "hdr")]
image::ImageFormat::HDR => decoder_to_image(hdr::HDRAdapter::new(BufReader::new(r))?),
#[cfg(feature = "pnm")]
image::ImageFormat::PNM => decoder_to_image(pnm::PNMDecoder::new(BufReader::new(r))?),
_ => Err(image::ImageError::UnsupportedError(format!(
"A decoder for {:?} is not available.",
format
))),
}
}

static MAGIC_BYTES: [(&[u8], ImageFormat); 17] = [
(b"\x89PNG\r\n\x1a\n", ImageFormat::PNG),
(&[0xff, 0xd8, 0xff], ImageFormat::JPEG),
(b"GIF89a", ImageFormat::GIF),
(b"GIF87a", ImageFormat::GIF),
(b"RIFF", ImageFormat::WEBP), // TODO: better magic byte detection, see https://github.com/image-rs/image/issues/660
(b"MM\x00*", ImageFormat::TIFF),
(b"II*\x00", ImageFormat::TIFF),
(b"BM", ImageFormat::BMP),
(&[0, 0, 1, 0], ImageFormat::ICO),
(b"#?RADIANCE", ImageFormat::HDR),
(b"P1", ImageFormat::PNM),
(b"P2", ImageFormat::PNM),
(b"P3", ImageFormat::PNM),
(b"P4", ImageFormat::PNM),
(b"P5", ImageFormat::PNM),
(b"P6", ImageFormat::PNM),
(b"P7", ImageFormat::PNM),
];

/// Create a new image from a byte slice
///
/// Makes an educated guess about the image format.
/// TGA is not supported by this function.
///
/// Try [`io::Reader`] for more advanced uses.
///
/// [`io::Reader`]: io/struct.Reader.html
pub fn load_from_memory(buffer: &[u8]) -> ImageResult<DynamicImage> {
load_from_memory_with_format(buffer, guess_format(buffer)?)
let format = free_functions::guess_format(buffer)?;
load_from_memory_with_format(buffer, format)
}

/// Create a new image from a byte slice
///
/// This is just a simple wrapper that constructs an `std::io::Cursor` around the buffer and then
/// calls `load` with that reader.
///
/// Try [`io::Reader`] for more advanced uses.
///
/// [`load`]: fn.load.html
/// [`io::Reader`]: io/struct.Reader.html
#[inline(always)]
pub fn load_from_memory_with_format(buf: &[u8], format: ImageFormat) -> ImageResult<DynamicImage> {
let b = io::Cursor::new(buf);
load(b, format)
}

/// Guess image format from memory block
///
/// Makes an educated guess about the image format based on the Magic Bytes at the beginning.
/// TGA is not supported by this function.
/// This is not to be trusted on the validity of the whole memory block
pub fn guess_format(buffer: &[u8]) -> ImageResult<ImageFormat> {
for &(signature, format) in &MAGIC_BYTES {
if buffer.starts_with(signature) {
return Ok(format);
}
}
Err(image::ImageError::UnsupportedError(
"Unsupported image format".to_string(),
))
free_functions::load(b, format)
}

/// Calculates the width and height an image should be resized to.
Expand Down
30 changes: 2 additions & 28 deletions src/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,34 +148,8 @@ impl ImageFormat {
/// Return the image format specified by the path's file extension.
pub fn from_path<P>(path: P) -> ImageResult<Self> where P : AsRef<Path> {
// thin wrapper function to strip generics before calling from_path_impl
Self::from_path_impl(path.as_ref())
}

fn from_path_impl(path: &Path) -> ImageResult<Self> {
let ext = path
.extension()
.and_then(|s| s.to_str())
.map_or("".to_string(), |s| s.to_ascii_lowercase());

let format = match &ext[..] {
"jpg" | "jpeg" => ImageFormat::JPEG,
"png" => ImageFormat::PNG,
"gif" => ImageFormat::GIF,
"webp" => ImageFormat::WEBP,
"tif" | "tiff" => ImageFormat::TIFF,
"tga" => ImageFormat::TGA,
"bmp" => ImageFormat::BMP,
"ico" => ImageFormat::ICO,
"hdr" => ImageFormat::HDR,
"pbm" | "pam" | "ppm" | "pgm" => ImageFormat::PNM,
format => {
return Err(ImageError::UnsupportedError(format!(
"Image format image/{:?} is not supported.",
format
)))
}
};
Ok(format)
::io::free_functions::guess_format_from_path_impl(path.as_ref())
.map_err(Into::into)
}
}

Expand Down
Loading

0 comments on commit ac93e75

Please sign in to comment.