SkillAgentSearch skills...

Swizzler

🦀 CLI packing multiple texture channels into a single output texture

Install / Use

/learn @DavidPeicho/Swizzler
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

<h1 align="center">Swizzler!</h1> <div align="center"> Fast CLI to pack multiple images into a single output </div>

Swizzler Demo

<p style="text-align: center">Thanks to the <a href="https://freepbr.com/about-free-pbr/">Free PBR</a> website for the textures used in this demo</p>

In simple terms, Swizzler! can pack your textures channels together into a single output image.

Take as an example an asset containing an albedo map and an ambient occlusion map. The albedo is encoded on the RGB channels, and the ambient is encoded only on the R channel. It's thus possible to combine them into a single RGBA texture containing the albedo (in the RGB channels) and the ambient occlusion (in the A channel).

Swizzler! provides two modes:

  • Manual ⟶ Generates a single output from multiple sources
  • Session ⟶ Traverses a directory and automatically generates textures based on a predefined configuration

Installation

1. Download CLI

If you just need the CLI, you can download it directly in the Release Tab.

2. Install CLI from sources

Alternatively, you can download, build, and install the CLI locally using:

$ cargo install --git https://github.com/albedo-engine/swizzler.git

Check that the installation was successful:

$ swizzler --version
swizzler-cli 0.1.0

3. Install library as a dependency

Swizzler! can also be used programmatically. Simply add a dependency to Swizzler! in your Cargo.toml:

[dependencies]
swizzler = { git = "https://github.com/albedo-engine/swizzler.git" }

CLI Usage

Manual

You can manually generate a new texture by providing the channel to extract for each source:

$ swizzler manual -i ./source_1.png:0 -i ./source_2.png:0 ...

Each -i argument takes a source image followed by the delimiting character : and the channel to read.

The position of each -i argument is used to select the destination channel.

For instance, if you have an RGB source image (source.png), and you want to shuffle the channels as BGR, you simply need to run:

$ swizzler manual -i ./source.png:2 -i ./source.png:1 -i ./source.png:0

The number of arguments defines the number of channels of the output image. For instance, generating a grayscale image is done by using:

$ swizzler manual -i ./source.png:0

You can leave some channels empty by specifying the none keyword for an input:

$ swizzler manual -i red.png:0 -i none -i none -i alpha.png:3

Session

You may want to process a folder containing several textures. The Manual Command is handy but can be difficult to use when you need to find what files should be grouped together.

Let's see how you could process an entire folder of images. In this example, we are going to generate a texture mixing the metalness in the red channel, and the roughness in the alpha channel.

Let's assume we have some textures in a folder ./textures:

$ ls ./textures
enemy_albedo.png    enemy_metalness.png enemy_roughness.png
hero_albedo.png     hero_metalness.png  hero_roughness.png

We can define a configuration file to process those textures:

$ cat ./config.json
{
  "base": "(.*)_.*",
  "matchers": [
      { "id": "metalness", "matcher": "(?i)metal(ness)?" },
      { "id": "roughness", "matcher": "(?i)rough(ness)?" },
      { "id": "albedo", "matcher": "(?i)albedo" }
  ],
  "targets": [
    {
      "name": "-metalness-roughness.png",
      "output_format": "png",
      "inputs": [
          [ "metalness", 0 ],
          null,
          null,
          [ "roughness", 0 ]
      ]
    }
  ]
}
  • base attribute is used to extract the name of the asset (here "hero" or "enemy")
  • matchers attribute is used to identify the type of textures. Each entry will look for a particular match
  • targets attributes is used to generate new textures, using the files resolved by the matchers.

To learn more about each attribute, please take a look at the Configuration File section.

We can now run the CLI on our textures folder:

$ swizzler session --folder ./textures --config ./config.json

Alternatively, you can provide the config.json file on STDIN:

$ cat ./config.json | swizzler session --folder ./textures

The results will be generated in the folder __swizzler_build:

$ ls ./__swizzler_build
enemy-metalness-roughness.png hero-metalness-roughness.png

As you can see, the CLI extracted two kind of assets (hero and enemy), and generated two textures. Each generated texture contains the metalness and the roughness swizzled together.

Configuration File

{

  "base": String,

  "matchers": [

      { "id": String, "matcher": String },
      ...

  ],

  "targets": [

      {
          "name": String,

          "output_format": String,

          "inputs": [

              [ "metalness", 0 ],
              ...

          ]
      }

  ]
}

base attribute

