plutofilter-rs

Crates.ioplutofilter-rs
lib.rsplutofilter-rs
version0.4.1
created_at2025-11-27 16:56:48.848214+00
updated_at2025-12-23 08:49:36.586739+00
descriptionPort of https://github.com/sammycage/plutofilter to rust
homepagehttps://docs.rs/plutofilter-rs/0.2.0/plutofilter_rs/
repositoryhttps://github.com/mimanshu-maheshwari/plutofilter-rs.git
max_upload_size
id1954102
size179,285
Mimanshu Maheshwari (mimanshu-maheshwari)

documentation

README

PlutoFilter port to rust

PlutoFilter is a zero-allocation image filter library. A Rust port of the original C library that applies fast, chainable image effects without any dynamic memory allocation. Compatible with SVG and CSS filter semantics, it makes it easy to reproduce visual effects consistently across platforms.

Notice

  • This is a personal project to learn rust and in no way production ready yet.

Whats next

✓ Have a better interface to interact with API. (Design pattern like Builder | Partially Done)

Seeing more usage in older versions;exposed the Surface API's again as in 1st version. Using the ColorChannel you can specify how to deserialize the pixel, either as ARGB32 or as RGBA32

use image::ImageResult;
#[cfg(feature = "image")]
fn main() -> ImageResult<()> {
  use plutofilter_rs::{ImageEditor, get_resource_path};

  let base_file = get_resource_path(&["original_images"], "test-image.jpg");
  let editor = ImageEditor::open(&base_file);
  let output_path = get_resource_path(&["test_output_images", "example"], "test-image.jpg");
  editor
      .color_transform_contrast_inplace(0.97)
      .color_transform_hue_rotate_inplace(330.0)
      .color_transform_saturate_inplace(1.11)
      .save_to(&output_path)
}

#[cfg(not(feature = "image"))]
fn main() -> Result<(), SurfaceError> {
  use plutofilter_rs::ColorChannel;
  use plutofilter_rs::Surface;
  use plutofilter_rs::SurfaceError;

  let (width, height) = (100, 100);
  let mut input_image_vec = vec![0xFFAA22; (width * height) as usize];
  let mut output_image_vec = vec![0xAAFF22; (width * height) as usize];
  let mut input_surface = Surface::make(&mut input_image_vec, width, height, width, Some(ColorChannel::ARGB32))?;
  // ColorChannel::ARGB32 is default so no need to mention it.
  let mut output_surface = Surface::make(&mut output_image_vec, width, height, width, None)?;
  Surface::color_transform_opacity(&mut input_surface, &mut output_surface, 0.5);
  Ok(())
}

