Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add no_std support #226

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
strategy:
matrix:
rust: ["1.48.0", stable, beta, nightly]
features: ["", "rayon"]
features: ["", "std", "rayon"]
command: [test, benchmark]

steps:
Expand Down
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ name = "large_image"
harness = false

[features]
default = ["rayon"]
default = ["std", "rayon"]
std = []
platform_independent = []
nightly_aarch64_neon = []

27 changes: 13 additions & 14 deletions src/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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};

Expand Down Expand Up @@ -98,7 +97,7 @@ pub struct Decoder<R> {
coefficients_finished: [u64; MAX_COMPONENTS],
}

impl<R: Read> Decoder<R> {
impl<R: JpegRead> Decoder<R> {
/// Creates a new `Decoder` using the reader `reader`.
pub fn new(reader: R) -> Decoder<R> {
Decoder {
Expand Down Expand Up @@ -227,8 +226,8 @@ impl<R: Read> Decoder<R> {
// 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(),
Expand Down Expand Up @@ -591,19 +590,19 @@ impl<R: Read> Decoder<R> {
// 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 {
Expand Down Expand Up @@ -898,7 +897,7 @@ impl<R: Read> Decoder<R> {
}
}

fn decode_block<R: Read>(
fn decode_block<R: JpegRead>(
reader: &mut R,
coefficients: &mut [i16; 64],
huffman: &mut HuffmanDecoder,
Expand Down Expand Up @@ -986,7 +985,7 @@ fn decode_block<R: Read>(
Ok(())
}

fn decode_block_successive_approximation<R: Read>(
fn decode_block_successive_approximation<R: JpegRead>(
reader: &mut R,
coefficients: &mut [i16; 64],
huffman: &mut HuffmanDecoder,
Expand Down Expand Up @@ -1072,7 +1071,7 @@ fn decode_block_successive_approximation<R: Read>(
Ok(())
}

fn refine_non_zeroes<R: Read>(
fn refine_non_zeroes<R: JpegRead>(
reader: &mut R,
coefficients: &mut [i16; 64],
huffman: &mut HuffmanDecoder,
Expand Down Expand Up @@ -1257,7 +1256,7 @@ fn color_convert_line_ycbcr(data: &[Vec<u8>], 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)]
Expand All @@ -1269,7 +1268,7 @@ fn color_convert_line_ycbcr(data: &[Vec<u8>], 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
Expand Down
8 changes: 6 additions & 2 deletions src/decoder/lossless.rs
Original file line number Diff line number Diff line change
@@ -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<R: Read> Decoder<R> {
impl<R: JpegRead> Decoder<R> {
/// decode_scan_lossless
pub fn decode_scan_lossless(
&mut self,
Expand Down
21 changes: 19 additions & 2 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -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<T> = result::Result<T, Error>;
Expand Down Expand Up @@ -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<dyn StdError + Send + Sync + 'static>), //TODO: not used, can be removed with the next version bump
Comment on lines +50 to 56
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having extensively worked with no_std-but-std-compatible crates in networking, variants guarded behind cfg flags are a major pita. This seems like the biggest risk to reject the PR.

If the enum itself is not non_exhaustive then consumers intending to match on these variants will having to work quite awkwardly around the presence and absence of certain variants/branches. Especially since it depends on the feature of a dependency, which they may not want to publicly expose as a dependency/toggle themselves. This works just good enough if the program is small and jpeg-decoder is a direct dependency but doesn't scale well. (Again, consider the feature may be activated by an unrelated crate).

This forces downstream crates, if they intend to be compatible with both, to run check coverage of both with and without the feature combinations. And in the end you get this where it's a jungle of things that may or may not work with each other.

}

#[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 {
Expand All @@ -64,6 +80,7 @@ impl StdError for Error {
}
}

#[cfg(feature = "std")]
impl From<IoError> for Error {
fn from(err: IoError) -> Error {
Error::Io(err)
Expand Down
21 changes: 10 additions & 11 deletions src/huffman.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -28,7 +27,7 @@ impl HuffmanDecoder {

// Section F.2.2.3
// Figure F.16
pub fn decode<R: Read>(&mut self, reader: &mut R, table: &HuffmanTable) -> Result<u8> {
pub fn decode<R: JpegRead>(&mut self, reader: &mut R, table: &HuffmanTable) -> Result<u8> {
if self.num_bits < 16 {
self.read_bits(reader)?;
}
Expand Down Expand Up @@ -57,7 +56,7 @@ impl HuffmanDecoder {
}
}

pub fn decode_fast_ac<R: Read>(&mut self, reader: &mut R, table: &HuffmanTable) -> Result<Option<(i16, u8)>> {
pub fn decode_fast_ac<R: JpegRead>(&mut self, reader: &mut R, table: &HuffmanTable) -> Result<Option<(i16, u8)>> {
if let Some(ref ac_lut) = table.ac_lut {
if self.num_bits < LUT_BITS {
self.read_bits(reader)?;
Expand All @@ -78,7 +77,7 @@ impl HuffmanDecoder {
}

#[inline]
pub fn get_bits<R: Read>(&mut self, reader: &mut R, count: u8) -> Result<u16> {
pub fn get_bits<R: JpegRead>(&mut self, reader: &mut R, count: u8) -> Result<u16> {
if self.num_bits < count {
self.read_bits(reader)?;
}
Expand All @@ -90,7 +89,7 @@ impl HuffmanDecoder {
}

#[inline]
pub fn receive_extend<R: Read>(&mut self, reader: &mut R, count: u8) -> Result<i16> {
pub fn receive_extend<R: JpegRead>(&mut self, reader: &mut R, count: u8) -> Result<i16> {
let value = self.get_bits(reader, count)?;
Ok(extend(value, count))
}
Expand All @@ -100,7 +99,7 @@ impl HuffmanDecoder {
self.num_bits = 0;
}

pub fn take_marker<R: Read>(&mut self, reader: &mut R) -> Result<Option<Marker>> {
pub fn take_marker<R: JpegRead>(&mut self, reader: &mut R) -> Result<Option<Marker>> {
self.read_bits(reader).map(|_| self.marker.take())
}

Expand All @@ -120,16 +119,16 @@ impl HuffmanDecoder {
self.num_bits -= count;
}

fn read_bits<R: Read>(&mut self, reader: &mut R) -> Result<()> {
fn read_bits<R: JpegRead>(&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 {
Expand All @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions src/idct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
}
Expand Down
23 changes: 8 additions & 15 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))]
Expand All @@ -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;
Expand All @@ -56,15 +60,4 @@ mod marker;
mod parser;
mod upsampler;
mod worker;

fn read_u8<R: io::Read>(reader: &mut R) -> io::Result<u8> {
let mut buf = [0];
reader.read_exact(&mut buf)?;
Ok(buf[0])
}

fn read_u16_from_be<R: io::Read>(reader: &mut R) -> io::Result<u16> {
let mut buf = [0, 0];
reader.read_exact(&mut buf)?;
Ok(u16::from_be_bytes(buf))
}
mod reader;
Loading