Repository navigation
v0.6.0
Migrating to FormatDesc and ImageRef
Format, color space and alpha mode moved from each Surface to one FormatDesc on the image: Image::desc.
convert takes an ImageRef, the borrowed form of Image. SurfaceRef is the borrowed form of Surface. The surface helpers (tight_data, tile_to_blocks, ...) and validate moved to the borrowed types and take the format as an argument.
split_cubemap returns a cubemap Image. CubemapInput::SeparateFaces is removed: build the cubemap Image yourself.
Rust
let image = Image {
surfaces: vec![vec![Surface {
data, width, height, depth: 1, stride, slice_stride: 0,
- format: Format::R8G8B8A8_SRGB,
- color_space: ColorSpace::Srgb,
- alpha: AlphaMode::Straight,
}]],
kind: TextureKind::Texture2D,
+ desc: FormatDesc {
+ format: Format::R8G8B8A8_SRGB,
+ color_space: ColorSpace::Srgb,
+ alpha: AlphaMode::Straight,
+ },
};
-let output = convert(image, settings)?;
+let output = convert(image.to_ref(), settings)?;To convert borrowed bytes, build an ImageRef:
let image = ImageRef {
surfaces: vec![vec![SurfaceRef { data: &pixels, width, height, depth: 1, stride, slice_stride: 0 }]],
kind: TextureKind::Texture2D,
desc,
};
let output = convert(image, settings)?;Metadata and helpers:
-let format = image.surfaces[0][0].format;
+let format = image.desc.format;
-let bytes = surface.tight_data();
+let bytes = surface.as_ref().tight_data(image.desc.format);Cubemaps:
-let faces: [Surface; 6] = split_cubemap(CubemapInput::Cross(atlas))?;
-let cube = Image { surfaces: faces.map(|f| vec![f]).into(), kind: TextureKind::Cubemap };
+let cube: Image = split_cubemap(CubemapInput::Cross { surface: atlas.as_ref(), desc })?;
+// `CubemapInput::Strip { surface, desc }` changes the same way.
-let faces = split_cubemap(CubemapInput::SeparateFaces(Box::new(faces)))?;
+// Faces in +X, -X, +Y, -Y, +Z, -Z order.
+let cube = Image {
+ surfaces: faces.into_iter().map(|face| vec![face]).collect(),
+ kind: TextureKind::Cubemap,
+ desc,
+};C
ctt_convert, ctt_split_cubemap_cross and ctt_split_cubemap_strip now borrow their input. The caller keeps ownership and must destroy the input with ctt_image_destroy or ctt_surface_destroy. One image can go to more than one ctt_convert call. ctt_image_push_mip still consumes its surface.
-ctt_surface *s = ctt_surface_create(px, len, w, h, 1, w * 4, 0,
- CTT_FORMAT_R8G8B8A8_SRGB, CTT_COLOR_SPACE_SRGB, CTT_ALPHA_MODE_STRAIGHT);
-ctt_image *img = ctt_image_create(CTT_TEXTURE_KIND_TEXTURE2D);
+ctt_surface *s = ctt_surface_create(px, len, w, h, 1, w * 4, 0);
+ctt_format_desc desc = {
+ CTT_FORMAT_R8G8B8A8_SRGB, CTT_COLOR_SPACE_SRGB, CTT_ALPHA_MODE_STRAIGHT};
+ctt_image *img = ctt_image_create(CTT_TEXTURE_KIND_TEXTURE2D, desc);
-ctt_status st = ctt_convert(img, &cfg, &out); /* consumes img */
+ctt_status st = ctt_convert(img, &cfg, &out);
+ctt_image_destroy(img); /* not consumed */
-ctt_format f = ctt_image_surface_format(img, layer, mip);
+ctt_format f = ctt_image_desc(img).format;ctt_image_create returns NULL if desc.format is zero or disagrees with desc.color_space.
The ctt_cubemap_input handle is removed. The split functions borrow the atlas and write one cubemap image:
-ctt_cubemap_input *in = ctt_cubemap_input_cross(atlas); /* consumes atlas */
-ctt_surface *faces[6];
-ctt_status st = ctt_split_cubemap(in, faces); /* consumes in */
+ctt_image *cube = NULL;
+ctt_status st = ctt_split_cubemap_cross(atlas, desc, &cube);
+ctt_surface_destroy(atlas); /* not consumed */To replace ctt_cubemap_input_separate_faces, create a CTT_TEXTURE_KIND_CUBEMAP image, add six layers with ctt_image_add_layer, and push one face into each with ctt_image_push_mip.
Migrating to matching formats and color spaces
A format must agree with its color space. With ColorSpace::Srgb, a format that has an sRGB variant must be that variant. With ColorSpace::Linear, a format must not be an sRGB variant. Formats without an sRGB variant, such as R16G16B16A16_SFLOAT, are valid with both.
The rule applies to the FormatDesc of the input image and to the target format of convert. Before, R8G8B8A8_UNORM tagged ColorSpace::Srgb was accepted. Now convert returns an error. FormatExt::with_color_space gives the matching variant.
desc: FormatDesc {
- format: Format::R8G8B8A8_UNORM,
+ format: Format::R8G8B8A8_SRGB,
color_space: ColorSpace::Srgb,
alpha,
},
// sRGB output
-format: Some(TargetFormat::Compressed { format: Format::BC7_UNORM_BLOCK, encoder }),
+format: Some(TargetFormat::Compressed { format: Format::BC7_SRGB_BLOCK, encoder }),In C, the same rule applies to ctt_format_desc and to the target format in ctt_convert_settings. The CLI is not affected: short format names (-f bc7) resolve to the variant that agrees with the output color space.
BC7F encoder
BC7F, the analytical BC7 encoder from Basis Universal, is supported.
Added
- BC7F bindings and Rust/CLI support for BC7 compression, with six quality presets. Automatic selection prefers BC7F after bc7enc. Rust:
Encoder::Bc7fbehind the defaultencoder-bc7ffeature. CLI:--bc7f-opts. See BC7F encoder. @cwfitzgerald - Equirectangular panorama to cubemap projection with anisotropic filtering. Rust:
CubemapInput::EquirectangularwithEquirectangularOrientationandEquirectangularFront. CLI:--cubemap-layout equirectangular,--cubemap-face-size,--equirectangular-frontand--equirectangular-mirror. Faces areR32G32B32A32_SFLOATin linear space. FormatExt::with_color_spacereturns the variant of a format that agrees with a color space.- C API:
ctt_format_desc,ctt_image_desc,ctt_split_cubemap_crossandctt_split_cubemap_strip.
Changed
- BREAKING: Formats must agree with their color space:
ColorSpace::Srgbrequires the sRGB variant when the format has one (R8G8B8A8_SRGB, notR8G8B8A8_UNORM), andColorSpace::Linearrejects sRGB variants.ImageRef::validatechecks input surfaces, andconvertchecks the target format against the output color space. The KTX2 and DDS readers with input color-space overrides produce the matching variant. When the CLI is used with short format names (eg.-f bc7,-f rgba8) it derives the full format from the input/output color space. See Migrating to matching formats and color spaces. - With the
rayonfeature, all mips and layers of an image now encode concurrently instead of one surface at a time, improving worker utilization on images with many small surfaces. - With the
rayonfeature, the pre-compression pipeline (load, swizzle, mipmap, store) also runs concurrently across mips and layers; only the per-layer mip resize chain remains serial. - CLI: input images are read and decoded in parallel.
--threadsnow governs input decoding as well as compression. - CLI: EXR and HDR input without color-space metadata defaults to linear. Integer images (PNG, JPEG, ...) still default to sRGB. Pass
--input-color-space srgbfor the previous behavior. - SIMD load, store and sRGB kernels use
fearless_simdfor runtime dispatch. - BREAKING: Format metadata moved from
SurfacetoImage,converttakes a borrowedImageRef, andsplit_cubemapreturns a cubemapImage. In the C API,ctt_convertno longer consumes the image. See Migrating toFormatDescandImageRef. @cwfitzgerald
Removed
- BREAKING:
CubemapInput::SeparateFaces. Build a cubemapImagefrom the six faces directly. @cwfitzgerald - BREAKING:
Error::CubemapNonUniformFacesand the C statusCTT_STATUS_CUBEMAP_NON_UNIFORM_FACES.ImageRef::validatereports faces of different sizes asError::InvalidImage. @cwfitzgerald - BREAKING: C API:
ctt_surface_format,ctt_surface_color_space,ctt_surface_alpha,ctt_image_surface_format,ctt_image_surface_color_space,ctt_image_surface_alpha,ctt_split_cubemapand thectt_cubemap_input_*functions and type. @cwfitzgerald
Fixed
- All encoders accept the sRGB variants of the block formats they support (for example
BC7_SRGB_BLOCK). Before, only the UNORM variants were accepted. ImageRef::validaterejects images whose layers have different sizes at the same mip level. Before, such 2D arrays and cubemaps were accepted. @cwfitzgerald- AMD encoder:
BC4_SNORM_BLOCKandBC5_SNORM_BLOCKencode SNORM input correctly. Before, SNORM input was converted to UNORM and then read as signed, which corrupted the output. UNORM input is converted by value and is not remapped to[-1, 1]. - Intel and AMD encoders:
BC6H_UFLOAT_BLOCKencodes negative input as 0. Before, negative values (for example the ringing of the Lanczos3 mipmap filter near bright HDR pixels) encoded as the f16 maximum, 65504. @cwfitzgerald - AMD encoder:
BC6H_SFLOAT_BLOCKkeeps the sign of negative input. Before, negative values encoded as the f16 maximum, 65504. @cwfitzgerald