From 1c0b9410ef631c2390b49fba905fc7dcdf1073f0 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 | 244 +++++++++----------------- 3 files changed, 102 insertions(+), 181 deletions(-) diff --git a/tests/cts b/tests/cts index 83b23ce3e2..0325f78ff0 160000 --- a/tests/cts +++ b/tests/cts @@ -1 +1 @@ -Subproject commit 83b23ce3e24728576a67d7e3c8c128f0f5a6927f +Subproject commit 0325f78ff0bfe302c85b48392e9cda5ebd411355 diff --git a/tools/imageio/exr.imageio/exrinput.cc b/tools/imageio/exr.imageio/exrinput.cc index 107b22374f..4b5f8236b4 100644 --- a/tools/imageio/exr.imageio/exrinput.cc +++ b/tools/imageio/exr.imageio/exrinput.cc @@ -228,10 +228,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: " @@ -252,12 +248,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); }; @@ -265,22 +257,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 95d74a853a..a1443ba872 100644 --- a/tools/ktx/command_create.cpp +++ b/tools/ktx/command_create.cpp @@ -1259,99 +1259,22 @@ std::unique_ptr CommandCreate::loadInputImage(ImageInput& inputImageFile) 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; if (inputImageFile.formatType() == ImageInputFormatType::exr_float) { - switch (requestChannelCount) { - case 1: - image = std::make_unique(width, height); - loadFormat = createFormatDescriptor(VK_FORMAT_R32_SFLOAT, *this); - break; - case 2: - image = std::make_unique(width, height); - loadFormat = createFormatDescriptor(VK_FORMAT_R32G32_SFLOAT, *this); - break; - case 3: - image = std::make_unique(width, height); - loadFormat = createFormatDescriptor(VK_FORMAT_R32G32B32_SFLOAT, *this); - break; - case 4: - image = std::make_unique(width, height); - loadFormat = createFormatDescriptor(VK_FORMAT_R32G32B32A32_SFLOAT, *this); - break; - } + image = std::make_unique(width, height); + loadFormat = createFormatDescriptor(VK_FORMAT_R32G32B32A32_SFLOAT, *this); } else if (requestBitLength == 8) { - switch (requestChannelCount) { - case 1: - image = std::make_unique(width, height); - loadFormat = createFormatDescriptor(VK_FORMAT_R8_UNORM, *this); - break; - case 2: - image = std::make_unique(width, height); - loadFormat = createFormatDescriptor(VK_FORMAT_R8G8_UNORM, *this); - break; - case 3: - image = std::make_unique(width, height); - loadFormat = createFormatDescriptor(VK_FORMAT_R8G8B8_UNORM, *this); - break; - case 4: - image = std::make_unique(width, height); - loadFormat = createFormatDescriptor(VK_FORMAT_R8G8B8A8_UNORM, *this); - break; - } + image = std::make_unique(width, height); + loadFormat = createFormatDescriptor(VK_FORMAT_R8G8B8A8_UNORM, *this); } else if (requestBitLength == 16) { - switch (requestChannelCount) { - case 1: - image = std::make_unique(width, height); - loadFormat = createFormatDescriptor(VK_FORMAT_R16_UNORM, *this); - break; - case 2: - image = std::make_unique(width, height); - loadFormat = createFormatDescriptor(VK_FORMAT_R16G16_UNORM, *this); - break; - case 3: - image = std::make_unique(width, height); - loadFormat = createFormatDescriptor(VK_FORMAT_R16G16B16_UNORM, *this); - break; - case 4: - image = std::make_unique(width, height); - loadFormat = createFormatDescriptor(VK_FORMAT_R16G16B16A16_UNORM, *this); - break; - } + image = std::make_unique(width, height); + loadFormat = createFormatDescriptor(VK_FORMAT_R16G16B16A16_UNORM, *this); } else if (requestBitLength == 32) { - switch (requestChannelCount) { - case 1: - image = std::make_unique(width, height); - loadFormat = createFormatDescriptor(VK_FORMAT_R32_UINT, *this); - break; - case 2: - image = std::make_unique(width, height); - loadFormat = createFormatDescriptor(VK_FORMAT_R32G32_UINT, *this); - break; - case 3: - image = std::make_unique(width, height); - loadFormat = createFormatDescriptor(VK_FORMAT_R32G32B32_UINT, *this); - break; - case 4: - image = std::make_unique(width, height); - loadFormat = createFormatDescriptor(VK_FORMAT_R32G32B32A32_UINT, *this); - break; - } + image = std::make_unique(width, height); + loadFormat = createFormatDescriptor(VK_FORMAT_R32G32B32A32_UINT, *this); } 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 +1379,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 +1389,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 +1403,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 +1419,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 +1435,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 +1445,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 +1467,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 +1509,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,118 +1613,118 @@ 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.