diff --git a/Common/CMakeLists.txt b/Common/CMakeLists.txt index 66cff4888..d6d647f66 100644 --- a/Common/CMakeLists.txt +++ b/Common/CMakeLists.txt @@ -22,6 +22,7 @@ set(INTERFACE interface/FixedBlockMemoryAllocator.hpp interface/GeometryPrimitives.h interface/HashUtils.hpp + interface/ImageTools.h interface/LRUCache.hpp interface/FixedLinearAllocator.hpp interface/DynamicLinearAllocator.hpp @@ -58,6 +59,7 @@ set(SOURCE src/FileWrapper.cpp src/FixedBlockMemoryAllocator.cpp src/GeometryPrimitives.cpp + src/ImageTools.cpp src/MemoryFileStream.cpp src/Serializer.cpp src/SpinLock.cpp diff --git a/Common/interface/ImageTools.h b/Common/interface/ImageTools.h new file mode 100644 index 000000000..edeec0f72 --- /dev/null +++ b/Common/interface/ImageTools.h @@ -0,0 +1,88 @@ +/* + * Copyright 2025 Diligent Graphics LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * In no event and under no legal theory, whether in tort (including negligence), + * contract, or otherwise, unless required by applicable law (such as deliberate + * and grossly negligent acts) or agreed to in writing, shall any Contributor be + * liable for any damages, including any direct, indirect, special, incidental, + * or consequential damages of any character arising as a result of this License or + * out of the use or inability to use the software (including but not limited to damages + * for loss of goodwill, work stoppage, computer failure or malfunction, or any and + * all other commercial damages or losses), even if such Contributor has been advised + * of the possibility of such damages. + */ + +#pragma once + +/// \file +/// Image processing tools + +#include "../../Primitives/interface/BasicTypes.h" + + +DILIGENT_BEGIN_NAMESPACE(Diligent) + +#include "../../Primitives/interface/DefineRefMacro.h" + +/// Image difference information +struct ImageDiffInfo +{ + /// The number of pixels that differ + Uint32 NumDiffPixels DEFAULT_INITIALIZER(0); + + /// The number of pixels that differ above the threshold + Uint32 NumDiffPixelsAboveThreshold DEFAULT_INITIALIZER(0); + + /// The maximum difference between any two pixels + Uint32 MaxDiff DEFAULT_INITIALIZER(0); + + /// The average difference between all pixels, not counting pixels that are equal + float AvgDiff DEFAULT_INITIALIZER(0); + + /// The root mean square difference between all pixels, not counting pixels that are equal + float RmsDiff DEFAULT_INITIALIZER(0); +}; + +/// Compute the difference between two images +/// +/// \param [in] Width Image width +/// \param [in] Height Image height +/// \param [in] NumChannels Number of channels in the image +/// \param [in] pImage1 Pointer to the first image data +/// \param [in] Stride1 Row stride of the first image data, in bytes +/// \param [in] pImage2 Pointer to the second image data +/// \param [in] Stride2 Row stride of the second image data, in bytes +/// \param [in] Threshold Difference threshold +/// \return The image difference information, see Diligent::ImageDiffInfo. +/// +/// \remarks The difference between two pixels is calculated as the maximum of the +/// absolute differences of all channels. The average difference is the +/// average of all differences, not counting pixels that are equal. +/// The root mean square difference is calculated as the square root of +/// the average of the squares of all differences, not counting pixels that +/// are equal. +ImageDiffInfo DILIGENT_GLOBAL_FUNCTION(GetImageDifference)( + Uint32 Width, + Uint32 Height, + Uint32 NumChannels, + const void* pImage1, + Uint32 Stride1, + const void* pImage2, + Uint32 Stride2, + Uint32 Threshold); + +#include "../../Primitives/interface/UndefRefMacro.h" + +DILIGENT_END_NAMESPACE // namespace Diligent diff --git a/Common/src/ImageTools.cpp b/Common/src/ImageTools.cpp new file mode 100644 index 000000000..1f9351a1f --- /dev/null +++ b/Common/src/ImageTools.cpp @@ -0,0 +1,121 @@ +/* + * Copyright 2025 Diligent Graphics LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * In no event and under no legal theory, whether in tort (including negligence), + * contract, or otherwise, unless required by applicable law (such as deliberate + * and grossly negligent acts) or agreed to in writing, shall any Contributor be + * liable for any damages, including any direct, indirect, special, incidental, + * or consequential damages of any character arising as a result of this License or + * out of the use or inability to use the software (including but not limited to damages + * for loss of goodwill, work stoppage, computer failure or malfunction, or any and + * all other commercial damages or losses), even if such Contributor has been advised + * of the possibility of such damages. + */ + +#include "ImageTools.h" + +#include +#include + +#include "DebugUtilities.hpp" + +namespace Diligent +{ + +ImageDiffInfo GetImageDifference(Uint32 Width, + Uint32 Height, + Uint32 NumChannels, + const void* pImage1, + Uint32 Stride1, + const void* pImage2, + Uint32 Stride2, + Uint32 Threshold) +{ + ImageDiffInfo Diff; + + if (pImage1 == nullptr || pImage2 == nullptr) + { + UNEXPECTED("Image pointers cannot be null"); + return Diff; + } + + if (Stride1 < Width * NumChannels) + { + UNEXPECTED("Stride1 is too small. It must be at least ", Width * NumChannels, " bytes long."); + return Diff; + } + + if (Stride2 < Width * NumChannels) + { + UNEXPECTED("Stride2 is too small. It must be at least ", Width * NumChannels, " bytes long."); + return Diff; + } + + for (Uint32 row = 0; row < Height; ++row) + { + const Uint8* pRow1 = reinterpret_cast(pImage1) + row * Stride1; + const Uint8* pRow2 = reinterpret_cast(pImage2) + row * Stride2; + + for (Uint32 col = 0; col < Width; ++col) + { + Uint32 PixelDiff = 0; + for (Uint32 ch = 0; ch < NumChannels; ++ch) + { + const Uint32 ChannelDiff = static_cast( + std::abs(static_cast(pRow1[col * NumChannels + ch]) - + static_cast(pRow2[col * NumChannels + ch]))); + PixelDiff = std::max(PixelDiff, ChannelDiff); + } + + if (PixelDiff != 0) + { + ++Diff.NumDiffPixels; + Diff.AvgDiff += static_cast(PixelDiff); + Diff.RmsDiff += static_cast(PixelDiff * PixelDiff); + Diff.MaxDiff = std::max(Diff.MaxDiff, PixelDiff); + + if (PixelDiff > Threshold) + { + ++Diff.NumDiffPixelsAboveThreshold; + } + } + } + } + + if (Diff.NumDiffPixels > 0) + { + Diff.AvgDiff /= static_cast(Diff.NumDiffPixels); + Diff.RmsDiff = std::sqrt(Diff.RmsDiff / static_cast(Diff.NumDiffPixels)); + } + + return Diff; +} + +} // namespace Diligent + +extern "C" +{ + Diligent::ImageDiffInfo Diligent_GetImageDifference(Diligent::Uint32 Width, + Diligent::Uint32 Height, + Diligent::Uint32 NumChannels, + const void* pImage1, + Diligent::Uint32 Stride1, + const void* pImage2, + Diligent::Uint32 Stride2, + Diligent::Uint32 Threshold) + { + return Diligent::GetImageDifference(Width, Height, NumChannels, pImage1, Stride1, pImage2, Stride2, Threshold); + } +} diff --git a/Tests/DiligentCoreTest/src/Common/ImageToolsTest.cpp b/Tests/DiligentCoreTest/src/Common/ImageToolsTest.cpp new file mode 100644 index 000000000..971008a94 --- /dev/null +++ b/Tests/DiligentCoreTest/src/Common/ImageToolsTest.cpp @@ -0,0 +1,76 @@ +/* + * Copyright 2025 Diligent Graphics LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * In no event and under no legal theory, whether in tort (including negligence), + * contract, or otherwise, unless required by applicable law (such as deliberate + * and grossly negligent acts) or agreed to in writing, shall any Contributor be + * liable for any damages, including any direct, indirect, special, incidental, + * or consequential damages of any character arising as a result of this License or + * out of the use or inability to use the software (including but not limited to damages + * for loss of goodwill, work stoppage, computer failure or malfunction, or any and + * all other commercial damages or losses), even if such Contributor has been advised + * of the possibility of such damages. + */ + +#include "ImageTools.h" + +#include "gtest/gtest.h" + +using namespace Diligent; + +namespace +{ + +TEST(Common_ImageTools, GetImageDifference) +{ + constexpr Uint32 Width = 3; + constexpr Uint32 Height = 2; + constexpr Uint32 Stride1 = 11; + constexpr Uint32 Stride2 = 12; + // clang-format off + constexpr char Image1[Stride1 * Height] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, + 9, 8, 7, 5, 6, 4, 3, 2, 1, 30, 40, + }; + constexpr char Image2[Stride2 * Height] = { + 1, 2, 3, 5, 8, 8, 7, 8, 9, 10, 20, 30, +// ^ ^ ^ +// -1 -3 -2 + 6, 4, 2, 5, 6, 4, 7, 6, 1, 40, 50, 60, +// ^ ^ ^ ^ ^ +// 3 4 5 4 4 + }; + // clang-format on + + { + ImageDiffInfo Diff = GetImageDifference(Width, Height, 3, Image1, Stride1, Image1, Stride1, 3); + EXPECT_EQ(Diff.NumDiffPixels, 0); + EXPECT_EQ(Diff.NumDiffPixelsAboveThreshold, 0); + EXPECT_EQ(Diff.MaxDiff, 0); + EXPECT_EQ(Diff.AvgDiff, 0.f); + EXPECT_EQ(Diff.RmsDiff, 0.f); + } + + { + ImageDiffInfo Diff = GetImageDifference(Width, Height, 3, Image1, Stride1, Image2, Stride2, 3); + EXPECT_EQ(Diff.NumDiffPixels, 3); + EXPECT_EQ(Diff.NumDiffPixelsAboveThreshold, 2); + EXPECT_EQ(Diff.MaxDiff, 5); + EXPECT_FLOAT_EQ(Diff.AvgDiff, 4.f); + EXPECT_FLOAT_EQ(Diff.RmsDiff, std::sqrt((9.f + 16.f + 25.f) / 3.f)); + } +} + +} // namespace