Magnum::Trade::BasisImageConverter class new in 2019.10

Basis Universal image converter plugin.

Creates Basis Universal compressed image files (*.basis or *.ktx2) from 2D and 2D array images with optional mip levels. You can use BasisImporter to import images in this format.

This plugin provides BasisKtxImageConverter.

Usage

This plugin depends on the Trade and Basis Universal libraries and is built if MAGNUM_WITH_BASISIMAGECONVERTER is enabled when building Magnum Plugins. To use as a dynamic plugin, load "BasisImageConverter" via Corrade::PluginManager::Manager. Current version of the plugin is tested against the v1_15_update2 tag, but could possibly compile against newer versions as well.

Additionally, if you're using Magnum as a CMake subproject, bundle the magnum-plugins, zstd and basis-universal repositories and do the following. Basis uses Zstd for KTX2 images, instead of bundling you can depend on an externally-installed zstd package, in which case omit the first part of the setup below. If Zstd isn't bundled and isn't found externally, Basis will be compiled without Zstd support.

set(ZSTD_BUILD_PROGRAMS OFF CACHE BOOL "" FORCE)
# Create a static library so the plugin is self-contained
set(ZSTD_BUILD_SHARED OFF CACHE BOOL "" FORCE)
set(ZSTD_BUILD_STATIC ON CACHE BOOL "" FORCE)
# Basis doesn't use any multithreading in zstd, this prevents a need to link to
# pthread on Linux
set(ZSTD_MULTITHREAD_SUPPORT OFF CACHE BOOL "" FORCE)
# Don't build Zstd tests if enable_testing() was called in parent project
set(ZSTD_BUILD_TESTS OFF CACHE BOOL "" FORCE)
add_subdirectory(zstd/build/cmake EXCLUDE_FROM_ALL)

set(BASIS_UNIVERSAL_DIR ${CMAKE_CURRENT_SOURCE_DIR}/basis-universal)
set(MAGNUM_WITH_BASISIMAGECONVERTER ON CACHE BOOL "" FORCE)
add_subdirectory(magnum-plugins EXCLUDE_FROM_ALL)

# So the dynamically loaded plugin gets built implicitly
add_dependencies(your-app MagnumPlugins::BasisImageConverter)

To use as a static plugin or as a dependency of another plugin with CMake, put FindMagnumPlugins.cmake and FindBasisUniversal.cmake into your modules/ directory, request the BasisImageConverter component of the MagnumPlugins package and link to the MagnumPlugins::BasisImageConverter target:

find_package(MagnumPlugins REQUIRED BasisImageConverter)

# ...
target_link_libraries(your-app PRIVATE MagnumPlugins::BasisImageConverter)

See Downloading and building plugins, Plugin usage with CMake, Loading and using plugins and File format support for more information.

Behavior and limitations

Accepts 2D, 2D array, cube map and cube map array images, recognizing ImageFlag3D::Array and/or ImageFlag3D::CubeMap in passed images. The PixelFormat::R8Unorm, R8Srgb, RG8Unorm, RG8Srgb, RGB8Unorm, RGB8Srgb, RGBA8Unorm and RGBA8Srgb formats are supported.

Even though the KTX container format supports 1D, 1D array and 3D images, Basis Universal doesn't. In particular, if a 2D image with ImageFlag2D::Array is passed, the conversion will fail as it's not possible to represent 1D array images without a significant loss in quality and layer cross-talk. On the other hand, if a 3D image without ImageFlag3D::Array is passed, a warning is printed and the file is saved as a 2D array image.

The plugin recognizes ImageConverterFlag::Quiet, which will cause all conversion warnings to be suppressed.

Multilevel images

Images can be saved with multiple levels by using the list variants of convertToFile() / convertToData(). Largest level is expected to be first, with each following level having width and height divided by two, rounded down. Because only 2D array images are supported, depth has to have the same size in all levels. Incomplete mip chains are supported.

To generate mip levels from a single top-level image instead, you can use the mip_gen configuration option.

Implicit swizzling

If no user-specified channel mapping is supplied through the swizzle configuration option, the converter swizzles 1- and 2-channel formats before compression as follows:

  • 1-channel formats (PixelFormat::R8Unorm / PixelFormat::R8Srgb) are remapped as RRR, producing an opaque gray-scale image
  • 2-channel formats (PixelFormat::RG8Unorm / PixelFormat::RG8Srgb) are remapped as RRRG, ie. G becomes the alpha channel. This significantly improves compressed image quality because RGB and alpha get separate slices instead of the two channels being compressed into a single slice.

Setting the swizzle option to any value disables this behavior. To keep the original channel order, set swizzle=rgba.

Converting to KTX2

To create Khronos Texture 2.0 (*.ktx2) files, either load the plugin as BasisKtxImageConverter, call convertToFile() with the .ktx2 extension or pass Format::Ktx to the constructor. In all other cases, a Basis Universal (*.basis) file is created.

The extension() is "ktx2" and mimeType() "image/ktx2" if the plugin is loaded as BasisKtxImageConverter or Format::Ktx is passed to the constructor. In all other cases (and independently of what file extension is used in a call to convertToFile()), extension is "basis" and mimeType() is an empty string as the Basis Universal file format doesn't have a registered MIME type.

