Rust Imaging Library: A high-level Rust imaging crate.

Overview

ril

Rust Imaging Library: A performant and high-level Rust imaging crate.
DocumentationCrates.ioDiscord

What's this?

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.

Features

  • Support for encoding from/decoding to a wide range of image formats
  • Variety of image processing and manipulation operations, including drawing
  • Robust support for animated images such as GIFs via FrameIterator and ImageSequence
  • Robust and performant support for fonts and text rendering
  • A streamlined front-facing interface

Support

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

16-bit pixel formats are currently downscaled to 8-bits. We do plan to have actual support 16-bit pixel formats in the future.

Requirements

MSRV (Minimum Supported Rust Version) is v1.61.0.

Installation

Add the following to your Cargo.toml dependencies:

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.

Benchmarks

Decode GIF + Invert each frame + Encode GIF (600x600, 77 frames)

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

Rasterize and render text (Inter font, 20px, 1715 glyphs)

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

Cargo Features

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

Examples

Open an image, invert it, and then save it:

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?

Image::open("sample.png")?
    .inverted()
    .save_inferred("inverted.png")?;

Create a new black image, open the sample image, and paste it on top of the black image:

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:

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")?;

Open an image and mask it to a circle:

let image = Image::<Rgba>::open("sample.png")?;
let (width, height) = image.dimensions();

let ellipse = 
    Ellipse::from_bounding_box(0, 0, width, height).with_fill(L(255));

let mask = Image::new(width, height, L(0));
mask.draw(&ellipse);

image.mask_alpha(&mask);
image.save_inferred("sample_circle.png")?;

Animated Image Support

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.

Open an animated image and invert each frame as they are decoded, then saving them:

let mut output = ImageSequence::<Rgba>::new();

// ImageSequence::open is lazy
for frame in ImageSequence::<Rgba>::open("sample.gif")? {
    let frame = frame?;
    frame.invert();
    output.push(frame);

    // or...
    output.push_frame(frame?.map_image(|image| image.inverted()));
}

output.save_inferred("inverted.gif")?;

Open an animated image and save each frame into a separate PNG image as they are decoded:

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 DynamicFrameIterators.

Additionally, Frames house Images, but they are not Images themselves. However, Frames are able to dereference into Images, so calling image methods on frames will seem transparent.

Rendering Text

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.

TextLayouts 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.

Render text with a TextSegment:

let mut image = Image::new(512, 256, Rgb::black());
// Open the font at the given path. You can try using `Font::from_bytes` along with the `include_bytes!` 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")?;

Render text in the center of the image with a TextLayout:

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
    .with_wrap(WrapStyle::Word) // RIL supports word wrapping
    .with_width(image.width()) // This is the width to wrap text at. Only required if you want to wrap text.
    .with_position(x, y); // Position the anchor (which is the center) at the center of the image
    .with_segment(&TextSegment::new(&font, "Here is some ", Rgb::white()))
    .with_segment(&TextSegment::new(&bold, "bold ", Rgb::white()))
    .with_segment(&TextSegment::new(&font, "text.", Rgb::white()));

image.draw(&layout);

Contributing

See CONTRIBUTING.md for more information.

