diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 4daf442f..780dd100 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -13,7 +13,7 @@ jobs: strategy: matrix: rust: ["1.48.0", stable, beta, nightly] - features: ["", "rayon"] + features: ["", "std", "rayon"] command: [test, benchmark] steps: diff --git a/Cargo.toml b/Cargo.toml index 8369ebff..a6522aab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,8 @@ name = "large_image" harness = false [features] -default = ["rayon"] +default = ["std", "rayon"] +std = [] platform_independent = [] nightly_aarch64_neon = [] diff --git a/src/decoder.rs b/src/decoder.rs index a860b342..5310b774 100644 --- a/src/decoder.rs +++ b/src/decoder.rs @@ -5,9 +5,7 @@ use alloc::{format, vec}; use core::cmp; use core::mem; use core::ops::Range; -use std::convert::TryInto; -use std::io::Read; -use crate::read_u8; +use core::convert::TryInto; use crate::error::{Error, Result, UnsupportedFeature}; use crate::huffman::{fill_default_mjpeg_tables, HuffmanDecoder, HuffmanTable}; use crate::marker::Marker; @@ -16,6 +14,7 @@ use crate::parser::{ AdobeColorTransform, AppData, CodingProcess, Component, Dimensions, EntropyCoding, FrameInfo, IccChunk, ScanInfo, }; +use crate::reader::JpegRead; use crate::upsampler::Upsampler; use crate::worker::{PlatformWorker, RowData, Worker}; @@ -98,7 +97,7 @@ pub struct Decoder { coefficients_finished: [u64; MAX_COMPONENTS], } -impl Decoder { +impl Decoder { /// Creates a new `Decoder` using the reader `reader`. pub fn new(reader: R) -> Decoder { Decoder { @@ -227,8 +226,8 @@ impl Decoder { // The metadata has already been read. return Ok(Vec::new()); } else if self.frame.is_none() - && (read_u8(&mut self.reader)? != 0xFF - || Marker::from_u8(read_u8(&mut self.reader)?) != Some(Marker::SOI)) + && (self.reader.read_u8()? != 0xFF + || Marker::from_u8(self.reader.read_u8()?) != Some(Marker::SOI)) { return Err(Error::Format( "first two bytes are not an SOI marker".to_owned(), @@ -591,19 +590,19 @@ impl Decoder { // libjpeg allows this though and there are images in the wild utilising it, so we are // forced to support this behavior. // Sony Ericsson P990i is an example of a device which produce this sort of JPEGs. - while read_u8(&mut self.reader)? != 0xFF {} + while self.reader.read_u8()? != 0xFF {} // Section B.1.1.2 // All markers are assigned two-byte codes: an X’FF’ byte followed by a // byte which is not equal to 0 or X’FF’ (see Table B.1). Any marker may // optionally be preceded by any number of fill bytes, which are bytes // assigned code X’FF’. - let mut byte = read_u8(&mut self.reader)?; + let mut byte = self.reader.read_u8()?; // Section B.1.1.2 // "Any marker may optionally be preceded by any number of fill bytes, which are bytes assigned code X’FF’." while byte == 0xFF { - byte = read_u8(&mut self.reader)?; + byte = self.reader.read_u8()?; } if byte != 0x00 && byte != 0xFF { @@ -898,7 +897,7 @@ impl Decoder { } } -fn decode_block( +fn decode_block( reader: &mut R, coefficients: &mut [i16; 64], huffman: &mut HuffmanDecoder, @@ -986,7 +985,7 @@ fn decode_block( Ok(()) } -fn decode_block_successive_approximation( +fn decode_block_successive_approximation( reader: &mut R, coefficients: &mut [i16; 64], huffman: &mut HuffmanDecoder, @@ -1072,7 +1071,7 @@ fn decode_block_successive_approximation( Ok(()) } -fn refine_non_zeroes( +fn refine_non_zeroes( reader: &mut R, coefficients: &mut [i16; 64], huffman: &mut HuffmanDecoder, @@ -1257,7 +1256,7 @@ fn color_convert_line_ycbcr(data: &[Vec], output: &mut [u8]) { assert!(data.len() == 3, "wrong number of components for ycbcr"); let [y, cb, cr]: &[_; 3] = data.try_into().unwrap(); - #[cfg(not(feature = "platform_independent"))] + #[cfg(all(feature = "std", not(feature = "platform_independent")))] let arch_specific_pixels = { if let Some(ycbcr) = crate::arch::get_color_convert_line_ycbcr() { #[allow(unsafe_code)] @@ -1269,7 +1268,7 @@ fn color_convert_line_ycbcr(data: &[Vec], output: &mut [u8]) { } }; - #[cfg(feature = "platform_independent")] + #[cfg(any(not(feature = "std"), feature = "platform_independent"))] let arch_specific_pixels = 0; for (((chunk, y), cb), cr) in output diff --git a/src/decoder/lossless.rs b/src/decoder/lossless.rs index d4462f17..50fc1703 100644 --- a/src/decoder/lossless.rs +++ b/src/decoder/lossless.rs @@ -1,12 +1,16 @@ -use std::io::Read; +use alloc::borrow::ToOwned; +use alloc::vec::Vec; +use alloc::{format, vec}; + use crate::decoder::{Decoder, MAX_COMPONENTS}; use crate::error::{Error, Result}; use crate::huffman::HuffmanDecoder; use crate::marker::Marker; use crate::parser::Predictor; use crate::parser::{Component, FrameInfo, ScanInfo}; +use crate::reader::JpegRead; -impl Decoder { +impl Decoder { /// decode_scan_lossless pub fn decode_scan_lossless( &mut self, diff --git a/src/error.rs b/src/error.rs index aafa62cc..2c81c5b0 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,8 +1,15 @@ -use alloc::boxed::Box; use alloc::string::String; -use alloc::fmt; use core::result; + +#[cfg(feature = "std")] +use alloc::boxed::Box; + +#[cfg(feature = "std")] +use alloc::fmt; + +#[cfg(feature = "std")] use std::error::Error as StdError; +#[cfg(feature = "std")] use std::io::Error as IoError; pub type Result = result::Result; @@ -37,23 +44,32 @@ pub enum Error { Format(String), /// The image makes use of a JPEG feature not (currently) supported by this library. Unsupported(UnsupportedFeature), + /// Error reading input data. + Read(String), + + #[cfg(feature = "std")] /// An I/O error occurred while decoding the image. Io(IoError), + + #[cfg(feature = "std")] /// An internal error occurred while decoding the image. Internal(Box), //TODO: not used, can be removed with the next version bump } +#[cfg(feature = "std")] impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Error::Format(ref desc) => write!(f, "invalid JPEG format: {}", desc), Error::Unsupported(ref feat) => write!(f, "unsupported JPEG feature: {:?}", feat), + Error::Read(ref desc) => write!(f, "error reading input: {}", desc), Error::Io(ref err) => err.fmt(f), Error::Internal(ref err) => err.fmt(f), } } } +#[cfg(feature = "std")] impl StdError for Error { fn source(&self) -> Option<&(dyn StdError + 'static)> { match *self { @@ -64,6 +80,7 @@ impl StdError for Error { } } +#[cfg(feature = "std")] impl From for Error { fn from(err: IoError) -> Error { Error::Io(err) diff --git a/src/huffman.rs b/src/huffman.rs index fca57c1b..2dfb7d48 100644 --- a/src/huffman.rs +++ b/src/huffman.rs @@ -2,11 +2,10 @@ use alloc::borrow::ToOwned; use alloc::vec; use alloc::vec::Vec; use core::iter; -use std::io::Read; -use crate::read_u8; use crate::error::{Error, Result}; use crate::marker::Marker; use crate::parser::ScanInfo; +use crate::reader::JpegRead; const LUT_BITS: u8 = 8; @@ -28,7 +27,7 @@ impl HuffmanDecoder { // Section F.2.2.3 // Figure F.16 - pub fn decode(&mut self, reader: &mut R, table: &HuffmanTable) -> Result { + pub fn decode(&mut self, reader: &mut R, table: &HuffmanTable) -> Result { if self.num_bits < 16 { self.read_bits(reader)?; } @@ -57,7 +56,7 @@ impl HuffmanDecoder { } } - pub fn decode_fast_ac(&mut self, reader: &mut R, table: &HuffmanTable) -> Result> { + pub fn decode_fast_ac(&mut self, reader: &mut R, table: &HuffmanTable) -> Result> { if let Some(ref ac_lut) = table.ac_lut { if self.num_bits < LUT_BITS { self.read_bits(reader)?; @@ -78,7 +77,7 @@ impl HuffmanDecoder { } #[inline] - pub fn get_bits(&mut self, reader: &mut R, count: u8) -> Result { + pub fn get_bits(&mut self, reader: &mut R, count: u8) -> Result { if self.num_bits < count { self.read_bits(reader)?; } @@ -90,7 +89,7 @@ impl HuffmanDecoder { } #[inline] - pub fn receive_extend(&mut self, reader: &mut R, count: u8) -> Result { + pub fn receive_extend(&mut self, reader: &mut R, count: u8) -> Result { let value = self.get_bits(reader, count)?; Ok(extend(value, count)) } @@ -100,7 +99,7 @@ impl HuffmanDecoder { self.num_bits = 0; } - pub fn take_marker(&mut self, reader: &mut R) -> Result> { + pub fn take_marker(&mut self, reader: &mut R) -> Result> { self.read_bits(reader).map(|_| self.marker.take()) } @@ -120,16 +119,16 @@ impl HuffmanDecoder { self.num_bits -= count; } - fn read_bits(&mut self, reader: &mut R) -> Result<()> { + fn read_bits(&mut self, reader: &mut R) -> Result<()> { while self.num_bits <= 56 { // Fill with zero bits if we have reached the end. let byte = match self.marker { Some(_) => 0, - None => read_u8(reader)?, + None => reader.read_u8()?, }; if byte == 0xFF { - let mut next_byte = read_u8(reader)?; + let mut next_byte = reader.read_u8()?; // Check for byte stuffing. if next_byte != 0x00 { @@ -140,7 +139,7 @@ impl HuffmanDecoder { // Section B.1.1.2 // "Any marker may optionally be preceded by any number of fill bytes, which are bytes assigned code X’FF’." while next_byte == 0xFF { - next_byte = read_u8(reader)?; + next_byte = reader.read_u8()?; } match next_byte { diff --git a/src/idct.rs b/src/idct.rs index ad8dc4c5..e7b25d25 100644 --- a/src/idct.rs +++ b/src/idct.rs @@ -244,7 +244,7 @@ pub fn dequantize_and_idct_block_8x8( output_linestride: usize, output: &mut [u8], ) { - #[cfg(not(feature = "platform_independent"))] + #[cfg(all(feature="std", not(feature = "platform_independent")))] if let Some(idct) = crate::arch::get_dequantize_and_idct_block_8x8() { #[allow(unsafe_code)] unsafe { @@ -636,7 +636,7 @@ fn test_dequantize_and_idct_block_8x8_all_zero() { #[test] fn test_dequantize_and_idct_block_8x8_saturated() { // Arch-specific IDCT implementations need not handle i16::MAX values. - #[cfg(not(feature = "platform_independent"))] + #[cfg(all(feature = "std", not(feature = "platform_independent")))] if crate::arch::get_dequantize_and_idct_block_8x8().is_some() { return; } diff --git a/src/lib.rs b/src/lib.rs index f688f39a..bb8bf271 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,6 +26,8 @@ //! let metadata = decoder.info().unwrap(); //! ``` +#![no_std] + #![deny(missing_docs)] #![deny(unsafe_code)] #![cfg_attr(feature = "platform_independent", forbid(unsafe_code))] @@ -37,16 +39,18 @@ extern crate alloc; extern crate core; +#[cfg(feature = "std")] +extern crate std; + #[cfg(feature = "rayon")] extern crate rayon; pub use decoder::{Decoder, ImageInfo, PixelFormat}; pub use error::{Error, UnsupportedFeature}; pub use parser::CodingProcess; +pub use reader::JpegRead; -use std::io; - -#[cfg(not(feature = "platform_independent"))] +#[cfg(all(feature="std", not(feature = "platform_independent")))] mod arch; mod decoder; mod error; @@ -56,15 +60,4 @@ mod marker; mod parser; mod upsampler; mod worker; - -fn read_u8(reader: &mut R) -> io::Result { - let mut buf = [0]; - reader.read_exact(&mut buf)?; - Ok(buf[0]) -} - -fn read_u16_from_be(reader: &mut R) -> io::Result { - let mut buf = [0, 0]; - reader.read_exact(&mut buf)?; - Ok(u16::from_be_bytes(buf)) -} +mod reader; diff --git a/src/parser.rs b/src/parser.rs index 72ba00dc..cacc7ef9 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2,12 +2,11 @@ use alloc::borrow::ToOwned; use alloc::{format, vec}; use alloc::vec::Vec; use core::ops::{self, Range}; -use std::io::{self, Read}; -use crate::{read_u16_from_be, read_u8}; use crate::error::{Error, Result, UnsupportedFeature}; use crate::huffman::{HuffmanTable, HuffmanTableClass}; use crate::marker::Marker; use crate::marker::Marker::*; +use crate::reader::JpegRead; #[derive(Clone, Copy, Debug, PartialEq)] pub struct Dimensions { @@ -113,6 +112,12 @@ pub struct IccChunk { pub data: Vec, } +fn calc_output_size(size: u16, idct_size: usize) -> u16 { + let size = size as u32 * idct_size as u32; + let dim = size / 8 + u32::from(size % 8 != 0); + dim as u16 +} + impl FrameInfo { pub(crate) fn update_idct_size(&mut self, idct_size: usize) -> Result<()> { for component in &mut self.components { @@ -122,19 +127,19 @@ impl FrameInfo { update_component_sizes(self.image_size, &mut self.components)?; self.output_size = Dimensions { - width: (self.image_size.width as f32 * idct_size as f32 / 8.0).ceil() as u16, - height: (self.image_size.height as f32 * idct_size as f32 / 8.0).ceil() as u16 + width: calc_output_size(self.image_size.width, idct_size), + height: calc_output_size(self.image_size.height, idct_size), }; Ok(()) } } -fn read_length(reader: &mut R, marker: Marker) -> Result { +fn read_length(reader: &mut R, marker: Marker) -> Result { assert!(marker.has_length()); // length is including itself. - let length = usize::from(read_u16_from_be(reader)?); + let length = usize::from(reader.read_u16_from_be()?); if length < 2 { return Err(Error::Format(format!("encountered {:?} with invalid length {}", marker, length))); @@ -143,19 +148,8 @@ fn read_length(reader: &mut R, marker: Marker) -> Result { Ok(length - 2) } -fn skip_bytes(reader: &mut R, length: usize) -> Result<()> { - let length = length as u64; - let to_skip = &mut reader.by_ref().take(length); - let copied = io::copy(to_skip, &mut io::sink())?; - if copied < length { - Err(Error::Io(io::ErrorKind::UnexpectedEof.into())) - } else { - Ok(()) - } -} - // Section B.2.2 -pub fn parse_sof(reader: &mut R, marker: Marker) -> Result { +pub fn parse_sof(reader: &mut R, marker: Marker) -> Result { let length = read_length(reader, marker)?; if length <= 6 { @@ -180,7 +174,7 @@ pub fn parse_sof(reader: &mut R, marker: Marker) -> Result { _ => panic!(), }; - let precision = read_u8(reader)?; + let precision = reader.read_u8()?; match precision { 8 => {}, @@ -196,8 +190,8 @@ pub fn parse_sof(reader: &mut R, marker: Marker) -> Result { }, } - let height = read_u16_from_be(reader)?; - let width = read_u16_from_be(reader)?; + let height = reader.read_u16_from_be()?; + let width = reader.read_u16_from_be()?; // height: // "Value 0 indicates that the number of lines shall be defined by the DNL marker and @@ -210,7 +204,7 @@ pub fn parse_sof(reader: &mut R, marker: Marker) -> Result { return Err(Error::Format("zero width in frame header".to_owned())); } - let component_count = read_u8(reader)?; + let component_count = reader.read_u8()?; if component_count == 0 { return Err(Error::Format("zero component count in frame header".to_owned())); @@ -226,14 +220,14 @@ pub fn parse_sof(reader: &mut R, marker: Marker) -> Result { let mut components: Vec = Vec::with_capacity(component_count as usize); for _ in 0 .. component_count { - let identifier = read_u8(reader)?; + let identifier = reader.read_u8()?; // Each component's identifier must be unique. if components.iter().any(|c| c.identifier == identifier) { return Err(Error::Format(format!("duplicate frame component identifier {}", identifier))); } - let byte = read_u8(reader)?; + let byte = reader.read_u8()?; let horizontal_sampling_factor = byte >> 4; let vertical_sampling_factor = byte & 0x0f; @@ -244,7 +238,7 @@ pub fn parse_sof(reader: &mut R, marker: Marker) -> Result { return Err(Error::Format(format!("invalid vertical sampling factor {}", vertical_sampling_factor))); } - let quantization_table_index = read_u8(reader)?; + let quantization_table_index = reader.read_u8()?; if quantization_table_index > 3 || (coding_process == CodingProcess::Lossless && quantization_table_index != 0) { return Err(Error::Format(format!("invalid quantization table index {}", quantization_table_index))); @@ -326,13 +320,13 @@ fn test_update_component_sizes() { } // Section B.2.3 -pub fn parse_sos(reader: &mut R, frame: &FrameInfo) -> Result { +pub fn parse_sos(reader: &mut R, frame: &FrameInfo) -> Result { let length = read_length(reader, SOS)?; if 0 == length { return Err(Error::Format("zero length in SOS".to_owned())); } - let component_count = read_u8(reader)?; + let component_count = reader.read_u8()?; if component_count == 0 || component_count > 4 { return Err(Error::Format(format!("invalid component count {} in scan header", component_count))); @@ -347,7 +341,7 @@ pub fn parse_sos(reader: &mut R, frame: &FrameInfo) -> Result let mut ac_table_indices = Vec::with_capacity(component_count as usize); for _ in 0 .. component_count { - let identifier = read_u8(reader)?; + let identifier = reader.read_u8()?; let component_index = match frame.components.iter().position(|c| c.identifier == identifier) { Some(value) => value, @@ -364,7 +358,7 @@ pub fn parse_sos(reader: &mut R, frame: &FrameInfo) -> Result return Err(Error::Format("the scan component order does not follow the order in the frame header".to_owned())); } - let byte = read_u8(reader)?; + let byte = reader.read_u8()?; let dc_table_index = byte >> 4; let ac_table_index = byte & 0x0f; @@ -389,11 +383,11 @@ pub fn parse_sos(reader: &mut R, frame: &FrameInfo) -> Result } // Also utilized as 'Predictor' in lossless coding, as MEAN in JPEG-LS etc. - let spectral_selection_start = read_u8(reader)?; + let spectral_selection_start = reader.read_u8()?; // Also utilized as ILV parameter in JPEG-LS. - let mut spectral_selection_end = read_u8(reader)?; + let mut spectral_selection_end = reader.read_u8()?; - let byte = read_u8(reader)?; + let byte = reader.read_u8()?; let successive_approximation_high = byte >> 4; let successive_approximation_low = byte & 0x0f; @@ -473,13 +467,13 @@ pub fn parse_sos(reader: &mut R, frame: &FrameInfo) -> Result } // Section B.2.4.1 -pub fn parse_dqt(reader: &mut R) -> Result<[Option<[u16; 64]>; 4]> { +pub fn parse_dqt(reader: &mut R) -> Result<[Option<[u16; 64]>; 4]> { let mut length = read_length(reader, DQT)?; let mut tables = [None; 4]; // Each DQT segment may contain multiple quantization tables. while length > 0 { - let byte = read_u8(reader)?; + let byte = reader.read_u8()?; let precision = (byte >> 4) as usize; let index = (byte & 0x0f) as usize; @@ -505,8 +499,8 @@ pub fn parse_dqt(reader: &mut R) -> Result<[Option<[u16; 64]>; 4]> { for item in table.iter_mut() { *item = match precision { - 0 => u16::from(read_u8(reader)?), - 1 => read_u16_from_be(reader)?, + 0 => u16::from(reader.read_u8()?), + 1 => reader.read_u16_from_be()?, _ => unreachable!(), }; } @@ -523,14 +517,14 @@ pub fn parse_dqt(reader: &mut R) -> Result<[Option<[u16; 64]>; 4]> { } // Section B.2.4.2 -pub fn parse_dht(reader: &mut R, is_baseline: Option) -> Result<(Vec>, Vec>)> { +pub fn parse_dht(reader: &mut R, is_baseline: Option) -> Result<(Vec>, Vec>)> { let mut length = read_length(reader, DHT)?; let mut dc_tables = vec![None, None, None, None]; let mut ac_tables = vec![None, None, None, None]; // Each DHT segment may contain multiple huffman tables. while length > 17 { - let byte = read_u8(reader)?; + let byte = reader.read_u8()?; let class = byte >> 4; let index = (byte & 0x0f) as usize; @@ -579,18 +573,18 @@ pub fn parse_dht(reader: &mut R, is_baseline: Option) -> Result<( } // Section B.2.4.4 -pub fn parse_dri(reader: &mut R) -> Result { +pub fn parse_dri(reader: &mut R) -> Result { let length = read_length(reader, DRI)?; if length != 2 { return Err(Error::Format("DRI with invalid length".to_owned())); } - Ok(read_u16_from_be(reader)?) + Ok(reader.read_u16_from_be()?) } // Section B.2.4.5 -pub fn parse_com(reader: &mut R) -> Result> { +pub fn parse_com(reader: &mut R) -> Result> { let length = read_length(reader, COM)?; let mut buffer = vec![0u8; length]; @@ -600,7 +594,7 @@ pub fn parse_com(reader: &mut R) -> Result> { } // Section B.2.4.6 -pub fn parse_app(reader: &mut R, marker: Marker) -> Result> { +pub fn parse_app(reader: &mut R, marker: Marker) -> Result> { let length = read_length(reader, marker)?; let mut bytes_read = 0; let mut result = None; @@ -680,6 +674,6 @@ pub fn parse_app(reader: &mut R, marker: Marker) -> Result {}, } - skip_bytes(reader, length - bytes_read)?; + reader.skip_bytes(length - bytes_read)?; Ok(result) } diff --git a/src/reader.rs b/src/reader.rs new file mode 100644 index 00000000..01d66870 --- /dev/null +++ b/src/reader.rs @@ -0,0 +1,83 @@ +#[cfg(feature = "std")] +use std::io::Read; + +use crate::Error; + +/// A `no_std` compliant replacement for [std::io::Read]. +pub trait JpegRead { + /// Read the exact number of bytes required to fill buf. + /// + /// See [std::io::Read::read_exact] + fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), Error>; + + /// Skip `length` amount of bytes + fn skip_bytes(&mut self, length: usize) -> Result<(), Error>; + + /// Read a single `u8` value + fn read_u8(&mut self) -> Result { + let mut buf = [0]; + self.read_exact(&mut buf)?; + Ok(buf[0]) + } + + /// Read a single big endian encoded `u16` value + fn read_u16_from_be(&mut self) -> Result { + let mut buf = [0, 0]; + self.read_exact(&mut buf)?; + Ok(u16::from_be_bytes(buf)) + } +} + +#[cfg(feature = "std")] +impl JpegRead for T { + fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), Error> { + Ok(Read::read_exact(self, buf)?) + } + + fn skip_bytes(&mut self, length: usize) -> Result<(), Error> { + let length = length as u64; + let to_skip = &mut Read::by_ref(self).take(length); + let copied = std::io::copy(to_skip, &mut std::io::sink())?; + if copied < length { + Err(Error::Io(std::io::ErrorKind::UnexpectedEof.into())) + } else { + Ok(()) + } + } +} + +#[cfg(not(feature = "std"))] +impl JpegRead for &mut W { + fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), Error> { + (**self).read_exact(buf) + } + + fn skip_bytes(&mut self, length: usize) -> Result<(), Error> { + (**self).skip_bytes(length) + } +} + +#[cfg(not(feature = "std"))] +impl JpegRead for &[u8] { + fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), Error> { + if buf.len() <= self.len() { + let (data, remaining) = self.split_at(buf.len()); + buf.copy_from_slice(data); + *self = remaining; + Ok(()) + } else { + panic!(); + } + } + + fn skip_bytes(&mut self, length: usize) -> Result<(), Error> { + if length <= self.len() { + let (_, remaining) = self.split_at(length); + *self = remaining; + Ok(()) + } else { + panic!(); + } + } +} + diff --git a/src/upsampler.rs b/src/upsampler.rs index a5c39d4e..f8b1b3a5 100644 --- a/src/upsampler.rs +++ b/src/upsampler.rs @@ -164,6 +164,13 @@ impl Upsample for UpsamplerH2V1 { } } +// no_std version to calculate fractional part of positive numbers +fn fract(v: f32) -> f32 { + debug_assert!(v >= 0.0 && v <= (1 << core::f32::MANTISSA_DIGITS - 1) as f32); + let int = v as usize as f32; + v - int +} + impl Upsample for UpsamplerH1V2 { fn upsample_row(&self, input: &[u8], @@ -176,7 +183,7 @@ impl Upsample for UpsamplerH1V2 { let row_near = row as f32 / 2.0; // If row_near's fractional is 0.0 we want row_far to be the previous row and if it's 0.5 we // want it to be the next row. - let row_far = (row_near + row_near.fract() * 3.0 - 0.25).min((input_height - 1) as f32); + let row_far = (row_near + fract(row_near) * 3.0 - 0.25).min((input_height - 1) as f32); let input_near = &input[row_near as usize * row_stride ..]; let input_far = &input[row_far as usize * row_stride ..]; @@ -202,7 +209,7 @@ impl Upsample for UpsamplerH2V2 { let row_near = row as f32 / 2.0; // If row_near's fractional is 0.0 we want row_far to be the previous row and if it's 0.5 we // want it to be the next row. - let row_far = (row_near + row_near.fract() * 3.0 - 0.25).min((input_height - 1) as f32); + let row_far = (row_near + fract(row_near) * 3.0 - 0.25).min((input_height - 1) as f32); let input_near = &input[row_near as usize * row_stride ..]; let input_far = &input[row_far as usize * row_stride ..]; @@ -250,3 +257,17 @@ impl Upsample for UpsamplerGeneric { } } } + +#[cfg(test)] +mod tests { + use crate::upsampler::fract; + + #[test] + fn test_fract() { + let value = 3.6_f32; + let frac = fract(value); + let diff = frac - 0.6_f32; + + assert!(diff >= -core::f32::EPSILON && diff <= core::f32::EPSILON); + } +} \ No newline at end of file diff --git a/src/worker/mod.rs b/src/worker/mod.rs index 35a88ba4..d1b0bf31 100644 --- a/src/worker/mod.rs +++ b/src/worker/mod.rs @@ -1,9 +1,11 @@ mod immediate; + +#[cfg(feature = "std")] mod multithreaded; -#[cfg(not(any(target_arch = "asmjs", target_arch = "wasm32")))] +#[cfg(all(feature = "std", not(any(target_arch = "asmjs", target_arch = "wasm32"))))] pub use self::multithreaded::MultiThreadedWorker as PlatformWorker; -#[cfg(any(target_arch = "asmjs", target_arch = "wasm32"))] +#[cfg(any(not(feature = "std"), target_arch = "asmjs", target_arch = "wasm32"))] pub use self::immediate::ImmediateWorker as PlatformWorker; use alloc::sync::Arc; diff --git a/src/worker/multithreaded.rs b/src/worker/multithreaded.rs index c06bb9fa..f848f28d 100644 --- a/src/worker/multithreaded.rs +++ b/src/worker/multithreaded.rs @@ -4,6 +4,8 @@ //! and allow scaling to more cores. //! However, that would be more complex, so we use this as a starting point. +use alloc::vec::Vec; +use alloc::format; use std::{mem, io, sync::mpsc::{self, Sender}}; use crate::decoder::MAX_COMPONENTS; use crate::error::Result; diff --git a/tests/lib.rs b/tests/lib.rs index 2d49e4e7..022f1d69 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -1,134 +1,145 @@ extern crate jpeg_decoder as jpeg; -extern crate png; -extern crate walkdir; - -use std::path::Path; -use std::fs::File; +#[cfg(feature = "std")] mod common; + +#[cfg(feature = "std")] mod crashtest; + +#[cfg(feature = "std")] mod reftest; -#[test] -fn read_info() { - let path = Path::new("tests").join("reftest").join("images").join("mozilla").join("jpg-progressive.jpg"); +mod no_std; + +#[cfg(feature = "std")] +mod std_tests { + extern crate png; + extern crate walkdir; + + use std::fs::File; + use std::path::Path; + + #[test] + fn read_info() { + let path = Path::new("tests").join("reftest").join("images").join("mozilla").join("jpg-progressive.jpg"); + + let mut decoder = jpeg::Decoder::new(File::open(&path).unwrap()); + let ref_data = decoder.decode().unwrap(); + let ref_info = decoder.info().unwrap(); + + decoder = jpeg::Decoder::new(File::open(&path).unwrap()); + decoder.read_info().unwrap(); + let info = decoder.info().unwrap(); + let data = decoder.decode().unwrap(); + + assert_eq!(info, decoder.info().unwrap()); + assert_eq!(info, ref_info); + assert_eq!(data, ref_data); + } + + #[test] + fn read_icc_profile() { + let path = Path::new("tests") + .join("reftest") + .join("images") + .join("mozilla") + .join("jpg-srgb-icc.jpg"); + + let mut decoder = jpeg::Decoder::new(File::open(&path).unwrap()); + decoder.decode().unwrap(); + + let profile = decoder.icc_profile().unwrap(); + // "acsp" is a mandatory string in ICC profile headers. + assert_eq!(&profile[36..40], b"acsp"); + } + + // Test if chunks are concatenated in the correct order + #[test] + fn read_icc_profile_random_order() { + let path = Path::new("tests") + .join("icc") + .join("icc_chunk_order.jpeg"); + + let mut decoder = jpeg::Decoder::new(File::open(&path).unwrap()); + decoder.decode().unwrap(); + + let profile = decoder.icc_profile().unwrap(); + + assert_eq!(profile.len(), 254); + + for i in 1..=254 { + assert_eq!(profile[i - 1], i as u8); + } + } + + // Check if ICC profiles with invalid chunk number 0 are discarded + #[test] + fn read_icc_profile_seq_no_0() { + let path = Path::new("tests") + .join("icc") + .join("icc_chunk_seq_no_0.jpeg"); - let mut decoder = jpeg::Decoder::new(File::open(&path).unwrap()); - let ref_data = decoder.decode().unwrap(); - let ref_info = decoder.info().unwrap(); + let mut decoder = jpeg::Decoder::new(File::open(&path).unwrap()); + decoder.decode().unwrap(); - decoder = jpeg::Decoder::new(File::open(&path).unwrap()); - decoder.read_info().unwrap(); - let info = decoder.info().unwrap(); - let data = decoder.decode().unwrap(); + let profile = decoder.icc_profile(); + assert!(profile.is_none()); + } + + // Check if ICC profiles with multiple chunks with the same number are discarded + #[test] + fn read_icc_profile_double_seq_no() { + let path = Path::new("tests") + .join("icc") + .join("icc_chunk_double_seq_no.jpeg"); - assert_eq!(info, decoder.info().unwrap()); - assert_eq!(info, ref_info); - assert_eq!(data, ref_data); -} + let mut decoder = jpeg::Decoder::new(File::open(&path).unwrap()); + decoder.decode().unwrap(); -#[test] -fn read_icc_profile() { - let path = Path::new("tests") - .join("reftest") - .join("images") - .join("mozilla") - .join("jpg-srgb-icc.jpg"); + let profile = decoder.icc_profile(); + assert!(profile.is_none()); + } - let mut decoder = jpeg::Decoder::new(File::open(&path).unwrap()); - decoder.decode().unwrap(); + // Check if ICC profiles with mismatching number of chunks and total chunk count are discarded + #[test] + fn read_icc_profile_chunk_count_mismatch() { + let path = Path::new("tests") + .join("icc") + .join("icc_chunk_count_mismatch.jpeg"); - let profile = decoder.icc_profile().unwrap(); - // "acsp" is a mandatory string in ICC profile headers. - assert_eq!(&profile[36..40], b"acsp"); -} + let mut decoder = jpeg::Decoder::new(File::open(&path).unwrap()); + decoder.decode().unwrap(); -// Test if chunks are concatenated in the correct order -#[test] -fn read_icc_profile_random_order() { - let path = Path::new("tests") - .join("icc") - .join("icc_chunk_order.jpeg"); + let profile = decoder.icc_profile(); + assert!(profile.is_none()); + } - let mut decoder = jpeg::Decoder::new(File::open(&path).unwrap()); - decoder.decode().unwrap(); + // Check if ICC profiles with missing chunk are discarded + #[test] + fn read_icc_profile_missing_chunk() { + let path = Path::new("tests") + .join("icc") + .join("icc_missing_chunk.jpeg"); + + let mut decoder = jpeg::Decoder::new(File::open(&path).unwrap()); + decoder.decode().unwrap(); + + let profile = decoder.icc_profile(); + assert!(profile.is_none()); + } - let profile = decoder.icc_profile().unwrap(); + #[test] + fn read_exif_data() { + let path = Path::new("tests") + .join("reftest") + .join("images") + .join("ycck.jpg"); - assert_eq!(profile.len(), 254); + let mut decoder = jpeg::Decoder::new(File::open(&path).unwrap()); + decoder.decode().unwrap(); - for i in 1..=254 { - assert_eq!(profile[i - 1], i as u8); + let exif_data = decoder.exif_data().unwrap(); + // exif data start as a TIFF header + assert_eq!(&exif_data[0..8], b"\x49\x49\x2A\x00\x08\x00\x00\x00"); } -} - -// Check if ICC profiles with invalid chunk number 0 are discarded -#[test] -fn read_icc_profile_seq_no_0() { - let path = Path::new("tests") - .join("icc") - .join("icc_chunk_seq_no_0.jpeg"); - - let mut decoder = jpeg::Decoder::new(File::open(&path).unwrap()); - decoder.decode().unwrap(); - - let profile = decoder.icc_profile(); - assert!(profile.is_none()); -} - -// Check if ICC profiles with multiple chunks with the same number are discarded -#[test] -fn read_icc_profile_double_seq_no() { - let path = Path::new("tests") - .join("icc") - .join("icc_chunk_double_seq_no.jpeg"); - - let mut decoder = jpeg::Decoder::new(File::open(&path).unwrap()); - decoder.decode().unwrap(); - - let profile = decoder.icc_profile(); - assert!(profile.is_none()); -} - -// Check if ICC profiles with mismatching number of chunks and total chunk count are discarded -#[test] -fn read_icc_profile_chunk_count_mismatch() { - let path = Path::new("tests") - .join("icc") - .join("icc_chunk_count_mismatch.jpeg"); - - let mut decoder = jpeg::Decoder::new(File::open(&path).unwrap()); - decoder.decode().unwrap(); - - let profile = decoder.icc_profile(); - assert!(profile.is_none()); -} - -// Check if ICC profiles with missing chunk are discarded -#[test] -fn read_icc_profile_missing_chunk() { - let path = Path::new("tests") - .join("icc") - .join("icc_missing_chunk.jpeg"); - - let mut decoder = jpeg::Decoder::new(File::open(&path).unwrap()); - decoder.decode().unwrap(); - - let profile = decoder.icc_profile(); - assert!(profile.is_none()); -} - -#[test] -fn read_exif_data() { - let path = Path::new("tests") - .join("reftest") - .join("images") - .join("ycck.jpg"); - - let mut decoder = jpeg::Decoder::new(File::open(&path).unwrap()); - decoder.decode().unwrap(); - - let exif_data = decoder.exif_data().unwrap(); - // exif data start as a TIFF header - assert_eq!(&exif_data[0..8], b"\x49\x49\x2A\x00\x08\x00\x00\x00"); -} +} \ No newline at end of file diff --git a/tests/no_std/jpg-size-8x8.raw b/tests/no_std/jpg-size-8x8.raw new file mode 100644 index 00000000..97f98293 Binary files /dev/null and b/tests/no_std/jpg-size-8x8.raw differ diff --git a/tests/no_std/mod.rs b/tests/no_std/mod.rs new file mode 100644 index 00000000..6819f014 --- /dev/null +++ b/tests/no_std/mod.rs @@ -0,0 +1,15 @@ +#[test] +pub fn test_rgb() { + let input = include_bytes!("../reftest/images/mozilla/jpg-size-8x8.jpg"); + let reference = include_bytes!("jpg-size-8x8.raw"); + + let mut decoder = jpeg::Decoder::new(input.as_ref()); + let result = decoder.decode().unwrap(); + + let err = result.iter() + .zip(reference) + .find(|(&v1, &v2)| (v1 as i16 - v2 as i16).abs() > 1) + .is_some(); + + assert!(!err); +}