Rust Imaging Library: A performant and high-level Rust imaging crate.
Documentation •
Crates.io •
Discord
This is a Rust crate designed to provide an easy-to-use, high-level interface around image processing in Rust. Image and animation processing has never been this easy before, and it's hard to find a good crate for it.
RIL was designed not only for static single-frame images in mind, but also for animated images such as GIFs or APNGs that have multiple frames. RIL provides a streamlined API for this.
Even better, benchmarks prove that RIL, even with its high-level interface, is as
performant and usually even faster than leading imaging crates such as image-rs
. See
benchmarks for more information.
⚠ This crate is a work in progress
By the first stable release, we plan to support the following image encodings:
| Encoding Format | Current Status | |-----------------|-------------------| | PNG/APNG | Supported | | JPEG | Supported | | GIF | Supported | | WebP | Not yet supported | | BMP | Not yet supported | | TIFF | Not yet supported |
Additionally, we also plan to support the following pixel formats:
| Pixel Format | Current Status |
|----------------------------------------|-----------------------------|
| RGB8 | Supported as Rgb
|
| RGBA8 | Supported as Rgba
|
| L8 (grayscale) | Supported as L
|
| LA8 (grayscale + alpha) | Not yet supported |
| 1 (single-bit pixel, equivalent to L1) | Supported as BitPixel
|
| Indexed RGB8 (palette) | Supported as PalettedRgb
|
| Indexed RGBA8 (palette) | Supported as PalettedRgba
|
16-bit pixel formats are currently downscaled to 8-bits. We do plan to have actual support 16-bit pixel formats in the future.
MSRV (Minimum Supported Rust Version) is v1.61.0.
Add the following to your Cargo.toml
dependencies:
toml
ril = { version = "0", features = ["all"] }
Or, you can run cargo add ril --features=all
if you have Rust 1.62.0 or newer.
The above enables all features. See Cargo Features for more information on how you can tune these features to reduce dependencies.
Performed locally (10-cores) (Source)
| Benchmark | Time (average of runs in 10 seconds, lower is better) | |-----------------------------------------------|-------------------------------------------------------| | ril (combinator) | 902.54 ms | | ril (for-loop) | 922.08 ms | | ril (low-level hardcoded GIF en/decoder) | 902.28 ms | | image-rs (low-level hardcoded GIF en/decoder) | 940.42 ms | | Python, wand (ImageMagick) | 1049.09 ms |
Performed locally (10-cores) (Source)
| Benchmark | Time (average of runs in 10 seconds, lower is better) | |-----------------------------------------------|-------------------------------------------------------| | ril (combinator) | 1.5317 ms | | image-rs + imageproc | 2.4332 ms |
RIL currently depends on a few dependencies for certain features - especially for various image encodings.
By default RIL comes with no encoding dependencies but with the text
and resize
dependencies, which give you text
and resizing capabilities respectively.
You can use the all
feature to enable all features, including encoding features. This enables the widest range of
image format support, but adds a lot of dependencies you may not need.
For every image encoding that requires a dependency, a corresponding feature can be enabled for it:
| Encoding | Feature | Dependencies | Default? |
|---------------|---------|--------------------------------|----------|
| PNG and APNG | png
| png
| no |
| JPEG | jpeg
| jpeg-decoder
, jpeg-encoder
| no |
| GIF | gif
| gif
| no |
Other features:
| Description | Feature | Dependencies | Default? |
|-----------------------------------------------------------|----------|---------------------|----------|
| Font/Text Rendering | text
| fontdue
| yes |
| Image Resizing | resize
| fast_image_resize
| yes |
| Enable all features,
including all encoding features | all
| | no |
```rust use ril::prelude::*;
fn main() -> ril::Result<()> { let image = Image::open("sample.png")?; image.invert(); image.save_inferred("inverted.png")?;
Ok(())
} ```
or, why not use method chaining?
rust
Image::open("sample.png")?
.inverted()
.save_inferred("inverted.png")?;
rust
let image = Image::new(600, 600, Rgb::black());
image.paste(100, 100, Image::open("sample.png")?);
image.save_inferred("sample_on_black.png")?;
you can still use method chaining, but this accesses a lower level interface:
rust
let image = Image::new(600, 600, Rgb::black())
.with(&Paste::new(Image::open("sample.png")?).with_position(100, 100))
.save_inferred("sample_on_black.png")?;
```rust
let image = Image::
let ellipse = Ellipse::fromboundingbox(0, 0, width, height).with_fill(L(255));
let mask = Image::new(width, height, L(0)); mask.draw(&ellipse);
image.maskalpha(&mask); image.saveinferred("sample_circle.png")?; ```
RIL supports high-level encoding, decoding, and processing of animated images of any format, such as GIF or APNGs.
Animated images can be lazily decoded. This means you can process the frames of an animated image one by one as each frame is decoded. This can lead to huge performance and memory gains when compared to decoding all frames at once, processing those frames individually, and then encoding the image back to a file.
For lazy animated image decoding, the DynamicFrameIterator
is used as a high-level iterator interface
to iterate through all frames of an animated image, lazily. These implement Iterator<Item = Frame<_>>
.
For times when you need to collect all frames of an image, ImageSequence
is used as a high-level
interface around a sequence of images. This can hold extra metadata about the animation such as loop count.
```rust
let mut output = ImageSequence::
// ImageSequence::open is lazy
for frame in ImageSequence::
// or...
output.push_frame(frame?.map_image(|image| image.inverted()));
}
output.save_inferred("inverted.gif")?; ```
rust
ImageSequence::<Rgba>::open("sample.gif")?
.enumerate()
.for_each(|(idx, frame)| {
frame
.unwrap()
.save_inferred(format!("frames/{}.png", idx))
.unwrap();
});
Although a bit misleading a first, ImageSequence::open
and ImageSequence::decode_[inferred_]from_bytes
return lazy DynamicFrameIterator
s.
Additionally, Frame
s house Image
s, but they are not Image
s themselves. However, Frame
s are able
to dereference into Image
s, so calling image methods on frames will seem transparent.
RIL provides a streamlined interface for rendering text.
There are two ways to render text: with a TextSegment
or with a TextLayout
. A TextSegment
is faster and more lightweight than a TextLayout
(and it's cloneable, unlike TextLayout
), but
lacks many of the features of a TextLayout
.
A TextSegment
supports only one font and either represents a segment in a TextLayout
, or it can
be directly rendered more efficiently than a TextLayout
. You should only use TextLayout
if you
need what TextSegment
can't provide.
TextLayout
s support anchor-style text-alignment, and can be used to render text with multiple fonts
and styles, such as different sizes or colors. It also provides the ability to grab the dimensions
of the text before rendering such as width and height. TextSegment
cannot do this.
TextSegment
:``rust
let mut image = Image::new(512, 256, Rgb::black());
// Open the font at the given path. You can try using
Font::frombytesalong with the
includebytes!` macro
// since fonts can usually be statically loaded.
let font = Font::open(
"Arial.ttf",
// Do note that the following is a specified optimal size
// and not a fixed size for the font. It specifies what size
// to optimize rasterizing for. You do not have to load the same
// font multiple times for different sizes.
36.0,
)?;
let text = TextSegment::new(&font, "Hello, world", Rgb::white()) .with_position(20, 20);
image.draw(&text); image.save_inferred("text.png")?; ```
TextLayout
:```rust let mut image = Image::new(512, 256, Rgb::black()); let font = Font::open("Arial.ttf", 36.0)?; let bold = Font::open("Arial Bold.ttf", 36.0)?;
let (x, y) = image.center(); let layout = TextLayout::new() .centered() // Shorthand for centering horizontally and vertically .withwrap(WrapStyle::Word) // RIL supports word wrapping .withwidth(image.width()) // This is the width to wrap text at. Only required if you want to wrap text. .withposition(x, y); // Position the anchor (which is the center) at the center of the image .withsegment(&TextSegment::new(&font, "Here is some ", Rgb::white())) .withsegment(&TextSegment::new(&bold, "bold ", Rgb::white())) .withsegment(&TextSegment::new(&font, "text.", Rgb::white()));
image.draw(&layout); ```
See CONTRIBUTING.md for more information.