Comments
  • Cropping returning odd results.

    Cropping returning odd results.

    First: this is a superb crate. I was able to convert https://github.com/hrbrmstr/rust-ipv4heatmap over to it (from the image crate) with ease, and the API is well-crafted.

    I'm trying to use the crop() function as such:

    use ril::{Image, Rgba};
    
    fn main() {
      let mut image = Image::<Rgba>::open("assets/rustacean-flat-happy.png").unwrap();
      image.crop(0, 0, 512, 512);
      image.save_inferred("assets/cropped.png").unwrap();
    }
    

    This is the image being cropped:

    rustacean-flat-happy

    This is what crop produced: cropped

    I have a full "working" example I can push up to GH if that'll help.

    opened by hrbrmstr 6
  • Indexed (colormap) PNGs not supported

    Indexed (colormap) PNGs not supported

    use ril::prelude::*;
    
    fn main() -> ril::Result<()> {
        let mut image: Image<Dynamic> = Image::open("/home/cel/projects/cowmic/cowmic/src/cow.png")?;
        image.invert();
        image.save_inferred("inverted.png")?;
        Ok(())
    }
    

    When attempting to open an indexed PNG, the error Unsupported color type. Try using the Dynamic pixel type instead. is thrown.

    there is no PixelData::Palette in from_pixel_data:

    src/pixel.rs:839

    fn from_pixel_data(data: PixelData) -> Result<Self> {
        #[allow(clippy::match_wildcard_for_single_variants)]
        Ok(match data {
            PixelData::Bit(value) => Self::BitPixel(BitPixel(value)),
            PixelData::L(l) => Self::L(L(l)),
            // TODO: LA pixel type
            PixelData::LA(l, _a) => Self::L(L(l)),
            PixelData::Rgb(r, g, b) => Self::Rgb(Rgb { r, g, b }),
            PixelData::Rgba(r, g, b, a) => Self::Rgba(Rgba { r, g, b, a }),
            _ => return Err(UnsupportedColorType),
        })
    }
    
    opened by cellularnetwork 2
  • Implement SIMD acceleration

    Implement SIMD acceleration

    Adds performance boosts to RIL by adding an optional simd feature (nightly only), which utilizes the portable_simd feature to bring SIMD instructions to common pixel operations.

    opened by jay3332 2
  • SIMD implementations

    SIMD implementations

    Hello all,

    This is a PR that introduces performance increase to RIL by utilizing platform specific instructions (otherwise known as SIMD).

    There are a few functions which can very likely be rewritten to incorporate SIMD, doing so can lead to dramatic performance increase.

    Functions to be rewritten

    • [x] Rgba::merge (sse, fma)

    Compatibility

    SIMD is supported on almost all modern CPUs.

    There are runtime checks to ensure that the given CPU feature is supported on the target CPU, otherwise it will fallback to the non-SIMD implementation.

    The one this PR uses are:

    • SSE
    • FMA
    enhancement perf 
    opened by Cryptex-github 0
  • Support gradients when rendering text

    Support gradients when rendering text

    Limitations:

    • much slower than just rendering a gradient masked to text
    • apart from dynamic dispatch, Rust's type system does not allow for different types of fills in text layouts (e.g. layout.with_basic_text(..., <Rgba>).with_basic_text(..., <LinearGradient>) will not work)

    Once these limitations are resolved, they can be merged into main

    opened by jay3332 0
  • Polygon incorrect offset

    Polygon incorrect offset

    code example

    let mut image = Image::new(100, 100, Rgba::white());
    Polygon {
        vertices: vec![(0,100), (100,100), (100,0), (0,0)],
        ..Polygon::default()
    }.with_fill(Rgba::black()).draw(&mut image);
    

    output image

    new

    and if you turn on the antialias, rust will panic

    ❯ RUST_BACKTRACE=1 cargo run
        Finished dev [unoptimized + debuginfo] target(s) in 0.15s
         Running `target\debug\image.exe`
    thread 'main' panicked at 'invalid bounding box', D:\scoop\persist\rustup\.cargo\registry\src\mirrors.tuna.tsinghua.edu.cn-df7c3c540f42cdbd\ril-0.9.0\src\draw.rs:925:9
    stack backtrace:
       0: std::panicking::begin_panic_handler
                 at /rustc/e0098a5cc3a87d857e597af824d0ce1ed1ad85e0/library\std\src\panicking.rs:575
       1: core::panicking::panic_fmt
                 at /rustc/e0098a5cc3a87d857e597af824d0ce1ed1ad85e0/library\core\src\panicking.rs:65
       2: ril::draw::Rectangle<ril::pixel::Rgba>::from_bounding_box<ril::pixel::Rgba>
                 at D:\scoop\persist\rustup\.cargo\registry\src\mirrors.tuna.tsinghua.edu.cn-df7c3c540f42cdbd\ril-0.9.0\src\draw.rs:925
       3: ril::draw::Line<ril::pixel::Rgba>::plot_perfect_line<ril::pixel::Rgba>
                 at D:\scoop\persist\rustup\.cargo\registry\src\mirrors.tuna.tsinghua.edu.cn-df7c3c540f42cdbd\ril-0.9.0\src\draw.rs:351
       4: ril::draw::impl$7::draw<ril::pixel::Rgba,ref_mut$<ril::image::Image<ril::pixel::Rgba> > >
                 at D:\scoop\persist\rustup\.cargo\registry\src\mirrors.tuna.tsinghua.edu.cn-df7c3c540f42cdbd\ril-0.9.0\src\draw.rs:511
       5: ril::image::Image<ril::pixel::Rgba>::draw<ril::pixel::Rgba,ril::draw::Line<ril::pixel::Rgba> >
                 at D:\scoop\persist\rustup\.cargo\registry\src\mirrors.tuna.tsinghua.edu.cn-df7c3c540f42cdbd\ril-0.9.0\src\image.rs:1079
       6: ril::draw::impl$10::draw<ril::pixel::Rgba,ref_mut$<ril::image::Image<ril::pixel::Rgba> > >
                 at D:\scoop\persist\rustup\.cargo\registry\src\mirrors.tuna.tsinghua.edu.cn-df7c3c540f42cdbd\ril-0.9.0\src\draw.rs:818
       7: imagers_roundcorner::main
                 at .\src\main.rs:5
       8: core::ops::function::FnOnce::call_once<enum2$<core::result::Result<tuple$<>,enum2$<ril::error::Error> > > (*)(),tuple$<> >
                 at /rustc/e0098a5cc3a87d857e597af824d0ce1ed1ad85e0\library\core\src\ops\function.rs:507
    note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
    error: process didn't exit successfully: `target\debug\image.exe` (exit code: 101)
    
    opened by fantasyzhjk 1
