From 1d639ef88f7fb5a7e93a995e3e9acbbcbb8b81c0 Mon Sep 17 00:00:00 2001 From: Daniel Rakos Date: Mon, 21 Aug 2023 18:09:59 +0200 Subject: [PATCH] Add support for fewer components in input files --- tests/cts | 2 +- tools/imageio/exr.imageio/exrinput.cc | 37 +++--- tools/ktx/command_create.cpp | 183 ++++++++++++-------------- 3 files changed, 106 insertions(+), 116 deletions(-) diff --git a/tests/cts b/tests/cts index 4ac86b12e5..3bb8db3b92 160000 --- a/tests/cts +++ b/tests/cts @@ -1 +1 @@ -Subproject commit 4ac86b12e5b43d8421b1c4757a7be2cd69b328f9 +Subproject commit 3bb8db3b922e32b1e2e7c8da1935dca083c545ff diff --git a/tools/imageio/exr.imageio/exrinput.cc b/tools/imageio/exr.imageio/exrinput.cc index 20c0eb42af..75ddab2ad2 100644 --- a/tools/imageio/exr.imageio/exrinput.cc +++ b/tools/imageio/exr.imageio/exrinput.cc @@ -229,10 +229,6 @@ void ExrInput::readImage(void* outputBuffer, size_t bufferByteCount, const auto numSourceChannels = static_cast(image.num_channels); const auto numTargetChannels = targetFormat.channelCount(); - if (numTargetChannels > numSourceChannels) - throw std::runtime_error(fmt::format("EXR load error: " - "Requested {} channels but the input file only has {}.", numTargetChannels, numSourceChannels)); - const auto expectedBufferByteCount = height * width * numTargetChannels * targetBitDepth / 8; if (bufferByteCount != expectedBufferByteCount) throw std::runtime_error(fmt::format("EXR load error: " @@ -253,12 +249,8 @@ void ExrInput::readImage(void* outputBuffer, size_t bufferByteCount, warning(fmt::format("EXR load warning: Unrecognized channel \"{}\" is ignored.", header.channels[i].name)); } - for (uint32_t i = 0; i < numTargetChannels; ++i) - if (!channels[i]) - throw std::runtime_error(fmt::format("EXR load error: Requested channel {} is not present in the input file.", i)); - // Copy the data - const auto copyData = [&](unsigned char* ptr, uint32_t dataSize) { + const auto copyData = [&](unsigned char* ptr, uint32_t dataSize, const void* defaultColor) { const auto sourcePtr = [&](uint32_t channel, uint32_t x, uint32_t y) { return reinterpret_cast(image.images[channel] + (y * width + x) * dataSize); }; @@ -266,22 +258,33 @@ void ExrInput::readImage(void* outputBuffer, size_t bufferByteCount, for (uint32_t y = 0; y < height; ++y) { for (uint32_t x = 0; x < width; ++x) { auto* targetPixel = ptr + (y * width * numTargetChannels + x * numTargetChannels) * dataSize; - for (uint32_t c = 0; c < numTargetChannels; ++c) - std::memcpy(targetPixel + c * dataSize, sourcePtr(*channels[c], x, y), dataSize); + for (uint32_t c = 0; c < numTargetChannels; ++c) { + if (channels[c].has_value()) { + std::memcpy(targetPixel + c * dataSize, sourcePtr(*channels[c], x, y), dataSize); + } else { + std::memcpy(targetPixel + c * dataSize, static_cast(defaultColor) + c * dataSize, dataSize); + } + } } } }; switch (requestedType) { - case TINYEXR_PIXELTYPE_HALF: - copyData(reinterpret_cast(outputBuffer), 2); // sizeof(half) + case TINYEXR_PIXELTYPE_HALF: { + uint16_t defaultColor[] = { 0x0000, 0x0000, 0x0000, 0x3C00 }; // { 0.h, 0.h, 0.h,1.h } + copyData(reinterpret_cast(outputBuffer), sizeof(defaultColor[0]), &defaultColor[0]); break; - case TINYEXR_PIXELTYPE_FLOAT: - copyData(reinterpret_cast(outputBuffer), sizeof(float)); + } + case TINYEXR_PIXELTYPE_FLOAT: { + float defaultColor[] = { 0.f, 0.f, 0.f, 1.f }; + copyData(reinterpret_cast(outputBuffer), sizeof(defaultColor[0]), &defaultColor[0]); break; - case TINYEXR_PIXELTYPE_UINT: - copyData(reinterpret_cast(outputBuffer), sizeof(uint32_t)); + } + case TINYEXR_PIXELTYPE_UINT: { + uint32_t defaultColor[] = { 0, 0, 0, 1 }; + copyData(reinterpret_cast(outputBuffer), sizeof(defaultColor[0]), &defaultColor[0]); break; + } default: assert(false && "Internal error"); break; diff --git a/tools/ktx/command_create.cpp b/tools/ktx/command_create.cpp index 6298bf8442..7010f21204 100644 --- a/tools/ktx/command_create.cpp +++ b/tools/ktx/command_create.cpp @@ -819,7 +819,7 @@ class CommandCreate : public Command { uint32_t numMipLevels, uint32_t layerIndex, uint32_t faceIndex, uint32_t depthSliceIndex); [[nodiscard]] std::string readRawFile(const std::filesystem::path& filepath); - [[nodiscard]] std::unique_ptr loadInputImage(ImageInput& inputImageFile); + [[nodiscard]] std::unique_ptr loadInputImage(ImageInput& inputImageFile, const ImageSpec& target); std::vector convert(const std::unique_ptr& image, VkFormat format, ImageInput& inputFile); std::unique_ptr createColorPrimaries(khr_df_primaries_e primaries) const; @@ -1123,7 +1123,7 @@ void CommandCreate::executeCreate() { fatal(rc::INVALID_FILE, "Input image \"{}\" with size {}x{} does not match expected size {}x{} for level {}.", fmtInFile(inputFilepath), inputImageFile->spec().width(), inputImageFile->spec().height(), imageWidth, imageHeight, levelIndex); - auto image = loadInputImage(*inputImageFile); + auto image = loadInputImage(*inputImageFile, target); if (colorSpaceInfo.dstTransferFunction != nullptr) { assert(colorSpaceInfo.srcTransferFunction != nullptr); @@ -1250,31 +1250,22 @@ void CommandCreate::compress(KTXTexture2& texture, const OptionsCompress& opts) // ------------------------------------------------------------------------------------------------- -std::unique_ptr CommandCreate::loadInputImage(ImageInput& inputImageFile) { +std::unique_ptr CommandCreate::loadInputImage(ImageInput& inputImageFile, const ImageSpec& target) { std::unique_ptr image = nullptr; const auto& inputFormat = inputImageFile.spec().format(); const auto width = inputImageFile.spec().width(); const auto height = inputImageFile.spec().height(); + const uint32_t channelCount = std::min(4u, std::max(target.format().channelCount(), inputFormat.channelCount())); const auto inputBitLength = inputFormat.largestChannelBitLength(); const auto requestBitLength = std::max(bit_ceil(inputBitLength), 8u); - const auto requestChannelCount = [&]() -> uint32_t { - switch (inputImageFile.formatType()) { - case ImageInputFormatType::png_l: - // Load luminance images as RGB for processing as: L -> LLL1 - return 3; - case ImageInputFormatType::png_la: - // Load luminance-alpha images as RGBA for processing as: L -> LLLA - return 4; - default: - return inputFormat.channelCount(); - } - }(); FormatDescriptor loadFormat; + assert(channelCount >= 1 && channelCount <= 4); + if (inputImageFile.formatType() == ImageInputFormatType::exr_float) { - switch (requestChannelCount) { + switch (channelCount) { case 1: image = std::make_unique(width, height); loadFormat = createFormatDescriptor(VK_FORMAT_R32_SFLOAT, *this); @@ -1293,7 +1284,7 @@ std::unique_ptr CommandCreate::loadInputImage(ImageInput& inputImageFile) break; } } else if (requestBitLength == 8) { - switch (requestChannelCount) { + switch (channelCount) { case 1: image = std::make_unique(width, height); loadFormat = createFormatDescriptor(VK_FORMAT_R8_UNORM, *this); @@ -1312,7 +1303,7 @@ std::unique_ptr CommandCreate::loadInputImage(ImageInput& inputImageFile) break; } } else if (requestBitLength == 16) { - switch (requestChannelCount) { + switch (channelCount) { case 1: image = std::make_unique(width, height); loadFormat = createFormatDescriptor(VK_FORMAT_R16_UNORM, *this); @@ -1331,7 +1322,7 @@ std::unique_ptr CommandCreate::loadInputImage(ImageInput& inputImageFile) break; } } else if (requestBitLength == 32) { - switch (requestChannelCount) { + switch (channelCount) { case 1: image = std::make_unique(width, height); loadFormat = createFormatDescriptor(VK_FORMAT_R32_UINT, *this); @@ -1350,8 +1341,7 @@ std::unique_ptr CommandCreate::loadInputImage(ImageInput& inputImageFile) break; } } else { - fatal(rc::INVALID_FILE, "Unsupported format with {}-bit and {} channel.", - requestBitLength, requestChannelCount); + fatal(rc::INVALID_FILE, "Unsupported format with {}-bit channels.", requestBitLength); } inputImageFile.readImage(static_cast(*image), image->getByteCount(), 0, 0, loadFormat); @@ -1456,14 +1446,9 @@ std::vector convertSINT(const std::unique_ptr& image, std::strin std::vector CommandCreate::convert(const std::unique_ptr& image, VkFormat vkFormat, ImageInput& inputFile) { - const uint32_t inputChannelCount = image->getComponentCount(); const uint32_t inputBitDepth = std::max(8u, inputFile.spec().format().largestChannelBitLength()); - const auto require = [&](uint32_t channelCount, uint32_t bitDepth) { - if (inputChannelCount < channelCount) - fatal(rc::INVALID_FILE, "{}: Input file channel count {} is less than the required {} for {}.", - inputFile.filename(), inputChannelCount, channelCount, toString(vkFormat)); - + const auto require = [&](uint32_t bitDepth) { if (inputBitDepth < bitDepth) fatal(rc::INVALID_FILE, "{}: Not enough precision to convert {} bit input to {} bit output for {}.", inputFile.filename(), inputBitDepth, bitDepth, toString(vkFormat)); @@ -1471,7 +1456,7 @@ std::vector CommandCreate::convert(const std::unique_ptr& image, warning("{}: Possible loss of precision with converting {} bit input to {} bit output for {}.", inputFile.filename(), inputBitDepth, bitDepth, toString(vkFormat)); }; - const auto requireUNORM = [&](uint32_t channelCount, uint32_t bitDepth) { + const auto requireUNORM = [&](uint32_t bitDepth) { switch (inputFile.formatType()) { case ImageInputFormatType::png_l: [[fallthrough]]; case ImageInputFormatType::png_la: [[fallthrough]]; @@ -1485,9 +1470,9 @@ std::vector CommandCreate::convert(const std::unique_ptr& image, fatal(rc::INVALID_FILE, "{}: Input file data type \"{}\" does not match the expected input data type of {} bit \"{}\" for {}.", inputFile.filename(), toString(inputFile.formatType()), bitDepth, "UNORM", toString(vkFormat)); } - require(channelCount, bitDepth); + require(bitDepth); }; - const auto requireSFloat = [&](uint32_t channelCount, uint32_t bitDepth) { + const auto requireSFloat = [&](uint32_t bitDepth) { switch (inputFile.formatType()) { case ImageInputFormatType::exr_float: break; // Accept @@ -1501,9 +1486,9 @@ std::vector CommandCreate::convert(const std::unique_ptr& image, fatal(rc::INVALID_FILE, "{}: Input file data type \"{}\" does not match the expected input data type of {} bit \"{}\" for {}.", inputFile.filename(), toString(inputFile.formatType()), bitDepth, "SFLOAT", toString(vkFormat)); } - require(channelCount, bitDepth); + require(bitDepth); }; - const auto requireUINT = [&](uint32_t channelCount, uint32_t bitDepth) { + const auto requireUINT = [&](uint32_t bitDepth) { switch (inputFile.formatType()) { case ImageInputFormatType::exr_uint: break; // Accept @@ -1517,7 +1502,7 @@ std::vector CommandCreate::convert(const std::unique_ptr& image, fatal(rc::INVALID_FILE, "{}: Input file data type \"{}\" does not match the expected input data type of {} bit \"{}\" for {}.", inputFile.filename(), toString(inputFile.formatType()), bitDepth, "UINT", toString(vkFormat)); } - require(channelCount, bitDepth); + require(bitDepth); }; // ------------ @@ -1527,19 +1512,19 @@ std::vector CommandCreate::convert(const std::unique_ptr& image, case VK_FORMAT_R8_UNORM: [[fallthrough]]; case VK_FORMAT_R8_SRGB: - requireUNORM(1, 8); + requireUNORM(8); return convertUNORM(image); case VK_FORMAT_R8G8_UNORM: [[fallthrough]]; case VK_FORMAT_R8G8_SRGB: - requireUNORM(2, 8); + requireUNORM(8); return convertUNORM(image); case VK_FORMAT_R8G8B8_UNORM: [[fallthrough]]; case VK_FORMAT_R8G8B8_SRGB: - requireUNORM(3, 8); + requireUNORM(8); return convertUNORM(image); case VK_FORMAT_B8G8R8_UNORM: [[fallthrough]]; case VK_FORMAT_B8G8R8_SRGB: - requireUNORM(3, 8); + requireUNORM(8); return convertUNORM(image, "bgr1"); // Verbatim copy with component reordering if needed, extra channels must be dropped. @@ -1549,11 +1534,11 @@ std::vector CommandCreate::convert(const std::unique_ptr& image, case VK_FORMAT_R8G8B8A8_UNORM: [[fallthrough]]; case VK_FORMAT_R8G8B8A8_SRGB: - requireUNORM(4, 8); + requireUNORM(8); return convertUNORM(image); case VK_FORMAT_B8G8R8A8_UNORM: [[fallthrough]]; case VK_FORMAT_B8G8R8A8_SRGB: - requireUNORM(4, 8); + requireUNORM(8); return convertUNORM(image, "bgra"); // Verbatim copy with component reordering if needed, extra channels must be dropped. @@ -1591,91 +1576,91 @@ std::vector CommandCreate::convert(const std::unique_ptr& image, case VK_FORMAT_ASTC_12x12_SRGB_BLOCK: // ASTC texture data composition is performed via // R8G8B8A8_UNORM followed by the ASTC encoding - requireUNORM(4, 8); + requireUNORM(8); assert(false && "Internal error"); return {}; // Passthrough CLI options to the ASTC encoder. case VK_FORMAT_R4G4_UNORM_PACK8: - requireUNORM(2, 8); + requireUNORM(8); return convertUNORMPacked(image, 4, 4, 0, 0); case VK_FORMAT_R5G6B5_UNORM_PACK16: - requireUNORM(3, 8); + requireUNORM(8); return convertUNORMPacked(image, 5, 6, 5, 0); case VK_FORMAT_B5G6R5_UNORM_PACK16: - requireUNORM(3, 8); + requireUNORM(8); return convertUNORMPacked(image, 5, 6, 5, 0, "bgr1"); case VK_FORMAT_R4G4B4A4_UNORM_PACK16: - requireUNORM(4, 8); + requireUNORM(8); return convertUNORMPacked(image, 4, 4, 4, 4); case VK_FORMAT_B4G4R4A4_UNORM_PACK16: - requireUNORM(4, 8); + requireUNORM(8); return convertUNORMPacked(image, 4, 4, 4, 4, "bgra"); case VK_FORMAT_R5G5B5A1_UNORM_PACK16: - requireUNORM(4, 8); + requireUNORM(8); return convertUNORMPacked(image, 5, 5, 5, 1); case VK_FORMAT_B5G5R5A1_UNORM_PACK16: - requireUNORM(4, 8); + requireUNORM(8); return convertUNORMPacked(image, 5, 5, 5, 1, "bgra"); case VK_FORMAT_A1R5G5B5_UNORM_PACK16: - requireUNORM(4, 8); + requireUNORM(8); return convertUNORMPacked(image, 1, 5, 5, 5, "argb"); case VK_FORMAT_A4R4G4B4_UNORM_PACK16_EXT: - requireUNORM(4, 8); + requireUNORM(8); return convertUNORMPacked(image, 4, 4, 4, 4, "argb"); case VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT: - requireUNORM(4, 8); + requireUNORM(8); return convertUNORMPacked(image, 4, 4, 4, 4, "abgr"); // Input values must be rounded to the target precision. // When the input file contains an sBIT chunk, its values must be taken into account. case VK_FORMAT_R10X6_UNORM_PACK16: - requireUNORM(1, 10); + requireUNORM(10); return convertUNORMPackedPadded(image, 10, 6); case VK_FORMAT_R10X6G10X6_UNORM_2PACK16: - requireUNORM(2, 10); + requireUNORM(10); return convertUNORMPackedPadded(image, 10, 6, 10, 6); case VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16: - requireUNORM(4, 10); + requireUNORM(10); return convertUNORMPackedPadded(image, 10, 6, 10, 6, 10, 6, 10, 6); case VK_FORMAT_R12X4_UNORM_PACK16: - requireUNORM(1, 12); + requireUNORM(12); return convertUNORMPackedPadded(image, 12, 4); case VK_FORMAT_R12X4G12X4_UNORM_2PACK16: - requireUNORM(2, 12); + requireUNORM(12); return convertUNORMPackedPadded(image, 12, 4, 12, 4); case VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16: - requireUNORM(4, 12); + requireUNORM(12); return convertUNORMPackedPadded(image, 12, 4, 12, 4, 12, 4, 12, 4); // Input values must be rounded to the target precision. // When the input file contains an sBIT chunk, its values must be taken into account. case VK_FORMAT_R16_UNORM: - requireUNORM(1, 16); + requireUNORM(16); return convertUNORM(image); case VK_FORMAT_R16G16_UNORM: - requireUNORM(2, 16); + requireUNORM(16); return convertUNORM(image); case VK_FORMAT_R16G16B16_UNORM: - requireUNORM(3, 16); + requireUNORM(16); return convertUNORM(image); case VK_FORMAT_R16G16B16A16_UNORM: - requireUNORM(4, 16); + requireUNORM(16); return convertUNORM(image); // Verbatim copy, extra channels must be dropped. // Input PNG file must be 16-bit with sBIT chunk missing or signaling 16 bits. case VK_FORMAT_A2R10G10B10_UNORM_PACK32: - requireUNORM(4, 10); + requireUNORM(10); return convertUNORMPacked(image, 2, 10, 10, 10, "argb"); case VK_FORMAT_A2B10G10R10_UNORM_PACK32: - requireUNORM(4, 10); + requireUNORM(10); return convertUNORMPacked(image, 2, 10, 10, 10, "abgr"); // Input values must be rounded to the target precision. @@ -1695,126 +1680,128 @@ std::vector CommandCreate::convert(const std::unique_ptr& image, // EXR: case VK_FORMAT_R8_UINT: - requireSFloat(1, 16); + requireSFloat(16); return convertUINT(image); case VK_FORMAT_R8_SINT: - requireSFloat(1, 16); + requireSFloat(16); return convertSINT(image); case VK_FORMAT_R16_UINT: - requireSFloat(1, 32); + requireSFloat(32); return convertUINT(image); case VK_FORMAT_R16_SINT: - requireSFloat(1, 32); + requireSFloat(32); return convertSINT(image); case VK_FORMAT_R32_UINT: - requireUINT(1, 32); + requireUINT(32); return convertUINT(image); case VK_FORMAT_R8G8_UINT: - requireSFloat(2, 16); + requireSFloat(16); return convertUINT(image); case VK_FORMAT_R8G8_SINT: - requireSFloat(2, 16); + requireSFloat(16); return convertSINT(image); case VK_FORMAT_R16G16_UINT: - requireSFloat(2, 32); + requireSFloat(32); return convertUINT(image); case VK_FORMAT_R16G16_SINT: - requireSFloat(2, 32); + requireSFloat(32); return convertSINT(image); case VK_FORMAT_R32G32_UINT: - requireUINT(2, 32); + requireUINT(32); return convertUINT(image); case VK_FORMAT_R8G8B8_UINT: - requireSFloat(3, 16); + requireSFloat(16); return convertUINT(image); case VK_FORMAT_R8G8B8_SINT: - requireSFloat(3, 16); + requireSFloat(16); return convertSINT(image); case VK_FORMAT_B8G8R8_UINT: - requireSFloat(3, 16); + requireSFloat(16); return convertUINT(image, "bgr1"); case VK_FORMAT_B8G8R8_SINT: - requireSFloat(3, 16); + requireSFloat(16); return convertSINT(image, "bgr1"); case VK_FORMAT_R16G16B16_UINT: - requireSFloat(3, 32); + requireSFloat(32); return convertUINT(image); case VK_FORMAT_R16G16B16_SINT: - requireSFloat(3, 32); + requireSFloat(32); return convertSINT(image); case VK_FORMAT_R32G32B32_UINT: - requireUINT(3, 32); + requireUINT(32); return convertUINT(image); case VK_FORMAT_R8G8B8A8_UINT: - requireSFloat(4, 16); + requireSFloat(16); return convertUINT(image); case VK_FORMAT_R8G8B8A8_SINT: - requireSFloat(4, 16); + requireSFloat(16); return convertSINT(image); case VK_FORMAT_B8G8R8A8_UINT: - requireSFloat(4, 16); + requireSFloat(16); return convertUINT(image, "bgra"); case VK_FORMAT_B8G8R8A8_SINT: - requireSFloat(4, 16); + requireSFloat(16); return convertSINT(image, "bgra"); case VK_FORMAT_R16G16B16A16_UINT: - requireSFloat(4, 32); + requireSFloat(32); return convertUINT(image); case VK_FORMAT_R16G16B16A16_SINT: - requireSFloat(4, 32); + requireSFloat(32); return convertSINT(image); case VK_FORMAT_R32G32B32A32_UINT: - requireUINT(4, 32); + requireUINT(32); return convertUINT(image); case VK_FORMAT_A2R10G10B10_UINT_PACK32: - requireSFloat(4, 16); + requireSFloat(16); return convertUINTPacked(image, 2, 10, 10, 10, "argb"); case VK_FORMAT_A2R10G10B10_SINT_PACK32: - requireSFloat(4, 16); + requireSFloat(16); return convertSINTPacked(image, 2, 10, 10, 10, "argb"); case VK_FORMAT_A2B10G10R10_UINT_PACK32: - requireSFloat(4, 16); + requireSFloat(16); return convertUINTPacked(image, 2, 10, 10, 10, "abgr"); case VK_FORMAT_A2B10G10R10_SINT_PACK32: - requireSFloat(4, 16); + requireSFloat(16); return convertSINTPacked(image, 2, 10, 10, 10, "abgr"); // The same EXR pixel types as for the decoding must be enforced. // Extra channels must be dropped. case VK_FORMAT_R16_SFLOAT: - requireSFloat(1, 16); + requireSFloat(16); return convertSFLOAT(image); case VK_FORMAT_R16G16_SFLOAT: - requireSFloat(2, 16); + requireSFloat(16); return convertSFLOAT(image); case VK_FORMAT_R16G16B16_SFLOAT: - requireSFloat(3, 16); + requireSFloat(16); return convertSFLOAT(image); case VK_FORMAT_R16G16B16A16_SFLOAT: - requireSFloat(4, 16); + requireSFloat(16); return convertSFLOAT(image); case VK_FORMAT_R32_SFLOAT: - requireSFloat(1, 32); + requireSFloat(32); return convertSFLOAT(image); case VK_FORMAT_R32G32_SFLOAT: - requireSFloat(2, 32); + requireSFloat(32); return convertSFLOAT(image); case VK_FORMAT_R32G32B32_SFLOAT: - requireSFloat(3, 32); + requireSFloat(32); return convertSFLOAT(image); case VK_FORMAT_R32G32B32A32_SFLOAT: - requireSFloat(4, 32); + requireSFloat(32); return convertSFLOAT(image); // The same EXR pixel types as for the decoding must be enforced. // Extra channels must be dropped. case VK_FORMAT_B10G11R11_UFLOAT_PACK32: + requireSFloat(16); return convertB10G11R11(image); case VK_FORMAT_E5B9G9R9_UFLOAT_PACK32: + requireSFloat(16); return convertE5B9G9R9(image); // Input data must be rounded to the target precision.