Files
2026-06-14 19:09:18 +01:00

766 lines
30 KiB
C++

/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2023 The Khronos Group Inc.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @file
* @~English
*
* @brief Data Format Descriptor for imageio.
*
* @author Mark Callow
*/
#include <exception>
#include <iterator>
#include <string>
#include <vector>
#include <KHR/khr_df.h>
/// @brief Image format descriptor
///
/// Based on Khronos Data Format specification. Omits the parts needed for
/// serialization (size, descriptorType, etc.) and, since there is no worry
/// about writing & reading across compilers and platforms, uses a struct
/// instead of the khr\_df.h macros.
///
/// Note that @e samples are not @e channels, a.k.a @e components. @e samples
/// represent a series of contiguous bits in the bitstream representing
/// a pixel of the image. Since the various ImageInput derived classes
/// convert incoming data to local endianness most @e channels need only
/// a single sample. Only formats such as those with a shared exponent
/// need multiple samples per component.
///
/// This descriptor is way more general than is needed by the current set of
/// supported input formats for which there will always be one sample per
/// channel. We use this because it is familiar from use elsewhere in
/// KTX-Software and because of the large number of useful enums provided
/// by khr\_df.h.
///
/// @note This class uses the Data Format Specification nomenclature of
/// @e channel for consistency with thar spec. Elsewhere in KTX-Software
/// @e component is widely used.
///
struct FormatDescriptor {
/// @internal
/// @brief Basic descriptor.
struct basicDescriptor {
uint32_t colorModel: 8;
uint32_t colorPrimaries: 8;
uint32_t transferFunction: 8;
uint32_t dataFlags: 8;
uint32_t texelBlockDimension0: 8;
uint32_t texelBlockDimension1: 8;
uint32_t texelBlockDimension2: 8;
uint32_t texelBlockDimension3: 8;
uint32_t bytesPlane0: 8;
uint32_t bytesPlane1: 8;
uint32_t bytesPlane2: 8;
uint32_t bytesPlane3: 8;
uint32_t bytesPlane4: 8;
uint32_t bytesPlane5: 8;
uint32_t bytesPlane6: 8;
uint32_t bytesPlane7: 8;
/// @brief Default constructor
basicDescriptor() {
memset(this, 0, sizeof(*this));
}
/// @brief Constructor for unpacked, non-compressed textures.
basicDescriptor(uint32_t pixelByteCount,
khr_df_transfer_e t = KHR_DF_TRANSFER_UNSPECIFIED,
khr_df_primaries_e p = KHR_DF_PRIMARIES_BT709,
khr_df_model_e m = KHR_DF_MODEL_RGBSDA,
khr_df_flags_e f = KHR_DF_FLAG_ALPHA_STRAIGHT) {
colorModel = m;
colorPrimaries = p;
transferFunction = t;
dataFlags = f;
texelBlockDimension0 = 0; // Uncompressed means only 1x1x1x1 blocks.
texelBlockDimension1 = 0;
texelBlockDimension2 = 0;
texelBlockDimension3 = 0;
bytesPlane0 = pixelByteCount;
bytesPlane1 = bytesPlane2 = bytesPlane3 = 0;
bytesPlane4 = bytesPlane5 = bytesPlane6 = bytesPlane7 = 0;
}
bool operator==(const basicDescriptor& rhs) const {
const uint32_t* a = reinterpret_cast<const uint32_t*>(this);
const uint32_t* b = reinterpret_cast<const uint32_t*>(&rhs);
for (uint32_t i = 0; i < 4; i++) {
if (a[i] != b[i]) return false;
}
return true;
}
bool operator!=(const basicDescriptor& rhs) const {
return !(*this == rhs);
}
khr_df_model_e model() const noexcept {
return static_cast<khr_df_model_e>(colorModel);
}
khr_df_primaries_e primaries() const noexcept {
return static_cast<khr_df_primaries_e>(colorPrimaries);
}
khr_df_transfer_e transfer() const noexcept {
return static_cast<khr_df_transfer_e>(transferFunction);
}
khr_df_flags_e flags() const noexcept {
return static_cast<khr_df_flags_e>(dataFlags);
}
} basic;
/// @internal
/// @brief Extended descriptor.
///
/// In a true DFD this would be an extension descriptor type complete with
/// size, vendorId, descriptorType, etc.
struct extendedDescriptor {
uint32_t channelCount; /// Saved channel count to avoid having to loop
/// over samples to figure out the count.
bool sameUnitAllChannels = false; /// All samples have same types and sizes.
float oeGamma = -1; /// Power function exponent used when the image was
/// encoded, if one was used. -1 otherwise.
/// @internal
/// @brief ICC profile descriptor.
struct iccProfileDescriptor {
std::string name;
std::vector<uint8_t> profile;
iccProfileDescriptor() { }
iccProfileDescriptor(std::string& n, uint8_t* p, size_t ps) {
name = n;
profile.resize(ps);
profile.insert(profile.begin(), p, &p[ps]);
}
bool operator==(const iccProfileDescriptor& rhs) const {
bool result = !this->name.compare(rhs.name);
return result && (this->profile == rhs.profile);
}
bool operator!=(const iccProfileDescriptor& rhs) const {
return !(*this == rhs);
}
} iccProfile;
extendedDescriptor(uint32_t cc = 0) :
channelCount(cc) { }
bool operator==(const extendedDescriptor& rhs) const {
if (this->channelCount != rhs.channelCount) return false;
if (this->sameUnitAllChannels != rhs.sameUnitAllChannels) return false;
if (this->oeGamma != rhs.oeGamma) return false;
return this->iccProfile == rhs.iccProfile;
}
bool operator!=(const extendedDescriptor& rhs) const {
return !(*this == rhs);
}
} extended;
struct sample {
uint32_t bitOffset: 16;
uint32_t bitLength: 8;
// uint32_t channelType: 8;
uint32_t channelType: 4;
uint32_t qualifierLinear: 1;
uint32_t qualifierExponent: 1;
uint32_t qualifierSigned: 1;
uint32_t qualifierFloat: 1;
uint32_t samplePosition0: 8;
uint32_t samplePosition1: 8;
uint32_t samplePosition2: 8;
uint32_t samplePosition3: 8;
uint32_t lower;
uint32_t upper;
bool operator==(const sample& rhs) const {
const uint32_t* a = reinterpret_cast<const uint32_t*>(this);
const uint32_t* b = reinterpret_cast<const uint32_t*>(&rhs);
for (uint32_t i = 0; i < 4; i++) {
if (a[i] != b[i]) return false;
}
return true;
}
/// @brief Constructs an uninitialized sample object
sample() = default;
/// @brief Construct a sample with default sampleUpper and sampleLower.
///
/// For uncompressed formats. Handle integer data as normalized. For
/// unsigned use the full range of the number of bits. For signed set
/// sampleUpper and sampleLower so 0 is representable.
sample(uint32_t chanType,
uint32_t bLength, uint32_t offset,
khr_df_sample_datatype_qualifiers_e dataType
= static_cast<khr_df_sample_datatype_qualifiers_e>(0),
khr_df_transfer_e tf = KHR_DF_TRANSFER_UNSPECIFIED,
khr_df_model_e m = KHR_DF_MODEL_RGBSDA) {
bitOffset = offset;
bitLength = bLength - 1;
channelType = chanType;
if (channelType == 3 && m != KHR_DF_MODEL_XYZW) {
/// XYZW does not have an alpha chennel. *_ALPHA has the same
/// value for all other 4-channel-capable uncompressed models.
channelType = KHR_DF_CHANNEL_RGBSDA_ALPHA;
}
qualifierFloat = (dataType & KHR_DF_SAMPLE_DATATYPE_FLOAT) != 0;
qualifierSigned = (dataType & KHR_DF_SAMPLE_DATATYPE_SIGNED) != 0;
qualifierExponent = (dataType & KHR_DF_SAMPLE_DATATYPE_EXPONENT) != 0;
qualifierLinear = (dataType & KHR_DF_SAMPLE_DATATYPE_LINEAR) != 0;
if (tf > KHR_DF_TRANSFER_LINEAR
&& channelType == KHR_DF_CHANNEL_RGBSDA_ALPHA) {
qualifierLinear = 1;
}
union {
uint32_t i;
float f;
} uLower, uUpper;
if (qualifierFloat) {
if (qualifierSigned) {
uUpper.f = 1.0f;
uLower.f = -1.0f;
} else {
uUpper.f = 1.0f;
uLower.f = 0.0f;
}
} else {
if (qualifierSigned) {
// signed normalized
if (bitLength > 32) {
uUpper.i = 0x7FFFFFFF;
} else {
uUpper.i = (1U << (bLength - 1)) - 1;
}
uLower.i = ~uUpper.i;
uLower.i += 1;
} else {
// unsigned normalized
if (bitLength > 32) {
uUpper.i = 0xFFFFFFFFU;
} else {
uUpper.i = (uint32_t)((1U << bLength) - 1U);
}
uLower.i = 0U;
}
}
lower = uLower.i;
upper = uUpper.i;
}
/// @brief Construct a sample with custom sampleLower and sampleUpper.
///
/// For uncompressed formats.
sample(uint32_t chanType,
uint32_t bitLength, uint32_t offset,
uint32_t sampleLower, uint32_t sampleUpper,
khr_df_sample_datatype_qualifiers_e dataType
= static_cast<khr_df_sample_datatype_qualifiers_e>(0),
khr_df_transfer_e tf = KHR_DF_TRANSFER_UNSPECIFIED,
khr_df_model_e m = KHR_DF_MODEL_RGBSDA)
: sample(chanType, bitLength, offset, dataType, tf, m)
{
if (qualifierFloat) {
throw std::runtime_error(
"Invalid use of constructor for float data"
);
}
lower = sampleLower;
upper = sampleUpper;
}
};
std::vector<sample> samples;
/// @brief Default constructor
///
/// Will have zero samples which means format unknown.
FormatDescriptor() { }
/// @brief Constructor for unpacked, non-compressed data.
///
/// All channels have the same number of bits and basic data type.
/// As all wide data types will be in local endianness we need only
/// one sample per channel.
///
/// Data is assumed to be unsigned normalized. @c sampleUpper will be
/// set to the max value representable by @a channelBitLength.
///
/// @c channelType will be set to the standard channel types for @a channelCount
/// and @a m.
FormatDescriptor(uint32_t channelCount, uint32_t channelBitLength,
khr_df_sample_datatype_qualifiers_e dt
= static_cast<khr_df_sample_datatype_qualifiers_e>(0),
khr_df_transfer_e t = KHR_DF_TRANSFER_UNSPECIFIED,
khr_df_primaries_e p = KHR_DF_PRIMARIES_BT709,
khr_df_model_e m = KHR_DF_MODEL_RGBSDA,
khr_df_flags_e f = KHR_DF_FLAG_ALPHA_STRAIGHT)
: basic((channelBitLength * channelCount) / 8, t, p, m, f),
extended(channelCount)
{
for (uint32_t s = 0; s < channelCount; s++) {
samples.push_back(sample(s, channelBitLength,
s * channelBitLength,
dt, t, m));
}
if (m == KHR_DF_MODEL_YUVSDA && channelCount == 2) {
samples[1].channelType = KHR_DF_CHANNEL_YUVSDA_ALPHA;
}
extended.sameUnitAllChannels = true;
}
/// @brief Constructor for unpacked, non-compressed data with custom
/// sampleLower and sampleUpper
///
/// All channels have the same number of bits and basic data type.
/// Use this for unnormalized integer data or normalized data that does not
/// use the full range representable by @a channelBitLength.
FormatDescriptor(uint32_t channelCount, uint32_t channelBitLength,
uint32_t sampleLower, uint32_t sampleUpper,
khr_df_sample_datatype_qualifiers_e dt
= static_cast<khr_df_sample_datatype_qualifiers_e>(0),
khr_df_transfer_e t = KHR_DF_TRANSFER_UNSPECIFIED,
khr_df_primaries_e p = KHR_DF_PRIMARIES_BT709,
khr_df_model_e m = KHR_DF_MODEL_RGBSDA,
khr_df_flags_e f = KHR_DF_FLAG_ALPHA_STRAIGHT)
: basic((channelBitLength * channelCount) / 8, t, p, m, f),
extended(channelCount)
{
for (uint32_t s = 0; s < channelCount; s++) {
samples.push_back(sample(s, channelBitLength,
s * channelBitLength,
sampleLower, sampleUpper,
dt, t, m));
}
if (m == KHR_DF_MODEL_YUVSDA && channelCount == 2) {
samples[1].channelType = KHR_DF_CHANNEL_YUVSDA_ALPHA;
}
extended.sameUnitAllChannels = true;
}
static uint32_t totalBits(uint32_t sampleCount, std::vector<uint32_t>& bits) {
uint32_t totalBits = 0;
for (uint32_t s = 0; s < sampleCount; s++) {
totalBits += bits[s];
}
return totalBits;
}
/// @brief Constructor for non-compressed textures with varying bit lengths or channel types.
///
/// Each channel has the same basic data type.
FormatDescriptor(uint32_t channelCount,
std::vector<uint32_t>& channelBitLengths,
std::vector<khr_df_model_channels_e>& channelTypes,
khr_df_sample_datatype_qualifiers_e dt
= static_cast<khr_df_sample_datatype_qualifiers_e>(0),
khr_df_transfer_e t = KHR_DF_TRANSFER_UNSPECIFIED,
khr_df_primaries_e p = KHR_DF_PRIMARIES_BT709,
khr_df_model_e m = KHR_DF_MODEL_RGBSDA,
khr_df_flags_e f = KHR_DF_FLAG_ALPHA_STRAIGHT)
: basic(totalBits(channelCount, channelBitLengths) >> 3, t, p, m, f),
extended(channelCount)
{
if (channelCount > channelBitLengths.size()
|| channelCount > channelTypes.size()) {
throw std::runtime_error(
"Not enough channelBits or channelType specfications."
);
}
uint32_t bitOffset = 0;
bool bitLengthsEqual = true;
uint32_t firstBitLength = channelBitLengths[0];
for (uint32_t s = 0; s < channelCount; s++) {
samples.push_back(sample(channelTypes[s], channelBitLengths[s],
bitOffset, dt, t, m));
bitOffset += channelBitLengths[s];
if (firstBitLength != channelBitLengths[s]) {
bitLengthsEqual = false;
}
}
if (bitLengthsEqual) {
extended.sameUnitAllChannels = true;
}
if (m == KHR_DF_MODEL_YUVSDA && channelCount == 2) {
samples[1].channelType = KHR_DF_CHANNEL_YUVSDA_ALPHA;
}
}
/// @brief Constructor for non-compressed textures with varying bit lengths
/// or channel types and custom sampleLower and sampleUpper.
///
/// Each channel has the same basic data type. Use this for unnormalized
/// integer data or normalized data that does not use the full bit range.
FormatDescriptor(uint32_t channelCount,
std::vector<uint32_t>& channelBitLengths,
std::vector<khr_df_model_channels_e>& channelTypes,
std::vector<uint32_t>& samplesLower,
std::vector<uint32_t>& samplesUpper,
khr_df_sample_datatype_qualifiers_e dt
= static_cast<khr_df_sample_datatype_qualifiers_e>(0),
khr_df_transfer_e t = KHR_DF_TRANSFER_UNSPECIFIED,
khr_df_primaries_e p = KHR_DF_PRIMARIES_BT709,
khr_df_model_e m = KHR_DF_MODEL_RGBSDA,
khr_df_flags_e f = KHR_DF_FLAG_ALPHA_STRAIGHT)
: basic(totalBits(channelCount, channelBitLengths) >> 3, t, p, m, f),
extended(channelCount)
{
if (channelCount > channelBitLengths.size()
|| channelCount > channelTypes.size()) {
throw std::runtime_error(
"Not enough channelBits or channelType specfications."
);
}
uint32_t bitOffset = 0;
bool bitLengthsEqual = true;
uint32_t firstBitLength = channelBitLengths[0];
for (uint32_t s = 0; s < channelCount; s++) {
samples.push_back(sample(channelTypes[s], channelBitLengths[s],
samplesLower[s], samplesUpper[s],
bitOffset, dt, t, m));
bitOffset += channelBitLengths[s];
if (firstBitLength != channelBitLengths[s]) {
bitLengthsEqual = false;
}
}
if (bitLengthsEqual) {
extended.sameUnitAllChannels = true;
}
if (m == KHR_DF_MODEL_YUVSDA && channelCount == 2) {
samples[1].channelType = KHR_DF_CHANNEL_YUVSDA_ALPHA;
}
}
/// @brief Constructor for non-compressed, shared exponent format.
///
/// Each channel is a floating point. All channels share the same exponent
/// and have the same number of mantissa bits.
// TODO: Handle whether there is an implicit 1 and a sign bit.
FormatDescriptor(uint32_t channelCount,
uint32_t mantissaBitLength,
uint32_t exponentBitLength,
khr_df_sample_datatype_qualifiers_e dt
= static_cast<khr_df_sample_datatype_qualifiers_e>(0),
khr_df_transfer_e t = KHR_DF_TRANSFER_LINEAR,
khr_df_primaries_e p = KHR_DF_PRIMARIES_BT709,
khr_df_model_e m = KHR_DF_MODEL_RGBSDA,
khr_df_flags_e f = KHR_DF_FLAG_ALPHA_STRAIGHT)
: basic((channelCount * mantissaBitLength + exponentBitLength) >> 3,
t, p, m, f),
extended(channelCount)
{
if (dt & KHR_DF_SAMPLE_DATATYPE_FLOAT) {
throw std::runtime_error(
"DATATYPE_FLOAT is set for a shared exponent format");
}
for (uint32_t s = 0; s < channelCount; s++) {
uint32_t sampleLower = 0, sampleUpper;
// sampleUpper and sampleLower values for the mantissa should be
// set to indicate the representation of 1.0 and 0.0 (for unsigned
// formats) or -1.0 (for signed formats) respectively when the
// exponent is in a 0 position after any bias has been corrected.
// If there is an implicit 1 bit, these values for the mantissa
// will exceed what can be represented in the number of available
// mantissa bits.
sampleUpper = 1U << mantissaBitLength;
samples.push_back(sample(s, mantissaBitLength,
s * mantissaBitLength,
sampleLower, sampleUpper,
dt, t, m));
// The sampleLower for the exponent should indicate the exponent
// bias. That is, the mantissa should be scaled by two raised to
// the power of the stored exponent minus this sampleLower value.
//
// The sampleUpper for the exponent indicates the maximum legal
// exponent value. Values above this are used to encode infinities
// and not-a-number (NaN) values. sampleUpper can therefore be used
// to indicate whether or not the format supports these encodings.
//sampleLower = exponentBias;
//sampleUpper = maxLegalExponentValue;
samples.push_back(sample(s, exponentBitLength,
channelCount * mantissaBitLength,
sampleLower, sampleUpper,
static_cast<khr_df_sample_datatype_qualifiers_e>(dt | KHR_DF_SAMPLE_DATATYPE_EXPONENT),
t, m));
}
extended.sameUnitAllChannels = true;
}
/// @brief Constructor from pre-constructed basic and sample descriptors
FormatDescriptor(
FormatDescriptor::basicDescriptor basic,
std::vector<FormatDescriptor::sample> samples_)
: basic(basic),
extended(static_cast<uint32_t>(samples_.size())),
samples(std::move(samples_))
{
extended.sameUnitAllChannels = true;
if (!samples.empty()) {
for (uint32_t i = 1; i < static_cast<uint32_t>(samples.size()); ++i) {
if (samples[0].bitLength != samples[i].bitLength
|| samples[0].qualifierLinear != samples[i].qualifierLinear
|| samples[0].qualifierExponent != samples[i].qualifierExponent
|| samples[0].qualifierSigned != samples[i].qualifierSigned
|| samples[0].qualifierFloat != samples[i].qualifierFloat) {
extended.sameUnitAllChannels = false;
break;
}
}
}
}
bool isUnknown() const noexcept {
return samples.size() == 0;
}
bool sameUnitAllChannels() const noexcept {
return extended.sameUnitAllChannels;
}
bool operator==(const FormatDescriptor& rhs) const {
if (this->basic != rhs.basic) return false;
if (this->extended != rhs.extended) return false;
return this->samples == rhs.samples;
}
bool operator!=(const FormatDescriptor& rhs) const {
return !(*this == rhs);
}
khr_df_model_e model() const noexcept {
return basic.model();
}
khr_df_primaries_e primaries() const noexcept {
return basic.primaries();
}
khr_df_transfer_e transfer() const noexcept {
return basic.transfer();
}
khr_df_flags_e flags() const noexcept {
return basic.flags();
}
float oeGamma() const noexcept {
return extended.oeGamma;
}
const std::string& iccProfileName() const noexcept {
return extended.iccProfile.name;
}
const std::vector<uint8_t>& iccProfile() const noexcept {
return extended.iccProfile.profile;
}
void setModel(khr_df_model_e m) {
basic.colorModel = m;
}
void setPrimaries(khr_df_primaries_e p) {
basic.colorPrimaries = p;
}
void setTransfer(khr_df_transfer_e t) {
khr_df_transfer_e oldOetf = transfer();
basic.transferFunction = t;
if ((oldOetf <= KHR_DF_TRANSFER_LINEAR) != (t <= KHR_DF_TRANSFER_LINEAR))
{
std::vector<sample>::iterator sit = samples.begin();
for (; sit < samples.end(); sit++) {
if (sit->channelType == KHR_DF_CHANNEL_RGBSDA_ALPHA) {
sit->qualifierLinear = t > KHR_DF_TRANSFER_LINEAR;
}
}
}
}
uint32_t pixelByteCount() const noexcept {
return basic.bytesPlane0;
}
uint32_t sampleCount() const noexcept {
return static_cast<uint32_t>(samples.size());
}
uint32_t sampleBitLength(uint32_t s) const noexcept {
return samples[s].bitLength + 1;
}
// TODO: remove?
uint32_t sampleByteCount(uint32_t s) const noexcept {
// Use integer division so 0 is returned when length is < a byte.
return sampleBitLength(s) / 8;
}
uint32_t sampleUpper(uint32_t s) const noexcept {
return samples[s].upper;
}
uint32_t channelCount() const noexcept {
return extended.channelCount;
}
uint32_t channelBitLength(khr_df_model_channels_e c) const {
std::vector<sample>::const_iterator it = samples.begin();
uint32_t bitLength = 0;
for (; it < samples.end(); it++) {
if (it->channelType == static_cast<uint32_t>(c)) {
bitLength += it->bitLength + 1;
}
}
if (bitLength == 0) {
throw std::runtime_error("No such channel.");
}
return bitLength;
}
uint32_t channelBitLength() const {
if (channelCount() == 1)
return samples[0].bitLength + 1;
if (!extended.sameUnitAllChannels) {
throw std::runtime_error(
"Differing size channels. Specify channel to query."
);
}
return channelBitLength(KHR_DF_CHANNEL_RGBSDA_R);
}
uint32_t largestChannelBitLength() const {
uint32_t maxBitLength = 0;
for (uint32_t i = 0; i < 16; ++i) {
uint32_t bitLength = 0;
for (const auto& sample : samples)
if (sample.channelType == i)
bitLength += sample.bitLength + 1;
if (bitLength > maxBitLength)
maxBitLength = bitLength;
}
return maxBitLength;
}
bool anyChannelBitLengthNotEqual(uint32_t bitLength) const {
for (uint32_t i = 0; i < 16; ++i) {
uint32_t channelBitLength = 0;
for (const auto& sample : samples)
if (sample.channelType == i)
channelBitLength += sample.bitLength + 1;
if (bitLength != channelBitLength)
return true;
}
return false;
}
khr_df_sample_datatype_qualifiers_e
channelDataType(khr_df_model_channels_e c) const {
// TODO: Fix for shared exponent case...
std::vector<sample>::const_iterator it = samples.begin();
for (; it < samples.end(); it++) {
if (it->channelType == static_cast<uint32_t>(c)) {
return static_cast<khr_df_sample_datatype_qualifiers_e>
(it->channelType & KHR_DF_SAMPLEMASK_QUALIFIERS);
}
}
throw std::runtime_error("No such channel.");
}
khr_df_sample_datatype_qualifiers_e channelDataType() const {
if (!extended.sameUnitAllChannels) {
throw std::runtime_error(
"Differing size channels. Specify channel to query."
);
}
return channelDataType(KHR_DF_CHANNEL_RGBSDA_R);
}
uint32_t channelUpper() const {
if (extended.channelCount != samples.size()) {
throw std::runtime_error(
"Multiple samples per channel. Call sampleUpper(uint32_t s)."
);
}
if (!extended.sameUnitAllChannels) {
throw std::runtime_error(
"Differing size channels. Call sampleUpper(uint32_t s)."
);
}
return sampleUpper(KHR_DF_CHANNEL_RGBSDA_R);
}
void updateSampleInfo(uint32_t channelCount, uint32_t channelBitLength,
uint32_t sampleLower, uint32_t sampleUpper,
khr_df_sample_datatype_qualifiers_e dt
= static_cast<khr_df_sample_datatype_qualifiers_e>(0))
{
samples.clear();
for (uint32_t s = 0; s < channelCount; s++) {
samples.push_back(sample(s, channelBitLength,
s * channelBitLength,
sampleLower, sampleUpper,
dt,
basic.transfer(),
basic.model()));
}
if (basic.model() == KHR_DF_MODEL_YUVSDA && channelCount == 2) {
samples[1].channelType = KHR_DF_CHANNEL_YUVSDA_ALPHA;
}
extended.channelCount = channelCount;
extended.sameUnitAllChannels = true;
}
void updateSampleBitCounts(std::vector<uint32_t>& bits) {
uint32_t b, s;
uint32_t offset = 0;
for (b = 0, s = 0; s < samples.size(); s++) {
samples[s].bitLength = bits[b] - 1;
samples[s].bitOffset = offset;
offset += bits[b];
if (b < bits.size() - 1)
b++;
// else set remaining sample sizes to last available bits value.
}
}
void updateChannelCount(uint32_t newCount) {
if (newCount == extended.channelCount)
return;
if (extended.channelCount != samples.size()) {
// TODO: Either fix error handling or implement
throw std::runtime_error(
"changeChannelCount not supported when # samples != # channels");
}
extended.channelCount = newCount;
if (newCount < samples.size()) {
samples.erase(samples.begin() + newCount, samples.end());
return;
}
uint32_t firstNewIndex = static_cast<uint32_t>(samples.size());
uint32_t offset = samples.back().bitOffset
+ samples.back().bitLength + 1;
samples.resize(newCount, samples.back());
std::vector<sample>::iterator sit = samples.begin() + firstNewIndex;
for (; sit < samples.end(); sit++) {
sit->bitOffset = offset;
offset += (sit->bitLength + 1);
}
}
[[nodiscard]] const sample* find(khr_df_model_channels_e channel) const {
for (const auto& sample : samples)
if (sample.channelType == static_cast<uint32_t>(channel))
return &sample;
return nullptr;
}
void removeLastChannel() {
const auto numChannels = static_cast<uint32_t>(samples.size());
assert(numChannels > 1);
assert(basic.bytesPlane0 % numChannels == 0);
samples.pop_back();
basic.bytesPlane0 = basic.bytesPlane0 / numChannels * (numChannels - 1u);
if (extended.channelCount != 0)
--extended.channelCount;
}
friend std::ostream& operator<< (std::ostream& o, khr_df_sample_datatype_qualifiers_e q) {
if (q & KHR_DF_SAMPLE_DATATYPE_SIGNED)
o << " signed ";
if (q & KHR_DF_SAMPLE_DATATYPE_FLOAT) {
if (!(q & KHR_DF_SAMPLE_DATATYPE_SIGNED))
o << " ";
o << "float ";
}
return o;
}
};