✓ Introduce examples

  • Implement bench marks
  • Implement SIMD for same operations. (I'm learning to do that)
  • Have a small TUI, using which you can just add the image; using a few sliders or something more suitable implement changes.

Example

use image::ImageResult;
use plutofilter_rs::{ImageEditor, get_resource_path};

fn main() -> ImageResult<()> {
    let base_file = get_resource_path(&["original_images"], "test-image.jpg");
    let editor = ImageEditor::open(&base_file);
    let output_path = get_resource_path(&["test_output_images", "example"], "test-image.jpg");
    editor
        .color_transform_contrast_inplace(0.97)
        .color_transform_hue_rotate_inplace(330.0)
        .color_transform_saturate_inplace(1.11)
        .save_to(&output_path)
}
input.jpg output.jpg
Example input image before applying filters Example output image with contrast, hue rotate, and saturate applied

Features

Roadmap

Gaussian Blur

Applies a Gaussian blur to the input surface using separable convolution. The amount of blur is controlled by the standard deviation along the horizontal and vertical axes. A value of 0 applies no blur.

0x0 5x5 10x10
Gaussian blur with 0x0 standard deviation (no blur) Gaussian blur with 5x5 standard deviation Gaussian blur with 10x10 standard deviation

Color Transform

Applies a 5×4 color transformation matrix to each pixel in the input surface. The matrix operates on color and alpha channels, allowing both isolated and cross-channel transformations. The input and output surfaces may be the same for in-place filtering.

Example

const ORIGINAL: [f32;20] = [
    1.0, 0.0, 0.0, 0.0, 0.0,
    0.0, 1.0, 0.0, 0.0, 0.0,
    0.0, 0.0, 1.0, 0.0, 0.0,
    0.0, 0.0, 0.0, 1.0, 0.0
];

const GRAYSCALE: [f32;20] = [
    0.2126, 0.7152, 0.0722, 0.0, 0.0,
    0.2126, 0.7152, 0.0722, 0.0, 0.0,
    0.2126, 0.7152, 0.0722, 0.0, 0.0,
    0.0,    0.0,    0.0,    1.0, 0.0
];

const SEPIA: [f32;20] = [
    0.393, 0.769, 0.189, 0.0, 0.0,
    0.349, 0.686, 0.168, 0.0, 0.0,
    0.272, 0.534, 0.131, 0.0, 0.0,
    0.0,   0.0,   0.0,   1.0, 0.0
];

const CONTRAST: [f32;20] = [
    1.75, 0.0,  0.0,  0.0, -0.375,
    0.0,  1.75, 0.0,  0.0, -0.375,
    0.0,  0.0,  1.75, 0.0, -0.375,
    0.0,  0.0,  0.0,  1.0,   0.0
];
use plutofilter_rs::{ImageEditor, get_resource_path};
use image::ImageResult
fn main() -> ImageResult<()> {
    const ORIGINAL: [f32; 20] = [
        1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0,
        0.0, 1.0, 0.0,
    ];
    let base_file = get_resource_path(&["original_images"], "zhang-hanyun.jpg");
    let mut editor = ImageEditor::open(base_file);
    editor = editor.color_transform(ORIGINAL);
    let output_path =
        get_resource_path(&["test_output_images", "color_transform"], "original.png");
    editor.save_to(output_path)
}
original grayscale sepia contrast
Original image without color transform Image with grayscale color transform applied Image with sepia color transform applied Image with contrast color transform applied

Grayscale

Applies a grayscale effect to the input surface, controlled by a blending amount between the original color and fully desaturated grayscale. A value of 0 preserves the original image, while 1 results in complete grayscale.

use plutofilter_rs::{ImageEditor, get_resource_path};
use image::ImageResult
fn main() -> ImageResult<()> {
    let base_file = "input.png";
    let mut editor = ImageEditor::open(base_file);
    editor = editor.color_transform_grayscale(0.25);
    let output_path = "output.png";
    editor.save_to(output_path)
}
0 0.25 0.5 0.75 1
Grayscale amount 0 - original colors Grayscale amount 0.25 - 25% grayscale blend Grayscale amount 0.5 - 50% grayscale blend Grayscale amount 0.75 - 75% grayscale blend Grayscale amount 1 - full grayscale

Sepia

Applies a sepia tone to the input surface, blending between the original image and a warm, brownish tone. The amount controls the intensity, where 0 leaves the image unchanged and 1 applies full sepia coloration.

use plutofilter_rs::{ImageEditor, get_resource_path};
use image::ImageResult
fn main() -> ImageResult<()> {
    let base_file = "input.png";
    let mut editor = ImageEditor::open(base_file);
    editor = editor.color_transform_sepia(0.25);
    let output_path = "output.png";
    editor.save_to(output_path)
}
0 0.25 0.5 0.75 1
Sepia amount 0 - original colors Sepia amount 0.25 - 25% sepia blend Sepia amount 0.5 - 50% sepia blend Sepia amount 0.75 - 75% sepia blend Sepia amount 1 - full sepia tone

Saturate

Adjusts the color saturation of the input surface. The amount controls how vivid or muted the colors become: 1 leaves the image unchanged, values less than 1 reduce saturation toward grayscale, and values greater than 1 enhance the intensity of colors.

use plutofilter_rs::{ImageEditor, get_resource_path};
use image::ImageResult
fn main() -> ImageResult<()> {
    let base_file = "input.png";
    let mut editor = ImageEditor::open(base_file);
    editor = editor.color_transform_saturate(0.25);
    let output_path = "output.png";
    editor.save_to(output_path)
}
0 0.5 1 4
Saturate amount 0 - desaturated to grayscale Saturate amount 0.5 - 50% desaturation Saturate amount 1 - original saturation Saturate amount 4 - 4x enhanced saturation

Contrast

Adjusts the contrast of the input surface. An amount of 1 leaves the image unchanged, values below 1 reduce contrast, and values above 1 increase it. The image is scaled around the midpoint of the color range.

use plutofilter_rs::{ImageEditor, get_resource_path};
use image::ImageResult
fn main() -> ImageResult<()> {
    let base_file = "input.png";
    let mut editor = ImageEditor::open(base_file);
    editor = editor.color_transform_contrast(0.25);
    let output_path = "output.png";
    editor.save_to(output_path)
}
0 1 1.75
Contrast amount 0 - no contrast (flat image) Contrast amount 1 - original contrast Contrast amount 1.75 - 1.75x increased contrast

Brightness

Adjusts the brightness of the input surface. An amount of 1 preserves the original brightness, values below 1 darken the image, and values above 1 brighten it uniformly across all color channels.

use plutofilter_rs::{ImageEditor, get_resource_path};
use image::ImageResult
fn main() -> ImageResult<()> {
    let base_file = "input.png";
    let mut editor = ImageEditor::open(base_file);
    editor = editor.color_transform_brightness(0.25);
    let output_path = "output.png";
    editor.save_to(output_path)
}
0 0.5 1 1.75
Brightness amount 0 - completely dark Brightness amount 0.5 - 50% darkness Brightness amount 1 - original brightness Brightness amount 1.75 - 1.75x increased brightness

Opacity

Adjusts the opacity (alpha) of the input surface. An amount of 1 leaves opacity unchanged, while values between 0 and 1 scale the alpha channel linearly. A value of 0 makes the image fully transparent.

use plutofilter_rs::{ImageEditor, get_resource_path};
use image::ImageResult
fn main() -> ImageResult<()> {
    let base_file = "input.png";
    let mut editor = ImageEditor::open(base_file);
    editor = editor.color_transform_opacity(0.25);
    let output_path = "output.png";
    editor.save_to(output_path)
}
0 0.25 0.5 0.75 1
Opacity amount 0 - fully transparent Opacity amount 0.25 - 25% opacity Opacity amount 0.5 - 50% opacity Opacity amount 0.75 - 75% opacity Opacity amount 1 - fully opaque

Invert

Applies a color inversion effect to the input surface. The amount controls the strength of the inversion: 0 leaves the image unchanged, 1 fully inverts the RGB channels, and intermediate values blend between the original and inverted colors.

use plutofilter_rs::{ImageEditor, get_resource_path};
use image::ImageResult
fn main() -> ImageResult<()> {
    let base_file = "input.png";
    let mut editor = ImageEditor::open(base_file);
    editor = editor.color_transform_invert(0.25);
    let output_path = "output.png";
    editor.save_to(output_path)
}
0 0.25 0.5 0.75 1
Invert amount 0 - original colors Invert amount 0.25 - 25% inversion Invert amount 0.5 - 50% inversion Invert amount 0.75 - 75% inversion Invert amount 1 - fully inverted colors

Hue Rotate

Rotates the hue of each pixel in the input surface by the given angle in degrees. The rotation is applied in the RGB color space, preserving luminance and alpha. A value of 0 leaves colors unchanged, while 360 completes a full rotation back to the original.

use plutofilter_rs::{ImageEditor, get_resource_path};
use image::ImageResult
fn main() -> ImageResult<()> {
    let base_file = "input.png";
    let mut editor = ImageEditor::open(base_file);
    editor = editor.color_transform_hue_rotate(180.0);
    let output_path = "output.png";
    editor.save_to(output_path)
}
30° 90° 180° 270° 360°
Hue rotate 0 degrees - original hues Hue rotate 30 degrees Hue rotate 90 degrees Hue rotate 180 degrees - opposite hues Hue rotate 270 degrees Hue rotate 360 degrees - back to original

Blend

Blends two surfaces using the specified blend mode. The source surface is blended over the backdrop, and the result is written to the output.

use plutofilter_rs::{ImageEditor, get_resource_path, BlendMode};
use image::ImageResult
fn main() -> ImageResult<()> {
    let base_file = "input.png";
    let blend_image = "blend.png";
    let mut editor = ImageEditor::open(base_file);
    editor = editor.blend(blend_image, BlendMode::Normal);
    let output_path = "output.png";
    editor.save_to(output_path)
}
Mode Description Preview
Normal Displays the source over the backdrop using standard alpha compositing. Normal blend mode result
Multiply Multiplies the colors of the source and backdrop, resulting in a darker image. Multiply blend mode result
Screen Brightens the result by inverting, multiplying, and inverting again. Screen blend mode result
Overlay Applies Multiply on dark areas and Screen on light areas to add contrast. Overlay blend mode result
Darken Keeps the darker color of each pixel from the source or backdrop. Darken blend mode result
Lighten Keeps the lighter color of each pixel from the source or backdrop. Lighten blend mode result
Color Dodge Brightens the backdrop based on the content of the source by dividing by the inverse. Color Dodge blend mode result
Color Burn Darkens the backdrop based on the source by dividing the inverse of the backdrop by the source. Color Burn blend mode result
Hard Light A strong effect that applies Overlay with the source as the source. Hard Light blend mode result
Soft Light Gently adjusts contrast based on the source, giving a softer result. Soft Light blend mode result
Difference Subtracts the darker color from the lighter one at each pixel. Difference blend mode result
Exclusion Similar to Difference, but with reduced contrast and softer transitions. Exclusion blend mode result

Composite

use plutofilter_rs::{CompositeOperator, ImageEditor, get_resource_path};
use image::ImageResult
fn main() -> ImageResult<()> {
    let base_file = "input.png";
    let composite_image = "composite.png";
    let mut editor = ImageEditor::open(base_file);
    editor = editor.composite(composite_image, CompositeOperator::In);
    let output_path = "output.png";
    editor.save_to(output_path)
}

Composites two surfaces using a Porter-Duff compositing operator. The source surface is composited over the backdrop using the specified operator. The result is written to the output.

Operator Description Preview
Over Draws the source over the backdrop, preserving transparency. This is the default mode. Over composite operator result
In Shows the part of the source that overlaps with the backdrop. Everything else is hidden. In composite operator result
Out Shows the part of the source that lies outside the backdrop. Removes overlapping areas. Out composite operator result
Atop Keeps the overlapping part of the source, but only where the backdrop is present. Atop composite operator result

Arithmetic

Blends two input surfaces using a flexible arithmetic combination of their color values. The output is based on the colors from both inputs, combined according to the four constants: k1, k2, k3, and k4.

use plutofilter_rs::{ImageEditor, get_resource_path};
use image::ImageResult
fn main() -> ImageResult<()> {
    let base_file = "input.png";
    let composite_image = "composite.png";
    let mut editor = ImageEditor::open(base_file);
    editor = editor.composite_arithmatic(composite_image, 0.0, 1.0, 0.0, 1.0);
    let output_path = "output.png";
    editor.save_to(output_path)
}
Test 1 Test 2 Test 3 Test 4
Arithmetic composite test 1 result Arithmetic composite test 2 result Arithmetic composite test 3 result Arithmetic composite test 4 result
Commit count: 0

cargo fmt