Browse Source

DebugTools: rewrite CompareImage guts to use StridedArrayView.

Yay. Much simpler code.
pull/364/head
Vladimír Vondruš 7 years ago
parent
commit
f4fd8a0648
  1. 80
      src/Magnum/DebugTools/CompareImage.cpp

80
src/Magnum/DebugTools/CompareImage.cpp

@ -27,6 +27,7 @@
#include <map> #include <map>
#include <sstream> #include <sstream>
#include <Corrade/Containers/StridedArrayView.h>
#include <Corrade/Containers/Optional.h> #include <Corrade/Containers/Optional.h>
#include <Corrade/PluginManager/Manager.h> #include <Corrade/PluginManager/Manager.h>
#include <Corrade/Utility/DebugStl.h> #include <Corrade/Utility/DebugStl.h>
@ -45,30 +46,21 @@ namespace Magnum { namespace DebugTools { namespace Implementation {
namespace { namespace {
template<std::size_t size, class T> Math::Vector<size, T> pixelAt(const char* const pixels, const std::size_t stride, const Vector2i& pos) { template<std::size_t size, class T> Float calculateImageDelta(const Containers::StridedArrayView2D<const Math::Vector<size, T>>& actual, const Containers::StridedArrayView2D<const Math::Vector<size, T>>& expected, const Containers::StridedArrayView2D<Float>& output) {
return reinterpret_cast<const Math::Vector<size, T>*>(pixels + stride*pos.y())[pos.x()]; CORRADE_INTERNAL_ASSERT(actual.size() == output.size());
} CORRADE_INTERNAL_ASSERT(output.size() == expected.size());
template<std::size_t size, class T> Float calculateImageDelta(const ImageView2D& actual, const ImageView2D& expected, Containers::ArrayView<Float> output) {
CORRADE_INTERNAL_ASSERT(output.size() == std::size_t(expected.size().product()));
/* Precalculate parameters for pixel access */
Math::Vector2<std::size_t> dataOffset, dataSize;
std::tie(dataOffset, dataSize) = actual.dataProperties();
const char* const actualPixels = actual.data() + dataOffset.sum();
const std::size_t actualStride = dataSize.x();
std::tie(dataOffset, dataSize) = expected.dataProperties();
const char* const expectedPixels = expected.data() + dataOffset.sum();
const std::size_t expectedStride = dataSize.x();
/* Calculate deltas and maximal value of them */ /* Calculate deltas and maximal value of them */
Float max{}; Float max{};
for(std::int_fast32_t y = 0; y != expected.size().y(); ++y) { for(std::size_t i = 0, iMax = expected.size()[0]; i != iMax; ++i) {
for(std::int_fast32_t x = 0; x != expected.size().x(); ++x) { Containers::StridedArrayView1D<const Math::Vector<size, T>> actualRow = actual[i];
Math::Vector<size, Float> actualPixel{pixelAt<size, T>(actualPixels, actualStride, {Int(x), Int(y)})}; Containers::StridedArrayView1D<const Math::Vector<size, T>> expectedRow = expected[i];
Math::Vector<size, Float> expectedPixel{pixelAt<size, T>(expectedPixels, expectedStride, {Int(x), Int(y)})}; Containers::StridedArrayView1D<Float> outputRow = output[i];
for(std::size_t j = 0, jMax = expectedRow.size(); j != jMax; ++j) {
/* Explicitly convert from T to Float */
auto actualPixel = Math::Vector<size, Float>(actualRow[j]);
auto expectedPixel = Math::Vector<size, Float>(expectedRow[j]);
/* First calculate a classic difference */ /* First calculate a classic difference */
Math::Vector<size, Float> diff = Math::abs(actualPixel - expectedPixel); Math::Vector<size, Float> diff = Math::abs(actualPixel - expectedPixel);
@ -83,7 +75,7 @@ template<std::size_t size, class T> Float calculateImageDelta(const ImageView2D&
/* Calculate the difference and save it to the output image even /* Calculate the difference and save it to the output image even
with NaN and ±Inf (as the user should know) */ with NaN and ±Inf (as the user should know) */
output[y*expected.size().x() + x] = diff.sum()/size; outputRow[j] = diff.sum()/size;
/* On the other hand, infs and NaNs should not contribute to the /* On the other hand, infs and NaNs should not contribute to the
max delta -- because all other differences would be zero max delta -- because all other differences would be zero
@ -99,7 +91,10 @@ template<std::size_t size, class T> Float calculateImageDelta(const ImageView2D&
std::tuple<Containers::Array<Float>, Float, Float> calculateImageDelta(const ImageView2D& actual, const ImageView2D& expected) { std::tuple<Containers::Array<Float>, Float, Float> calculateImageDelta(const ImageView2D& actual, const ImageView2D& expected) {
/* Calculate a delta image */ /* Calculate a delta image */
Containers::Array<Float> delta{Containers::NoInit, std::size_t(expected.size().product())}; Containers::Array<Float> deltaData{Containers::NoInit,
std::size_t(expected.size().product())};
Containers::StridedArrayView2D<Float> delta{deltaData,
{std::size_t(expected.size().y()), std::size_t(expected.size().x())}};
CORRADE_ASSERT(!isPixelFormatImplementationSpecific(expected.format()), CORRADE_ASSERT(!isPixelFormatImplementationSpecific(expected.format()),
"DebugTools::CompareImage: can't compare implementation-specific pixel formats", {}); "DebugTools::CompareImage: can't compare implementation-specific pixel formats", {});
@ -108,12 +103,16 @@ std::tuple<Containers::Array<Float>, Float, Float> calculateImageDelta(const Ima
switch(expected.format()) { switch(expected.format()) {
#define _c(format, size, T) \ #define _c(format, size, T) \
case PixelFormat::format: \ case PixelFormat::format: \
max = calculateImageDelta<size, T>(actual, expected, delta); \ max = calculateImageDelta<size, T>( \
actual.pixels<Math::Vector<size, T>>(), \
expected.pixels<Math::Vector<size, T>>(), delta); \
break; break;
#define _d(first, second, size, T) \ #define _d(first, second, size, T) \
case PixelFormat::first: \ case PixelFormat::first: \
case PixelFormat::second: \ case PixelFormat::second: \
max = calculateImageDelta<size, T>(actual, expected, delta); \ max = calculateImageDelta<size, T>( \
actual.pixels<Math::Vector<size, T>>(), \
expected.pixels<Math::Vector<size, T>>(), delta); \
break; break;
/* LCOV_EXCL_START */ /* LCOV_EXCL_START */
_d(R8Unorm, R8UI, 1, UnsignedByte) _d(R8Unorm, R8UI, 1, UnsignedByte)
@ -164,9 +163,9 @@ std::tuple<Containers::Array<Float>, Float, Float> calculateImageDelta(const Ima
*deliberately* leaves specials in. The `max` has them already filtered *deliberately* leaves specials in. The `max` has them already filtered
out so if this would filter them out as well, there would be nothing out so if this would filter them out as well, there would be nothing
left that could cause the comparison to fail. */ left that could cause the comparison to fail. */
const Float mean = Math::Algorithms::kahanSum(delta.begin(), delta.end())/delta.size(); const Float mean = Math::Algorithms::kahanSum(deltaData.begin(), deltaData.end())/deltaData.size();
return std::make_tuple(std::move(delta), max, mean); return std::make_tuple(std::move(deltaData), max, mean);
} }
namespace { namespace {
@ -220,16 +219,18 @@ void printDeltaImage(Debug& out, Containers::ArrayView<const Float> deltas, cons
namespace { namespace {
void printPixelAt(Debug& out, const char* const pixels, const std::size_t stride, const Vector2i& pos, const PixelFormat format) { void printPixelAt(Debug& out, const Containers::StridedArrayView3D<const char>& pixels, const Vector2i& pos, const PixelFormat format) {
const char* const pixel = &pixels[pos.y()][pos.x()][0];
switch(format) { switch(format) {
#define _c(format, size, T) \ #define _c(format, size, T) \
case PixelFormat::format: \ case PixelFormat::format: \
out << pixelAt<size, T>(pixels, stride, pos); \ out << *reinterpret_cast<const Math::Vector<size, T>*>(pixel); \
break; break;
#define _d(first, second, size, T) \ #define _d(first, second, size, T) \
case PixelFormat::first: \ case PixelFormat::first: \
case PixelFormat::second: \ case PixelFormat::second: \
out << pixelAt<size, T>(pixels, stride, pos); \ out << *reinterpret_cast<const Math::Vector<size, T>*>(pixel); \
break; break;
/* LCOV_EXCL_START */ /* LCOV_EXCL_START */
_d(R8Unorm, R8UI, 1, UnsignedByte) _d(R8Unorm, R8UI, 1, UnsignedByte)
@ -267,10 +268,10 @@ void printPixelAt(Debug& out, const char* const pixels, const std::size_t stride
/* Take the opportunity and print 8-bit colors in hex */ /* Take the opportunity and print 8-bit colors in hex */
case PixelFormat::RGB8Unorm: case PixelFormat::RGB8Unorm:
out << Color3ub{pixelAt<3, UnsignedByte>(pixels, stride, pos)}; out << *reinterpret_cast<const Color3ub*>(pixel);
break; break;
case PixelFormat::RGBA8Unorm: case PixelFormat::RGBA8Unorm:
out << Color4ub{pixelAt<4, UnsignedByte>(pixels, stride, pos)}; out << *reinterpret_cast<const Color4ub*>(pixel);
break; break;
case PixelFormat::R16F: case PixelFormat::R16F:
@ -285,17 +286,6 @@ void printPixelAt(Debug& out, const char* const pixels, const std::size_t stride
} }
void printPixelDeltas(Debug& out, Containers::ArrayView<const Float> delta, const ImageView2D& actual, const ImageView2D& expected, const Float maxThreshold, const Float meanThreshold, std::size_t maxCount) { void printPixelDeltas(Debug& out, Containers::ArrayView<const Float> delta, const ImageView2D& actual, const ImageView2D& expected, const Float maxThreshold, const Float meanThreshold, std::size_t maxCount) {
/* Precalculate parameters for pixel access */
Math::Vector2<std::size_t> offset, size;
std::tie(offset, size) = actual.dataProperties();
const char* const actualPixels = actual.data() + offset.sum();
const std::size_t actualStride = size.x();
std::tie(offset, size) = expected.dataProperties();
const char* const expectedPixels = expected.data() + offset.sum();
const std::size_t expectedStride = size.x();
/* Find first maxCount values above mean threshold and put them into a /* Find first maxCount values above mean threshold and put them into a
sorted map. Need to reverse the condition in order to catch NaNs. */ sorted map. Need to reverse the condition in order to catch NaNs. */
std::multimap<Float, std::size_t> large; std::multimap<Float, std::size_t> large;
@ -321,11 +311,11 @@ void printPixelDeltas(Debug& out, Containers::ArrayView<const Float> delta, cons
<< Debug::nospace << "," << Debug::nospace << pos.y() << Debug::nospace << "," << Debug::nospace << pos.y()
<< Debug::nospace << "]"; << Debug::nospace << "]";
printPixelAt(out, actualPixels, actualStride, pos, expected.format()); printPixelAt(out, actual.pixels(), pos, expected.format());
out << Debug::nospace << ", expected"; out << Debug::nospace << ", expected";
printPixelAt(out, expectedPixels, expectedStride, pos, expected.format()); printPixelAt(out, expected.pixels(), pos, expected.format());
out << "(Δ =" << Debug::boldColor(delta[it->second] > maxThreshold ? out << "(Δ =" << Debug::boldColor(delta[it->second] > maxThreshold ?
Debug::Color::Red : Debug::Color::Yellow) << delta[it->second] Debug::Color::Red : Debug::Color::Yellow) << delta[it->second]

Loading…
Cancel
Save