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

basic no_std support #1868

Draft
wants to merge 16 commits into
base: main
Choose a base branch
from
6 changes: 3 additions & 3 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: Rust CI

on:
push:
branches: [ master, next ]
pull_request:
branches: [ master, next ]
# branches: [ master, next ]
# pull_request:
# branches: [ master, next ]

jobs:
build:
Expand Down
52 changes: 47 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@ path = "./src/lib.rs"

[dependencies]
bytemuck = { version = "1.7.0", features = ["extern_crate_alloc"] } # includes cast_vec
byteorder = "1.3.2"
byteorder = { version = "1.3.2", default-features = false }
num-rational = { version = "0.4", default-features = false }
num-traits = "0.2.0"
gif = { version = "0.12", optional = true }
num-traits = { version = "0.2", default-features = false }
snafu = { version = "0.7", default-features = false }
gif = { version = "0.12", optional = true, default-features = false }
jpeg = { package = "jpeg-decoder", version = "0.3.0", default-features = false, optional = true }
png = { version = "0.17.6", optional = true }
scoped_threadpool = { version = "0.1", optional = true }
Expand All @@ -44,7 +45,9 @@ rgb = { version = "0.8.25", optional = true }
mp4parse = { version = "0.12.0", optional = true }
dav1d = { version = "0.6.0", optional = true }
dcv-color-primitives = { version = "0.4.0", optional = true }
color_quant = "1.1"
# TODO(interstellar) no_std not supported on crate.io; need a fork/patch
# NOTE: also an optional dependency of "gif"
color_quant = { version = "1.1", git = "https://github.com/Interstellar-Network/color_quant.git", branch = "sgx-nostd-compat", default-features = false, optional = true }
exr = { version = "1.5.0", optional = true }
qoi = { version = "0.4", optional = true }
libwebp = { package = "webp", version = "0.2.2", default-features = false, optional = true }
Expand All @@ -61,8 +64,9 @@ jpeg = { package = "jpeg-decoder", version = "0.3.0", default-features = false,

[features]
# TODO: Add "avif" to this list while preparing for 0.24.0
default = ["gif", "jpeg", "ico", "png", "pnm", "tga", "tiff", "webp", "bmp", "hdr", "dxt", "dds", "farbfeld", "jpeg_rayon", "openexr", "qoi"]
default = ["std", "gif", "jpeg", "ico", "png", "pnm", "tga", "tiff", "webp", "bmp", "hdr", "dxt", "dds", "farbfeld", "jpeg_rayon", "openexr", "qoi"]

std = ["snafu/std", "num-traits/std", "byteorder/std", "gif/default", "color_quant/std"]
ico = ["bmp", "png"]
pnm = []
tga = []
Expand Down Expand Up @@ -94,6 +98,8 @@ avif-decoder = ["mp4parse", "dcv-color-primitives", "dav1d"]
# Requires rustc nightly for feature test.
benchmarks = []

alloc = ["num-traits/libm", "color_quant/alloc"]

[[bench]]
path = "benches/decode.rs"
name = "decode"
Expand All @@ -107,3 +113,39 @@ harness = false
[[bench]]
name = "copy_from"
harness = false

[[example]]
name = "tile"
required-features = ["std"]

[[example]]
name = "fractal"
required-features = ["std"]

[[example]]
name = "scaleup"
required-features = ["std"]

[[example]]
name = "concat"
required-features = ["std"]

[[example]]
name = "gradient"
required-features = ["std"]

[[example]]
name = "opening"
required-features = ["std"]

[[example]]
name = "decode"
required-features = ["std"]

[[example]]
name = "scaledown"
required-features = ["std"]

[[example]]
name = "convert"
required-features = ["std"]
37 changes: 21 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,21 @@ All image processing functions provided operate on types that implement the `Gen

`image` provides implementations of common image format encoders and decoders.

| Format | Decoding | Encoding |
| ------ | -------- | -------- |
| PNG | All supported color types | Same as decoding |
| JPEG | Baseline and progressive | Baseline JPEG |
| GIF | Yes | Yes |
| BMP | Yes | Rgb8, Rgba8, Gray8, GrayA8 |
| ICO | Yes | Yes |
| TIFF | Baseline(no fax support) + LZW + PackBits | Rgb8, Rgba8, Gray8 |
| WebP | Yes | Rgb8, Rgba8 \* |
| AVIF | Only 8-bit \*\* | Lossy |
| PNM | PBM, PGM, PPM, standard PAM | Yes |
| DDS | DXT1, DXT3, DXT5 | No |
| TGA | Yes | Rgb8, Rgba8, Bgr8, Bgra8, Gray8, GrayA8 |
| OpenEXR | Rgb32F, Rgba32F (no dwa compression) | Rgb32F, Rgba32F (no dwa compression) |
| farbfeld | Yes | Yes |
| Format | Decoding | Encoding |
| -------- | ----------------------------------------- | --------------------------------------- |
| PNG | All supported color types | Same as decoding |
| JPEG | Baseline and progressive | Baseline JPEG |
| GIF | Yes | Yes |
| BMP | Yes | Rgb8, Rgba8, Gray8, GrayA8 |
| ICO | Yes | Yes |
| TIFF | Baseline(no fax support) + LZW + PackBits | Rgb8, Rgba8, Gray8 |
| WebP | Yes | Rgb8, Rgba8 \* |
| AVIF | Only 8-bit \*\* | Lossy |
| PNM | PBM, PGM, PPM, standard PAM | Yes |
| DDS | DXT1, DXT3, DXT5 | No |
| TGA | Yes | Rgb8, Rgba8, Bgr8, Bgra8, Gray8, GrayA8 |
| OpenEXR | Rgb32F, Rgba32F (no dwa compression) | Rgb32F, Rgba32F (no dwa compression) |
| farbfeld | Yes | Yes |

- \* Requires the `webp-encoder` feature, uses the libwebp C library.
- \*\* Requires the `avif-decoder` feature, uses the libdav1d C library.
Expand Down Expand Up @@ -60,7 +60,7 @@ The most important methods for decoders are...
All pixels are parameterised by their component type.

## Images
Individual pixels within images are indexed with (0,0) at the top left corner.
Individual pixels within images are indexed with (0,0) at the top left corner.
### The [`GenericImageView`](https://docs.rs/image/*/image/trait.GenericImageView.html) and [`GenericImage`](https://docs.rs/image/*/image/trait.GenericImage.html) Traits

Traits that provide methods for inspecting (`GenericImageView`) and manipulating (`GenericImage`) images, parameterised over the image's pixel type.
Expand Down Expand Up @@ -166,6 +166,7 @@ reader which offer some more control.
```rust,no_run
use image::GenericImageView;

#[cfg(feature = "std")]
fn main() {
// Use the open function to load an image from a Path.
// `open` returns a `DynamicImage` on success.
Expand All @@ -180,6 +181,8 @@ fn main() {
// Write the contents of this image to the Writer in PNG format.
img.save("test.png").unwrap();
}
#[cfg(not(feature = "std"))]
fn main() {}
```

### Generating Fractals
Expand Down Expand Up @@ -225,6 +228,7 @@ fn main() {
}

// Save the image as “fractal.png”, the format is deduced from the path
#[cfg(feature = "std")]
imgbuf.save("fractal.png").unwrap();
}
```
Expand All @@ -242,6 +246,7 @@ fn main() {
let buffer: &[u8] = unimplemented!(); // Generate the image data

// Save the buffer as "image.png"
#[cfg(feature = "std")]
image::save_buffer("image.png", buffer, 800, 600, image::ColorType::Rgb8).unwrap()
}
```
10 changes: 7 additions & 3 deletions src/animation.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
use std::iter::Iterator;
use std::time::Duration;
use alloc::boxed::Box;
use alloc::vec::Vec;
use core::cmp::Ordering::Equal;
use core::cmp::Ordering::Less;
use core::iter::Iterator;
use core::time::Duration;

use num_rational::Ratio;

Expand Down Expand Up @@ -179,7 +183,7 @@ impl Delay {
/// Note that `denom_bound` bounds nominator and denominator of all intermediate
/// approximations and the end result.
fn closest_bounded_fraction(denom_bound: u32, nom: u32, denom: u32) -> (u32, u32) {
use std::cmp::Ordering::{self, *};
use core::cmp::Ordering::{self, *};
assert!(0 < denom);
assert!(0 < denom_bound);
assert!(nom < denom);
Expand Down
25 changes: 18 additions & 7 deletions src/buffer.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
//! Contains the generic `ImageBuffer` struct.
use alloc::vec::Vec;
use core::fmt;
use core::marker::PhantomData;
use core::ops::{Deref, DerefMut, Index, IndexMut, Range};
use core::slice::{ChunksExact, ChunksExactMut};
use num_traits::Zero;
use std::fmt;
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut, Index, IndexMut, Range};
use std::path::Path;
use std::slice::{ChunksExact, ChunksExactMut};

use crate::color::{FromColor, Luma, LumaA, Rgb, Rgba};
#[cfg(feature = "std")]
use crate::dynimage::{save_buffer, save_buffer_with_format, write_buffer_with_format};
use crate::error::ImageResult;
use crate::flat::{FlatSamples, SampleLayout};
Expand Down Expand Up @@ -629,6 +630,7 @@ where
/// Overlays an image on top of a larger background raster.
///
/// ```no_run
/// # #[cfg(feature = "std")] {
/// use image::{GenericImage, GenericImageView, ImageBuffer, open};
///
/// let on_top = open("path/to/some.png").unwrap().into_rgb8();
Expand All @@ -641,15 +643,18 @@ where
/// });
///
/// image::imageops::overlay(&mut img, &on_top, 128, 128);
/// }
/// ```
///
/// Convert an RgbaImage to a GrayImage.
///
/// ```no_run
/// # #[cfg(feature = "std")] {
/// use image::{open, DynamicImage};
///
/// let rgba = open("path/to/some.png").unwrap().into_rgba8();
/// let gray = DynamicImage::ImageRgba8(rgba).into_luma8();
/// }
/// ```
#[derive(Debug, Hash, PartialEq, Eq)]
pub struct ImageBuffer<P: Pixel, Container> {
Expand Down Expand Up @@ -979,6 +984,7 @@ where
}
}

#[cfg(feature = "std")]
impl<P, Container> ImageBuffer<P, Container>
where
P: Pixel,
Expand All @@ -990,7 +996,7 @@ where
/// The image format is derived from the file extension.
pub fn save<Q>(&self, path: Q) -> ImageResult<()>
where
Q: AsRef<Path>,
Q: AsRef<std::path::Path>,
P: PixelWithColorType,
{
save_buffer(
Expand All @@ -1003,6 +1009,7 @@ where
}
}

#[cfg(feature = "std")]
impl<P, Container> ImageBuffer<P, Container>
where
P: Pixel,
Expand All @@ -1016,7 +1023,7 @@ where
/// supported types.
pub fn save_with_format<Q>(&self, path: Q, format: ImageFormat) -> ImageResult<()>
where
Q: AsRef<Path>,
Q: AsRef<std::path::Path>,
P: PixelWithColorType,
{
// This is valid as the subpixel is u8.
Expand All @@ -1031,6 +1038,7 @@ where
}
}