The base attribute describes how to extract the name of the asset from a path. This has to be a Regular Expression with one capturing group.

Example:

"base": "(.*)_.*"

Captures everything before the last _ occurence.

matchers attribute

The matchers attribute provide a list of files to match under the same asset.

  • id is used to identify mathched files
  • matcher provides a regular expression checking input files for a match.

Example:

"matchers": [
    { "id": "metalness", "matcher": "(?i)metal(ness)?" },
    { "id": "roughness", "matcher": "(?i)rough(ness)?" }
]

In this example, file containing "metalness" will be assigned the id 'metalness', and files containing "roughness" will be assigned the id 'roughness'.

targets attributes

The targets attribute makes use of the matchers list to generate a new texture.

  • name gets appended to the base name of the asset
  • output_format chooses the encoding format of the generated texture. Take a look at the encoding formats for all available options.

Example:

"targets": [
    {
      "name": "-metalness-roughness.png",
      "output_format": "png",
      "inputs": [
          [ "metalness", 0 ],
          null,
          null,
          [ "roughness", 0 ]
      ]
    }
]

Here, this target configuration will create a texture with the name '{base}-metalness-roughness.png', for each asset containing a match for a metalness and roughness source.

Arguments

Manual command

Usage:

$ swizzler manual [-i PATH] ... [-i PATH]

|Argument|Value|Description| |:--:|:--:|:--------------------| |-o, --output|Path|Relative path to which output the texture| |-i, --input|Path|Relative path to the texture source to use| |-f, --format|String|Format to use for saving. Default to the extension format if not provided|

Session command

Usage:

$ swizzler session --folder PATH [--config PATH_TO_CONFIG]

|Argument|Value|Description| |:--:|:--:|:--------------------| |-f, --folder|Path|Relative path to the folder to process| |-o, --output|[Path]|Relative path to the folder in which to output files| |-c, --config|[Path]|Relative path to the config to use| |-n, --num_threads|[Number]|Number of threads to use. Default to the number of logical core of the machine|

Encoding formats

  • png
  • jpg
  • tga
  • tif
  • pnm
  • ico
  • bmp

Those formats can be used directly on the CLI using the manual command, or via a configuration file (for session run).

Library usage

Swizzle

You can generate a new texture from those descriptors using:

  • to_luma() ⟶ swizzle inputs into a Grayscale image
  • to_luma_a() ⟶ swizzle inputs into a Grayscale-Alpha image
  • to_rgb() ⟶ swizzle inputs into a RGB image
  • to_rgba() ⟶ swizzle inputs into a RGBA image

Those functions use descriptors (ChannelDescriptor) to generate the final texture.

There are several ways to create descriptors:

use swizzler::{ChannelDescriptor};

// From a string.
let descriptor = ChannelDescriptor::from_description("./my_input.png:0").unwrap();

// From path + channel
let path = std::Path::PathBuf::from("./my_input.png");
let descriptor = ChannelDescriptor::from_path(path, 0).unwrap();

// From an image + channel
let descriptor = ChannelDescriptor::from_path(my_image, 0).unwrap();

Example generating a RGBA texture:

use swizzler::{to_rgba};

let r_channel = ChannelDescriptor::from_path(..., ...).unwrap();
let a_channel = ChannelDescriptor::from_path(..., ...).unwrap();

// Generates a RGBA image with two descriptors. The output image `green`
// and `blue` channels are left empty.
let result = to_rgba(Some(r_channel), None, None, Some(a_channel)).unwrap();

NOTE: you can use None to leave a channel empty.

The result image is an ImageBuffer from the image crate, that you can manipulate like any other image:

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

Running a session

You can run a session programmatically by creating an AssetReader (A.K.A a "resolver"), and a Session.

use regex::Regex;
use swizzler::session::{
    GenericAssetReader
    GenericTarget,
    RegexMatcher,
    Session,
};

// Creates a resolver and add matcher to it. Remember that matchers
// are used to group files together under a common asset.
let resolver = GenericAssetReader::new()
  .set_base(Regex::new("(.*)_.*").unwrap())
  .add_matcher(
    Box::new(RegexMatcher::new("metalness", Regex::new(r"(?i)metal(ness)?").unwrap()))
  )
  .add_matcher(
    Box::new(RegexMatcher::new("roughness", Regex::new(r"(?i)rough(ness)?")
View on GitHub
GitHub Stars22
CategoryDevelopment
Updated1y ago
Forks1

Languages

Rust

Security Score

80/100

Audited on Jan 19, 2025

No findings