From 17ab578443995097fa682a641ac15c158eaf949d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 11 May 2023 13:34:21 +0200 Subject: [PATCH] Add a pixelFormat(PixelFormat, UnsignedInt, bool) helper. Useful for creating pixel formats with different channel count, adding/removing the sRGB bit and such. Counterpart to vertexFormat(VertexFormat, UnsignedInt, bool) that got added back in 2020.06 already. --- doc/changelog.dox | 5 +- doc/snippets/Magnum.cpp | 12 ++++ src/Magnum/PixelFormat.cpp | 43 ++++++++++++ src/Magnum/PixelFormat.h | 42 +++++++++-- src/Magnum/Test/PixelFormatTest.cpp | 104 ++++++++++++++++++++++++++++ src/Magnum/VertexFormat.h | 6 +- 6 files changed, 202 insertions(+), 10 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index 7e4fbc0e3..9652223f8 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -55,8 +55,9 @@ See also: @ref DebugTools::CompareImage - New @ref pixelFormatChannelFormat(), @ref pixelFormatChannelCount(), @ref isPixelFormatNormalized(), @ref isPixelFormatIntegral(), - @ref isPixelFormatFloatingPoint(), @ref isPixelFormatSrgb() and - @ref isPixelFormatDepthOrStencil() helpers + @ref isPixelFormatFloatingPoint(), @ref isPixelFormatSrgb(), + @ref isPixelFormatDepthOrStencil() and + @ref pixelFormat(PixelFormat, UnsignedInt, bool) helpers - New @ref Degh, @ref Radh, @ref Range1Dh, @ref Range2Dh and @ref Range3Dh typedefs for half-float angles and ranges - New @ref Range1Dui, @ref Range2Dui and @ref Range3Dui typedefs for unsigned diff --git a/doc/snippets/Magnum.cpp b/doc/snippets/Magnum.cpp index dd0e230e4..cdf939a07 100644 --- a/doc/snippets/Magnum.cpp +++ b/doc/snippets/Magnum.cpp @@ -333,4 +333,16 @@ VertexFormat tangentFormat = vertexFormat( static_cast(tangentFormat); } +{ +/* [pixelFormat] */ +PixelFormat grayscaleFormat = DOXYGEN_ELLIPSIS({}); + +PixelFormat rgbFormat = pixelFormat( + grayscaleFormat, + pixelFormatChannelCount(grayscaleFormat) == 2 ? 4 : 3, + isPixelFormatSrgb(grayscaleFormat)); +/* [pixelFormat] */ +static_cast(rgbFormat); +} + } diff --git a/src/Magnum/PixelFormat.cpp b/src/Magnum/PixelFormat.cpp index 1600a1a13..4d2b82a08 100644 --- a/src/Magnum/PixelFormat.cpp +++ b/src/Magnum/PixelFormat.cpp @@ -684,6 +684,49 @@ bool isPixelFormatDepthOrStencil(const PixelFormat format) { CORRADE_ASSERT_UNREACHABLE("isPixelFormatDepthOrStencil(): invalid format" << format, {}); } +PixelFormat pixelFormat(const PixelFormat format, const UnsignedInt channelCount, const bool srgb) { + CORRADE_ASSERT(!isPixelFormatImplementationSpecific(format), + "pixelFormat(): can't assemble a format out of an implementation-specific format" << reinterpret_cast(pixelFormatUnwrap(format)), {}); + CORRADE_ASSERT(!isPixelFormatDepthOrStencil(format), + "pixelFormat(): can't assemble a format out of" << format, {}); + + PixelFormat channelFormat = pixelFormatChannelFormat(format); + + /* First turn the format into a sRGB one or remove the sRGB property, if + requested. The [RGBA]8Srgb formats follow [RGBA]8Unorm in the same order + so it's just constant addition / subtraction for all four variants. */ + if(srgb && channelFormat != PixelFormat::R8Srgb) { + CORRADE_ASSERT(channelFormat == PixelFormat::R8Unorm, + "pixelFormat():" << format << "can't be made sRGB", {}); + channelFormat = PixelFormat(UnsignedInt(channelFormat) - UnsignedInt(PixelFormat::R8Unorm) + UnsignedInt(PixelFormat::R8Srgb)); + } else if(!srgb && channelFormat == PixelFormat::R8Srgb) { + channelFormat = PixelFormat(UnsignedInt(channelFormat) - UnsignedInt(PixelFormat::R8Srgb) + UnsignedInt(PixelFormat::R8Unorm)); + } + + CORRADE_ASSERT(channelCount >= 1 && channelCount <= 4, + "pixelFormat(): invalid component count" << channelCount, {}); + + /* The two-, three- and four-channel variants follow each other, so it's + just addition again. There may be packed formats in the future, so + whitelist for the known set of single-channel formats. */ + if(channelFormat == PixelFormat::R8Unorm || + channelFormat == PixelFormat::R8Snorm || + channelFormat == PixelFormat::R8Srgb || + channelFormat == PixelFormat::R8UI || + channelFormat == PixelFormat::R8I || + channelFormat == PixelFormat::R16Unorm || + channelFormat == PixelFormat::R16Snorm || + channelFormat == PixelFormat::R16UI || + channelFormat == PixelFormat::R16I || + channelFormat == PixelFormat::R32UI || + channelFormat == PixelFormat::R32I || + channelFormat == PixelFormat::R16F || + channelFormat == PixelFormat::R32F) + return PixelFormat(UnsignedInt(channelFormat) + channelCount - 1); + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + namespace { #ifndef DOXYGEN_GENERATING_OUTPUT /* It gets *really* confused */ diff --git a/src/Magnum/PixelFormat.h b/src/Magnum/PixelFormat.h index e2060ca0b..411a8de56 100644 --- a/src/Magnum/PixelFormat.h +++ b/src/Magnum/PixelFormat.h @@ -26,7 +26,7 @@ */ /** @file - * @brief Enum @ref Magnum::PixelFormat, @ref Magnum::CompressedPixelFormat, function @ref Magnum::pixelFormatSize(), @ref Magnum::pixelFormatChannelFormat(), @ref Magnum::pixelFormatChannelCount(), @ref Magnum::isPixelFormatNormalized(), @ref Magnum::isPixelFormatNormalized(), @ref Magnum::isPixelFormatIntegral(), @ref Magnum::isPixelFormatFloatingPoint(), @ref Magnum::isPixelFormatSrgb(), @ref Magnum::isPixelFormatDepthOrStencil(), @ref Magnum::isPixelFormatImplementationSpecific(), @ref Magnum::pixelFormatWrap(), @ref Magnum::pixelFormatUnwrap(), @ref Magnum::compressedPixelFormatBlockSize(), @ref Magnum::compressedPixelFormatBlockDataSize(), @ref Magnum::isCompressedPixelFormatImplementationSpecific(), @ref Magnum::compressedPixelFormatWrap(), @ref Magnum::compressedPixelFormatUnwrap() + * @brief Enum @ref Magnum::PixelFormat, @ref Magnum::CompressedPixelFormat, function @ref Magnum::pixelFormatSize(), @ref Magnum::pixelFormatChannelFormat(), @ref Magnum::pixelFormatChannelCount(), @ref Magnum::isPixelFormatNormalized(), @ref Magnum::isPixelFormatNormalized(), @ref Magnum::isPixelFormatIntegral(), @ref Magnum::isPixelFormatFloatingPoint(), @ref Magnum::isPixelFormatSrgb(), @ref Magnum::isPixelFormatDepthOrStencil(), @ref Magnum::pixelFormat(), @ref Magnum::isPixelFormatImplementationSpecific(), @ref Magnum::pixelFormatWrap(), @ref Magnum::pixelFormatUnwrap(), @ref Magnum::compressedPixelFormatBlockSize(), @ref Magnum::compressedPixelFormatBlockDataSize(), @ref Magnum::isCompressedPixelFormatImplementationSpecific(), @ref Magnum::compressedPixelFormatWrap(), @ref Magnum::compressedPixelFormatUnwrap() */ #include @@ -68,8 +68,9 @@ See documentation of each value for more information about the mapping. See also @ref pixelFormatSize(), @ref pixelFormatChannelFormat(), @ref pixelFormatChannelCount(), @ref isPixelFormatNormalized(), @ref isPixelFormatIntegral(), @ref isPixelFormatFloatingPoint(), -@ref isPixelFormatSrgb() and @ref isPixelFormatDepthOrStencil() for querying -various aspects of a format. +@ref isPixelFormatSrgb(), @ref isPixelFormatDepthOrStencil() and +@ref pixelFormat() for querying various aspects of a format and assembling it +from a set of singular properties. @see @ref CompressedPixelFormat, @ref Image, @ref ImageView, @ref VertexFormat */ enum class PixelFormat: UnsignedInt { @@ -828,6 +829,7 @@ For any pixel format, exactly one of @ref isPixelFormatNormalized(), @cpp true @ce. @see @ref isPixelFormatImplementationSpecific(), @ref isPixelFormatDepthOrStencil(), @ref isPixelFormatSrgb(), + @ref pixelFormat(PixelFormat, UnsignedInt, bool), @ref isVertexFormatNormalized() */ MAGNUM_EXPORT bool isPixelFormatNormalized(PixelFormat format); @@ -845,7 +847,8 @@ For any pixel format, exactly one of @ref isPixelFormatNormalized(), @ref isPixelFormatIntegral() and @ref isPixelFormatFloatingPoint() returns @cpp true @ce. @see @ref isPixelFormatImplementationSpecific(), - @ref isPixelFormatDepthOrStencil(), @ref isPixelFormatSrgb() + @ref isPixelFormatDepthOrStencil(), @ref isPixelFormatSrgb(), + @ref pixelFormat(PixelFormat, UnsignedInt, bool) */ MAGNUM_EXPORT bool isPixelFormatIntegral(PixelFormat format); @@ -863,7 +866,8 @@ For any pixel format, exactly one of @ref isPixelFormatNormalized(), @ref isPixelFormatIntegral() and @ref isPixelFormatFloatingPoint() returns @cpp true @ce. @see @ref isPixelFormatImplementationSpecific(), - @ref isPixelFormatDepthOrStencil(), @ref isPixelFormatSrgb() + @ref isPixelFormatDepthOrStencil(), @ref isPixelFormatSrgb(), + @ref pixelFormat(PixelFormat, UnsignedInt, bool) */ MAGNUM_EXPORT bool isPixelFormatFloatingPoint(PixelFormat format); @@ -875,7 +879,8 @@ Returns @cpp true @ce for `*Srgb` formats, @cpp false @ce otherwise. If this function returns true, @ref isPixelFormatNormalized() also returns true. Expects that the pixel format is *not* implementation-specific and not a depth/stencil format. -@see @ref isPixelFormatImplementationSpecific() +@see @ref isPixelFormatImplementationSpecific(), + @ref pixelFormat(PixelFormat, UnsignedInt, bool) */ MAGNUM_EXPORT bool isPixelFormatSrgb(PixelFormat format); @@ -889,6 +894,31 @@ formats, @cpp false @ce otherwise. Expects that the pixel format is *not* implem */ MAGNUM_EXPORT bool isPixelFormatDepthOrStencil(PixelFormat format); +/** +@brief Assemble a pixel format from parts +@m_since_latest + +Converts @p format to a new format with desired channel count and +normalization. Expects that the pixel format is *not* implementation-specific +and not a depth/stencil format, @p channelCount is @cpp 1 @ce, @cpp 2 @ce, +@cpp 3 @ce or @cpp 4 @ce and @p srgb is @cpp true @ce only for 8-bit integer +formats. + +Example usage --- picking a three or four-channel format corresponding to a +grayscale or grayscale + alpha format: + +@snippet Magnum.cpp pixelFormat + +@see @ref pixelFormatChannelFormat(), @ref pixelFormatChannelCount(), + @ref vertexFormat(VertexFormat, UnsignedInt, bool) +@todo Unlike @ref vertexFormat(), this doesn't allow changing the normalized + property because I don't see a point as integral pixels are usually treated + as a vastly different kind of data. Once such use case exists, it might + make sense to add a pixelFormat(PixelFormat, UnsignedInt, bool, bool) + overload, the two bools are rather nasty though. +*/ +MAGNUM_EXPORT PixelFormat pixelFormat(PixelFormat format, UnsignedInt channelCount, bool srgb); + #ifdef MAGNUM_BUILD_DEPRECATED /** * @brief @copybrief pixelFormatSize() diff --git a/src/Magnum/Test/PixelFormatTest.cpp b/src/Magnum/Test/PixelFormatTest.cpp index 45cf6551f..daaef1fa8 100644 --- a/src/Magnum/Test/PixelFormatTest.cpp +++ b/src/Magnum/Test/PixelFormatTest.cpp @@ -61,6 +61,12 @@ struct PixelFormatTest: TestSuite::Tester { void isDepthOrStencilInvalid(); void isDepthOrStencilImplementationSpecific(); + void assemble(); + void assembleRoundtrip(); + void assembleInvalidSrgb(); + void assembleInvalidComponentCount(); + void assembleDepthStencilImplementationSpecific(); + void compressedBlockSize(); void compressedBlockSizeInvalid(); void compressedBlockSizeImplementationSpecific(); @@ -91,6 +97,25 @@ struct PixelFormatTest: TestSuite::Tester { void compresedConfiguration(); }; +const struct { + PixelFormat channelType; + bool srgb; +} AssembleRoundtripData[]{ + {PixelFormat::R8Unorm, false}, + {PixelFormat::R8Snorm, false}, + {PixelFormat::R8Srgb, true}, + {PixelFormat::R8UI, false}, + {PixelFormat::R8I, false}, + {PixelFormat::R16Unorm, false}, + {PixelFormat::R16Snorm, false}, + {PixelFormat::R16UI, false}, + {PixelFormat::R16I, false}, + {PixelFormat::R32UI, false}, + {PixelFormat::R32I, false}, + {PixelFormat::R16F, false}, + {PixelFormat::R32F, false}, +}; + PixelFormatTest::PixelFormatTest() { addTests({&PixelFormatTest::mapping, &PixelFormatTest::compressedMapping, @@ -115,6 +140,15 @@ PixelFormatTest::PixelFormatTest() { &PixelFormatTest::isDepthOrStencilInvalid, &PixelFormatTest::isDepthOrStencilImplementationSpecific, + &PixelFormatTest::assemble}); + + addRepeatedInstancedTests({&PixelFormatTest::assembleRoundtrip}, 4, + Containers::arraySize(AssembleRoundtripData)); + + addTests({&PixelFormatTest::assembleInvalidSrgb, + &PixelFormatTest::assembleInvalidComponentCount, + &PixelFormatTest::assembleDepthStencilImplementationSpecific, + &PixelFormatTest::compressedBlockSize, &PixelFormatTest::compressedBlockSizeInvalid, &PixelFormatTest::compressedBlockSizeImplementationSpecific, @@ -449,6 +483,76 @@ void PixelFormatTest::isDepthOrStencilImplementationSpecific() { "isPixelFormatDepthOrStencil(): can't determine type of an implementation-specific format 0xdead\n"); } +void PixelFormatTest::assemble() { + /* Changing component count */ + CORRADE_COMPARE(pixelFormat(PixelFormat::RGB16F, 4, false), PixelFormat::RGBA16F); + CORRADE_COMPARE(pixelFormat(PixelFormat::RGBA32UI, 2, false), PixelFormat::RG32UI); + CORRADE_COMPARE(pixelFormat(PixelFormat::R8Snorm, 3, false), PixelFormat::RGB8Snorm); + + /* Same as pixelFormatChannelFormat() */ + CORRADE_COMPARE(pixelFormat(PixelFormat::RGB32F, 1, false), pixelFormatChannelFormat(PixelFormat::RGB32F)); + + /* Adding / removing a sRGB property */ + CORRADE_COMPARE(pixelFormat(PixelFormat::RGB8Unorm, 3, true), PixelFormat::RGB8Srgb); + CORRADE_COMPARE(pixelFormat(PixelFormat::RGBA8Srgb, 4, false), PixelFormat::RGBA8Unorm); +} + +void PixelFormatTest::assembleRoundtrip() { + auto&& data = AssembleRoundtripData[testCaseInstanceId()]; + + std::ostringstream out; + { + Debug d{&out, Debug::Flag::NoNewlineAtTheEnd}; + d << data.channelType; + if(data.srgb) d << Debug::nospace << ", srgb"; + } + setTestCaseDescription(out.str()); + + PixelFormat result = pixelFormat(data.channelType, testCaseRepeatId() + 1, data.srgb); + CORRADE_COMPARE(pixelFormat(result, testCaseRepeatId() + 1, data.srgb), result); + CORRADE_COMPARE(pixelFormatChannelFormat(result), data.channelType); + CORRADE_COMPARE(pixelFormatChannelCount(result), testCaseRepeatId() + 1); + CORRADE_COMPARE(isPixelFormatSrgb(result), data.srgb); +} + +void PixelFormatTest::assembleInvalidSrgb() { + CORRADE_SKIP_IF_NO_ASSERT(); + + std::ostringstream out; + Error redirectError{&out}; + pixelFormat(PixelFormat::R8Snorm, 1, true); + pixelFormat(PixelFormat::RGB16Unorm, 4, true); + pixelFormat(PixelFormat::RGBA16F, 3, true); + CORRADE_COMPARE(out.str(), + "pixelFormat(): PixelFormat::R8Snorm can't be made sRGB\n" + "pixelFormat(): PixelFormat::RGB16Unorm can't be made sRGB\n" + "pixelFormat(): PixelFormat::RGBA16F can't be made sRGB\n"); +} + +void PixelFormatTest::assembleInvalidComponentCount() { + CORRADE_SKIP_IF_NO_ASSERT(); + + std::ostringstream out; + Error redirectError{&out}; + pixelFormat(PixelFormat::RGB8Unorm, 0, false); + pixelFormat(PixelFormat::RGB8Unorm, 5, false); + CORRADE_COMPARE(out.str(), + "pixelFormat(): invalid component count 0\n" + "pixelFormat(): invalid component count 5\n"); +} + +void PixelFormatTest::assembleDepthStencilImplementationSpecific() { + CORRADE_SKIP_IF_NO_ASSERT(); + + std::ostringstream out; + Error redirectError{&out}; + pixelFormat(pixelFormatWrap(0xdead), 1, true); + pixelFormat(PixelFormat::Depth32F, 1, true); + CORRADE_COMPARE(out.str(), + "pixelFormat(): can't assemble a format out of an implementation-specific format 0xdead\n" + "pixelFormat(): can't assemble a format out of PixelFormat::Depth32F\n"); +} + void PixelFormatTest::compressedBlockSize() { CORRADE_COMPARE(compressedPixelFormatBlockSize(CompressedPixelFormat::Etc2RGB8A1Srgb), (Vector3i{4, 4, 1})); CORRADE_COMPARE(compressedPixelFormatBlockDataSize(CompressedPixelFormat::Etc2RGB8A1Srgb), 8); diff --git a/src/Magnum/VertexFormat.h b/src/Magnum/VertexFormat.h index 8f45978e9..1f185d662 100644 --- a/src/Magnum/VertexFormat.h +++ b/src/Magnum/VertexFormat.h @@ -1463,7 +1463,8 @@ existing (three-component) vertex normal format: @see @ref isVertexFormatImplementationSpecific(), @ref vertexFormat(VertexFormat, UnsignedInt, UnsignedInt, bool), @ref vertexFormatComponentFormat(), @ref vertexFormatComponentCount(), - @ref isVertexFormatNormalized() + @ref isVertexFormatNormalized(), + @ref pixelFormat(PixelFormat, UnsignedInt, bool) */ MAGNUM_EXPORT VertexFormat vertexFormat(VertexFormat format, UnsignedInt componentCount, bool normalized); @@ -1480,7 +1481,8 @@ implementation-specific. @ref vertexFormat(VertexFormat, UnsignedInt, bool), @ref vertexFormatComponentFormat(), @ref vertexFormatComponentCount(), @ref vertexFormatVectorCount(), @ref vertexFormatVectorStride(), - @ref isVertexFormatNormalized() + @ref isVertexFormatNormalized(), + @ref pixelFormat(PixelFormat, UnsignedInt, bool) */ MAGNUM_EXPORT VertexFormat vertexFormat(VertexFormat format, UnsignedInt vectorCount, UnsignedInt componentCount, bool aligned);