From 3ccb7a9fc942b19b8f1a5e6c824adbd7dcbd3a24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 1 Jun 2023 17:26:21 +0200 Subject: [PATCH] Math: utilities for flipping BC1/3/4/5 blocks. Most of the testing scaffolding here is a preparation for the actually complex formats like BC6/7 or ASTC. Also, it's great to be able to use Magnum from Python to prepare data for testing the C++ Magnum APIs. --- doc/changelog.dox | 2 + src/Magnum/CMakeLists.txt | 1 + src/Magnum/Math/CMakeLists.txt | 1 + src/Magnum/Math/ColorBatch.cpp | 201 +++ src/Magnum/Math/ColorBatch.h | 126 ++ src/Magnum/Math/Test/CMakeLists.txt | 14 + src/Magnum/Math/Test/ColorBatchTest.cpp | 353 +++++ .../Math/Test/ColorBatchTestFiles/README.md | 23 + .../Math/Test/ColorBatchTestFiles/bc1.png | Bin 0 -> 232 bytes .../Math/Test/ColorBatchTestFiles/bc3.png | Bin 0 -> 189 bytes .../Math/Test/ColorBatchTestFiles/bc4.png | Bin 0 -> 113 bytes .../Math/Test/ColorBatchTestFiles/bc5.png | Bin 0 -> 146 bytes .../checkerboard-odd.in.png | Bin 0 -> 460 bytes .../ColorBatchTestFiles/checkerboard-odd.png | Bin 0 -> 412 bytes .../ColorBatchTestFiles/checkerboard.in.png | Bin 0 -> 542 bytes .../Test/ColorBatchTestFiles/checkerboard.png | Bin 0 -> 355 bytes .../extract-interesting-blocks.py | 127 ++ .../ColorBatchTestFiles/format-block-data.py | 52 + src/Magnum/Math/Test/configure.h.cmake | 48 + src/Magnum/PixelFormat.h | 30 +- .../Shaders/Test/TestFiles/checkerboard.svg | 1259 +++++++++++++---- 21 files changed, 1983 insertions(+), 254 deletions(-) create mode 100644 src/Magnum/Math/ColorBatch.cpp create mode 100644 src/Magnum/Math/ColorBatch.h create mode 100644 src/Magnum/Math/Test/ColorBatchTest.cpp create mode 100644 src/Magnum/Math/Test/ColorBatchTestFiles/README.md create mode 100644 src/Magnum/Math/Test/ColorBatchTestFiles/bc1.png create mode 100644 src/Magnum/Math/Test/ColorBatchTestFiles/bc3.png create mode 100644 src/Magnum/Math/Test/ColorBatchTestFiles/bc4.png create mode 100644 src/Magnum/Math/Test/ColorBatchTestFiles/bc5.png create mode 100644 src/Magnum/Math/Test/ColorBatchTestFiles/checkerboard-odd.in.png create mode 100644 src/Magnum/Math/Test/ColorBatchTestFiles/checkerboard-odd.png create mode 100644 src/Magnum/Math/Test/ColorBatchTestFiles/checkerboard.in.png create mode 100644 src/Magnum/Math/Test/ColorBatchTestFiles/checkerboard.png create mode 100755 src/Magnum/Math/Test/ColorBatchTestFiles/extract-interesting-blocks.py create mode 100755 src/Magnum/Math/Test/ColorBatchTestFiles/format-block-data.py create mode 100644 src/Magnum/Math/Test/configure.h.cmake diff --git a/doc/changelog.dox b/doc/changelog.dox index 384080f6cb..220603f97e 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -214,6 +214,8 @@ See also: @ref Math::Quaternion::reflectVector(), but mainly just for documentation purposes as reflections cannot be combined with rotations and thus are mostly useless in practice +- New @ref Magnum/Math/ColorBatch.h header with utilities for performing Y + flip of various block-compressed formats @subsubsection changelog-latest-new-materialtools MaterialTools library diff --git a/src/Magnum/CMakeLists.txt b/src/Magnum/CMakeLists.txt index 106c43ae34..8d392b51b7 100644 --- a/src/Magnum/CMakeLists.txt +++ b/src/Magnum/CMakeLists.txt @@ -140,6 +140,7 @@ set(MagnumMath_SRCS Math/instantiation.cpp) set(MagnumMath_GracefulAssert_SRCS + Math/ColorBatch.cpp Math/Functions.cpp Math/PackingBatch.cpp) diff --git a/src/Magnum/Math/CMakeLists.txt b/src/Magnum/Math/CMakeLists.txt index c759eb3e86..be883cb0ca 100644 --- a/src/Magnum/Math/CMakeLists.txt +++ b/src/Magnum/Math/CMakeLists.txt @@ -32,6 +32,7 @@ set(MagnumMath_HEADERS Bezier.h BitVector.h Color.h + ColorBatch.h Complex.h Constants.h ConfigurationValue.h diff --git a/src/Magnum/Math/ColorBatch.cpp b/src/Magnum/Math/ColorBatch.cpp new file mode 100644 index 0000000000..4ecd7d76ea --- /dev/null +++ b/src/Magnum/Math/ColorBatch.cpp @@ -0,0 +1,201 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include "ColorBatch.h" + +#include +#include + +#include "Magnum/Magnum.h" + +namespace Magnum { namespace Math { + +namespace { + +inline void yFlipBc1BlockInPlace(char* const data) { + /* The 64-bit block is laid out as follows: + + - 2 bytes for first endpoint color + - 2 bytes for second endpoint color + - 4 bytes for 4x4 2-bit color indices in this order: + + a b c d + e f g h + i j k l + m n o p + + Which means each row is one byte, so the Y-flip reduces down to a simple + byte swap. See the official specification for details: + https://learn.microsoft.com/cs-cz/windows/win32/direct3d10/d3d10-graphics-programming-guide-resources-block-compression#bc1 */ + { + char tmp = data[4]; + data[4] = data[7]; + data[7] = tmp; + } { + char tmp = data[5]; + data[5] = data[6]; + data[6] = tmp; + } +} + +inline void yFlipBc4BlockInPlace(char* data) { + /* The 64-bit block is laid out as follows: + + - 1 byte for first endpoint color channel + - 1 byte for second endpoint color channel + - 6 bytes for 4x4 3-bit color indices + interpolation factors, same + order as BC1 + + Compared to BC1, this means swapping groups of 12 bits instead of 8. + https://learn.microsoft.com/cs-cz/windows/win32/direct3d10/d3d10-graphics-programming-guide-resources-block-compression#bc4 */ + + /* Load & byte-swap the whole block */ + union { + char bytes[8]; + UnsignedLong value; + } block; + for(std::size_t i = 0; i != 8; ++i) { + #ifndef CORRADE_TARGET_BIG_ENDIAN + block.bytes[i] = data[i]; + #else + block.bytes[i] = data[7 - i]; + #endif + } + + /* Flip the 12-bit groups */ + block.value = + (block.value & 0x000000000000ffffull) | + (block.value & 0xfff0000000000000ull) >> 36 | + (block.value & 0x000fff0000000000ull) >> 12 | + (block.value & 0x000000fff0000000ull) << 12 | + (block.value & 0x000000000fff0000ull) << 36; + + /* Byte-swap & store the block back */ + for(std::size_t i = 0; i != 8; ++i) { + #ifndef CORRADE_TARGET_BIG_ENDIAN + data[i] = block.bytes[i]; + #else + data[i] = block.bytes[7 - i]; + #endif + } +} + +inline void yFlipBc3BlockInPlace(char* const data) { + yFlipBc4BlockInPlace(data); + yFlipBc1BlockInPlace(data + 8); +} + +inline void yFlipBc5BlockInPlace(char* const data) { + yFlipBc4BlockInPlace(data); + yFlipBc4BlockInPlace(data + 8); +} + +template void yFlipBlocksInPlace(const Containers::StridedArrayView4D& blocks + #ifndef CORRADE_NO_ASSERT + , const char* messagePrefix + #endif +) { + const std::size_t* const size = blocks.size().begin(); + CORRADE_ASSERT(size[3] == blockSize, + messagePrefix << "expected last dimension to be" << blockSize << "bytes but got" << size[3], ); + CORRADE_ASSERT(blocks.isContiguous<3>(), + messagePrefix << "last dimension is not contiguous", ); + + /* The high-level logic is mostly a copy of Utility::flipInPlace() without + the "leftovers" part. It's however not calling that function directly + because it'd mean going through memory twice, once for copying whole + blocks and once for recalculating each block. */ + + CORRADE_INTERNAL_ASSERT(blocks.template isContiguous<3>()); + + char* const ptr = static_cast(blocks.data()); + const std::ptrdiff_t* const stride = blocks.stride().begin(); + for(std::size_t z = 0; z != size[0]; ++z) { + char* const ptrZ = ptr + z*stride[0]; + + /* Go through half of the items in Y and swap them with the other + half, flipping their contents along the way */ + for(std::size_t y = 0, yMax = size[1]/2; y != yMax; ++y) { + char* const ptrYTop = ptrZ + y*stride[1]; + char* const ptrYBottom = ptrZ + (size[1] - y - 1)*stride[1]; + + /* Copy a block at a time and flip its contents as well */ + alignas(blockSize) char tmp[blockSize]; + for(std::size_t x = 0; x != size[2]; ++x) { + char* const ptrXTop = ptrYTop + x*stride[2]; + char* const ptrXBottom = ptrYBottom + x*stride[2]; + flipBlock(ptrXTop); + flipBlock(ptrXBottom); + std::memcpy(tmp, ptrXTop, blockSize); + std::memcpy(ptrXTop, ptrXBottom, blockSize); + std::memcpy(ptrXBottom, tmp, blockSize); + } + } + + /* If there was an odd number of rows, make sure to flip contents of + the middle row as well */ + if(size[1] % 2) { + char* const ptrYMid = ptrZ + (size[1]/2)*stride[1]; + for(std::size_t x = 0; x != size[2]; ++x) + flipBlock(ptrYMid + x*stride[2]); + } + } +} + +} + +void yFlipBc1InPlace(const Containers::StridedArrayView4D& blocks) { + yFlipBlocksInPlace<8, yFlipBc1BlockInPlace>(blocks + #ifndef CORRADE_NO_ASSERT + , "Math::yFlipBc1InPlace():" + #endif + ); +} + +void yFlipBc3InPlace(const Containers::StridedArrayView4D& blocks) { + yFlipBlocksInPlace<16, yFlipBc3BlockInPlace>(blocks + #ifndef CORRADE_NO_ASSERT + , "Math::yFlipBc3InPlace():" + #endif + ); +} + +void yFlipBc4InPlace(const Containers::StridedArrayView4D& blocks) { + yFlipBlocksInPlace<8, yFlipBc4BlockInPlace>(blocks + #ifndef CORRADE_NO_ASSERT + , "Math::yFlipBc4InPlace():" + #endif + ); +} + +void yFlipBc5InPlace(const Containers::StridedArrayView4D& blocks) { + yFlipBlocksInPlace<16, yFlipBc5BlockInPlace>(blocks + #ifndef CORRADE_NO_ASSERT + , "Math::yFlipBc5InPlace():" + #endif + ); +} + +}} diff --git a/src/Magnum/Math/ColorBatch.h b/src/Magnum/Math/ColorBatch.h new file mode 100644 index 0000000000..98ffa82ff5 --- /dev/null +++ b/src/Magnum/Math/ColorBatch.h @@ -0,0 +1,126 @@ +#ifndef Magnum_Math_ColorBatch_h +#define Magnum_Math_ColorBatch_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +/** @file + * @brief Function @ref Magnum::Math::yFlipBc1InPlace(), @ref Magnum::Math::yFlipBc3InPlace(), @ref Magnum::Math::yFlipBc4InPlace(), @ref Magnum::Math::yFlipBc5InPlace() + * @m_since_latest + */ + +#include + +#include "Magnum/visibility.h" + +namespace Magnum { namespace Math { + +/** +@brief Y-flip BC1 texture blocks in-place +@m_since_latest + +Performs a Y flip of given 3D image by flipping block order and modifying +internal block representation to encode the same information, just upside down. +No decoding or re-encoding of the block data is performed, thus the operation +is lossless. However note that this operation flips full blocks --- if size of +the actual image isn't whole blocks, the flipped image will be shifted compared +to the original, possibly with garbage data appearing in the first few rows. + +First dimension is expected to be image slices, second block rows, third +2D blocks, fourth the 64-bit 4x4 block data, i.e. the last dimension is +expected to be contiguous with size of 8. +@see @ref CompressedPixelFormat::Bc1RGBUnorm, + @ref CompressedPixelFormat::Bc1RGBSrgb, + @ref CompressedPixelFormat::Bc1RGBAUnorm, + @ref CompressedPixelFormat::Bc1RGBASrgb +*/ +MAGNUM_EXPORT void yFlipBc1InPlace(const Corrade::Containers::StridedArrayView4D& blocks); + +/** @todo BC2, if used at all for anything */ + +/** +@brief Y-flip BC3 texture blocks in-place +@m_since_latest + +Performs a Y flip of given 3D image by flipping block order and modifying +internal block representation to encode the same information, just upside down. +No decoding or re-encoding of the block data is performed, thus the operation +is lossless. However note that this operation flips full blocks --- if size of +the actual image isn't whole blocks, the flipped image will be shifted compared +to the original, possibly with garbage data appearing in the first few rows. + +First dimension is expected to be image slices, second block rows, third +2D blocks, fourth the 128-bit 4x4 block data, i.e. the last dimension is +expected to be contiguous with size of 16. As BC3 is internally a 64-bit BC4 +block for alpha followed by a 64-bit BC1 block for RGB, the operation is the +same as performing @ref yFlipBc4InPlace() on the first half and +@ref yFlipBc1InPlace() on second half of each block. +@see @ref CompressedPixelFormat::Bc3RGBAUnorm, + @ref CompressedPixelFormat::Bc3RGBASrgb +*/ +MAGNUM_EXPORT void yFlipBc3InPlace(const Corrade::Containers::StridedArrayView4D& blocks); + +/** +@brief Y-flip BC4 texture blocks in-place +@m_since_latest + +Performs a Y flip of given 3D image by flipping block order and modifying +internal block representation to encode the same information, just upside down. +No decoding or re-encoding of the block data is performed, thus the operation +is lossless. However note that this operation flips full blocks --- if size of +the actual image isn't whole blocks, the flipped image will be shifted compared +to the original, possibly with garbage data appearing in the first few rows. + +First dimension is expected to be image slices, second block rows, third +2D blocks, fourth the 64-bit 4x4 block data, i.e. the last dimension is +expected to be contiguous with size of 8. +@see @ref CompressedPixelFormat::Bc4RUnorm, + @ref CompressedPixelFormat::Bc4RSnorm +*/ +MAGNUM_EXPORT void yFlipBc4InPlace(const Corrade::Containers::StridedArrayView4D& blocks); + +/** +@brief Y-flip BC5 texture blocks in-place +@m_since_latest + +Performs a Y flip of given 3D image by flipping block order and modifying +internal block representation to encode the same information, just upside down. +No decoding or re-encoding of the block data is performed, thus the operation +is lossless. However note that this operation flips full blocks --- if size of +the actual image isn't whole blocks, the flipped image will be shifted compared +to the original, possibly with garbage data appearing in the first few rows. + +First dimension is expected to be image slices, second block rows, third +2D blocks, fourth the 128-bit 4x4 block data, i.e. the last dimension is +expected to be contiguous with size of 16. As BC5 is internally two 64-bit BC4 +blocks, the operation is the same as performing @ref yFlipBc4InPlace() on both +halves of each block. +@see @ref CompressedPixelFormat::Bc5RGUnorm, + @ref CompressedPixelFormat::Bc5RGSnorm +*/ +MAGNUM_EXPORT void yFlipBc5InPlace(const Corrade::Containers::StridedArrayView4D& blocks); + +}} + +#endif diff --git a/src/Magnum/Math/Test/CMakeLists.txt b/src/Magnum/Math/Test/CMakeLists.txt index 9251da3964..1849d8ecaf 100644 --- a/src/Magnum/Math/Test/CMakeLists.txt +++ b/src/Magnum/Math/Test/CMakeLists.txt @@ -43,6 +43,20 @@ corrade_add_test(MathVector3Test Vector3Test.cpp LIBRARIES MagnumMathTestLib) corrade_add_test(MathVector4Test Vector4Test.cpp LIBRARIES MagnumMathTestLib) corrade_add_test(MathColorTest ColorTest.cpp LIBRARIES MagnumMathTestLib) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/configure.h.cmake + ${CMAKE_CURRENT_BINARY_DIR}/configure.h) + +corrade_add_test(MathColorBatchTest ColorBatchTest.cpp + LIBRARIES MagnumMathTestLib MagnumDebugTools + FILES + ColorBatchTestFiles/bc1.png + ColorBatchTestFiles/bc3.png + ColorBatchTestFiles/bc4.png + ColorBatchTestFiles/bc5.png + ColorBatchTestFiles/checkerboard.png + ColorBatchTestFiles/checkerboard-odd.png) +target_include_directories(MathColorBatchTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) + corrade_add_test(MathRectangularMatrixTest RectangularMatrixTest.cpp LIBRARIES MagnumMathTestLib) corrade_add_test(MathMatrixTest MatrixTest.cpp LIBRARIES MagnumMathTestLib) corrade_add_test(MathMatrix3Test Matrix3Test.cpp LIBRARIES MagnumMathTestLib) diff --git a/src/Magnum/Math/Test/ColorBatchTest.cpp b/src/Magnum/Math/Test/ColorBatchTest.cpp new file mode 100644 index 0000000000..2136f9a294 --- /dev/null +++ b/src/Magnum/Math/Test/ColorBatchTest.cpp @@ -0,0 +1,353 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include /** @todo remove once Debug is stram-free */ +#include + +#include "Magnum/ImageView.h" +#include "Magnum/PixelFormat.h" +#include "Magnum/DebugTools/CompareImage.h" +#include "Magnum/Math/ColorBatch.h" +#include "Magnum/Math/Vector4.h" +#include "Magnum/Trade/AbstractImageConverter.h" +#include "Magnum/Trade/AbstractImporter.h" +#include "Magnum/Trade/ImageData.h" + +#include "configure.h" + +namespace Magnum { namespace Math { namespace Test { namespace { + +struct ColorBatchTest: Corrade::TestSuite::Tester { + explicit ColorBatchTest(); + + void yFlip(); + void yFlip3D(); + + void yFlipInvalidLastDimension(); + + PluginManager::Manager _converterManager{MAGNUM_PLUGINS_IMAGECONVERTER_INSTALL_DIR}; + PluginManager::Manager _importerManager{MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR}; +}; + +/* The expected arrays are formatted from the test failure output with + print(', '.join(["'\\x{:02x}'".format(i + 255 if i < 0 else i) for i in vals])) + with vals being an array printed by the test */ +/** @todo make something more convenient for binary data comparison in + TestSuite already, FFS */ + +/* Used by both yFlip() and yFlip3D() */ +const char CheckerboardBC1[]{ + /* ./format-block-data.py checkerboard.in.png checkerboard.png */ + '\x5c', '\xa6', '\x54', '\x74', '\x00', '\x00', '\x40', '\x40', + '\x3c', '\xa6', '\x95', '\x7c', '\x00', '\x00', '\x02', '\x01', + '\x8c', '\x3e', '\x69', '\x33', '\x00', '\x00', '\x40', '\x40', + '\x8c', '\x3e', '\x29', '\x33', '\x00', '\x00', '\x01', '\x01', + '\x3c', '\xa6', '\x6f', '\x5b', '\x00', '\x40', '\x00', '\x00', + '\x3c', '\xa6', '\xcc', '\x4a', '\x03', '\x01', '\x00', '\x00', + '\x8c', '\x3e', '\xc9', '\x32', '\x40', '\xc0', '\x00', '\x00', + '\x8c', '\x3e', '\x89', '\x33', '\x01', '\x01', '\x00', '\x00', + '\x6c', '\x3e', '\xe9', '\x32', '\x00', '\x00', '\xc0', '\x40', + '\x6c', '\x3e', '\x69', '\x33', '\x00', '\x00', '\x01', '\x01', + '\x66', '\xc6', '\x67', '\x84', '\x00', '\x00', '\x40', '\x40', + '\x66', '\xc6', '\xa6', '\x8c', '\x00', '\x00', '\x02', '\x01', + '\x8c', '\x3e', '\xc9', '\x32', '\x40', '\xc0', '\x00', '\x00', + '\x8c', '\x3e', '\x8a', '\x33', '\x03', '\x01', '\x00', '\x00', + '\x66', '\xc6', '\x87', '\x63', '\x00', '\x40', '\x00', '\x00', + '\x66', '\xc6', '\xc7', '\x52', '\x03', '\x01', '\x00', '\x00', + '\x66', '\xc6', '\x87', '\x6b', '\x00', '\x00', '\x40', '\x00', + '\x66', '\xc6', '\x67', '\x63', '\x00', '\x00', '\x01', '\x09', + '\xa6', '\xc9', '\xc7', '\x51', '\x00', '\x00', '\xc0', '\x40', + '\xa6', '\xc9', '\xa7', '\x61', '\x00', '\x00', '\x01', '\x01', + '\x66', '\xc6', '\x67', '\x42', '\x40', '\xc0', '\x00', '\x00', + '\x66', '\xc6', '\xc7', '\x73', '\x01', '\x01', '\x00', '\x00', + '\xa6', '\xc9', '\xa7', '\x51', '\x40', '\xc0', '\x00', '\x00', + '\xa6', '\xc9', '\xa7', '\x69', '\x03', '\x01', '\x00', '\x00', +}; +const char CheckerboardBC1Flipped[]{ + '\x66', '\xc6', '\x67', '\x42', '\x00', '\x00', '\xc0', '\x40', + '\x66', '\xc6', '\xc7', '\x73', '\x00', '\x00', '\x01', '\x01', + '\xa6', '\xc9', '\xa7', '\x51', '\x00', '\x00', '\xc0', '\x40', + '\xa6', '\xc9', '\xa7', '\x69', '\x00', '\x00', '\x01', '\x03', + '\x66', '\xc6', '\x87', '\x6b', '\x00', '\x40', '\x00', '\x00', + '\x66', '\xc6', '\x67', '\x63', '\x09', '\x01', '\x00', '\x00', + '\xa6', '\xc9', '\xc7', '\x51', '\x40', '\xc0', '\x00', '\x00', + '\xa6', '\xc9', '\xa7', '\x61', '\x01', '\x01', '\x00', '\x00', + '\x8c', '\x3e', '\xc9', '\x32', '\x00', '\x00', '\xc0', '\x40', + '\x8c', '\x3e', '\x8a', '\x33', '\x00', '\x00', '\x01', '\x03', + '\x66', '\xc6', '\x87', '\x63', '\x00', '\x00', '\x40', '\x00', + '\x66', '\xc6', '\xc7', '\x52', '\x00', '\x00', '\x01', '\x03', + '\x6c', '\x3e', '\xe9', '\x32', '\x40', '\xc0', '\x00', '\x00', + '\x6c', '\x3e', '\x69', '\x33', '\x01', '\x01', '\x00', '\x00', + '\x66', '\xc6', '\x67', '\x84', '\x40', '\x40', '\x00', '\x00', + '\x66', '\xc6', '\xa6', '\x8c', '\x01', '\x02', '\x00', '\x00', + '\x3c', '\xa6', '\x6f', '\x5b', '\x00', '\x00', '\x40', '\x00', + '\x3c', '\xa6', '\xcc', '\x4a', '\x00', '\x00', '\x01', '\x03', + '\x8c', '\x3e', '\xc9', '\x32', '\x00', '\x00', '\xc0', '\x40', + '\x8c', '\x3e', '\x89', '\x33', '\x00', '\x00', '\x01', '\x01', + '\x5c', '\xa6', '\x54', '\x74', '\x40', '\x40', '\x00', '\x00', + '\x3c', '\xa6', '\x95', '\x7c', '\x01', '\x02', '\x00', '\x00', + '\x8c', '\x3e', '\x69', '\x33', '\x40', '\x40', '\x00', '\x00', + '\x8c', '\x3e', '\x29', '\x33', '\x01', '\x01', '\x00', '\x00' +}; + +const struct { + const char* name; + CompressedPixelFormat format; + Vector2i blockCount; + Containers::Array input; + void(*function)(const Containers::StridedArrayView4D&); + const char* file; + Containers::Array expected; +} YFlipData[]{ + /* The multi-block behavior is tested just for one format as it's internally + a shared template for all */ + {"BC1, even block count", CompressedPixelFormat::Bc1RGBAUnorm, {4, 6}, + Containers::Array{const_cast(CheckerboardBC1), Containers::arraySize(CheckerboardBC1), [](char*, std::size_t){}}, + yFlipBc1InPlace, "checkerboard.png", + Containers::Array{const_cast(CheckerboardBC1Flipped), Containers::arraySize(CheckerboardBC1Flipped), [](char*, std::size_t){}}}, + {"BC1, odd block count", CompressedPixelFormat::Bc1RGBAUnorm, {6, 3}, {InPlaceInit, { + /* ./format-block-data.py checkerboard-odd.in.png checkerboard-odd.png */ + '\x8c', '\x3e', '\x8a', '\x33', '\x00', '\xa0', '\x50', '\x90', + '\x66', '\xc6', '\x8c', '\x3e', '\x05', '\x05', '\x05', '\x05', + '\x66', '\xc6', '\xe7', '\x73', '\x00', '\x02', '\x0b', '\x06', + '\xa6', '\xc9', '\xa7', '\x71', '\x00', '\xa0', '\x50', '\x50', + '\xa6', '\xc9', '\x19', '\x34', '\x50', '\x50', '\x50', '\x50', + '\x19', '\x34', '\x32', '\x33', '\x00', '\x0f', '\x07', '\x05', + '\x45', '\xc6', '\x2c', '\x3e', '\x55', '\x55', '\x00', '\xa0', + '\x25', '\xe2', '\x8a', '\x77', '\xf5', '\xf5', '\x0f', '\x0f', + '\x66', '\xbe', '\xa6', '\xb9', '\x0a', '\x00', '\x55', '\x55', + '\xa6', '\xc1', '\x18', '\x34', '\x00', '\x00', '\x55', '\x55', + '\xc5', '\xc9', '\x38', '\x34', '\x50', '\x50', '\xf5', '\xf5', + '\x8e', '\x73', '\x19', '\x34', '\x57', '\x55', '\x00', '\x00', + '\x66', '\xc6', '\x67', '\x63', '\x60', '\xd0', '\xa0', '\x00', + '\xa6', '\xc9', '\x66', '\xc6', '\x05', '\x05', '\x05', '\x05', + '\xa6', '\xc9', '\xa7', '\x71', '\x05', '\x0d', '\x0a', '\x00', + '\x19', '\x34', '\xd0', '\x32', '\xb0', '\x60', '\xf0', '\x00', + '\xae', '\x73', '\x19', '\x34', '\x05', '\x05', '\x05', '\x05', + '\xae', '\x73', '\x8a', '\x4a', '\x05', '\x05', '\x0a', '\x00', + }}, yFlipBc1InPlace, "checkerboard-odd.png", {InPlaceInit, { + '\x66', '\xc6', '\x67', '\x63', '\x00', '\xa0', '\xd0', '\x60', + '\xa6', '\xc9', '\x66', '\xc6', '\x05', '\x05', '\x05', '\x05', + '\xa6', '\xc9', '\xa7', '\x71', '\x00', '\x0a', '\x0d', '\x05', + '\x19', '\x34', '\xd0', '\x32', '\x00', '\xf0', '\x60', '\xb0', + '\xae', '\x73', '\x19', '\x34', '\x05', '\x05', '\x05', '\x05', + '\xae', '\x73', '\x8a', '\x4a', '\x00', '\x0a', '\x05', '\x05', + '\x45', '\xc6', '\x2c', '\x3e', '\xa0', '\x00', '\x55', '\x55', + '\x25', '\xe2', '\x8a', '\x77', '\x0f', '\x0f', '\xf5', '\xf5', + '\x66', '\xbe', '\xa6', '\xb9', '\x55', '\x55', '\x00', '\x0a', + '\xa6', '\xc1', '\x18', '\x34', '\x55', '\x55', '\x00', '\x00', + '\xc5', '\xc9', '\x38', '\x34', '\xf5', '\xf5', '\x50', '\x50', + '\x8e', '\x73', '\x19', '\x34', '\x00', '\x00', '\x55', '\x57', + '\x8c', '\x3e', '\x8a', '\x33', '\x90', '\x50', '\xa0', '\x00', + '\x66', '\xc6', '\x8c', '\x3e', '\x05', '\x05', '\x05', '\x05', + '\x66', '\xc6', '\xe7', '\x73', '\x06', '\x0b', '\x02', '\x00', + '\xa6', '\xc9', '\xa7', '\x71', '\x50', '\x50', '\xa0', '\x00', + '\xa6', '\xc9', '\x19', '\x34', '\x50', '\x50', '\x50', '\x50', + '\x19', '\x34', '\x32', '\x33', '\x05', '\x07', '\x0f', '\x00' + }}}, + {"BC1", CompressedPixelFormat::Bc1RGBUnorm, {1, 4}, {InPlaceInit, { + /* ./extract-interesting-blocks.py kodim23_bc1.dds bc1.png --offset 139 + (image taken from the bcdec repository test files) */ + /* [50, 53], 1.792 */ + '\x79', '\xd6', '\xa7', '\x39', '\x5c', '\x55', '\xd5', '\x35', + /* [32, 46], 1.784 */ + '\xdd', '\xff', '\xa8', '\x6b', '\x55', '\x95', '\x25', '\x09', + /* [48, 61], 1.780 */ + '\xba', '\xe6', '\x07', '\x52', '\x00', '\x00', '\x2a', '\xd5', + /* [132, 47], 1.780 */ + '\x7a', '\xfe', '\x46', '\x81', '\xe0', '\x78', '\xd7', '\x2d', + }}, yFlipBc1InPlace, "bc1.png", {InPlaceInit, { + '\x7a', '\xfe', '\x46', '\x81', '\x2d', '\xd7', '\x78', '\xe0', + '\xba', '\xe6', '\x07', '\x52', '\xd5', '\x2a', '\x00', '\x00', + '\xdd', '\xff', '\xa8', '\x6b', '\x09', '\x25', '\x95', '\x55', + '\x79', '\xd6', '\xa7', '\x39', '\x35', '\xd5', '\x55', '\x5c' + }}}, + {"BC3", CompressedPixelFormat::Bc3RGBAUnorm, {1, 4}, {InPlaceInit, { + /* ./extract-interesting-blocks.py dice_bc3.dds bc3.png --offset 148 + (image taken from the bcdec repository test files) */ + /* [105, 42], 2.392 */ + '\x26', '\x98', '\xb6', '\x0d', '\x00', '\x23', '\x99', '\x24', + '\x8e', '\xfb', '\x00', '\x18', '\x55', '\x00', '\x02', '\xaa', + /* [121, 122], 2.388 */ + '\x0d', '\x5f', '\x29', '\x57', '\x4e', '\x9c', '\x30', '\xc1', + '\x8b', '\xd6', '\x00', '\x00', '\x00', '\x00', '\x00', '\x40', + /* [160, 22], 2.388 */ + '\x02', '\x4a', '\x1c', '\x5c', '\xca', '\xe5', '\x90', '\x52', + '\x72', '\x97', '\x00', '\x00', '\x40', '\x40', '\x00', '\x00', + /* [96, 131], 2.376 */ + '\x03', '\x32', '\x49', '\xba', '\x6d', '\xb6', '\x6d', '\xdb', + '\x0c', '\xe7', '\x00', '\x00', '\x00', '\x00', '\x51', '\x55', + }}, yFlipBc3InPlace, "bc3.png", {InPlaceInit, { + '\x03', '\x32', '\xb6', '\x6d', '\xdb', '\xdb', '\x96', '\xa4', + '\x0c', '\xe7', '\x00', '\x00', '\x55', '\x51', '\x00', '\x00', + '\x02', '\x4a', '\x29', '\x55', '\x0e', '\xa5', '\xcc', '\xc1', + '\x72', '\x97', '\x00', '\x00', '\x00', '\x00', '\x40', '\x40', + '\x0d', '\x5f', '\x13', '\xcc', '\x09', '\xe5', '\x94', '\x72', + '\x8b', '\xd6', '\x00', '\x00', '\x40', '\x00', '\x00', '\x00', + '\x26', '\x98', '\x49', '\x32', '\x92', '\x00', '\x60', '\xdb', + '\x8e', '\xfb', '\x00', '\x18', '\xaa', '\x02', '\x00', '\x55' + }}}, + {"BC4", CompressedPixelFormat::Bc4RUnorm, {1, 4}, {InPlaceInit, { + /* ./extract-interesting-blocks.py dice_bc4.dds bc4.png + (image taken from the bcdec repository test files) */ + /* [88, 130], 1.000 */ + '\xec', '\xed', '\x3e', '\x62', '\xdb', '\xb6', '\x6d', '\xdb', + /* [87, 129], 1.000 */ + '\xd9', '\xec', '\xa3', '\xd0', '\x70', '\x7e', '\x62', '\xfb', + /* [82, 125], 1.000 */ + '\xdd', '\xfc', '\xa7', '\xe0', '\x4c', '\x36', '\x67', '\x9b', + /* [81, 124], 1.000 */ + '\xe9', '\xf5', '\x76', '\x60', '\x7f', '\xb6', '\x67', '\xfb', + }}, yFlipBc4InPlace, "bc4.png", {InPlaceInit, { + '\xe9', '\xf5', '\xb6', '\x6f', '\x7b', '\xf6', '\x67', '\x07', + '\xdd', '\xfc', '\xb6', '\x69', '\x73', '\xce', '\x74', '\x0a', + '\xd9', '\xec', '\xb6', '\xef', '\x27', '\x0d', '\x37', '\x0a', + '\xec', '\xed', '\xb6', '\x6d', '\xdb', '\xb6', '\xed', '\x23' + }}}, + {"BC5", CompressedPixelFormat::Bc5RGUnorm, {1, 4}, {InPlaceInit, { + /* ./extract-interesting-blocks.py dice_bc5.dds bc5.png --offset 26 + (image taken from the bcdec repository test files) */ + /* [120, 124], 2.000 */ + '\xd3', '\xdf', '\x58', '\xbf', '\xda', '\xb1', '\x7d', '\xdb', '\xd3', '\xdf', '\x58', '\xbf', '\xda', '\xb1', '\x7d', '\xdb', + /* [81, 124], 2.000 */ + '\xeb', '\xf6', '\x76', '\x60', '\x7f', '\xb6', '\x67', '\xfb', '\xeb', '\xf6', '\x76', '\x60', '\x7f', '\xb6', '\x67', '\xfb', + /* [121, 123], 2.000 */ + '\xd4', '\xe2', '\xc8', '\x1d', '\xdb', '\xb3', '\x6d', '\xdb', '\xd4', '\xe2', '\xc8', '\x1d', '\xdb', '\xb3', '\x6d', '\xdb', + /* [81, 123], 2.000 */ + '\xd7', '\xf3', '\x9d', '\x10', '\x4e', '\x2f', '\xe7', '\x77', '\xd7', '\xf3', '\x9d', '\x10', '\x4e', '\x2f', '\xe7', '\x77', + }}, yFlipBc5InPlace, "bc5.png", {InPlaceInit, { + '\xd7', '\xf3', '\x7e', '\xf7', '\x72', '\xe1', '\xd4', '\x09', + '\xd7', '\xf3', '\x7e', '\xf7', '\x72', '\xe1', '\xd4', '\x09', + '\xd4', '\xe2', '\xb6', '\x3d', '\xdb', '\xb1', '\x8d', '\xdc', + '\xd4', '\xe2', '\xb6', '\x3d', '\xdb', '\xb1', '\x8d', '\xdc', + '\xeb', '\xf6', '\xb6', '\x6f', '\x7b', '\xf6', '\x67', '\x07', + '\xeb', '\xf6', '\xb6', '\x6f', '\x7b', '\xf6', '\x67', '\x07', + '\xd3', '\xdf', '\xb7', '\x1d', '\xdb', '\xab', '\x8d', '\xf5', + '\xd3', '\xdf', '\xb7', '\x1d', '\xdb', '\xab', '\x8d', '\xf5' + }}}, +}; + +ColorBatchTest::ColorBatchTest() { + addInstancedTests({&ColorBatchTest::yFlip}, + Containers::arraySize(YFlipData)); + + addTests({&ColorBatchTest::yFlip3D, + + &ColorBatchTest::yFlipInvalidLastDimension}); +} + +void ColorBatchTest::yFlip() { + auto&& data = YFlipData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Copy to a mutable array first to operate in-place */ + Containers::Array blocks{Corrade::NoInit, data.input.size()}; + Utility::copy(data.input, blocks); + /* Using expanded() instead of the constructor as it catches issues where + the size would be smaller than the actual data */ + data.function(stridedArrayView(blocks).expanded<0>(Containers::Size3D{ + std::size_t(data.blockCount.y()), + std::size_t(data.blockCount.x()), + compressedPixelFormatBlockDataSize(data.format)})); + CORRADE_COMPARE_AS(blocks, + data.expected, + TestSuite::Compare::Container); + + /* Catch also ABI and interface mismatch errors */ + if(!(_converterManager.load("BcDecImageConverter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("BcDecImageConverter plugin can't be loaded, cannot test decoded image equality."); + if(!(_importerManager.load("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_importerManager.load("PngImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / PngImporter plugin can't be loaded, cannot test decoded image equality."); + + /* The flipped output should be exactly the same after decoding as an + Y-flipped decoded input */ + Containers::Pointer decoder = _converterManager.loadAndInstantiate("BcDecImageConverter"); + Containers::Optional decoded = decoder->convert(CompressedImageView2D{data.format, data.blockCount*compressedPixelFormatBlockSize(data.format).xy(), blocks}); + CORRADE_VERIFY(decoded); + + if(decoded->format() == PixelFormat::RGBA8Unorm) + CORRADE_COMPARE_WITH(decoded->pixels().flipped<0>(), + Corrade::Utility::Path::join(COLORBATCH_TEST_DIR, data.file), + (DebugTools::CompareImageToFile{_importerManager, _converterManager})); + else if(decoded->format() == PixelFormat::RG8Unorm) + CORRADE_COMPARE_WITH(decoded->pixels().flipped<0>(), + Corrade::Utility::Path::join(COLORBATCH_TEST_DIR, data.file), + (DebugTools::CompareImageToFile{_importerManager, _converterManager})); + else if(decoded->format() == PixelFormat::R8Unorm) + CORRADE_COMPARE_WITH(decoded->pixels().flipped<0>(), + Corrade::Utility::Path::join(COLORBATCH_TEST_DIR, data.file), + (DebugTools::CompareImageToFile{_importerManager, _converterManager})); + else CORRADE_FAIL("Unexpected format" << decoded->format()); +} + +void ColorBatchTest::yFlip3D() { + /* Copy to a mutable array first to operate in-place */ + Containers::Array blocks{Corrade::NoInit, Containers::arraySize(CheckerboardBC1)}; + Utility::copy(CheckerboardBC1, blocks); + + /* The 2D 4x6 blocks image is turned into 4 slices of 1x6 blocks each. + Y flipping should result in the exact same result as in the 2D case. */ + Containers::StridedArrayView4D view{blocks, + {4, 6, 1, 8}, + {1*8, /* Each slice is 1 block wide */ + 4*8, /* Each row is still 4 blocks */ + 8, /* Each block is 8 bytes */ + 1} + }; + yFlipBc1InPlace(view); + + CORRADE_COMPARE_AS(blocks, + Containers::arrayView(CheckerboardBC1Flipped), + TestSuite::Compare::Container); +} + +void ColorBatchTest::yFlipInvalidLastDimension() { + CORRADE_SKIP_IF_NO_ASSERT(); + + /* All formats delegate to the same template code with the assertions so + it's enough to test just some */ + char data[32]; + + std::ostringstream out; + Error redirectError{&out}; + yFlipBc3InPlace(Containers::stridedArrayView(data).expanded<0>(Containers::Size3D{1, 4, 8})); + yFlipBc1InPlace(Containers::stridedArrayView(data).expanded<0>(Containers::Size3D{1, 2, 16}).every({1, 1, 2})); + CORRADE_COMPARE(out.str(), + "Math::yFlipBc3InPlace(): expected last dimension to be 16 bytes but got 8\n" + "Math::yFlipBc1InPlace(): last dimension is not contiguous\n"); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Math::Test::ColorBatchTest) diff --git a/src/Magnum/Math/Test/ColorBatchTestFiles/README.md b/src/Magnum/Math/Test/ColorBatchTestFiles/README.md new file mode 100644 index 0000000000..3997ac0d04 --- /dev/null +++ b/src/Magnum/Math/Test/ColorBatchTestFiles/README.md @@ -0,0 +1,23 @@ +The `checkerboard.png` is exported as `checkerboard.in.png` with Inkscape from +`Shaders/Test/TestFiles/checkerboard.svg` with: + +- the non-alpha checkerboard layer enabled +- the bold text layer enabled +- the uneven top right area selected +- DPI set to 3, resulting in a 32x24 image (8x6 blocks) + +The `checkerboard-odd.png` is exported as `checkerboard-odd.in.png` with the +even top right area selected and the size set to 28x28, resulting in 7x7 +blocks. + +Both are then passed through StbDxtImageConverter and subsequently decoded +back to `checkerboard.png` and `checkerboard-odd.png` with +`compress-format-blocks.py` to act as a ground truth, the output of the script +and its full invocation is pasted to the `ColorBatchTest.cpp`. + +The remaining images are generated with `extract-interesting-blocks.py`, the +output of that script and its full invocation is again in `ColorBatchTest.cpp`. + +Unlike in other tests, the images are PNGs and not TGAs. The full test process +requires `BcDecImageConverter` from the plugins repo anyway, so there's no +point in having larger TGAs. diff --git a/src/Magnum/Math/Test/ColorBatchTestFiles/bc1.png b/src/Magnum/Math/Test/ColorBatchTestFiles/bc1.png new file mode 100644 index 0000000000000000000000000000000000000000..31440d67423466792e729918e7c7bc731b3ddb25 GIT binary patch literal 232 zcmVP)o2c~)txhAMk%&sESBXimg=FUj&amAmHbd6y z-uGVq_lspmIjJ>rppjZ5PLsr0V*m=^o|&`8U~dL%3^kv4iSaOU-l$%PI3M<9@V40( z095!>HLu%6yCZ4IE{$fRXzY$F($^KBnDsCQ#B4q)9j&F89Dp!TB-0oX^?#3rfhy15 iA1RgXW=$BVFZ=-d(>01g%+mq@0000(9780g=JqS{H9H8n%Fiwh zX;`SbGOqu~qn!+5r&+63#xa~{^a^Ud-I5R~q~f?yl4J3+{r}Zx@|PRmy7m5G#%q^Z zQWe~LU%gkZW;A}#;K})rVYy39-lMeOORN`K-!dgIJ0)CnD7o{eUR1r{kKp_RA0j_x oFVgL;tO}62%)D2Bdfcq{EEnYcFNBoc2fByB)78&qol`;+0HS0_f&c&j literal 0 HcmV?d00001 diff --git a/src/Magnum/Math/Test/ColorBatchTestFiles/bc4.png b/src/Magnum/Math/Test/ColorBatchTestFiles/bc4.png new file mode 100644 index 0000000000000000000000000000000000000000..1e4b3fcf7cbeca36a065fdce7f8f625efd7c1292 GIT binary patch literal 113 zcmeAS@N?(olHy`uVBq!ia0vp^EI=&40VEhi?l$TJDGN^*#}JO0p$8qg8VqhFoE;6ypB)X&h+OtWLGk7<6SIXU5>cX(b)M=82XAsM^zGR#4wTEh-=d3^Qtl{;e uf(C{U`}RKOubd|w<;}?;ak1L#8+*Vc>l(Gw3&VhxFnGH9xvX3^W4l0pcfMPREoZE|7T?jeY7raR~c+Is`-k`|d#sT7qN zD4}8gJI*-mCggmZzTJ6v-}5fc`>C6J%186UXNC@QdUC1dnV%~y54M!cAqw}F5Y&~y zqcWwl@c{Lrz}^0UPPZ=ua=9?WkrIY15ga6EX%kv+0)N(!l#1d zv}>@jC$OOu?!*n|!09-UIUWl`+IN{M&6$?t5M2>?J!a3$K|nI;$t6Gn7j!k4U)AcD z7Z+w`byz&tw&=pkX&3yrU4eSqaPXdbLQp7Jn#q&^<^ zczKpbz9!7Ap*eo<3tF!-Kv{}uWgzs`s!3{pB6tYs>*{Mpz#Y!$0I-g1d|#epd&|SN zB|p&r4lpg4`)gMaU?ieCIcu@0mhbqUzFo21nei78gKe*fIi=bF00008jeaADbL9YcNK| z7)EL_qck&*v;&uU_sn_syz_XmyNB{O5{Ppd3$^N{ls_3TU} z%tx<6u!ke;;V5d*Xe4Rt(LuK)d6pThoy8+8sev{z zT_h{ZQvJF%LDMD?{+K^Zf0@h-ltNOfl??~TBj-`K0__hF^lI>_cK0g)0000&c0FkRAys9+&t8UH~3LFyDq5ikT& zsE~h*;U+5a}Db|?_K_T7i8gZGuOc@d+=`^c%zH{ZW|3P z{@NrMaSFprVQ;TW*n#!+F11_gI?%{sPkNY#m6RA@tgfsscWI6%1F?IAD}^H1qX_L> zXWDJGirl~F<^}+1c$kS2_y!5Qlo+5J3$wE>z^U|~Ph<*z$I?A;A?EnA( literal 0 HcmV?d00001 diff --git a/src/Magnum/Math/Test/ColorBatchTestFiles/checkerboard.png b/src/Magnum/Math/Test/ColorBatchTestFiles/checkerboard.png new file mode 100644 index 0000000000000000000000000000000000000000..b294507bad11ca3e89d37f350193c7391e844f24 GIT binary patch literal 355 zcmV-p0i6DcP)feF7fVSr-5%2jL(>sE8(RJ@jfjbSbWsyvDTSFSSSwe30sGlL?- zU)%;XH-2aM@P&cl{sSBau|N#LX~43@5a%f>pxOchn*TAy83#NT7_BZP<8?vzpL4Wz z0g3^?*1u=?KL0&V#q$<(GW2%iNe6##{9s^KV8dxZQtWpIHFflK@P~l`j#(HO{@(e- zfFgzj_8;J65E1)@+Xc*O95`)<0X0=rJ_|C3&07$!W=Dtb7-E#CgQdrw;Y|lqd1~<* zK;3k(b@^0=rIT^n2uUZ;aU0+rnav=lqJz_bzc+qhh_N8Y!?^?d8H7dUaJyH8fq~%* zst`sP$SAAfHUJAiNe4y_ju@#A8E~dbVu(>33jh|9Q7rw$#+Cp8002ovPDHLkV1iWk BlAQnm literal 0 HcmV?d00001 diff --git a/src/Magnum/Math/Test/ColorBatchTestFiles/extract-interesting-blocks.py b/src/Magnum/Math/Test/ColorBatchTestFiles/extract-interesting-blocks.py new file mode 100755 index 0000000000..b768df4549 --- /dev/null +++ b/src/Magnum/Math/Test/ColorBatchTestFiles/extract-interesting-blocks.py @@ -0,0 +1,127 @@ +#!/usr/bin/python3 + +import argparse +from typing import List, Tuple + +from corrade import containers +from magnum import * +from magnum import math, trade + +parser = argparse.ArgumentParser() +parser.add_argument('input') +parser.add_argument('output') +parser.add_argument('--offset', type=int, default=0) +parser.add_argument('--count', type=int, default=4) +args = parser.parse_args() + +importer_manager = trade.ImporterManager() +importer_manager.metadata('DdsImporter').configuration['assumeYUpZBackward'] = True +# TODO something for KTX as well so it doesn't warn (or flip) +importer = importer_manager.load_and_instantiate('AnyImageImporter') +importer.open_file(args.input) + +image = importer.image2d(0) + +assert image.is_compressed, "expected a compressed image" + +print(f"format: {image.compressed_format}") + +block_size = image.compressed_format.block_size +assert image.size % block_size.xy == Vector2i(0), "expected whole blocks" + +block_data_size = image.compressed_format.block_data_size +# TODO use image.blocks once it exists +blocks = containers.StridedArrayView1D(image.data).expanded(0, ( + image.size.y//block_size.y, + image.size.x//block_size.x, + block_data_size +)) + +if image.compressed_format.name.startswith('BC'): + decoder_name = 'BcDecImageConverter' +elif image.compressed_format.name.startswith('ETC') or \ + image.compressed_format.name.startswith('EAC'): + decoder_name = 'EtcDecImageConverter' +else: assert False, f"unsupported format {image.compressed_format}" + +converter_manager = trade.ImageConverterManager() +decoder = converter_manager.load_and_instantiate(decoder_name) +decoded = decoder.convert(image) +assert decoded.size == image.size +# TODO some better way to get this directly from pixels? i.e., want to access +# the bytes and not the high-level type +# assert decoded.pixels.is_contiguous +decoded_pixel_size = decoded.format.size +decoded_pixel_data = containers.StridedArrayView1D(decoded.data).expanded(0, ( + image.size.y, + image.size.x, + decoded_pixel_size +)) + +block_count = decoded.size/block_size.xy + +pixels = decoded.pixels + +if decoded.format.channel_count == 1: + default_min = math.inf + default_max = -math.inf + sum_ = lambda i: i +elif decoded.format.channel_count == 2: + default_min = Vector2(math.inf) + default_max = Vector2(-math.inf) + sum_ = lambda i: i.sum() +elif decoded.format.channel_count == 3: + default_min = Vector3(math.inf) + default_max = Vector3(-math.inf) + sum_ = lambda i: i.sum() +elif decoded.format.channel_count == 4: + default_min = Vector4(math.inf) + default_max = Vector4(-math.inf) + sum_ = lambda i: i.sum() + +block_ranges: List[Tuple[int, int, float]] = [] + +# This is extremely slow. Pybind11 ugh what are you doing?! +for y in range(block_count.y): + oy = y*block_size.y + for x in range(block_count.x): + ox = x*block_size.x + min_ = default_min + max_ = default_max + for by in range(block_size.y): + for bx in range(block_size.x): + min_ = math.min(pixels[oy + by, ox + bx], min_) + max_ = math.max(pixels[oy + by, ox + bx], max_) + + block_ranges += [(x, y, sum_(max_ - min_))] + +# Get most interesting blocks +most_interesting_blocks = list(reversed(sorted(block_ranges, key=lambda block: block[2])))[args.offset:(args.offset + args.count)] + +print(" char blocks[]{") +for x, y, range_ in most_interesting_blocks: + print(" /* [{}, {}], {:1.3f} */\n {},".format( + x, y, range_, + ', '.join(["'\\x{:02x}'".format(ord(blocks[y, x, i])) for i in range(block_data_size)]) + )) +print(" };") + +# Combine the decoded pixels from the most interesting blocks +data = bytearray(b'\0'*(args.count*block_size.x*block_size.y*decoded_pixel_size)) +offset = 0 +for x, y, range_ in most_interesting_blocks: + oy = y*block_size.y + ox = x*block_size.x + for by in range(block_size.y): + for bx in range(block_size.x): + for i in range(decoded_pixel_size): + data[offset] = ord(decoded_pixel_data[oy + by, ox + bx, i]) + offset += 1 +assert offset == len(data) + +# Write them to a file +converter = converter_manager.load_and_instantiate('AnyImageConverter') +converter.convert_to_file(ImageView2D( + decoded.format, + (block_size.x, block_size.y*args.count), + data), args.output) diff --git a/src/Magnum/Math/Test/ColorBatchTestFiles/format-block-data.py b/src/Magnum/Math/Test/ColorBatchTestFiles/format-block-data.py new file mode 100755 index 0000000000..34757f4c8d --- /dev/null +++ b/src/Magnum/Math/Test/ColorBatchTestFiles/format-block-data.py @@ -0,0 +1,52 @@ +#!/usr/bin/python3 + +import argparse + +from corrade import containers +from magnum import * +from magnum import trade + +parser = argparse.ArgumentParser() +parser.add_argument('input') +parser.add_argument('output') +args = parser.parse_args() + +importer_manager = trade.ImporterManager() +importer = importer_manager.load_and_instantiate('StbImageImporter') +# Don't want the alpha in order to have just 64-bit blocks +importer.configuration['forceChannelCount'] = 3 +importer.open_file(args.input) + +image = importer.image2d(0) +print(f"format: {image.format}") + +converter_manager = trade.ImageConverterManager() +dxt_encoder = converter_manager.load_and_instantiate('StbDxtImageConverter') +dxt_encoder.configuration['highQuality'] = True + +compressed_image = dxt_encoder.convert(image) +print(f"compressed format: {compressed_image.compressed_format}") + +block_size = compressed_image.compressed_format.block_size +block_data_size = compressed_image.compressed_format.block_data_size +assert compressed_image.size % block_size.xy == Vector2i(0), "expected whole blocks" +# TODO use image.blocks once it exists +blocks = containers.StridedArrayView1D(compressed_image.data).expanded(0, ( + compressed_image.size.y//block_size.y, + compressed_image.size.x//block_size.x, + block_data_size +)) + +print(" char blocks[]{") +for y in range(compressed_image.size.y//block_size.y): + for x in range(compressed_image.size.x//block_size.x): + print(" {},".format( + ', '.join(["'\\x{:02x}'".format(ord(blocks[y, x, i])) for i in range(block_data_size)]) + )) +print(" };") + +decoder = converter_manager.load_and_instantiate('BcDecImageConverter') +decoded_image = decoder.convert(compressed_image) + +png_converter = converter_manager.load_and_instantiate('AnyImageConverter') +png_converter.convert_to_file(decoded_image, args.output) diff --git a/src/Magnum/Math/Test/configure.h.cmake b/src/Magnum/Math/Test/configure.h.cmake new file mode 100644 index 0000000000..48af4ffc1e --- /dev/null +++ b/src/Magnum/Math/Test/configure.h.cmake @@ -0,0 +1,48 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#define COLORBATCH_TEST_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ColorBatchTestFiles" + +#ifdef CORRADE_TARGET_WINDOWS +#ifdef CORRADE_IS_DEBUG_BUILD +#define MAGNUM_PLUGINS_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_DEBUG_BINARY_INSTALL_DIR}" +#define MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMPORTER_DEBUG_BINARY_INSTALL_DIR}" +#define MAGNUM_PLUGINS_IMAGECONVERTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMAGECONVERTER_DEBUG_BINARY_INSTALL_DIR}" +#else +#define MAGNUM_PLUGINS_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_RELEASE_BINARY_INSTALL_DIR}" +#define MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMPORTER_RELEASE_BINARY_INSTALL_DIR}" +#define MAGNUM_PLUGINS_IMAGECONVERTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMAGECONVERTER_RELEASE_BINARY_INSTALL_DIR}" +#endif +#else +#ifdef CORRADE_IS_DEBUG_BUILD +#define MAGNUM_PLUGINS_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_DEBUG_LIBRARY_INSTALL_DIR}" +#define MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMPORTER_DEBUG_LIBRARY_INSTALL_DIR}" +#define MAGNUM_PLUGINS_IMAGECONVERTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMAGECONVERTER_DEBUG_LIBRARY_INSTALL_DIR}" +#else +#define MAGNUM_PLUGINS_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_RELEASE_LIBRARY_INSTALL_DIR}" +#define MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMPORTER_RELEASE_LIBRARY_INSTALL_DIR}" +#define MAGNUM_PLUGINS_IMAGECONVERTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMAGECONVERTER_RELEASE_LIBRARY_INSTALL_DIR}" +#endif +#endif diff --git a/src/Magnum/PixelFormat.h b/src/Magnum/PixelFormat.h index abc79a1e97..2f0fce02ab 100644 --- a/src/Magnum/PixelFormat.h +++ b/src/Magnum/PixelFormat.h @@ -1014,7 +1014,8 @@ enum class CompressedPixelFormat: UnsignedInt { * @ref GL::TextureFormat::CompressedRGBS3tcDxt1 or * @ref Vk::PixelFormat::CompressedBc1RGBUnorm. No D3D or Metal * equivalent. - * @see @relativeref{Trade,BcDecImageConverter} + * @see @ref Math::yFlipBc1InPlace(), + * @relativeref{Trade,BcDecImageConverter} */ Bc1RGBUnorm = 1, @@ -1027,7 +1028,8 @@ enum class CompressedPixelFormat: UnsignedInt { * @ref GL::TextureFormat::CompressedSRGBS3tcDxt1 or * @ref Vk::PixelFormat::CompressedBc1RGBSrgb. No D3D or Metal * equivalent. - * @see @relativeref{Trade,BcDecImageConverter} + * @see @ref Math::yFlipBc1InPlace(), + * @relativeref{Trade,BcDecImageConverter} * @m_since{2019,10} */ Bc1RGBSrgb, @@ -1042,7 +1044,8 @@ enum class CompressedPixelFormat: UnsignedInt { * @ref Vk::PixelFormat::CompressedBc1RGBAUnorm; * @m_class{m-doc-external} [DXGI_FORMAT_BC1_UNORM](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) * or @m_class{m-doc-external} [MTLPixelFormatBC1_RGBA](https://developer.apple.com/documentation/metal/mtlpixelformat/mtlpixelformatbc1_rgba?language=objc). - * @see @relativeref{Trade,BcDecImageConverter} + * @see @ref Math::yFlipBc1InPlace(), + * @relativeref{Trade,BcDecImageConverter} * @m_keywords{DXGI_FORMAT_BC1_UNORM MTLPixelFormatBC1_RGBA} */ Bc1RGBAUnorm, @@ -1057,7 +1060,8 @@ enum class CompressedPixelFormat: UnsignedInt { * @ref Vk::PixelFormat::CompressedBc1RGBASrgb; * @m_class{m-doc-external} [DXGI_FORMAT_BC1_UNORM_SRGB](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) * or @m_class{m-doc-external} [MTLPixelFormatBC1_RGBA_sRGB](https://developer.apple.com/documentation/metal/mtlpixelformat/mtlpixelformatbc1_rgba_srgb?language=objc). - * @see @relativeref{Trade,BcDecImageConverter} + * @see @ref Math::yFlipBc1InPlace(), + * @relativeref{Trade,BcDecImageConverter} * @m_keywords{DXGI_FORMAT_BC1_UNORM_SRGB MTLPixelFormatBC1_RGBA_sRGB} * @m_since{2019,10} */ @@ -1107,7 +1111,8 @@ enum class CompressedPixelFormat: UnsignedInt { * @ref Vk::PixelFormat::CompressedBc3RGBAUnorm; * @m_class{m-doc-external} [DXGI_FORMAT_BC3_UNORM](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) * or @m_class{m-doc-external} [MTLPixelFormatBC3_RGBA](https://developer.apple.com/documentation/metal/mtlpixelformat/mtlpixelformatbc3_rgba?language=objc). - * @see @relativeref{Trade,BcDecImageConverter} + * @see @ref Math::yFlipBc3InPlace(), + * @relativeref{Trade,BcDecImageConverter} * @m_keywords{DXGI_FORMAT_BC3_UNORM MTLPixelFormatBC3_RGBA} */ Bc3RGBAUnorm, @@ -1123,7 +1128,8 @@ enum class CompressedPixelFormat: UnsignedInt { * @ref Vk::PixelFormat::CompressedBc3RGBASrgb; * @m_class{m-doc-external} [DXGI_FORMAT_BC3_UNORM_SRGB](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) * or @m_class{m-doc-external} [MTLPixelFormatBC3_RGBA_sRGB](https://developer.apple.com/documentation/metal/mtlpixelformat/mtlpixelformatbc3_rgba_srgb?language=objc). - * @see @relativeref{Trade,BcDecImageConverter} + * @see @ref Math::yFlipBc3InPlace(), + * @relativeref{Trade,BcDecImageConverter} * @m_keywords{DXGI_FORMAT_BC3_UNORM_SRGB MTLPixelFormatBC3_RGBA_sRGB} * @m_since{2019,10} */ @@ -1139,7 +1145,8 @@ enum class CompressedPixelFormat: UnsignedInt { * @ref Vk::PixelFormat::CompressedBc4RUnorm; * @m_class{m-doc-external} [DXGI_FORMAT_BC4_UNORM](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) * or @m_class{m-doc-external} [MTLPixelFormatBC4_RUnorm](https://developer.apple.com/documentation/metal/mtlpixelformat/mtlpixelformatbc4_runorm?language=objc). - * @see @relativeref{Trade,BcDecImageConverter} + * @see @ref Math::yFlipBc4InPlace(), + * @relativeref{Trade,BcDecImageConverter} * @m_keywords{DXGI_FORMAT_BC4_UNORM MTLPixelFormatBC4_RUnorm} * @m_since{2019,10} */ @@ -1155,7 +1162,8 @@ enum class CompressedPixelFormat: UnsignedInt { * @ref Vk::PixelFormat::CompressedBc4RSnorm; * @m_class{m-doc-external} [DXGI_FORMAT_BC4_SNORM](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) * or @m_class{m-doc-external} [MTLPixelFormatBC4_RSnorm](https://developer.apple.com/documentation/metal/mtlpixelformat/mtlpixelformatbc4_rsnorm?language=objc). - * @see @relativeref{Trade,BcDecImageConverter} + * @see @ref Math::yFlipBc4InPlace(), + * @relativeref{Trade,BcDecImageConverter} * @m_keywords{DXGI_FORMAT_BC4_SNORM MTLPixelFormatBC4_RSnorm} * @m_since{2019,10} */ @@ -1172,7 +1180,8 @@ enum class CompressedPixelFormat: UnsignedInt { * @ref Vk::PixelFormat::CompressedBc5RGUnorm; * @m_class{m-doc-external} [DXGI_FORMAT_BC5_UNORM](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) * or @m_class{m-doc-external} [MTLPixelFormatBC5_RGUnorm](https://developer.apple.com/documentation/metal/mtlpixelformat/mtlpixelformatbc5_rgunorm?language=objc). - * @see @relativeref{Trade,BcDecImageConverter} + * @see @ref Math::yFlipBc5InPlace(), + * @relativeref{Trade,BcDecImageConverter} * @m_keywords{DXGI_FORMAT_BC5_UNORM MTLPixelFormatBC5_RGUnorm} * @m_since{2019,10} */ @@ -1189,7 +1198,8 @@ enum class CompressedPixelFormat: UnsignedInt { * @ref Vk::PixelFormat::CompressedBc5RGSnorm; * @m_class{m-doc-external} [DXGI_FORMAT_BC5_SNORM](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) * or @m_class{m-doc-external} [MTLPixelFormatBC5_RGSnorm](https://developer.apple.com/documentation/metal/mtlpixelformat/mtlpixelformatbc5_rgsnorm?language=objc). - * @see @relativeref{Trade,BcDecImageConverter} + * @see @ref Math::yFlipBc5InPlace(), + * @relativeref{Trade,BcDecImageConverter} * @m_keywords{DXGI_FORMAT_BC5_SNORM MTLPixelFormatBC5_RGSnorm} * @m_since{2019,10} */ diff --git a/src/Magnum/Shaders/Test/TestFiles/checkerboard.svg b/src/Magnum/Shaders/Test/TestFiles/checkerboard.svg index fc7d1189f2..9456e60e2a 100644 --- a/src/Magnum/Shaders/Test/TestFiles/checkerboard.svg +++ b/src/Magnum/Shaders/Test/TestFiles/checkerboard.svg @@ -2,22 +2,22 @@ + enable-background="new" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + inkscape:snap-bbox-edge-midpoints="true" + inkscape:showpageshadow="2" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" /> @@ -274,9 +277,9 @@ inkscape:groupmode="layer" id="layer1" transform="translate(0,244.86668)" - style="display:none"> + style="display:inline"> + style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.058;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.058, 2.116;stroke-dashoffset:0;stroke-opacity:1;marker:none" /> + style="opacity:1;vector-effect:none;fill:#dcdcdc;fill-opacity:1;stroke:none;stroke-width:1.058;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.058, 2.116;stroke-dashoffset:0;stroke-opacity:1;marker:none" /> + style="opacity:1;vector-effect:none;fill:#2a75b6;fill-opacity:1;stroke:none;stroke-width:1.058;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.058, 2.116;stroke-dashoffset:0;stroke-opacity:1;marker:none" /> + style="opacity:1;vector-effect:none;fill:#3bd267;fill-opacity:1;stroke:none;stroke-width:1.058;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.058, 2.116;stroke-dashoffset:0;stroke-opacity:1;marker:none" /> + style="display:none"> @@ -801,16 +804,16 @@ height="67.733337" width="67.733337" id="use6190" - style="opacity:1;vector-effect:none;fill:#dcdcdc;fill-opacity:1;stroke:none;stroke-width:1.05799997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.05799996, 2.11599992;stroke-dashoffset:0;stroke-opacity:1;marker:none" /> + style="opacity:1;vector-effect:none;fill:#dcdcdc;fill-opacity:1;stroke:none;stroke-width:1.058;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.058, 2.116;stroke-dashoffset:0;stroke-opacity:1;marker:none" /> + style="opacity:1;vector-effect:none;fill:#3bd267;fill-opacity:1;stroke:none;stroke-width:1.058;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.058, 2.116;stroke-dashoffset:0;stroke-opacity:1;marker:none" /> + style="opacity:1;vector-effect:none;fill:#3bd267;fill-opacity:1;stroke:none;stroke-width:1.058;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.058, 2.116;stroke-dashoffset:0;stroke-opacity:1;marker:none" /> + style="opacity:1;vector-effect:none;fill:#3bd267;fill-opacity:1;stroke:none;stroke-width:1.058;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.058, 2.116;stroke-dashoffset:0;stroke-opacity:1;marker:none" /> 5 + style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:45.1556px;line-height:1.25;font-family:'Source Sans Pro';-inkscape-font-specification:'Source Sans Pro, Light';font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:center;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:middle;white-space:normal;shape-padding:0;vector-effect:none;fill:#2f363f;fill-opacity:1;stroke:none;stroke-width:0.264583;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1">5 6 7 + style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:45.1556px;line-height:1.25;font-family:'Source Sans Pro';-inkscape-font-specification:'Source Sans Pro, Light';font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:center;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:middle;white-space:normal;shape-padding:0;vector-effect:none;fill:#2f363f;fill-opacity:1;stroke:none;stroke-width:0.264583;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1">7 8 @@ -970,16 +972,16 @@ id="text1329" y="116.00463" x="507.52585" - style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:45.15555573px;line-height:1.25;font-family:'Source Sans Pro';-inkscape-font-specification:'Source Sans Pro, Light';font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:center;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:middle;white-space:normal;shape-padding:0;display:inline;opacity:1;vector-effect:none;fill:#2f363f;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:45.1556px;line-height:1.25;font-family:'Source Sans Pro';-inkscape-font-specification:'Source Sans Pro, Light';font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:center;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:middle;white-space:normal;shape-padding:0;display:inline;opacity:1;vector-effect:none;fill:#2f363f;fill-opacity:1;stroke:none;stroke-width:0.264583;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" xml:space="preserve">1 1 + style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:45.1556px;line-height:1.25;font-family:'Source Sans Pro';-inkscape-font-specification:'Source Sans Pro, Light';font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:center;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:middle;white-space:normal;shape-padding:0;vector-effect:none;fill:#2f363f;fill-opacity:1;stroke:none;stroke-width:0.264583;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1">1 1 2 + style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:45.1556px;line-height:1.25;font-family:'Source Sans Pro';-inkscape-font-specification:'Source Sans Pro, Light';font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:center;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:middle;white-space:normal;shape-padding:0;vector-effect:none;fill:#2f363f;fill-opacity:1;stroke:none;stroke-width:0.264583;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1">2 3 6 + style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:45.1556px;line-height:1.25;font-family:'Source Sans Pro';-inkscape-font-specification:'Source Sans Pro, Light';font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:center;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:middle;white-space:normal;shape-padding:0;vector-effect:none;fill:#2f363f;fill-opacity:1;stroke:none;stroke-width:0.264583;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1">6 7 88 + y="172.44913" /> 7 + style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:45.1556px;line-height:1.25;font-family:'Source Sans Pro';-inkscape-font-specification:'Source Sans Pro, Light';font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:center;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:middle;white-space:normal;shape-padding:0;vector-effect:none;fill:#2f363f;fill-opacity:1;stroke:none;stroke-width:0.264583;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1">7 8 88 + y="307.9158" /> + + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 2 + 3 + 4 + 1 + 3 + 4 + 1 + 2 + 4 + 1 + 2 + 3 + 5 + 6 + 7 + 8 + 5 + 6 + 7 + 8 + 5 + 6 + 7 + 8 + 1 + 2 + 3 + 5 + 6 + 7 + 8 + 1 + 2 + 3 + 6 + 7 + 8 + 1 + 2 + 3 + 7 + 8 + 1 + 2 + 3 + 8 + 4 + 5 + 6 + 7 + 5 + 6 + 4 + 4 + 4 + 5 + 2 + style="opacity:1;vector-effect:none;fill:#c92fe0;fill-opacity:0.432394;stroke:none;stroke-width:0.79375;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.2;marker:none" + inkscape:export-filename="../../../Math/Test/ColorBatchTestFiles/checkerboard-odd.png" /> + style="opacity:1;vector-effect:none;fill:#c92fe0;fill-opacity:0.432394;stroke:none;stroke-width:0.79375;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.2;marker:none" /> + +