Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3f05602a authored by David Hanna Jr's avatar David Hanna Jr Committed by David Hanna
Browse files

test-hwc2: Added virtual Display tests

VirtualDisplay tests added.
Test: Ran on (heavily modified) ryu
Change-Id: I5cc92d11d4cde6c3407d71652f87ea3c3fb63228
parent c7bad6ed
Loading
Loading
Loading
Loading
+5 −2
Original line number Diff line number Diff line
@@ -36,7 +36,9 @@ LOCAL_SHARED_LIBRARIES := \
    libui \
    libgui \
    liblog \
    libsync
    libsync \
    libskia \
    android.hardware.graphics.common@1.0
LOCAL_STATIC_LIBRARIES := \
    libbase \
    libadf \
@@ -49,6 +51,7 @@ LOCAL_SRC_FILES := \
    Hwc2TestLayers.cpp \
    Hwc2TestBuffer.cpp \
    Hwc2TestClientTarget.cpp \
    Hwc2TestVirtualDisplay.cpp
    Hwc2TestVirtualDisplay.cpp \
    Hwc2TestPixelComparator.cpp

include $(BUILD_NATIVE_TEST)
+213 −3
Original line number Diff line number Diff line
@@ -1775,6 +1775,145 @@ protected:
        }
    }

    void createAndPresentVirtualDisplay(size_t layerCnt,
            Hwc2TestCoverage coverage,
            const std::unordered_map<Hwc2TestPropertyName, Hwc2TestCoverage>&
            coverageExceptions)
    {
        Hwc2TestVirtualDisplay testVirtualDisplay(coverage);
        hwc2_display_t display;
        android_pixel_format_t desiredFormat = HAL_PIXEL_FORMAT_RGBA_8888;

        do {
            // Items dependent on the display dimensions
            hwc2_error_t err = HWC2_ERROR_NONE;
            const UnsignedArea& dimension =
                    testVirtualDisplay.getDisplayDimension();
            ASSERT_NO_FATAL_FAILURE(createVirtualDisplay(dimension.width,
                    dimension.height, &desiredFormat, &display, &err));
            ASSERT_TRUE(err == HWC2_ERROR_NONE)
                    << "Cannot allocate virtual display";

            ASSERT_NO_FATAL_FAILURE(setPowerMode(display, HWC2_POWER_MODE_ON));
            ASSERT_NO_FATAL_FAILURE(enableVsync(display));

            std::vector<hwc2_config_t> configs;
            ASSERT_NO_FATAL_FAILURE(getDisplayConfigs(display, &configs));

            for (auto config : configs) {
                ASSERT_NO_FATAL_FAILURE(setActiveConfig(display, config));

                Area displayArea;
                ASSERT_NO_FATAL_FAILURE(getActiveDisplayArea(display,
                        &displayArea));

                std::vector<hwc2_layer_t> layers;
                ASSERT_NO_FATAL_FAILURE(createLayers(display, &layers,
                        layerCnt));
                Hwc2TestLayers testLayers(layers, coverage, displayArea,
                        coverageExceptions);

                /*
                 * Layouts that do not cover an entire virtual display will
                 * cause undefined behavior.
                 * Enable optimizeLayouts to avoid this.
                 */
                testLayers.optimizeLayouts();
                do {
                    // Items dependent on the testLayers properties
                    std::set<hwc2_layer_t> clientLayers;
                    std::set<hwc2_layer_t> clearLayers;
                    uint32_t numTypes, numRequests;
                    bool hasChanges, skip;
                    bool flipClientTarget;
                    int32_t presentFence;
                    Hwc2TestClientTarget testClientTarget;
                    buffer_handle_t outputBufferHandle;
                    android::base::unique_fd outputBufferReleaseFence;

                    ASSERT_NO_FATAL_FAILURE(setLayerProperties(display, layers,
                            &testLayers, &skip));

                    if (skip)
                        continue;

                    ASSERT_NO_FATAL_FAILURE(validateDisplay(display, &numTypes,
                            &numRequests, &hasChanges));

                    if (hasChanges)
                        EXPECT_LE(numTypes, static_cast<uint32_t>(layers.size()))
                                << "wrong number of requests";

                    ASSERT_NO_FATAL_FAILURE(handleCompositionChanges(display,
                            testLayers, layers, numTypes, &clientLayers));

                    ASSERT_NO_FATAL_FAILURE(handleRequests(display, layers,
                            numRequests, &clearLayers, &flipClientTarget));
                    ASSERT_NO_FATAL_FAILURE(setClientTarget(display,
                            &testClientTarget, testLayers, clientLayers,
                            clearLayers, flipClientTarget, displayArea));
                    ASSERT_NO_FATAL_FAILURE(acceptDisplayChanges(display));

                    ASSERT_EQ(testVirtualDisplay.getOutputBuffer(
                            &outputBufferHandle, &outputBufferReleaseFence), 0);
                    ASSERT_NO_FATAL_FAILURE(setOutputBuffer(display,
                            outputBufferHandle, outputBufferReleaseFence));

                    EXPECT_NO_FATAL_FAILURE(presentDisplay(display,
                            &presentFence));
                    ASSERT_NO_FATAL_FAILURE(closeFences(display, presentFence));

                    ASSERT_EQ(testVirtualDisplay.verifyOutputBuffer(&testLayers,
                            &layers, &clearLayers), 0);

                    /*
                     * Upscaling the image causes minor pixel differences.
                     * Work around this by using some threshold.
                     *
                     * Fail test if we are off by more than 1% of our
                     * pixels.
                     */
                    ComparatorResult& comparatorResult = ComparatorResult::get();
                    int threshold = (dimension.width * dimension.height) / 100;
                    double diffPercent = (comparatorResult.getDifferentPixelCount() * 100.0) /
                            (dimension.width * dimension.height);

                    if (comparatorResult.getDifferentPixelCount() != 0)
                        EXPECT_TRUE(false)
                                << comparatorResult.getDifferentPixelCount() << " pixels ("
                                << diffPercent << "%) are different.";

                    if (comparatorResult.getDifferentPixelCount() > threshold) {
                        EXPECT_TRUE(false)
                                << "Mismatched pixel count exceeds threshold. "
                                << "Writing buffers to file.";

                        const ::testing::TestInfo* const test_info =
                                ::testing::UnitTest::GetInstance()
                                ->current_test_info();

                        EXPECT_EQ(testVirtualDisplay.writeBuffersToFile(
                                test_info->name()), 0)
                                << "Failed to write buffers.";
                    }

                    ASSERT_LE(comparatorResult.getDifferentPixelCount(), threshold)
                            << comparatorResult.getDifferentPixelCount() << " pixels ("
                            << diffPercent << "%) are different. "
                            << "Exceeds 1% threshold, terminating test. "
                            << "Test case: " << testLayers.dump();

                } while (testLayers.advance());

                ASSERT_NO_FATAL_FAILURE(destroyLayers(display,
                        std::move(layers)));
            }
            ASSERT_NO_FATAL_FAILURE(disableVsync(display));
            ASSERT_NO_FATAL_FAILURE(setPowerMode(display, HWC2_POWER_MODE_OFF));
            ASSERT_NO_FATAL_FAILURE(destroyVirtualDisplay(display));
        } while (testVirtualDisplay.advance());
    }

    hwc2_device_t* mHwc2Device = nullptr;

    enum class Hwc2TestHotplugStatus {
@@ -4479,7 +4618,7 @@ TEST_F(Hwc2Test, SET_OUTPUT_BUFFER)
                buffer_handle_t handle;
                android::base::unique_fd acquireFence;

                if (testVirtualDisplay->getBuffer(&handle, &acquireFence) >= 0)
                if (testVirtualDisplay->getOutputBuffer(&handle, &acquireFence) >= 0)
                    EXPECT_NO_FATAL_FAILURE(test->setOutputBuffer(display,
                            handle, acquireFence));
            }));