#[cfg(feature = "std")]
impl<P, Container> ImageBuffer<P, Container>
where
P: Pixel,
Expand Down Expand Up @@ -1360,11 +1368,13 @@ where
/// use image::GrayImage;
///
/// let image_path = "examples/fractal.png";
/// # #[cfg(feature = "std")] {
/// let image = image::open(&image_path)
/// .expect("Open file failed")
/// .to_rgba8();
///
/// let gray_image: GrayImage = image.convert();
/// }
/// ```
fn convert(&self) -> ImageBuffer<ToType, Vec<ToType::Subpixel>> {
let mut buffer: ImageBuffer<ToType, Vec<ToType::Subpixel>> =
Expand Down Expand Up @@ -1407,6 +1417,7 @@ mod test {
use crate::math::Rect;
use crate::GenericImage as _;
use crate::{color, Rgb};
use alloc::vec::Vec;

#[test]
/// Tests if image buffers from slices work
Expand Down
29 changes: 14 additions & 15 deletions src/codecs/png.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,10 +220,10 @@ impl<R: Read> PngDecoder<R> {
}

fn unsupported_color(ect: ExtendedColorType) -> ImageError {
ImageError::Unsupported(UnsupportedError::from_format_and_kind(
ImageFormat::Png.into(),
UnsupportedErrorKind::Color(ect),
))
ImageError::Unsupported {
format: ImageFormat::Png.into(),
kind: UnsupportedErrorKind::Color(ect),
}
}

impl<'a, R: 'a + Read> ImageDecoder<'a> for PngDecoder<R> {
Expand Down Expand Up @@ -668,21 +668,22 @@ impl ImageError {
fn from_png(err: png::DecodingError) -> ImageError {
use png::DecodingError::*;
match err {
#[cfg(features = "std")]
IoError(err) => ImageError::IoError(err),
// The input image was not a valid PNG.
err @ Format(_) => {
ImageError::Decoding(DecodingError::new(ImageFormat::Png.into(), err))
}
err @ Format(_) => ImageError::Decoding {
format: ImageFormat::Png.into(),
},
// Other is used when:
// - The decoder is polled for more animation frames despite being done (or not being animated
// in the first place).
// - The output buffer does not have the required size.
err @ Parameter(_) => ImageError::Parameter(ParameterError::from_kind(
ParameterErrorKind::Generic(err.to_string()),
)),
LimitsExceeded => {
ImageError::Limits(LimitError::from_kind(LimitErrorKind::InsufficientMemory))
}
err @ Parameter(_) => ImageError::Parameter {
kind: ParameterErrorKind::Generic(err.to_string()),
},
LimitsExceeded => ImageError::Limits {
kind: LimitErrorKind::InsufficientMemory,
},
}
}
}
Expand Down Expand Up @@ -749,8 +750,6 @@ mod tests {

#[test]
fn underlying_error() {
use std::error::Error;

let mut not_png =
std::fs::read("tests/images/png/bugfixes/debug_triangle_corners_widescreen.png")
.unwrap();
Expand Down
1 change: 1 addition & 0 deletions src/codecs/tga/encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ impl<W: Write> ImageEncoder for TgaEncoder<W> {
mod tests {
use super::{EncoderError, TgaEncoder};
use crate::{codecs::tga::TgaDecoder, ColorType, ImageDecoder, ImageError};
use alloc::vec::Vec;
use std::{error::Error, io::Cursor};

fn round_trip_image(image: &[u8], width: u32, height: u32, c: ColorType) -> Vec<u8> {
Expand Down
Loading