mirror of https://github.com/mosra/magnum.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
507 lines
21 KiB
507 lines
21 KiB
/* |
|
This file is part of Magnum. |
|
|
|
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016 |
|
Vladimír Vondruš <mosra@centrum.cz> |
|
|
|
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 "CompareImage.h" |
|
|
|
#include <map> |
|
#include <sstream> |
|
#include <vector> |
|
|
|
#include "Magnum/ImageView.h" |
|
#include "Magnum/PixelFormat.h" |
|
#include "Magnum/Math/Functions.h" |
|
#include "Magnum/Math/Color.h" |
|
#include "Magnum/Math/Algorithms/KahanSum.h" |
|
|
|
namespace Magnum { namespace DebugTools { namespace Implementation { |
|
|
|
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) { |
|
return reinterpret_cast<const Math::Vector<size, T>*>(pixels + stride*pos.y())[pos.x()]; |
|
} |
|
|
|
template<std::size_t size, class T> Float calculateImageDelta(const ImageView2D& actual, const ImageView2D& expected, std::vector<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, std::ignore) = actual.dataProperties(); |
|
const char* const actualPixels = actual.data() + dataOffset.sum(); |
|
const std::size_t actualStride = dataSize.x(); |
|
|
|
std::tie(dataOffset, dataSize, std::ignore) = expected.dataProperties(); |
|
const char* const expectedPixels = expected.data() + dataOffset.sum(); |
|
const std::size_t expectedStride = dataSize.x(); |
|
|
|
/* Calculate deltas and maximal value of them */ |
|
Float max{}; |
|
for(std::int_fast32_t y = 0; y != expected.size().y(); ++y) { |
|
for(std::int_fast32_t x = 0; x != expected.size().x(); ++x) { |
|
Math::Vector<size, Float> actualPixel{pixelAt<size, T>(actualPixels, actualStride, {Int(x), Int(y)})}; |
|
Math::Vector<size, Float> expectedPixel{pixelAt<size, T>(expectedPixels, expectedStride, {Int(x), Int(y)})}; |
|
|
|
const Float value = (Math::abs(actualPixel - expectedPixel)).sum()/size; |
|
output[y*expected.size().x() + x] = value; |
|
max = Math::max(max, value); |
|
} |
|
} |
|
|
|
return max; |
|
} |
|
|
|
template<class T> Float calculateIntegerImageDelta(const ImageView2D& actual, const ImageView2D& expected, std::vector<Float>& output) { |
|
if( |
|
#if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2)) |
|
expected.format() == PixelFormat::Red |
|
#endif |
|
#ifndef MAGNUM_TARGET_GLES2 |
|
|| expected.format() == PixelFormat::RedInteger |
|
#else |
|
#ifndef MAGNUM_TARGET_WEBGL |
|
|| |
|
#endif |
|
expected.format() == PixelFormat::Luminance |
|
#endif |
|
) |
|
return calculateImageDelta<1, T>(actual, expected, output); |
|
else if( |
|
#if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2)) |
|
expected.format() == PixelFormat::RG |
|
#endif |
|
#ifndef MAGNUM_TARGET_GLES2 |
|
|| expected.format() == PixelFormat::RGInteger |
|
#else |
|
#ifndef MAGNUM_TARGET_WEBGL |
|
|| |
|
#endif |
|
expected.format() == PixelFormat::LuminanceAlpha |
|
#endif |
|
) |
|
return calculateImageDelta<2, T>(actual, expected, output); |
|
else if(expected.format() == PixelFormat::RGB |
|
#ifndef MAGNUM_TARGET_GLES2 |
|
|| expected.format() == PixelFormat::RGBInteger |
|
#endif |
|
) |
|
return calculateImageDelta<3, T>(actual, expected, output); |
|
else if(expected.format() == PixelFormat::RGBA |
|
#ifndef MAGNUM_TARGET_GLES2 |
|
|| expected.format() == PixelFormat::RGBAInteger |
|
#endif |
|
) |
|
return calculateImageDelta<4, T>(actual, expected, output); |
|
|
|
CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ |
|
} |
|
|
|
template<class T> Float calculateImageDelta(const ImageView2D& actual, const ImageView2D& expected, std::vector<Float>& output) { |
|
if( |
|
#if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2)) |
|
expected.format() == PixelFormat::Red |
|
#endif |
|
#ifdef MAGNUM_TARGET_GLES2 |
|
#ifndef MAGNUM_TARGET_WEBGL |
|
|| |
|
#endif |
|
expected.format() == PixelFormat::Luminance |
|
#endif |
|
) |
|
return calculateImageDelta<1, T>(actual, expected, output); |
|
else if( |
|
#if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2)) |
|
expected.format() == PixelFormat::RG |
|
#endif |
|
#ifdef MAGNUM_TARGET_GLES2 |
|
#ifndef MAGNUM_TARGET_WEBGL |
|
|| |
|
#endif |
|
expected.format() == PixelFormat::LuminanceAlpha |
|
#endif |
|
) |
|
return calculateImageDelta<2, T>(actual, expected, output); |
|
else if(expected.format() == PixelFormat::RGB) |
|
return calculateImageDelta<3, T>(actual, expected, output); |
|
else if(expected.format() == PixelFormat::RGBA) |
|
return calculateImageDelta<4, T>(actual, expected, output); |
|
|
|
CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ |
|
} |
|
|
|
} |
|
|
|
std::tuple<std::vector<Float>, Float, Float> calculateImageDelta(const ImageView2D& actual, const ImageView2D& expected) { |
|
/* Calculate a delta image */ |
|
std::vector<Float> delta(expected.size().product()); |
|
|
|
Float max; |
|
if(expected.type() == PixelType::UnsignedByte) |
|
max = calculateIntegerImageDelta<UnsignedByte>(actual, expected, delta); |
|
else if(expected.type() == PixelType::UnsignedShort) |
|
max = calculateIntegerImageDelta<UnsignedShort>(actual, expected, delta); |
|
else if(expected.type() == PixelType::UnsignedInt) |
|
max = calculateIntegerImageDelta<UnsignedInt>(actual, expected, delta); |
|
#ifndef MAGNUM_TARGET_GLES2 |
|
else if(expected.type() == PixelType::Byte) |
|
max = calculateIntegerImageDelta<Byte>(actual, expected, delta); |
|
else if(expected.type() == PixelType::Short) |
|
max = calculateIntegerImageDelta<Short>(actual, expected, delta); |
|
else if(expected.type() == PixelType::Int) |
|
max = calculateIntegerImageDelta<Int>(actual, expected, delta); |
|
#endif |
|
else if(expected.type() == PixelType::Float) |
|
max = calculateImageDelta<Float>(actual, expected, delta); |
|
else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ |
|
|
|
/* Calculate mean delta. Do it the special way so we don't lose |
|
precision -- that would result in having false negatives! */ |
|
const Float mean = Math::Algorithms::kahanSum(delta.begin(), delta.end())/delta.size(); |
|
|
|
return std::make_tuple(delta, max, mean); |
|
} |
|
|
|
namespace { |
|
/* Done by printing an white to black gradient using one of the online |
|
ASCII converters. Yes, I'm lazy. Another one could be " .,:;ox%#@". */ |
|
const char Characters[] = " .,:~=+?7IZ$08DNM"; |
|
} |
|
|
|
void printDeltaImage(Debug& out, const std::vector<Float>& deltas, const Vector2i& size, const Float max, const Float maxThreshold, const Float meanThreshold) { |
|
CORRADE_INTERNAL_ASSERT(meanThreshold <= maxThreshold); |
|
|
|
/* At most 64 characters per line. The console fonts height is usually 2x |
|
the width, so there is twice the pixels per block */ |
|
const Vector2i pixelsPerBlock{(size.x() + 63)/64, 2*((size.x() + 63)/64)}; |
|
const Vector2i blockCount = (size + pixelsPerBlock - Vector2i{1})/pixelsPerBlock; |
|
|
|
for(std::int_fast32_t y = 0; y != blockCount.y(); ++y) { |
|
out << " |"; |
|
|
|
for(std::int_fast32_t x = 0; x != blockCount.x(); ++x) { |
|
/* Going bottom-up so we don't flip the image upside down when printing */ |
|
const Vector2i offset = Vector2i{Int(x), blockCount.y() - Int(y) - 1}*pixelsPerBlock; |
|
const Vector2i blockSize = Math::min(size - offset, Vector2i{pixelsPerBlock}); |
|
|
|
Float blockMax{}; |
|
for(std::int_fast32_t yb = 0; yb != blockSize.y(); ++yb) |
|
for(std::int_fast32_t xb = 0; xb != blockSize.x(); ++xb) |
|
blockMax = Math::max(blockMax, deltas[(offset.y() + yb)*size.x() + offset.x() + xb]); |
|
|
|
const char c = Characters[Int(Math::round(Math::min(blockMax/max, 1.0f)*(sizeof(Characters) - 2)))]; |
|
|
|
if(blockMax > maxThreshold) |
|
out << Debug::boldColor(Debug::Color::Red) << Debug::nospace << std::string{c} << Debug::resetColor; |
|
else if(blockMax > meanThreshold) |
|
out << Debug::boldColor(Debug::Color::Yellow) << Debug::nospace << std::string{c} << Debug::resetColor; |
|
else out << Debug::nospace << std::string{c}; |
|
} |
|
|
|
out << Debug::nospace << "|" << Debug::newline; |
|
} |
|
} |
|
|
|
namespace { |
|
|
|
template<class T> void printIntegerPixelAt(Debug& out, const char* const pixels, const std::size_t stride, const Vector2i& pos, const PixelFormat format) { |
|
if( |
|
#if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2)) |
|
format == PixelFormat::Red |
|
#endif |
|
#ifndef MAGNUM_TARGET_GLES2 |
|
|| format == PixelFormat::RedInteger |
|
#else |
|
#ifndef MAGNUM_TARGET_WEBGL |
|
|| |
|
#endif |
|
format == PixelFormat::Luminance |
|
#endif |
|
) |
|
out << pixelAt<1, T>(pixels, stride, pos); |
|
else if( |
|
#if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2)) |
|
format == PixelFormat::RG |
|
#endif |
|
#ifndef MAGNUM_TARGET_GLES2 |
|
|| format == PixelFormat::RGInteger |
|
#else |
|
#ifndef MAGNUM_TARGET_WEBGL |
|
|| |
|
#endif |
|
format == PixelFormat::LuminanceAlpha |
|
#endif |
|
) |
|
out << pixelAt<2, T>(pixels, stride, pos); |
|
/* Take the opportunity and print 8-bit colors in hex */ |
|
else if(format == PixelFormat::RGB |
|
#ifndef MAGNUM_TARGET_GLES2 |
|
|| format == PixelFormat::RGBInteger |
|
#endif |
|
) |
|
out << Math::Color3<T>{pixelAt<3, T>(pixels, stride, pos)}; |
|
else if(format == PixelFormat::RGBA |
|
#ifndef MAGNUM_TARGET_GLES2 |
|
|| format == PixelFormat::RGBAInteger |
|
#endif |
|
) |
|
out << Math::Color4<T>{pixelAt<4, T>(pixels, stride, pos)}; |
|
else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ |
|
} |
|
|
|
template<class T> void printPixelAt(Debug& out, const char* const pixels, const std::size_t stride, const Vector2i& pos, const PixelFormat format) { |
|
if( |
|
#if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2)) |
|
format == PixelFormat::Red |
|
#endif |
|
#ifdef MAGNUM_TARGET_GLES2 |
|
#ifndef MAGNUM_TARGET_WEBGL |
|
|| |
|
#endif |
|
format == PixelFormat::Luminance |
|
#endif |
|
) |
|
out << pixelAt<1, T>(pixels, stride, pos); |
|
else if( |
|
#if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2)) |
|
format == PixelFormat::RG |
|
#endif |
|
#ifdef MAGNUM_TARGET_GLES2 |
|
#ifndef MAGNUM_TARGET_WEBGL |
|
|| |
|
#endif |
|
format == PixelFormat::LuminanceAlpha |
|
#endif |
|
) |
|
out << pixelAt<2, T>(pixels, stride, pos); |
|
else if(format == PixelFormat::RGB) |
|
out << pixelAt<3, T>(pixels, stride, pos); |
|
else if(format == PixelFormat::RGBA) |
|
out << pixelAt<4, T>(pixels, stride, pos); |
|
else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ |
|
} |
|
|
|
void printPixelAt(Debug& out, const char* const pixels, const std::size_t stride, const Vector2i& pos, const PixelFormat format, const PixelType type) { |
|
if(type == PixelType::UnsignedByte) |
|
printIntegerPixelAt<UnsignedByte>(out, pixels, stride, pos, format); |
|
else if(type == PixelType::UnsignedShort) |
|
printIntegerPixelAt<UnsignedShort>(out, pixels, stride, pos, format); |
|
else if(type == PixelType::UnsignedInt) |
|
printIntegerPixelAt<UnsignedInt>(out, pixels, stride, pos, format); |
|
#ifndef MAGNUM_TARGET_GLES2 |
|
else if(type == PixelType::Byte) |
|
printIntegerPixelAt<Byte>(out, pixels, stride, pos, format); |
|
else if(type == PixelType::Short) |
|
printIntegerPixelAt<Short>(out, pixels, stride, pos, format); |
|
else if(type == PixelType::Int) |
|
printIntegerPixelAt<Int>(out, pixels, stride, pos, format); |
|
#endif |
|
else if(type == PixelType::Float) |
|
printPixelAt<Float>(out, pixels, stride, pos, format); |
|
else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ |
|
} |
|
|
|
} |
|
|
|
void printPixelDeltas(Debug& out, const std::vector<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, std::ignore) = actual.dataProperties(); |
|
const char* const actualPixels = actual.data() + offset.sum(); |
|
const std::size_t actualStride = size.x(); |
|
|
|
std::tie(offset, size, std::ignore) = 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 |
|
sorted map */ |
|
std::multimap<Float, std::size_t> large; |
|
for(std::size_t i = 0; i != delta.size(); ++i) { |
|
/* GCC 4.7 std::multimap doesn't have emplace() */ |
|
if(delta[i] > meanThreshold) large.insert({delta[i], i}); |
|
} |
|
|
|
CORRADE_INTERNAL_ASSERT(!large.empty()); |
|
|
|
if(large.size() > maxCount) |
|
out << " Top" << maxCount << "out of" << large.size() << "pixels above max/mean threshold:"; |
|
else |
|
out << " Pixels above max/mean threshold:"; |
|
|
|
/* Print the values from largest to smallest. Branching on the done in the |
|
inner loop but that doesn't matter as we always print just ~10 values. */ |
|
std::size_t count = 0; |
|
for(auto it = large.crbegin(); it != large.crend(); ++it) { |
|
if(++count > maxCount) break; |
|
|
|
Vector2i pos; |
|
std::tie(pos.y(), pos.x()) = Math::div(Int(it->second), expected.size().x()); |
|
out << Debug::newline << " [" << Debug::nospace << pos.x() |
|
<< Debug::nospace << "," << Debug::nospace << pos.y() |
|
<< Debug::nospace << "]"; |
|
|
|
printPixelAt(out, actualPixels, actualStride, pos, expected.format(), expected.type()); |
|
|
|
out << Debug::nospace << ", expected"; |
|
|
|
printPixelAt(out, expectedPixels, expectedStride, pos, expected.format(), expected.type()); |
|
|
|
out << "(Δ =" << Debug::boldColor(delta[it->second] > maxThreshold ? |
|
Debug::Color::Red : Debug::Color::Yellow) << delta[it->second] |
|
<< Debug::nospace << Debug::resetColor << ")"; |
|
} |
|
} |
|
|
|
}}} |
|
|
|
#ifndef DOXYGEN_GENERATING_OUTPUT |
|
/* If Doxygen sees this, all @ref Corrade::TestSuite links break (prolly |
|
because the namespace is undocumented in this project) */ |
|
namespace Corrade { namespace TestSuite { |
|
|
|
using namespace Magnum; |
|
|
|
Comparator<DebugTools::CompareImage>::Comparator(Float maxThreshold, Float meanThreshold): _maxThreshold{maxThreshold}, _meanThreshold{meanThreshold} { |
|
CORRADE_ASSERT(meanThreshold <= maxThreshold, |
|
"DebugTools::CompareImage: maxThreshold can't be smaller than meanThreshold", ); |
|
} |
|
|
|
bool Comparator<DebugTools::CompareImage>::operator()(const ImageView2D& actual, const ImageView2D& expected) { |
|
_actualImage = &actual; |
|
_expectedImage = &expected; |
|
|
|
/* Verify that the images are the same */ |
|
if(actual.size() != expected.size()) { |
|
_state = State::DifferentSize; |
|
return false; |
|
} |
|
if(actual.format() != expected.format() || actual.type() != expected.type()) { |
|
_state = State::DifferentFormat; |
|
return false; |
|
} |
|
|
|
/* Assert on unsupported format/storage */ |
|
#ifndef CORRADE_NO_DEBUG |
|
const bool formatSupported = ( |
|
( |
|
#if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2)) |
|
expected.format() == PixelFormat::Red || |
|
expected.format() == PixelFormat::RG || |
|
#endif |
|
#ifndef MAGNUM_TARGET_GLES2 |
|
expected.format() == PixelFormat::RedInteger || |
|
expected.format() == PixelFormat::RGInteger || |
|
expected.format() == PixelFormat::RGBInteger || |
|
expected.format() == PixelFormat::RGBAInteger || |
|
#else |
|
expected.format() == PixelFormat::Luminance || |
|
expected.format() == PixelFormat::LuminanceAlpha || |
|
#endif |
|
expected.format() == PixelFormat::RGB || |
|
expected.format() == PixelFormat::RGBA |
|
) && ( |
|
#ifndef MAGNUM_TARGET_GLES2 |
|
expected.type() == PixelType::Byte || |
|
expected.type() == PixelType::Short || |
|
expected.type() == PixelType::Int || |
|
#endif |
|
expected.type() == PixelType::UnsignedByte || |
|
expected.type() == PixelType::UnsignedShort || |
|
expected.type() == PixelType::UnsignedInt |
|
)) || (( |
|
#if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2)) |
|
expected.format() == PixelFormat::Red || |
|
expected.format() == PixelFormat::RG || |
|
#endif |
|
#ifdef MAGNUM_TARGET_GLES2 |
|
expected.format() == PixelFormat::Luminance || |
|
expected.format() == PixelFormat::LuminanceAlpha || |
|
#endif |
|
expected.format() == PixelFormat::RGB || |
|
expected.format() == PixelFormat::RGBA |
|
) && expected.type() == PixelType::Float); |
|
CORRADE_ASSERT( |
|
formatSupported, |
|
"DebugTools::CompareImage: format" << expected.format() << Debug::nospace << "/" << expected.type() << "is not supported", {}); |
|
#endif |
|
#ifndef MAGNUM_TARGET_GLES |
|
CORRADE_ASSERT(!actual.storage().swapBytes() && !expected.storage().swapBytes(), |
|
"DebugTools::CompareImage: pixel storage with byte swap is not supported", {}); |
|
#endif |
|
|
|
std::vector<Float> delta; |
|
std::tie(delta, _max, _mean) = DebugTools::Implementation::calculateImageDelta(actual, expected); |
|
|
|
/* If both values are not above threshold, success */ |
|
if(_max > _maxThreshold && _mean > _meanThreshold) |
|
_state = State::AboveThresholds; |
|
else if(_max > _maxThreshold) |
|
_state = State::AboveMaxThreshold; |
|
else if(_mean > _meanThreshold) |
|
_state = State::AboveMeanThreshold; |
|
else return true; |
|
|
|
/* Otherwise save the deltas and fail */ |
|
_delta = std::move(delta); |
|
return false; |
|
} |
|
|
|
void Comparator<DebugTools::CompareImage>::printErrorMessage(Debug& out, const std::string& actual, const std::string& expected) const { |
|
out << "Images" << actual << "and" << expected << "have"; |
|
if(_state == State::DifferentSize) |
|
out << "different size, actual" << _actualImage->size() << "but" |
|
<< _expectedImage->size() << "expected."; |
|
else if(_state == State::DifferentFormat) |
|
out << "different format, actual" << _actualImage->format() |
|
<< Debug::nospace << "/" << Debug::nospace << _actualImage->type() |
|
<< "but" << _expectedImage->format() << Debug::nospace << "/" |
|
<< Debug::nospace << _expectedImage->type() << "expected."; |
|
else { |
|
if(_state == State::AboveThresholds) |
|
out << "both max and mean delta above threshold, actual" |
|
<< _max << Debug::nospace << "/" << Debug::nospace << _mean |
|
<< "but at most" << _maxThreshold << Debug::nospace << "/" |
|
<< Debug::nospace << _meanThreshold << "expected."; |
|
else if(_state == State::AboveMaxThreshold) |
|
out << "max delta above threshold, actual" << _max |
|
<< "but at most" << _maxThreshold |
|
<< "expected. Mean delta" << _mean << "is below threshold" |
|
<< _meanThreshold << Debug::nospace << "."; |
|
else if(_state == State::AboveMeanThreshold) |
|
out << "mean delta above threshold, actual" << _mean |
|
<< "but at most" << _meanThreshold |
|
<< "expected. Max delta" << _max << "is below threshold" |
|
<< _maxThreshold << Debug::nospace << "."; |
|
else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ |
|
|
|
out << "Delta image:" << Debug::newline; |
|
DebugTools::Implementation::printDeltaImage(out, _delta, _expectedImage->size(), _max, _maxThreshold, _meanThreshold); |
|
DebugTools::Implementation::printPixelDeltas(out, _delta, *_actualImage, *_expectedImage, _maxThreshold, _meanThreshold, 10); |
|
} |
|
} |
|
|
|
}} |
|
#endif
|
|
|