Releases(v0.9)
  • v0.9(Dec 14, 2022)

    v0.9 (2022-12-13)

    Breaking changes

    • Pixel::force_into_rgb[a] method is now replaced with Pixel::as_rgb[a], which also takes self by reference instead of by value.
    • All provided Draw objects (but not the Draw trait itself) are now generic over F: IntoFill instead of P: Pixel
      • The trait IntoFill is explained below
      • There should be no change in usage because for any P: Pixel, P is implemented for IntoFill
      • If you are extracting the fill color from a Draw object, you will need to access the .color() method on the SolidColor struct. It is a const fn.
      • The Draw trait is still generic over P: Pixel, no changes there

    Other changes

    • ColorType::is_dynamic is now a const fn
    • Add ColorType::has_alpha for whether the color type has an alpha channel
    • Add new Fill trait, used to represent a fill color (or gradient, see below) for a Draw object. This replaces the simple Pixel trait previously used for this purpose.
      • Add new IntoFill trait, which provides a way to convert anything to a Fill object
        • Associated type <_ as IntoFill>::Pixel is the pixel type of the fill.
        • Associated type <_ as IntoFill>::Fill is the actual fill type.
        • IntoFill is implemented for all P: Pixel and turns into draw::SolidColor<P>
        • IntoFill is implemented for LinearGradient (see below) and turns into gradient::LinearGradientFill<P>
    • Add support for gradients
      • Enabled with the gradient feature, which is enabled by default
      • New LinearGradient struct, which represents a linear gradient
        • LinearGradientBlendMode and LinearGradientInterpolation enums are re-exports from the colorgrad crate, which is used to configure the gradient's blending mode and interpolation.
    • Add Polygon::regular method, which creates a regular polygon with the given amount of sides, center, and radius
      • This uses the Polygon::regular_rotated method, which is the same method, but you are able to specify the rotation of the polygon in radians.

    Linear gradient example

    use ril::prelude::*;
    
    fn main() -> ril::Result<()> {
        // Create a new 256x256 RGB image with a black background
        let mut image = Image::new(256, 256, Rgb::black());
        // Create the `LinearGradient` object
        let gradient = LinearGradient::new()
            // The gradient will be rotated 45 degrees
            .with_angle_degrees(45.0)
            // The first stop is at 0.0, and is red
            .with_color(Rgb::new(255, 0, 0))
            // The second stop is at 0.5, and is white
            .with_color(Rgb::new(255, 255, 255))
            // We can also specify color stop positions manually:
            // .with_color_at(0.5, Rgb::new(255, 255, 255)) 
            // ...  
            // The third stop is at 1.0, and is green
            .with_color(Rgb::new(0, 255, 0));
        
        // Fill a hexagon with the gradient and draw it to the image
        image.draw(&Polygon::regular(6, image.center(), 64).with_fill(gradient));
        // Save the image to a PNG file
        image.save_inferred("gradient_output.png")
    }
    

    Output

    image

    Full Changelog: https://github.com/jay3332/ril/compare/v0.8...v0.9

    Source code(tar.gz)
    Source code(zip)
  • v0.8(Dec 1, 2022)

    v0.8 (2022-11-30)

    Breaking changes

    • Paste draw struct now stores images and masks by reference instead of by value. This is to prevent unnecessary cloning of large images and/or masks.
      • Paste now has two generic lifetime arguments: Paste<'img, 'mask, _>.
      • This also means that Image::paste, Image::paste_with_mask, and Image::with methods now take images and masks by reference instead of by value too.

    Other changes

    • Add support for drawing lines and polygons using Line and Polygon draw entities
      • Drawing a line or polygon with rounded vertices and a non-centered border position results in undesired output as of now.
    • Add new static feature. When enabled, this will statically link any native dependencies
    • Add non-zero width/height assertions to image constructors

    Bug fixes

    • Fix GIF decoding bug for images with a global palette
    • Fix conversion using Pixel::from_arbitrary_palette with dynamic pixels

    Full Changelog: https://github.com/jay3332/ril/compare/v0.7...v0.8

    Source code(tar.gz)
    Source code(zip)
  • v0.7(Nov 23, 2022)

    v0.7 (2022-11-22)

    Breaking changes

    • ImageSequence::first_frame now returns Option<&Frame> instead of &Frame.
      • Also introduces new first_frame_mut and first_frame[_mut]_unchecked methods.

    Other changes

    • Add crate-level support for image quantization
      • The new quantize feature enables the color_quant dependency for more complex quantization algorithms
        This is enabled by default, mainly because color_quant appears to not pull any additional dependencies
      • Quantizer struct can handle direct quantization of raw pixel data
      • Image::quantize is a higher-level method that can quantize an image into a paletted image
      • Implement From<Image<Rgb[a]>> for Image<PalettedRgb[a]> which utlizes quantization
    • Fix decoding bug for JPEG images with L pixels
    Source code(tar.gz)
    Source code(zip)
