ktx2/dfd/mod.rs
1//! Data Format Descriptor (DFD) for KTX2 textures.
2//!
3//! Each ktx2 file contains an abstract definition of the data format of the texture data,
4//! called the [Data Format Descriptor (DFD)][dfd-spec]). The specification for the DFD is a separate
5//! Khronos standard, and is not specific to KTX2.
6//!
7//! Most of the DFD's data is relatively esoteric, but it is required by the KTX2 specification,
8//! and contains information like [`ColorModel`], [`ColorPrimaries`], and [`TransferFunction`] that
9//! may be useful to applications.
10//!
11//! # Structure
12//!
13//! A DFD contains one or more [`Block`]s. Each block has a [`BlockHeader`], which describes what type of block it is,
14//! and a data blob. The most common block type in KTX2 is the [`Basic`] block which contains information about
15//! texture-like data formats.
16//!
17//! [dfd-spec]: https://registry.khronos.org/DataFormat/specs/1.4/dataformat.1.4.inline.html
18
19mod generate;
20
21pub use generate::BuildError;
22
23use alloc::{vec, vec::Vec};
24use core::num::NonZeroU8;
25
26use crate::util::{bytes_to_u32, read_bytes, read_u16, shift_and_mask_lower};
27use crate::{ColorModel, ColorPrimaries, ParseError, TransferFunction};
28
29/// DFD block, containing a header and a data blob.
30///
31/// The header describes the type of block, and the data blob contains the block's contents.
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub enum Block {
34 /// "Basic" DFD block. This is what most KTX2 files contain.
35 Basic(Basic),
36 /// DFD block type unknown to the ktx2 crate. Use the header
37 /// to determine if this is a relevant block type for your application.
38 Unknown { header: BlockHeader, data: Vec<u8> },
39}
40
41impl Block {
42 /// Parses a single [`Block`] from the start of `bytes`, returning the block and the number
43 /// of bytes consumed.
44 pub(crate) fn parse(bytes: &[u8]) -> Result<(Self, usize), ParseError> {
45 let (header, descriptor_block_size) = BlockHeader::from_bytes(
46 &bytes
47 .get(..BlockHeader::LENGTH)
48 .ok_or(ParseError::UnexpectedEnd)?
49 .try_into()
50 .unwrap(),
51 )?;
52
53 if descriptor_block_size == 0 {
54 return Err(ParseError::UnexpectedEnd);
55 }
56
57 let data = &bytes
58 .get(BlockHeader::LENGTH..descriptor_block_size)
59 .ok_or(ParseError::UnexpectedEnd)?;
60
61 let block = match header {
62 BlockHeader::BASIC => Block::Basic(Basic::parse(data)?),
63 _ => Block::Unknown {
64 header,
65 data: data.to_vec(),
66 },
67 };
68
69 Ok((block, descriptor_block_size))
70 }
71
72 /// Number of bytes the serialized form of this block will take up,
73 /// including the [`BlockHeader`].
74 pub fn serialized_length(&self) -> usize {
75 let data_length = match self {
76 Block::Basic(basic) => basic.serialized_length(),
77 Block::Unknown { data, .. } => data.len(),
78 };
79 BlockHeader::LENGTH + data_length
80 }
81
82 /// Serializes this block to a given slice of bytes. The slice must be at least
83 /// [`serialized_length`](Self::serialized_length) bytes long.
84 pub fn to_bytes(&self, output: &mut [u8]) {
85 assert!(
86 output.len() >= self.serialized_length(),
87 "Output buffer is too small to serialize Block: expected at least {} bytes, got {}",
88 self.serialized_length(),
89 output.len()
90 );
91
92 let descriptor_block_size = self.serialized_length() as u16;
93
94 // Serialize the header
95 let header = match self {
96 Block::Basic(_) => BlockHeader::BASIC,
97 Block::Unknown { header, .. } => *header,
98 };
99 output[..BlockHeader::LENGTH].copy_from_slice(&header.as_bytes(descriptor_block_size));
100
101 // Serialize the data
102 match self {
103 Block::Basic(basic) => {
104 basic.to_bytes(&mut output[BlockHeader::LENGTH..]);
105 }
106 Block::Unknown { data, .. } => {
107 output[BlockHeader::LENGTH..][..data.len()].copy_from_slice(data);
108 }
109 }
110 }
111
112 /// Serializes this block to a vector of bytes.
113 pub fn to_vec(&self) -> Vec<u8> {
114 let mut output = vec![0u8; self.serialized_length()];
115 self.to_bytes(&mut output);
116 output
117 }
118}
119
120/// DFD block header, containing what type and version of block.
121///
122/// Implementations can skip blocks with unrecognized headers, allowing unknown data to be ignored.
123#[derive(Debug, Copy, Clone, PartialEq, Eq)]
124pub struct BlockHeader {
125 /// 17-bit organization identifier. `0` is Khronos. PCI SIG IDs use bits 0–15 with bit 16
126 /// clear; other IDs are assigned by Khronos starting at 65536.
127 pub vendor_id: u32, //: 17;
128 /// 15-bit vendor-defined identifier distinguishing between data representations.
129 pub descriptor_type: u32, //: 15;
130 /// Vendor-defined version, intended for backwards-compatible updates to a descriptor block.
131 pub version_number: u16, //: 16;
132}
133
134impl BlockHeader {
135 /// Number of bytes in a DFD block header.
136 pub const LENGTH: usize = 8;
137
138 /// The header for a [`Basic`] block.
139 pub const BASIC: Self = Self {
140 vendor_id: 0,
141 descriptor_type: 0,
142 version_number: 2,
143 };
144
145 /// Serializes the block header to bytes. `descriptor_block_size` is the
146 /// total size of the containing [`Block`] (header + data).
147 pub fn as_bytes(&self, descriptor_block_size: u16) -> [u8; Self::LENGTH] {
148 let mut output = [0u8; Self::LENGTH];
149
150 let first_word = (self.vendor_id & ((1 << 17) - 1)) | (self.descriptor_type << 17);
151 output[0..4].copy_from_slice(&first_word.to_le_bytes());
152 output[4..6].copy_from_slice(&self.version_number.to_le_bytes());
153 output[6..8].copy_from_slice(&descriptor_block_size.to_le_bytes());
154
155 output
156 }
157
158 /// Deserializes a block header from bytes, returning the header and the size
159 /// of the containing [`Block`] (header + data).
160 pub(crate) fn from_bytes(bytes: &[u8; Self::LENGTH]) -> Result<(Self, usize), ParseError> {
161 let mut offset = 0;
162
163 let v = bytes_to_u32(bytes, &mut offset)?;
164 let vendor_id = shift_and_mask_lower(0, 17, v);
165 let descriptor_type = shift_and_mask_lower(17, 15, v);
166
167 let version_number = read_u16(bytes, &mut offset)?;
168 let descriptor_block_size = read_u16(bytes, &mut offset)?;
169
170 Ok((
171 Self {
172 vendor_id,
173 descriptor_type,
174 version_number,
175 },
176 descriptor_block_size as usize,
177 ))
178 }
179}
180
181/// "Basic" DFD block, containing information about texture-like data.
182///
183/// This is the most common type of DFD block found in KTX2 files.
184#[derive(Debug, Clone, PartialEq, Eq)]
185pub struct Basic {
186 /// The set of color (or other data) channels which may be encoded within the data,
187 /// though there is no requirement that all of the possible channels from the colorModel
188 /// be present.
189 ///
190 /// See the [DFD specification][dfd-spec] for more information on this than you'd ever need.
191 ///
192 /// None means unknown/unspecified.
193 ///
194 /// [dfd-spec]: https://registry.khronos.org/DataFormat/specs/1.4/dataformat.1.4.inline.html#COLORMODEL
195 pub color_model: Option<ColorModel>, //: 8;
196 /// The color primaries used by the data.
197 ///
198 /// See the [DFD specification][dfd-spec] for more information than you can shake a stick at.
199 ///
200 /// None means unknown/unspecified.
201 ///
202 /// [dfd-spec]: https://registry.khronos.org/DataFormat/specs/1.4/dataformat.1.4.inline.html#_emphasis_role_strong_emphasis_colorprimaries_emphasis_emphasis
203 pub color_primaries: Option<ColorPrimaries>, //: 8;
204 /// The function converting the encoded data to a linear color space.
205 ///
206 /// See the [DFD specification][dfd-spec] for more information.
207 ///
208 /// None means unknown/unspecified.
209 ///
210 /// [dfd-spec]: https://registry.khronos.org/DataFormat/specs/1.4/dataformat.1.4.inline.html#_emphasis_role_strong_emphasis_transferfunction_emphasis_emphasis
211 pub transfer_function: Option<TransferFunction>, //: 8;
212 /// Boolean flags modifying properties of the data. In practice,
213 /// this is only used to indicate if the alpha channel is premultiplied.
214 ///
215 /// See the [DFD specification][dfd-spec] for more information.
216 ///
217 /// [dfd-spec]: https://registry.khronos.org/DataFormat/specs/1.4/dataformat.1.4.inline.html#_emphasis_role_strong_emphasis_flags_emphasis_emphasis
218 pub flags: DataFormatFlags, //: 8;
219 /// The dimensions of each "block" of texels in the image. For uncompressed formats, this is always 1x1x1x1.
220 /// For compressed formats, this represents the dimensions of the compression block (e.g. 4x4x1x1 for BCn formats).
221 ///
222 /// The dfd stores this as one less than the actual dimension. See the [DFD specification][dfd-spec] for more information.
223 ///
224 /// [dfd-spec]: https://registry.khronos.org/DataFormat/specs/1.4/dataformat.1.4.inline.html#_emphasis_role_strong_emphasis_texelblockdimension_0_3_emphasis_emphasis
225 pub texel_block_dimensions: [NonZeroU8; 4], //: 8 x 4;
226 /// The number of bytes in each plane of the data.
227 ///
228 /// See the [DFD specification][dfd-spec] for more information.
229 ///
230 /// [dfd-spec]: https://registry.khronos.org/DataFormat/specs/1.4/dataformat.1.4.inline.html#_emphasis_role_strong_emphasis_bytesplane_0_7_emphasis_emphasis
231 pub bytes_planes: [u8; 8], //: 8 x 8;
232 /// Information about each "sample" within the data, describing how to interpret their bits of the data as color or other information.
233 pub sample_information: Vec<SampleInformation>,
234}
235
236impl Basic {
237 /// Number of bytes in the constant-size prefix of a Basic block, before the variable-length sample information.
238 pub const FIXED_LENGTH: usize = 16;
239
240 /// Creates a [`Basic`] DFD block for the given [`Format`](crate::Format),
241 /// using the format's default transfer function, color primaries, color
242 /// model, and straight (non-premultiplied) alpha.
243 ///
244 /// Returns the DFD block and the [`crate::Header::type_size`] that corresponds
245 /// to the provided format.
246 ///
247 /// This is a convenience wrapper around [`from_format_with`](Self::from_format_with)
248 /// that uses the standard defaults for every parameter. If you need to
249 /// customize any of these, use `from_format_with` instead.
250 ///
251 /// # Errors
252 ///
253 /// Returns [`BuildError::UnsupportedFormat`] if the format is not
254 /// recognized by the DFD generation table.
255 pub fn from_format(format: crate::Format) -> Result<(Self, u32), BuildError> {
256 Self::from_format_with(format, false, None, None, None)
257 }
258
259 /// Creates a [`Basic`] DFD block for the given [`Format`](crate::Format),
260 /// with optional overrides for transfer function, color primaries, color
261 /// model, and alpha premultiplication.
262 ///
263 /// Returns the DFD block and the [`crate::Header::type_size`] that corresponds
264 /// to the provided format and overrides.
265 ///
266 /// Parameters set to `None` use the format's natural defaults:
267 ///
268 /// - `alpha_premultiplied` — when `true`, sets the
269 /// [`ALPHA_PREMULTIPLIED`](DataFormatFlags::ALPHA_PREMULTIPLIED) flag,
270 /// indicating that color channel values have already been scaled by the
271 /// alpha channel. When `false` (the default), alpha is straight
272 /// (non-premultiplied).
273 ///
274 /// - `transfer_function` — overrides how encoded sample values are
275 /// converted to linear light. The default depends on the format: sRGB
276 /// format variants (e.g.
277 /// [`R8G8B8A8_SRGB`](crate::Format::R8G8B8A8_SRGB)) default to
278 /// [`TransferFunction::SRGB`]; all others default to
279 /// [`TransferFunction::Linear`]. When you override the default
280 /// transfer function, unlike with `srgb`-variant formats, the
281 /// "alpha" channel is not automatically marked as linear by DFD generation,
282 /// due to an ambiguity in the specification. See [this issue][ktx-spec-231].
283 /// However, conventionally, the alpha channel of these formats is still expected to be linear.
284 ///
285 /// - `color_primaries` — overrides the color primaries of the data.
286 /// Defaults to [`ColorPrimaries::BT709`] (the sRGB/Rec. 709 primaries
287 /// used by most consumer content).
288 ///
289 /// - `color_model` — overrides the color model. Defaults to
290 /// [`ColorModel::RGBSDA`] for most formats, [`ColorModel::YUVSDA`] for
291 /// 4:2:2 subsampled formats, or the intrinsic model for compressed
292 /// formats. If this is changed from the default for the format,
293 /// the dfd will be formally invalid, but this may be useful for
294 /// some applications.
295 ///
296 /// # Format-specific restrictions
297 ///
298 /// Not all overrides are valid for all formats. The following restrictions
299 /// are enforced and will return an error if violated:
300 ///
301 /// ## Depth-stencil formats (e.g. [`D16_UNORM_S8_UINT`](crate::Format::D16_UNORM_S8_UINT), [`D32_SFLOAT_S8_UINT`](crate::Format::D32_SFLOAT_S8_UINT))
302 ///
303 /// Depth-stencil formats have a fixed DFD layout. No overrides are
304 /// permitted: `alpha_premultiplied` must be `false`, and
305 /// `transfer_function`, `color_primaries`, and `color_model` must all be
306 /// `None`.
307 ///
308 /// ## Compressed formats (e.g. [`BC7_UNORM_BLOCK`](crate::Format::BC7_UNORM_BLOCK), [`ASTC_4x4_SRGB_BLOCK`](crate::Format::ASTC_4x4_SRGB_BLOCK))
309 ///
310 /// Compressed formats must use their intrinsic color model (e.g.
311 /// [`ColorModel::BC7`], [`ColorModel::ASTC`]). The `color_model` parameter
312 /// must be `None`.
313 ///
314 /// ## sRGB variant rules
315 ///
316 /// Formats that exist in both UNORM and SRGB variants (e.g.
317 /// [`R8G8B8A8_UNORM`](crate::Format::R8G8B8A8_UNORM) /
318 /// [`R8G8B8A8_SRGB`](crate::Format::R8G8B8A8_SRGB),
319 /// [`ASTC_4x4_UNORM_BLOCK`](crate::Format::ASTC_4x4_UNORM_BLOCK) /
320 /// [`ASTC_4x4_SRGB_BLOCK`](crate::Format::ASTC_4x4_SRGB_BLOCK)) have
321 /// strict transfer function requirements:
322 ///
323 /// - UNORM variant (e.g.
324 /// [`R8G8B8A8_UNORM`](crate::Format::R8G8B8A8_UNORM)): the transfer
325 /// function must **not** be set to [`TransferFunction::SRGB`]. If you
326 /// want sRGB encoding, use the SRGB variant of the format instead.
327 ///
328 /// - SRGB variant (e.g.
329 /// [`R8G8B8A8_SRGB`](crate::Format::R8G8B8A8_SRGB)): the transfer
330 /// function **must** be [`TransferFunction::SRGB`] (or `None` to use
331 /// the default). Overriding it to any other value is an error.
332 ///
333 /// Formats without an sRGB counterpart (e.g.
334 /// [`R16_UNORM`](crate::Format::R16_UNORM),
335 /// [`R32_SFLOAT`](crate::Format::R32_SFLOAT),
336 /// [`ASTC_4x4_SFLOAT_BLOCK`](crate::Format::ASTC_4x4_SFLOAT_BLOCK))
337 /// have no transfer function restrictions.
338 ///
339 /// # Errors
340 ///
341 /// Returns a [`BuildError`] describing which constraint was
342 /// violated. See the variant documentation for details.
343 ///
344 /// [ktx-spec-231]: https://github.com/KhronosGroup/KTX-Specification/issues/231
345 pub fn from_format_with(
346 format: crate::Format,
347 alpha_premultiplied: bool,
348 transfer_function: Option<TransferFunction>,
349 color_primaries: Option<ColorPrimaries>,
350 color_model: Option<ColorModel>,
351 ) -> Result<(Self, u32), BuildError> {
352 let builder = generate::Builder::from_format(format).ok_or(BuildError::UnsupportedFormat)?;
353 let type_size = builder.type_size();
354 let dfd = builder.build(alpha_premultiplied, transfer_function, color_primaries, color_model)?;
355 Ok((dfd, type_size))
356 }
357
358 /// Parses a [`Basic`] block from the start of `bytes`.
359 pub fn parse(bytes: &[u8]) -> Result<Self, ParseError> {
360 let mut offset = 0;
361
362 let [model, primaries, transfer, flags] = read_bytes(bytes, &mut offset)?;
363 let texel_block_dimensions = read_bytes(bytes, &mut offset)?.map(|dim| NonZeroU8::new(dim + 1).unwrap());
364 let bytes_planes = read_bytes(bytes, &mut offset)?;
365
366 let remaining_bytes = &bytes[Self::FIXED_LENGTH..];
367 // TODO: If MSRV is bumped to 1.88, use as_chunks::<SampleInformation::LENGTH>().
368 let iterator = remaining_bytes.chunks_exact(SampleInformation::LENGTH);
369
370 if !iterator.remainder().is_empty() {
371 return Err(ParseError::UnexpectedEnd);
372 }
373
374 let sample_information = iterator
375 .map(|chunk| SampleInformation::from_bytes(chunk.try_into().unwrap()))
376 .collect::<Result<Vec<_>, _>>()?;
377
378 Ok(Self {
379 color_model: ColorModel::new(model),
380 color_primaries: ColorPrimaries::new(primaries),
381 transfer_function: TransferFunction::new(transfer),
382 flags: DataFormatFlags::from_bits_truncate(flags),
383 texel_block_dimensions,
384 bytes_planes,
385 sample_information,
386 })
387 }
388
389 /// Number of bytes the serialized form of this block will take up.
390 pub fn serialized_length(&self) -> usize {
391 Self::FIXED_LENGTH + self.sample_information.len() * SampleInformation::LENGTH
392 }
393
394 /// Serializes this block to a given slice of bytes. The slice must be at least [`serialized_length`](Self::serialized_length) bytes long.
395 pub fn to_bytes(&self, output: &mut [u8]) {
396 assert!(
397 output.len() >= self.serialized_length(),
398 "Output buffer is too small to serialize Basic block: expected at least {} bytes, got {}",
399 self.serialized_length(),
400 output.len()
401 );
402
403 let color_model = self.color_model.map_or(0, |c| c.value());
404 let color_primaries = self.color_primaries.map_or(0, |c| c.value());
405 let transfer_function = self.transfer_function.map_or(0, |t| t.value());
406
407 let texel_block_dimensions = self.texel_block_dimensions.map(|dim| dim.get() - 1);
408
409 output[0] = color_model;
410 output[1] = color_primaries;
411 output[2] = transfer_function;
412 output[3] = self.flags.bits();
413 output[4..8].copy_from_slice(&texel_block_dimensions);
414 output[8..16].copy_from_slice(&self.bytes_planes);
415 for (i, sample) in self.sample_information.iter().enumerate() {
416 let start = Self::FIXED_LENGTH + i * SampleInformation::LENGTH;
417 output[start..][..SampleInformation::LENGTH].copy_from_slice(&sample.as_bytes());
418 }
419 }
420
421 /// Serializes this block to a vector of bytes.
422 pub fn to_vec(&self) -> Vec<u8> {
423 let mut output = vec![0u8; self.serialized_length()];
424 self.to_bytes(&mut output);
425 output
426 }
427}
428
429/// Information about each "sample" within an image.
430///
431/// A "sample" consisting of a single channel of data and with a
432/// single corresponding "position" within the texel block.
433///
434/// See the [DFD specification][dfd-spec] for more extremely verbose information.
435///
436/// [dfd-spec]: https://registry.khronos.org/DataFormat/specs/1.4/dataformat.1.4.inline.html#_anchor_id_sample_xreflabel_sample_sample_information
437#[derive(Debug, Copy, Clone, PartialEq, Eq)]
438pub struct SampleInformation {
439 /// Offset from the beginning of the texel block in bits.
440 pub bit_offset: u16, //: 16;
441 /// The length of this sample in bits.
442 pub bit_length: NonZeroU8, //: 8;
443 /// The type of channel this sample represents. This varies by [`ColorModel`].
444 pub channel_type: u8, //: 4;
445 /// Qualifiers modifying the channel type.
446 pub channel_type_qualifiers: ChannelTypeQualifiers, //: 4;
447 /// The position in texels of this sample relative to the 0,0,0,0 texel in the 4D texel block.
448 pub sample_positions: [u8; 4], //: 8 x 4;
449 /// The sample value that maps to the format's logical minimum — typically `0` for unsigned
450 /// formats, `-1` for signed formats, or `-0.5` for chroma channels in color difference models
451 /// (e.g. Y′CbCr).
452 ///
453 /// Together with [`upper`](Self::upper), this defines how raw sample values are converted to
454 /// their conceptual numeric interpretation. Values are not guaranteed to fall within this
455 /// range — for example, HDR formats may define `1.0` as a nominal level well below the actual
456 /// maximum. When samples should be interpreted directly as integers (unnormalized), set
457 /// [`upper`](Self::upper) to `1` and `lower` to `0` (unsigned) or `-1` (signed).
458 ///
459 /// For integer formats, this is stored as a 32-bit integer (signed or unsigned matching the
460 /// channel encoding). For floating-point formats, it is stored as a 32-bit float. For formats
461 /// wider than 32 bits (e.g. 64-bit), integer values are expanded by preserving the sign bit
462 /// and replicating the top non-sign bit, and float values are converted to the native
463 /// representation (e.g. `f32` to `f64`).
464 ///
465 /// # Examples
466 ///
467 /// | Format | `lower` | `upper` | Effect |
468 /// |--------|---------|---------|--------|
469 /// | R8 unorm | `0` | `255` | Maps 0–255 to 0.0–1.0 |
470 /// | R8 snorm | `-127` (`0xFFFFFF81`) | `127` | Maps -127–127 to -1.0–1.0 |
471 /// | R8 uint | `0` | `1` | Integer value used directly |
472 /// | R16 sfloat | `0xBF800000` (-1.0f) | `0x3F800000` (1.0f) | Float range -1.0–1.0 |
473 /// | R64 uint | `0` | `1` | Expands to 64-bit `0` and `1` |
474 /// | R64 uint norm | `0` | `0xFFFFFFFF` | Expands to `u64::MAX`, maps to 0.0–1.0 |
475 /// | BT.709 Y′ (8-bit) | `16` | `235` | Maps 16–235 to 0.0–1.0 |
476 ///
477 /// For a very long and confusing explanation of this, please see the [DFD specification][dfd-spec]
478 ///
479 /// [dfd-spec]: https://registry.khronos.org/DataFormat/specs/1.4/dataformat.1.4.inline.html#_emphasis_role_strong_emphasis_samplelower_emphasis_emphasis_and_emphasis_role_strong_emphasis_sampleupper_emphasis_emphasis
480 pub lower: u32, //: 32;
481 /// The sample value that maps to `1.0` (the white point), or `0.5` for chroma channels in
482 /// color difference models (e.g. Y′CbCr).
483 ///
484 /// See [`lower`](Self::lower) for more details on interpretation and encoding and examples.
485 pub upper: u32, //: 32;
486}
487
488impl SampleInformation {
489 /// Number of bytes in a SampleInformation entry.
490 pub const LENGTH: usize = 16;
491
492 /// Serializes this sample information to bytes.
493 pub fn as_bytes(&self) -> [u8; Self::LENGTH] {
494 let mut bytes = [0u8; Self::LENGTH];
495
496 let channel_info = self.channel_type | (self.channel_type_qualifiers.bits() << 4);
497
498 bytes[0..2].copy_from_slice(&self.bit_offset.to_le_bytes());
499 bytes[2] = self.bit_length.get() - 1;
500 bytes[3] = channel_info;
501 bytes[4..8].copy_from_slice(&self.sample_positions);
502 bytes[8..12].copy_from_slice(&self.lower.to_le_bytes());
503 bytes[12..16].copy_from_slice(&self.upper.to_le_bytes());
504
505 bytes
506 }
507
508 /// Deserializes sample information from the given bytes.
509 pub fn from_bytes(bytes: &[u8; Self::LENGTH]) -> Result<Self, ParseError> {
510 let mut offset = 0;
511
512 let v = bytes_to_u32(bytes, &mut offset)?;
513 let bit_offset = shift_and_mask_lower(0, 16, v) as u16;
514 let bit_length = (shift_and_mask_lower(16, 8, v) as u8)
515 .checked_add(1)
516 .and_then(NonZeroU8::new)
517 .ok_or(ParseError::InvalidSampleBitLength)?;
518 let channel_type = shift_and_mask_lower(24, 4, v) as u8;
519 let channel_type_qualifiers = ChannelTypeQualifiers::from_bits_truncate(shift_and_mask_lower(28, 4, v) as u8);
520
521 let sample_positions = read_bytes(bytes, &mut offset)?;
522 let lower = bytes_to_u32(bytes, &mut offset)?;
523 let upper = bytes_to_u32(bytes, &mut offset)?;
524
525 Ok(Self {
526 bit_offset,
527 bit_length,
528 channel_type,
529 channel_type_qualifiers,
530 sample_positions,
531 lower,
532 upper,
533 })
534 }
535}
536
537bitflags::bitflags! {
538 /// Qualifiers modifying the channel type of a [`SampleInformation`] entry.
539 ///
540 /// Multiple samples with the same channel and position are combined into a single logical
541 /// ("virtual") sample. Samples without [`EXPONENT`](Self::EXPONENT) set contribute to the
542 /// **base value** — the primary numeric content of the channel (e.g. a color intensity or
543 /// mantissa). Samples with [`EXPONENT`](Self::EXPONENT) set act as **modifiers** that
544 /// transform the base value — the interpretation depends on the combination of flags:
545 ///
546 /// | `EXPONENT` | `LINEAR` | `FLOAT` | Interpretation |
547 /// |:----------:|:--------:|:-------:|----------------|
548 /// | - | - | - | Base value, modified by [`TransferFunction`] |
549 /// | - | L | - | Base value, always linear (ignores [`TransferFunction`]) |
550 /// | - | - | F | Base value is a standard float (10/11/16/32/64-bit) |
551 /// | E | - | - | Exponent: `base × 2^modifier` |
552 /// | E | - | F | Multiplier: `base × modifier` |
553 /// | E | L | - | Divisor: `base / modifier` |
554 /// | E | L | F | Power: `base ^ modifier` |
555 ///
556 /// For a long and more confusing explanation of this, see the [DFD specification][dfd-spec].
557 ///
558 /// [dfd-spec]: https://registry.khronos.org/DataFormat/specs/1.4/dataformat.1.4.inline.html#_sample_emphasis_role_strong_emphasis_channeltype_emphasis_emphasis_emphasis_role_strong_emphasis_channelid_emphasis_emphasis_and_qualifiers
559 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
560 #[repr(transparent)]
561 pub struct ChannelTypeQualifiers: u8 {
562 /// The sample contains a linearly-encoded value, bypassing the format's
563 /// [`TransferFunction`]. When combined with [`EXPONENT`](Self::EXPONENT), indicates a
564 /// divisor modifier instead.
565 const LINEAR = (1 << 0);
566 /// The sample is a modifier applied to the base value rather than part of the base value
567 /// itself. The type of modification depends on the [`LINEAR`](Self::LINEAR) and
568 /// [`FLOAT`](Self::FLOAT) flags — see the table on [`ChannelTypeQualifiers`].
569 const EXPONENT = (1 << 1);
570 /// The sample holds a signed two's complement value. When not set, the sample is unsigned.
571 const SIGNED = (1 << 2);
572 /// The sample holds floating-point data (10/11/16/32/64-bit IEEE 754). For custom float
573 /// formats with a separate [`EXPONENT`](Self::EXPONENT) sample, this flag on the base
574 /// value instead indicates an implicit leading `1` bit in the mantissa.
575 /// When combined with [`EXPONENT`](Self::EXPONENT), indicates a multiplier modifier.
576 const FLOAT = (1 << 3);
577 }
578}
579
580bitflags::bitflags! {
581 /// Flags modifying the interpretation of color data in a [`Basic`] block.
582 ///
583 /// Controls whether color values have been pre-scaled by the alpha channel. Has no effect
584 /// if the format does not contain an alpha channel.
585 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
586 #[repr(transparent)]
587 pub struct DataFormatFlags: u8 {
588 /// Color values are not premultiplied — they need to be scaled by alpha during blending.
589 ///
590 /// Not a flag itself, but an alias for when ALPHA_PREMULTIPLIED is not set.
591 const STRAIGHT_ALPHA = 0;
592 /// Color values have already been scaled by the alpha channel.
593 const ALPHA_PREMULTIPLIED = (1 << 0);
594 }
595}
596
597#[cfg(test)]
598mod tests {
599 use super::*;
600
601 fn to_nonzero<const N: usize>(input: [u8; N]) -> [NonZeroU8; N] {
602 input.map(|n| NonZeroU8::new(n).unwrap())
603 }
604
605 #[test]
606 fn basic_dfd_header_roundtrip() {
607 let basic = Basic {
608 color_model: Some(ColorModel::LabSDA),
609 color_primaries: Some(ColorPrimaries::ACES),
610 transfer_function: Some(TransferFunction::ITU),
611 flags: DataFormatFlags::STRAIGHT_ALPHA,
612 texel_block_dimensions: to_nonzero([1, 2, 3, 4]),
613 bytes_planes: [5, 6, 7, 8, 9, 10, 11, 12],
614 sample_information: vec![],
615 };
616
617 let bytes = basic.to_vec();
618 let decoded = Basic::parse(&bytes).unwrap();
619 assert_eq!(basic, decoded);
620 }
621
622 #[test]
623 fn sample_information_roundtrip() {
624 let info = SampleInformation {
625 bit_offset: 234,
626 bit_length: NonZeroU8::new(123).unwrap(),
627 channel_type: 2,
628 channel_type_qualifiers: ChannelTypeQualifiers::LINEAR,
629 sample_positions: [1, 2, 3, 4],
630 lower: 1234,
631 upper: 4567,
632 };
633
634 let bytes = info.as_bytes();
635 let decoded = SampleInformation::from_bytes(&bytes).unwrap();
636
637 assert_eq!(info, decoded);
638 }
639
640 #[test]
641 fn sample_info_invalid_bit_length() {
642 let bytes = &[
643 0u8, 0, // bit_offset
644 255, // bit_length
645 1, // channel_type | channel_type_qualifiers
646 0, 0, 0, 0, // sample_positions
647 0, 0, 0, 0, // lower
648 255, 255, 255, 255, // upper
649 ];
650
651 assert!(matches!(
652 SampleInformation::from_bytes(bytes),
653 Err(ParseError::InvalidSampleBitLength)
654 ));
655 }
656}