Loading the plugin fails with undefined symbol: pthread_create

On Linux it may happen that loading the plugin will fail with undefined symbol: pthread_create. The Basis encoder is optionally multithreaded and while linking the dynamic plugin library to pthread would resolve this particular error, the actual thread creation (if the threads configuration option is set to something else than 1) later would cause std::system_error to be thrown (or, worst case, crashing on a null function pointer call on some systems). Unfortunately there's no portable way to detect this case at runtime and fail gracefully, so the plugin requires the application to link to pthread instead. With CMake it can be done like this:

find_package(Threads REQUIRED)
target_link_libraries(your-application PRIVATE Threads::Threads)

Thread safety

While the encoder library should behave in a way that doesn't modify any global state, in versions before 1.16 the library initialization done at plugin load time (or using initialize() when using the class witout a plugin manager) is populating global safe in a non-thread-safe way. Thus you have to ensure that the plugin isn't loaded from multiple threads at the same time, or loaded while being already used from another thread.

Plugin-specific configuration

Basis compression can be configured to produce better quality or reduce encoding time. Configuration options are equivalent to parameters of the C++ encoder API in basis_compressor. The basisu tool options mostly match the encoder API parameters and its help text provides useful descriptions of most of the parameters, their ranges and the impact on quality/speed. The full form of the configuration is shown below:

[configuration]
# All following options correspond to parameters of the `basis_compressor`
# C++ API and may differ from what the basisu tool exposes.

# Options
quality_level=128
# Treat images as sRGB color data, rather than linear intensity. Leave blank
# to determine from the image format.
perceptual=
debug=false
validate=false
debug_images=false
compute_stats=false
compression_level=2

# More options
max_endpoint_clusters=512
max_selector_clusters=512
y_flip=true
# The `normal_map` setting is just an alias disabling `perceptual` and
# `mip_srgb` and enabling `no_selector_rdo` & `no_endpoint_rdo`
check_for_alpha=true
force_alpha=false
# Remap color channels before compression. Must be empty or 4 characters
# long, valid characters are r,g,b,a. This replaced
# separate_rg_to_color_alpha, for the same effect use 'rrrg'.
swizzle=
renormalize=false
resample_width=
resample_height=
resample_factor=
# Number of threads Basis should use during compression, 0 sets it to the
# value returned by std::thread::hardware_concurrency(), 1 disables
# multithreading. This value is clamped to
# std::thread::hardware_concurrency() internally by Basis itself.
threads=1
disable_hierarchical_endpoint_codebooks=false

# Mipmap generation options
# Generate mipmaps from the base image. If you pass custom mip levels into
# openData, this option will be ignored. Leave blank to determine from the
# number of levels passed to convertToData.
mip_gen=false
# Filter mipmaps assuming sRGB color data, rather than linear intensity.
# Leave blank to determine from the image format.
mip_srgb=
mip_scale=1.0
mip_filter=kaiser
mip_renormalize=false
mip_wrapping=true
mip_fast=true
mip_smallest_dimension=1

# Backend endpoint/selector RDO codec options
no_selector_rdo=false
selector_rdo_threshold=1.25
no_endpoint_rdo=false
endpoint_rdo_threshold=1.5

# UASTC options
uastc=false
pack_uastc_level=2
pack_uastc_flags=
rdo_uastc=false
rdo_uastc_quality_scalar=1.0
rdo_uastc_dict_size=4096
rdo_uastc_max_smooth_block_error_scale=10.0
rdo_uastc_smooth_block_max_std_dev=18.0
rdo_uastc_max_allowed_rms_increase_ratio=10.0
rdo_uastc_skip_block_rms_threshold=8.0
rdo_uastc_favor_simpler_modes_in_rdo_mode=true

# KTX2 options
ktx2_uastc_supercompression=true
ktx2_zstd_supercompression_level=6

# Set various fields in the Basis file header
userdata0=0
userdata1=0

See Editing plugin-specific configuration for more information and an example showing how to edit the configuration values.

Base classes

class AbstractImageConverter
Base for image converter plugins.

Public types

enum class Format: Int { Basis = 1, Ktx }
Output file format.

Public static functions

static void initialize() new in Git master
Initialize Basis encoder.

Constructors, destructors, conversion operators

BasisImageConverter(Format format = Format{}) explicit
Default constructor.
BasisImageConverter(PluginManager::AbstractManager& manager, const Containers::StringView& plugin) explicit
Plugin manager constructor.

Enum documentation

enum class Magnum::Trade::BasisImageConverter::Format: Int

Output file format.

Enumerators
Basis

Output Basis images

Ktx

Output KTX2 images

Function documentation

static void Magnum::Trade::BasisImageConverter::initialize() new in Git master

Initialize Basis encoder.

If the class is instantiated directly (not through a plugin manager), this function has to be called explicitly before using any instance.

Magnum::Trade::BasisImageConverter::BasisImageConverter(Format format = Format{}) explicit

Default constructor.

The converter outputs files in format defined by Format.