Cogo is a high-performance library for programming stackful coroutines with which you can easily develop and maintain massive concurrent programs.

Cogo is a high-performance library for programming stackful coroutines with which you can easily develop and maintain massive concurrent programs.

co-rs 47 Nov 17, 2022
Simple bit-level protocol definitions in Rust.

bin-proto Simple & fast structured bit-level binary co/dec in Rust. An improved and modernized fork of protocol. A more efficient but (slightly) less

null 16 Jun 13, 2024
Low level access to T-Head Xuantie RISC-V processors

XuanTie Low level access to T-Head XuanTie RISC-V processors Contributing We welcome contribution! Please send an issue or pull request if you are rea

Luo Jia 30 Aug 24, 2022
Higher-level toolkit for MSDF text rendering

MSDF Toolkit Higher-level toolkit for MSDF text rendering About MSDF - an abbreviation of Multi-channel Signed Distance Field. In short, an efficient

null 2 Aug 19, 2022
Low-level CPU and register abstractions for the N64 console

Description This crate is a low-level abstraction (aka a Peripheral Access Crate) over the CPU and memory-mapped registers available on the Nintendo 6

Luke Stadem 5 Dec 17, 2022
syntax-level async join enabling branching control flow and shared mutable borrow

enjoin enjoin's async join macros operate at the syntax level. It allows you to... break, continue, and return out of async code running in a join for