@@ -4499,7 +4638,7 @@ TEST_F(Hwc2Test, SET_OUTPUT_BUFFER_bad_display)

                ASSERT_NO_FATAL_FAILURE(test->getBadDisplay(&badDisplay));

                if (testVirtualDisplay->getBuffer(&handle, &acquireFence) < 0)
                if (testVirtualDisplay->getOutputBuffer(&handle, &acquireFence) < 0)
                    return;

                ASSERT_NO_FATAL_FAILURE(test->setOutputBuffer(badDisplay,
@@ -4539,7 +4678,7 @@ TEST_F(Hwc2Test, SET_OUTPUT_BUFFER_unsupported)
            android::base::unique_fd acquireFence;
            hwc2_error_t err = HWC2_ERROR_NONE;

            if (testVirtualDisplay.getBuffer(&handle, &acquireFence) < 0)
            if (testVirtualDisplay.getOutputBuffer(&handle, &acquireFence) < 0)
                continue;

            ASSERT_NO_FATAL_FAILURE(setOutputBuffer(display, handle,
@@ -4557,3 +4696,74 @@ TEST_F(Hwc2Test, DUMP)

    ASSERT_NO_FATAL_FAILURE(dump(&buffer));
}

/*
 * TODO(b/64724708): Hwc2TestPropertyName::BufferArea MUST be default for all
 * virtual display tests as we don't handle this case correctly.
 *
 * Only default dataspace is supported in our drawing code.
 */
const std::unordered_map<Hwc2TestPropertyName, Hwc2TestCoverage>
        virtualDisplayExceptions =
        {{Hwc2TestPropertyName::BufferArea, Hwc2TestCoverage::Default},
        {Hwc2TestPropertyName::Dataspace, Hwc2TestCoverage::Default}};

/* TESTCASE: Tests that the HWC2 can present 1 layer with default coverage on a
 * virtual display. */
TEST_F(Hwc2Test, PRESENT_VIRTUAL_DISPLAY_default_1)
{
    Hwc2TestCoverage coverage = Hwc2TestCoverage::Default;
    const size_t layerCnt = 1;
    ASSERT_NO_FATAL_FAILURE(createAndPresentVirtualDisplay(layerCnt, coverage,
            virtualDisplayExceptions));
}

/* TESTCASE: Tests that the HWC2 can present 1 layer with basic coverage on a
 * virtual display. */
TEST_F(Hwc2Test, PRESENT_VIRTUAL_DISPLAY_basic_1)
{
    Hwc2TestCoverage coverage = Hwc2TestCoverage::Basic;
    const size_t layerCnt = 1;
    ASSERT_NO_FATAL_FAILURE(createAndPresentVirtualDisplay(layerCnt, coverage,
            virtualDisplayExceptions));
}

/* TESTCASE: Tests that the HWC2 can present 2 layers with default coverage on a
 * virtual display. */
TEST_F(Hwc2Test, PRESENT_VIRTUAL_DISPLAY_default_2)
{
    Hwc2TestCoverage coverage = Hwc2TestCoverage::Default;
    const size_t layerCnt = 2;
    ASSERT_NO_FATAL_FAILURE(createAndPresentVirtualDisplay(layerCnt, coverage,
            virtualDisplayExceptions));
}

/* TESTCASE: Tests that the HWC2 can present 3 layers with default coverage on a
 * virtual display. */
TEST_F(Hwc2Test, PRESENT_VIRTUAL_DISPLAY_default_3)
{
    Hwc2TestCoverage coverage = Hwc2TestCoverage::Default;
    const size_t layerCnt = 3;
    ASSERT_NO_FATAL_FAILURE(createAndPresentVirtualDisplay(layerCnt, coverage,
            virtualDisplayExceptions));
}

/* TESTCASE: Tests that the HWC2 can present 4 layers with default coverage on a
 * virtual display. */
TEST_F(Hwc2Test, PRESENT_VIRTUAL_DISPLAY_default_4)
{
    Hwc2TestCoverage coverage = Hwc2TestCoverage::Default;
    const size_t layerCnt = 4;
    ASSERT_NO_FATAL_FAILURE(createAndPresentVirtualDisplay(layerCnt, coverage,
            virtualDisplayExceptions));
}

/* TESTCASE: Tests that the HWC2 can present 5 layers with default coverage on a
 * virtual display. */
TEST_F(Hwc2Test, PRESENT_VIRTUAL_DISPLAY_default_5)
{
    Hwc2TestCoverage coverage = Hwc2TestCoverage::Default;
    const size_t layerCnt = 5;
    ASSERT_NO_FATAL_FAILURE(createAndPresentVirtualDisplay(layerCnt, coverage,
            virtualDisplayExceptions));
}
+116 −26
Original line number Diff line number Diff line
@@ -27,7 +27,8 @@
#include <math/vec4.h>

#include <GLES3/gl3.h>

#include <SkImageEncoder.h>
#include <SkStream.h>
#include "Hwc2TestBuffer.h"
#include "Hwc2TestLayers.h"

@@ -462,33 +463,22 @@ Hwc2TestClientTargetBuffer::Hwc2TestClientTargetBuffer()

Hwc2TestClientTargetBuffer::~Hwc2TestClientTargetBuffer() { }

/* Generates a client target buffer using the layers assigned for client
 * composition. Takes into account the individual layer properties such as
/* Generates a buffer from layersToDraw.
 * Takes into account the individual layer properties such as
 * transform, blend mode, source crop, etc. */
int Hwc2TestClientTargetBuffer::get(buffer_handle_t* outHandle,
        int32_t* outFence, const Area& bufferArea,
static void compositeBufferFromLayers(
        const android::sp<android::GraphicBuffer>& graphicBuffer,
        android_pixel_format_t format, const Area& bufferArea,
        const Hwc2TestLayers* testLayers,
        const std::set<hwc2_layer_t>* clientLayers,
        const std::set<hwc2_layer_t>* layersToDraw,
        const std::set<hwc2_layer_t>* clearLayers)
{
    /* Create new graphic buffer with correct dimensions */
    mGraphicBuffer = new GraphicBuffer(bufferArea.width, bufferArea.height,
            mFormat, BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN |
            BufferUsage::COMPOSER_OVERLAY, "hwc2_test_buffer");

    int ret = mGraphicBuffer->initCheck();
    if (ret) {
        return ret;
    }
    if (!mGraphicBuffer->handle) {
        return -EINVAL;
    }

    /* Locks the buffer for writing */
    uint8_t* img;
    mGraphicBuffer->lock(static_cast<uint32_t>(BufferUsage::CPU_WRITE_OFTEN),
    graphicBuffer->lock(static_cast<uint32_t>(BufferUsage::CPU_WRITE_OFTEN),
            (void**)(&img));

    uint32_t stride = mGraphicBuffer->getStride();
    uint32_t stride = graphicBuffer->getStride();

    float bWDiv3 = bufferArea.width / 3;
    float bW2Div3 = bufferArea.width * 2 / 3;
@@ -503,10 +493,10 @@ int Hwc2TestClientTargetBuffer::get(buffer_handle_t* outHandle,
            uint8_t r = 0, g = 0, b = 0;
            float a = 0.0f;

            /* Cycle through each client layer from back to front and
            /* Cycle through each layer from back to front and
             * update the pixel color. */
            for (auto layer = clientLayers->rbegin();
                    layer != clientLayers->rend(); ++layer) {
            for (auto layer = layersToDraw->rbegin();
                    layer != layersToDraw->rend(); ++layer) {

                const hwc_rect_t df = testLayers->getDisplayFrame(*layer);

@@ -688,14 +678,114 @@ int Hwc2TestClientTargetBuffer::get(buffer_handle_t* outHandle,
            }

            /* Set the pixel color */
            setColor(x, y, mFormat, stride, img, r, g, b, a * 255);
            setColor(x, y, format, stride, img, r, g, b, a * 255);
        }
    }

    mGraphicBuffer->unlock();
    graphicBuffer->unlock();
}

/* Generates a client target buffer using the layers assigned for client
 * composition. Takes into account the individual layer properties such as
 * transform, blend mode, source crop, etc. */
int Hwc2TestClientTargetBuffer::get(buffer_handle_t* outHandle,
        int32_t* outFence, const Area& bufferArea,
        const Hwc2TestLayers* testLayers,
        const std::set<hwc2_layer_t>* clientLayers,
        const std::set<hwc2_layer_t>* clearLayers)
{
    /* Create new graphic buffer with correct dimensions */
    mGraphicBuffer = new GraphicBuffer(bufferArea.width, bufferArea.height,
            mFormat, BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN |
            BufferUsage::COMPOSER_OVERLAY, "hwc2_test_buffer");

    int ret = mGraphicBuffer->initCheck();
    if (ret)
        return ret;

    if (!mGraphicBuffer->handle)
        return -EINVAL;

    compositeBufferFromLayers(mGraphicBuffer, mFormat, bufferArea, testLayers,
            clientLayers, clearLayers);

    *outFence = mFenceGenerator->get();
    *outHandle = mGraphicBuffer->handle;

    return 0;
}

void Hwc2TestVirtualBuffer::updateBufferArea(const Area& bufferArea)
{
    mBufferArea.width = bufferArea.width;
    mBufferArea.height = bufferArea.height;
}

bool Hwc2TestVirtualBuffer::writeBufferToFile(std::string path)
{
    SkFILEWStream file(path.c_str());
    const SkImageInfo info = SkImageInfo::Make(mBufferArea.width,
            mBufferArea.height, SkColorType::kRGBA_8888_SkColorType,
            SkAlphaType::kPremul_SkAlphaType);

    uint8_t* img;
    mGraphicBuffer->lock(static_cast<uint32_t>(BufferUsage::CPU_WRITE_OFTEN),
            (void**)(&img));

    SkPixmap pixmap(info, img, mGraphicBuffer->getStride());
    bool result = file.isValid() && SkEncodeImage(&file, pixmap,
            SkEncodedImageFormat::kPNG, 100);

    mGraphicBuffer->unlock();
    return result;
}

/* Generates a buffer that holds the expected result of compositing all of our
 * layers */
int Hwc2TestExpectedBuffer::generateExpectedBuffer(
        const Hwc2TestLayers* testLayers,
        const std::vector<hwc2_layer_t>* allLayers,
        const std::set<hwc2_layer_t>* clearLayers)
{
    mGraphicBuffer = new GraphicBuffer(mBufferArea.width, mBufferArea.height,
            mFormat, BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN,
            "hwc2_test_buffer");

    int ret = mGraphicBuffer->initCheck();
    if (ret)
        return ret;

    if (!mGraphicBuffer->handle)
        return -EINVAL;

    const std::set<hwc2_layer_t> allLayerSet(allLayers->begin(),
            allLayers->end());

    compositeBufferFromLayers(mGraphicBuffer, mFormat, mBufferArea, testLayers,
            &allLayerSet, clearLayers);

    return 0;
}

int Hwc2TestOutputBuffer::getOutputBuffer(buffer_handle_t* outHandle,
        int32_t* outFence)
{
    if (mBufferArea.width == -1 || mBufferArea.height == -1)
        return -EINVAL;

    mGraphicBuffer = new GraphicBuffer(mBufferArea.width, mBufferArea.height,
            mFormat, BufferUsage::CPU_READ_OFTEN |
            BufferUsage::GPU_RENDER_TARGET, "hwc2_test_buffer");

    int ret = mGraphicBuffer->initCheck();
    if (ret)
        return ret;

    if (!mGraphicBuffer->handle)
        return -EINVAL;

    *outFence = -1;
    *outHandle = mGraphicBuffer->handle;

    return 0;
}
+34 −0
Original line number Diff line number Diff line
@@ -71,4 +71,38 @@ protected:
    const android_pixel_format_t mFormat = HAL_PIXEL_FORMAT_RGBA_8888;
};


class Hwc2TestVirtualBuffer {
public:
    void updateBufferArea(const Area& bufferArea);

    bool writeBufferToFile(std::string path);

    android::sp<android::GraphicBuffer>& graphicBuffer()
    {
        return mGraphicBuffer;
    }

protected:
    android::sp<android::GraphicBuffer> mGraphicBuffer;

    Area mBufferArea = {-1, -1};

    const android_pixel_format_t mFormat = HAL_PIXEL_FORMAT_RGBA_8888;
};


class Hwc2TestExpectedBuffer : public Hwc2TestVirtualBuffer {
public:
    int generateExpectedBuffer(const Hwc2TestLayers* testLayers,
            const std::vector<hwc2_layer_t>* allLayers,
            const std::set<hwc2_layer_t>* clearLayers);
};


class Hwc2TestOutputBuffer : public Hwc2TestVirtualBuffer {
public:
    int getOutputBuffer(buffer_handle_t* outHandle, int32_t* outFence);
};

#endif /* ifndef _HWC2_TEST_BUFFER_H */
+113 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * 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.
 */

#include <sstream>
#include <android/hardware/graphics/common/1.0/types.h>

#include "Hwc2TestPixelComparator.h"

using android::hardware::graphics::common::V1_0::BufferUsage;

uint32_t ComparatorResult::getPixel(int32_t x, int32_t y, uint32_t stride,
        uint8_t* img) const
{
    uint32_t r = img[(y * stride + x) * 4 + 0];
    uint32_t g = img[(y * stride + x) * 4 + 1];
    uint32_t b = img[(y * stride + x) * 4 + 2];
    uint32_t a = img[(y * stride + x) * 4 + 3];

    uint32_t pixel = 0;
    pixel |= r;
    pixel |= g << 8;
    pixel |= b << 16;
    pixel |= a << 24;
    return pixel;
}

void ComparatorResult::CompareBuffers(
        android::sp<android::GraphicBuffer>& resultBuffer,
        android::sp<android::GraphicBuffer>& expectedBuffer)
{
    uint8_t* resultBufferImg;
    uint8_t* expectedBufferImg;
    resultBuffer->lock(static_cast<uint32_t>(BufferUsage::CPU_READ_OFTEN),
            (void**)(&resultBufferImg));

    expectedBuffer->lock(static_cast<uint32_t>(BufferUsage::CPU_READ_OFTEN),
            (void**)(&expectedBufferImg));
    mComparisons.clear();
    int32_t mDifferentPixelCount = 0;
    int32_t mBlankPixelCount = 0;

    for (uint32_t y = 0; y < resultBuffer->getHeight(); y++) {
        for (uint32_t x = 0; x < resultBuffer->getWidth(); x++) {
            uint32_t result = getPixel(x, y, resultBuffer->getStride(),
                    resultBufferImg);
            uint32_t expected = getPixel(x, y, expectedBuffer->getStride(),
                    expectedBufferImg);

            if (result == 0)
                mBlankPixelCount++;

            if (result != expected)
                mDifferentPixelCount++;

            mComparisons.emplace_back(std::make_tuple(x, y, result, expected));
        }
    }
    resultBuffer->unlock();
    expectedBuffer->unlock();
}

std::string ComparatorResult::pixelDiff(uint32_t x, uint32_t y,
        uint32_t resultPixel, uint32_t expectedPixel) const
{
    uint32_t resultAlpha = (resultPixel >> 24) & 0xFF;
    uint32_t resultBlue = (resultPixel >> 16) & 0xFF;
    uint32_t resultGreen = (resultPixel >> 8) & 0xFF;
    uint32_t resultRed = resultPixel & 0xFF;

    uint32_t expectedAlpha = (expectedPixel >> 24) & 0xFF;
    uint32_t expectedBlue = (expectedPixel >> 16) & 0xFF;
    uint32_t expectedGreen = (expectedPixel >> 8) & 0xFF;
    uint32_t expectedRed = expectedPixel & 0xFF;

    std::ostringstream stream;

    stream << "x: " << x << " y: " << y << std::endl;
    stream << std::hex;
    stream << "Result pixel:   " << resultRed << "|" << resultGreen << "|"
           << resultBlue << "|" << resultAlpha << std::endl;

    stream << "Expected pixel: " << expectedRed << "|" << expectedGreen << "|"
           << expectedBlue << "|" << expectedAlpha << std::endl;

    return stream.str();
}

std::string ComparatorResult::dumpComparison() const
{
    std::ostringstream stream;
    stream << "Number of different pixels: " << mDifferentPixelCount;

    for (const auto& comparison : mComparisons) {
        if (std::get<2>(comparison) != std::get<3>(comparison))
            stream << pixelDiff(std::get<0>(comparison),
                    std::get<1>(comparison), std::get<2>(comparison),
                    std::get<3>(comparison));
    }
    return stream.str();
}
Loading