Skip to main content

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}