Wisha W. 15 Apr 16, 2023
A tool that helps you to turn in one command a Rust crate into a Haskell Cabal library!

cabal-pack A tool that helps you to turn in one command a Rust crate into a Haskell Cabal library! To generate bindings, you need to annotate the Rust

Yvan Sraka 18 Dec 31, 2022
c-library wrapper around the rust pdb crate

pdbcrust: pdbcrust is a c-library wrapper around the rust pdb crate. The API only exports a minimum subset of the pdb crate functionality. The project

Ulf Frisk 7 Feb 23, 2023
Omeglib, a portmanteau of "omegle" and "library", is a crate for interacting with omegle, simply and asynchronously

Omeglib, a portmanteau of "omegle" and "library", is a crate for interacting with omegle, simply and asynchronously. It is intended to suit one's every requirement regarding chat on omegle.

null 1 May 25, 2022
High Assurance Rust - A free book about developing secure and robust systems software.

High Assurance Rust - A free book about developing secure and robust systems software.

Tiemoko Ballo 1.1k Jan 9, 2023
High-order Virtual Machine (HVM) is a pure functional compile target that is lazy, non-garbage-collected and massively parallel

High-order Virtual Machine (HVM) High-order Virtual Machine (HVM) is a pure functional compile target that is lazy, non-garbage-collected and massivel

null 5.5k Jan 2, 2023
Nyah is a programming language runtime built for high performance and comes with a scripting language.

?? Nyah ( Unfinished ) Nyah is a programming language runtime built for high performance and comes with a scripting language. ??️ Status Nyah is not c

Stacker 3 Mar 6, 2022
🐱 A high-speed JIT programming language and its runtime, meow~

?? A high-speed JIT programming language and its runtime, meow~

EnabledFish 30 Dec 22, 2022
High concurrency, RealTime, In-memory storage inspired by erlang mnesia

DarkBird is a Document oriented, high concurrency in-memory Storage, also persist data to disk to avoid loss any data The darkbird provides the follow

DanyalMh 25 Dec 15, 2022
A fast, iterative, correct approach to Stackblur, resulting in a very smooth and high-quality output, with no edge bleeding

A fast, iterative, correct approach to Stackblur, resulting in a very smooth and high-quality output, with no edge bleeding. This crate implements a t

LoganDark 8 Nov 22, 2022
A type-safe, high speed programming language for scalable systems

A type-safe, high speed programming language for scalable systems! (featuring a cheesy logo!) note: the compiler is unfinished and probably buggy. if

Hail 0 Sep 14, 2022
Support SIMD low-memory overhead and high-performance adaptive radix tree.

Artful Artful is an adaptive radix tree library for Rust. At a high-level, it's like a BTreeMap. It is based on the implementation of paper, see The A

future 3 Sep 7, 2022
An AI-native lightweight, reliable, and high performance open-source vector database.

What is OasysDB? OasysDB is a vector database that can be used to store and query high-dimensional vectors. Our goal is to make OasysDB fast and easy

Oasys 3 Dec 25, 2023
An experimental Rust crate for sigstore

Continuous integration Docs License This is an experimental crate to interact with sigstore. This is under high development, many features and checks

Flavio Castelli 0 Jan 10, 2022