/lutgen-rs

Blazingly fast interpolated LUT generator and applicator for arbitrary and popular color palettes.

Primary LanguageRustMIT LicenseMIT

lutgen-rs

crate license ci publish

A blazingly fast interpolated LUT generator and applicator for arbitrary and popular color palettes. Theme any image to your dekstop colorscheme!


Example

Catppuccin Mocha Hald Clut
Example Image (Original)
Example Image (Corrected)

Usage

Note: The binary and library are still in a fairly experimental state, and breaking changes are made quite often. Any release that does make any changes as such, are bumped to 0.X.0

CLI

Source

git clone https://github.com/ozwaldorf/lutgen-rs
cd lutgen-rs
cargo install --path .

Releases

Packaging Status Installation Command
Crates.io cargo install lutgen
AUR yay -S lutgen-bin
Alpine apk add lutgen

Helptext

A blazingly fast interpolated LUT generator and applicator for arbitrary and popular color palettes.

Usage: lutgen [OPTIONS] [CUSTOM_COLORS]... [COMMAND]

Commands:
  apply  Correct an image using a hald clut, either generating it, or loading it externally
  help   Print this message or the help of the given subcommand(s)

Arguments:
  [CUSTOM_COLORS]...  Custom hexidecimal colors to add to the palette. If `-p` is not used to specify a base palette, at least 1 color is required

Options:
  -o, --output <OUTPUT>          Path to write output to
  -p, --palette <PALETTE>        Predefined popular color palettes. Use `lutgen -p` to view all options. Compatible with custom colors
  -l, --level <LEVEL>            Hald level (ex: 8 = 512x512 image) [default: 8]
  -a, --algorithm <ALGORITHM>    Algorithm to remap the LUT with [default: gaussian-rbf] [possible values: shepards-method, gaussian-rbf, linear-rbf, gaussian-sampling, nearest-neighbor]
  -n, --nearest <NEAREST>        Number of nearest palette colors to consider at any given time for RBF based algorithms. 0 uses unlimited (all) colors [default: 16]
  -s, --shape <SHAPE>            Gaussian RBF's shape parameter. Higher means less gradient between colors, lower mean more [default: 96]
      --power <POWER>            Shepard algorithm's power parameter [default: 4]
  -m, --mean <MEAN>              Gaussian sampling algorithm's mean parameter [default: 0]
      --std-dev <STD_DEV>        Gaussian sampling algorithm's standard deviation parameter [default: 20]
  -i, --iterations <ITERATIONS>  Gaussian sampling algorithm's target number of samples to take for each color [default: 512]
  -h, --help                     Print help (see more with '--help')
  -V, --version                  Print version

Examples

Generating a LUT

lutgen -p catppuccin-mocha -o mocha_lut.png

Correcting an image with a LUT generated on the fly

lutgen -p catppuccin-mocha apply assets/simon-berger-unsplash.jpg -o mocha_version.png

Correcting an image with a pre-generated LUT

lutgen apply --hald-clut mocha_lut.png input.jpg

Correcting Videos (using ffmpeg):

ffmpeg -i input.mkv -i hald_clut.png -filter_complex '[0][1] haldclut' output.mp4

Library

By default, the bin feature and dependencies are enabled. When used as a library, it's recommended to use default-features = false to minimalize the dependency tree and build time.

Generating a LUT (simple):

use exoquant::SimpleColorSpace;
use lutgen::{
    GenerateLut,
    interpolation::{
        GaussianRemapper, GaussianSamplingRemapper
    },
};
use lutgen_palettes::Palette;

// Get a premade palette
let palette = Palette::CatppuccinMocha.get();

// Setup the fast Gaussian RBF algorithm
let (shape, nearest) = (96.0, 0);
let remapper = GaussianRemapper::new(&palette, shape, nearest);

// Generate and remap a HALD:8 for the provided palette
let hald_clut = remapper.generate_lut(8);

// hald_clut.save("output.png").unwrap();

// Setup another palette to interpolate from, with custom colors
let palette = vec![
    [255, 0, 0],
    [0, 255, 0],
    [0, 0, 255],
];

// Setup the slower Gaussian Sampling algorithm
let (mean, std_dev, iters, seed) = (0.0, 20.0, 512, 420);
let remapper = GaussianSamplingRemapper::new(
    &palette, 
    mean, 
    std_dev, 
    iters, 
    seed, 
    SimpleColorSpace::default()
);

// Generate and remap a HALD:4 for the provided palette
let hald_clut = remapper.generate_lut(4);

// hald_clut.save("output.png").unwrap();

Applying a LUT:

use image::open;

use lutgen::{
    identity::correct_image,
    interpolation::GaussianRemapper,
    GenerateLut,
};
use lutgen_palettes::Palette;

// Generate a hald clut
let palette = Palette::CatppuccinMocha.get();
let remapper = GaussianRemapper::new(&palette, 96.0, 0);
let hald_clut = remapper.generate_lut(8);

// Save the LUT for later
hald_clut.save("docs/catppuccin-mocha-hald-clut.png").unwrap();

// Open an image to correct
let mut external_image = open("docs/example-image.jpg").unwrap().to_rgb8();

// Correct the image using the hald clut we generated
correct_image(&mut external_image, &hald_clut);

// Save the edited image
external_image.save("docs/catppuccin-mocha.jpg").unwrap()

Remapping an image directly (advanced):

Note: While the remappers can be used directly on an image, it's much faster to remap a LUT and correct an image with that.

use lutgen::{
    GenerateLut,
    interpolation::{GaussianRemapper, InterpolatedRemapper},
};

// Setup the palette to interpolate from
let palette = vec![
    [255, 0, 0],
    [0, 255, 0],
    [0, 0, 255],
];

// Setup a remapper
let (shape, nearest) = (96.0, 0);
let remapper = GaussianRemapper::new(&palette, shape, nearest);

// Generate an image (generally an identity lut to use on other images)
let mut hald_clut = lutgen::identity::generate(8);

// Remap the image
remapper.remap_image(&mut hald_clut);

// hald_clut.save("output.png").unwrap();

Tasks

  • Basic hald-clut identity generation
  • Gaussian Sampling interpolation for generating LUTs (thanks Gengeh for the original imagemagick strategy!)
  • Support a bunch of popular base color palettes (thanks Wezterm!)
  • Basic applying a lut to an image
  • Radial basis function interpolation for generating LUTs
  • Interpolation for more accuracy when correcting with low level luts (<16)
  • Replace exoquant and kiddo with a unified implementation of a k-d tree

Sources

Special Thanks

  • Stonks3141 for maintaining the Alpine Linux package