diff options
author | Allan Sandfeld Jensen <allan.jensen@digia.com> | 2013-09-13 12:51:20 +0200 |
---|---|---|
committer | The Qt Project <gerrit-noreply@qt-project.org> | 2013-09-19 20:50:05 +0200 |
commit | d441d6f39bb846989d95bcf5caf387b42414718d (patch) | |
tree | e367e64a75991c554930278175d403c072de6bb8 /Tools/ImageDiff | |
parent | 0060b2994c07842f4c59de64b5e3e430525c4b90 (diff) | |
download | qtwebkit-d441d6f39bb846989d95bcf5caf387b42414718d.tar.gz |
Import Qt5x2 branch of QtWebkit for Qt 5.2
Importing a new snapshot of webkit.
Change-Id: I2d01ad12cdc8af8cb015387641120a9d7ea5f10c
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@digia.com>
Diffstat (limited to 'Tools/ImageDiff')
-rw-r--r-- | Tools/ImageDiff/CMakeLists.txt | 17 | ||||
-rw-r--r-- | Tools/ImageDiff/ImageDiff.pro | 14 | ||||
-rw-r--r-- | Tools/ImageDiff/PlatformEfl.cmake | 19 | ||||
-rw-r--r-- | Tools/ImageDiff/efl/ImageDiff.cpp | 368 | ||||
-rw-r--r-- | Tools/ImageDiff/gtk/ImageDiff.cpp | 236 | ||||
-rw-r--r-- | Tools/ImageDiff/qt/ImageDiff.cpp | 144 |
6 files changed, 798 insertions, 0 deletions
diff --git a/Tools/ImageDiff/CMakeLists.txt b/Tools/ImageDiff/CMakeLists.txt new file mode 100644 index 000000000..6670fd574 --- /dev/null +++ b/Tools/ImageDiff/CMakeLists.txt @@ -0,0 +1,17 @@ +set(IMAGE_DIFF_DIR "${TOOLS_DIR}/ImageDiff") + +set(IMAGE_DIFF_INCLUDE_DIRECTORIES + ${CMAKE_BINARY_DIR} + ${WTF_DIR} +) + +set(IMAGE_DIFF_LIBRARIES + WTF +) + +INCLUDE_IF_EXISTS(${IMAGE_DIFF_DIR}/Platform${PORT}.cmake) + +include_directories(${IMAGE_DIFF_INCLUDE_DIRECTORIES}) +add_executable(ImageDiff ${IMAGE_DIFF_SOURCES}) +target_link_libraries(ImageDiff ${IMAGE_DIFF_LIBRARIES}) +set_target_properties(ImageDiff PROPERTIES FOLDER "Tools") diff --git a/Tools/ImageDiff/ImageDiff.pro b/Tools/ImageDiff/ImageDiff.pro new file mode 100644 index 000000000..f3e8a4910 --- /dev/null +++ b/Tools/ImageDiff/ImageDiff.pro @@ -0,0 +1,14 @@ +# ------------------------------------------------------------------- +# Project file for the ImageDiff binary +# +# See 'Tools/qmake/README' for an overview of the build system +# ------------------------------------------------------------------- + +TEMPLATE = app + +TARGET = ImageDiff +DESTDIR = $$ROOT_BUILD_DIR/bin + +QT = core gui widgets + +SOURCES = qt/ImageDiff.cpp diff --git a/Tools/ImageDiff/PlatformEfl.cmake b/Tools/ImageDiff/PlatformEfl.cmake new file mode 100644 index 000000000..68b444428 --- /dev/null +++ b/Tools/ImageDiff/PlatformEfl.cmake @@ -0,0 +1,19 @@ +set(IMAGE_DIFF_SOURCES + ${IMAGE_DIFF_DIR}/efl/ImageDiff.cpp +) + +list(APPEND IMAGE_DIFF_INCLUDE_DIRECTORIES + ${ECORE_EVAS_INCLUDE_DIRS} + ${ECORE_INCLUDE_DIRS} + ${EINA_INCLUDE_DIRS} + ${EO_INCLUDE_DIRS} + ${EVAS_INCLUDE_DIRS} +) + +list(APPEND IMAGE_DIFF_LIBRARIES + ${ECORE_EVAS_LIBRARIES} + ${ECORE_LIBRARIES} + ${EINA_LIBRARIES} + ${EO_LIBRARIES} + ${EVAS_LIBRARIES} +) diff --git a/Tools/ImageDiff/efl/ImageDiff.cpp b/Tools/ImageDiff/efl/ImageDiff.cpp new file mode 100644 index 000000000..129b6ca57 --- /dev/null +++ b/Tools/ImageDiff/efl/ImageDiff.cpp @@ -0,0 +1,368 @@ +/* + * Copyright (C) 2009 Zan Dobersek <zandobersek@gmail.com> + * Copyright (C) 2010 Igalia S.L. + * Copyright (C) 2011 ProFUSION Embedded Systems + * Copyright (C) 2011 Samsung Electronics + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#include <Ecore.h> +#include <Ecore_Evas.h> +#include <Evas.h> +#include <algorithm> +#include <cmath> +#include <cstdio> +#include <cstdlib> +#include <getopt.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> +#include <wtf/OwnArrayPtr.h> +#include <wtf/OwnPtr.h> +#include <wtf/PassOwnPtr.h> +#include <wtf/PassRefPtr.h> +#include <wtf/efl/RefPtrEfl.h> + +enum PixelComponent { + Red, + Green, + Blue, + Alpha +}; + +static OwnPtr<Ecore_Evas> gEcoreEvas; +static double gTolerance = 0; + +static void abortWithErrorMessage(const char* errorMessage); + +static unsigned char* pixelFromImageData(unsigned char* imageData, int rowStride, int x, int y) +{ + return imageData + (y * rowStride) + (x << 2); +} + +static Evas_Object* differenceImageFromDifferenceBuffer(Evas* evas, unsigned char* buffer, int width, int height) +{ + Evas_Object* image = evas_object_image_filled_add(evas); + if (!image) + abortWithErrorMessage("could not create difference image"); + + evas_object_image_size_set(image, width, height); + evas_object_image_colorspace_set(image, EVAS_COLORSPACE_ARGB8888); + + unsigned char* diffPixels = static_cast<unsigned char*>(evas_object_image_data_get(image, EINA_TRUE)); + const int rowStride = evas_object_image_stride_get(image); + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + unsigned char* diffPixel = pixelFromImageData(diffPixels, rowStride, x, y); + diffPixel[Red] = diffPixel[Green] = diffPixel[Blue] = *buffer++; + diffPixel[Alpha] = 0xff; + } + } + + evas_object_image_data_set(image, diffPixels); + + return image; +} + +static float computeDistanceBetweenPixelComponents(unsigned char actualComponent, unsigned char baseComponent) +{ + return (actualComponent - baseComponent) / std::max<float>(255 - baseComponent, baseComponent); +} + +static float computeDistanceBetweenPixelComponents(unsigned char* actualPixel, unsigned char* basePixel, PixelComponent component) +{ + return computeDistanceBetweenPixelComponents(actualPixel[component], basePixel[component]); +} + +static float calculatePixelDifference(unsigned char* basePixel, unsigned char* actualPixel) +{ + const float red = computeDistanceBetweenPixelComponents(actualPixel, basePixel, Red); + const float green = computeDistanceBetweenPixelComponents(actualPixel, basePixel, Green); + const float blue = computeDistanceBetweenPixelComponents(actualPixel, basePixel, Blue); + const float alpha = computeDistanceBetweenPixelComponents(actualPixel, basePixel, Alpha); + return sqrtf(red * red + green * green + blue * blue + alpha * alpha) / 2.0f; +} + +static float calculateDifference(Evas_Object* baselineImage, Evas_Object* actualImage, RefPtr<Evas_Object>& differenceImage) +{ + int width, height, baselineWidth, baselineHeight; + evas_object_image_size_get(actualImage, &width, &height); + evas_object_image_size_get(baselineImage, &baselineWidth, &baselineHeight); + + if (width != baselineWidth || height != baselineHeight) { + printf("Error, test and reference image have different sizes.\n"); + return 100; // Completely different. + } + + OwnArrayPtr<unsigned char> diffBuffer = adoptArrayPtr(new unsigned char[width * height]); + if (!diffBuffer) + abortWithErrorMessage("could not create difference buffer"); + + const int actualRowStride = evas_object_image_stride_get(actualImage); + const int baseRowStride = evas_object_image_stride_get(baselineImage); + unsigned numberOfDifferentPixels = 0; + float totalDistance = 0; + float maxDistance = 0; + unsigned char* actualPixels = static_cast<unsigned char*>(evas_object_image_data_get(actualImage, EINA_FALSE)); + unsigned char* basePixels = static_cast<unsigned char*>(evas_object_image_data_get(baselineImage, EINA_FALSE)); + unsigned char* currentDiffPixel = diffBuffer.get(); + + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + unsigned char* actualPixel = pixelFromImageData(actualPixels, actualRowStride, x, y); + unsigned char* basePixel = pixelFromImageData(basePixels, baseRowStride, x, y); + + const float distance = calculatePixelDifference(basePixel, actualPixel); + *currentDiffPixel++ = static_cast<unsigned char>(distance * 255.0f); + + if (distance >= 1.0f / 255.0f) { + ++numberOfDifferentPixels; + totalDistance += distance; + maxDistance = std::max<float>(maxDistance, distance); + } + } + } + + // When using evas_object_image_data_get(), a complementary evas_object_data_set() must be + // issued to balance the reference count, even if the image hasn't been changed. + evas_object_image_data_set(baselineImage, basePixels); + evas_object_image_data_set(actualImage, actualPixels); + + // Compute the difference as a percentage combining both the number of + // different pixels and their difference amount i.e. the average distance + // over the entire image + float difference = 0; + if (numberOfDifferentPixels) + difference = 100.0f * totalDistance / (height * width); + if (difference <= gTolerance) + difference = 0; + else { + difference = roundf(difference * 100.0f) / 100.0f; + difference = std::max(difference, 0.01f); // round to 2 decimal places + + differenceImage = adoptRef(differenceImageFromDifferenceBuffer(evas_object_evas_get(baselineImage), diffBuffer.get(), width, height)); + } + + return difference; +} + +static int getTemporaryFile(char *fileName, size_t fileNameLength) +{ + char* tempDirectory = getenv("TMPDIR"); + if (!tempDirectory) + tempDirectory = getenv("TEMP"); + + if (tempDirectory) + snprintf(fileName, fileNameLength, "%s/ImageDiffXXXXXX.png", tempDirectory); + else { +#if __linux__ + strcpy(fileName, "/dev/shm/ImageDiffXXXXXX.png"); + const int fileDescriptor = mkstemps(fileName, sizeof(".png") - 1); + if (fileDescriptor >= 0) + return fileDescriptor; +#endif // __linux__ + + strcpy(fileName, "ImageDiffXXXXXX.png"); + } + + return mkstemps(fileName, sizeof(".png") - 1); +} + +static void printImage(Evas_Object* image) +{ + char fileName[PATH_MAX]; + + const int tempImageFd = getTemporaryFile(fileName, PATH_MAX); + if (tempImageFd == -1) + abortWithErrorMessage("could not create temporary file"); + + evas_render(evas_object_evas_get(image)); + + if (evas_object_image_save(image, fileName, 0, 0)) { + struct stat fileInfo; + if (!stat(fileName, &fileInfo)) { + printf("Content-Length: %ld\n", fileInfo.st_size); + fflush(stdout); + + unsigned char buffer[2048]; + ssize_t bytesRead; + while ((bytesRead = read(tempImageFd, buffer, sizeof(buffer))) > 0) { + ssize_t bytesWritten = 0; + ssize_t count; + do { + if ((count = write(1, buffer + bytesWritten, bytesRead - bytesWritten)) <= 0) + break; + bytesWritten += count; + } while (bytesWritten < bytesRead); + } + } + } + close(tempImageFd); + unlink(fileName); +} + +static void printImageDifferences(Evas_Object* baselineImage, Evas_Object* actualImage) +{ + RefPtr<Evas_Object> differenceImage; + const float difference = calculateDifference(baselineImage, actualImage, differenceImage); + + if (difference > 0.0f) { + if (differenceImage) + printImage(differenceImage.get()); + + printf("diff: %01.2f%% failed\n", difference); + } else + printf("diff: %01.2f%% passed\n", difference); +} + +static void resizeEcoreEvasIfNeeded(Evas_Object* image) +{ + int newWidth, newHeight; + evas_object_image_size_get(image, &newWidth, &newHeight); + + int currentWidth, currentHeight; + ecore_evas_screen_geometry_get(gEcoreEvas.get(), 0, 0, ¤tWidth, ¤tHeight); + + if (newWidth > currentWidth) + currentWidth = newWidth; + if (newHeight > currentHeight) + currentHeight = newHeight; + + ecore_evas_resize(gEcoreEvas.get(), currentWidth, currentHeight); +} + +static PassRefPtr<Evas_Object> readImageFromStdin(Evas* evas, long imageSize) +{ + OwnArrayPtr<unsigned char> imageBuffer = adoptArrayPtr(new unsigned char[imageSize]); + if (!imageBuffer) + abortWithErrorMessage("cannot allocate image"); + + const size_t bytesRead = fread(imageBuffer.get(), 1, imageSize, stdin); + if (!bytesRead) + return PassRefPtr<Evas_Object>(); + + Evas_Object* image = evas_object_image_filled_add(evas); + evas_object_image_colorspace_set(image, EVAS_COLORSPACE_ARGB8888); + evas_object_image_memfile_set(image, imageBuffer.get(), bytesRead, 0, 0); + + resizeEcoreEvasIfNeeded(image); + + return adoptRef(image); +} + +static bool parseCommandLineOptions(int argc, char** argv) +{ + static const option options[] = { + { "tolerance", required_argument, 0, 't' }, + { 0, 0, 0, 0 } + }; + int option; + + while ((option = getopt_long(argc, (char* const*)argv, "t:", options, 0)) != -1) { + switch (option) { + case 't': + gTolerance = atof(optarg); + break; + case '?': + case ':': + return false; + } + } + + return true; +} + +static void shutdownEfl() +{ + ecore_evas_shutdown(); + ecore_shutdown(); + evas_shutdown(); +} + +static void abortWithErrorMessage(const char* errorMessage) +{ + shutdownEfl(); + + printf("Error, %s.\n", errorMessage); + exit(EXIT_FAILURE); +} + +static Evas* initEfl() +{ + evas_init(); + ecore_init(); + ecore_evas_init(); + + gEcoreEvas = adoptPtr(ecore_evas_buffer_new(1, 1)); + Evas* evas = ecore_evas_get(gEcoreEvas.get()); + if (!evas) + abortWithErrorMessage("could not create Ecore_Evas buffer"); + + return evas; +} + +int main(int argc, char* argv[]) +{ + if (!parseCommandLineOptions(argc, argv)) + return EXIT_FAILURE; + + Evas* evas = initEfl(); + + RefPtr<Evas_Object> actualImage; + RefPtr<Evas_Object> baselineImage; + + char buffer[2048]; + while (fgets(buffer, sizeof(buffer), stdin)) { + char* contentLengthStart = strstr(buffer, "Content-Length: "); + if (!contentLengthStart) + continue; + long imageSize; + if (sscanf(contentLengthStart, "Content-Length: %ld", &imageSize) == 1) { + if (imageSize <= 0) + abortWithErrorMessage("image size must be specified"); + + if (!actualImage) + actualImage = readImageFromStdin(evas, imageSize); + else if (!baselineImage) { + baselineImage = readImageFromStdin(evas, imageSize); + + printImageDifferences(baselineImage.get(), actualImage.get()); + + actualImage.clear(); + baselineImage.clear(); + } + } + + fflush(stdout); + } + + gEcoreEvas.clear(); // Make sure ecore_evas_free is called before the EFL are shut down + + shutdownEfl(); + return EXIT_SUCCESS; +} diff --git a/Tools/ImageDiff/gtk/ImageDiff.cpp b/Tools/ImageDiff/gtk/ImageDiff.cpp new file mode 100644 index 000000000..2cb9f3bce --- /dev/null +++ b/Tools/ImageDiff/gtk/ImageDiff.cpp @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2009 Zan Dobersek <zandobersek@gmail.com> + * Copyright (C) 2010 Igalia S.L. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <algorithm> +#include <cmath> +#include <cstdio> +#include <cstring> +#include <gdk/gdk.h> + +using namespace std; + +static double tolerance = 0; +static GOptionEntry commandLineOptionEntries[] = +{ + { "tolerance", 0, 0, G_OPTION_ARG_DOUBLE, &tolerance, "Percentage difference between images before considering them different", "T" }, + { 0, 0, 0, G_OPTION_ARG_NONE, 0, 0, 0 }, +}; + +GdkPixbuf* readPixbufFromStdin(long imageSize) +{ + unsigned char imageBuffer[2048]; + GdkPixbufLoader* loader = gdk_pixbuf_loader_new_with_type("png", 0); + GError* error = 0; + + while (imageSize > 0) { + size_t bytesToRead = min<int>(imageSize, 2048); + size_t bytesRead = fread(imageBuffer, 1, bytesToRead, stdin); + + if (!gdk_pixbuf_loader_write(loader, reinterpret_cast<const guchar*>(imageBuffer), bytesRead, &error)) { + g_error_free(error); + gdk_pixbuf_loader_close(loader, 0); + g_object_unref(loader); + return 0; + } + + imageSize -= static_cast<int>(bytesRead); + } + + gdk_pixbuf_loader_close(loader, 0); + GdkPixbuf* decodedImage = gdk_pixbuf_loader_get_pixbuf(loader); + g_object_ref(decodedImage); + return decodedImage; +} + +GdkPixbuf* differenceImageFromDifferenceBuffer(unsigned char* buffer, int width, int height) +{ + GdkPixbuf* image = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, width, height); + if (!image) + return image; + + int rowStride = gdk_pixbuf_get_rowstride(image); + unsigned char* diffPixels = gdk_pixbuf_get_pixels(image); + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + unsigned char* diffPixel = diffPixels + (y * rowStride) + (x * 3); + diffPixel[0] = diffPixel[1] = diffPixel[2] = *buffer++; + } + } + + return image; +} + +float calculateDifference(GdkPixbuf* baselineImage, GdkPixbuf* actualImage, GdkPixbuf** differenceImage) +{ + int width = gdk_pixbuf_get_width(actualImage); + int height = gdk_pixbuf_get_height(actualImage); + int numberOfChannels = gdk_pixbuf_get_n_channels(actualImage); + if ((width != gdk_pixbuf_get_width(baselineImage)) + || (height != gdk_pixbuf_get_height(baselineImage)) + || (numberOfChannels != gdk_pixbuf_get_n_channels(baselineImage)) + || (gdk_pixbuf_get_has_alpha(actualImage) != gdk_pixbuf_get_has_alpha(baselineImage))) { + fprintf(stderr, "Error, test and reference image have different properties.\n"); + return 100; // Completely different. + } + + unsigned char* diffBuffer = static_cast<unsigned char*>(malloc(width * height)); + float count = 0; + float sum = 0; + float maxDistance = 0; + int actualRowStride = gdk_pixbuf_get_rowstride(actualImage); + int baseRowStride = gdk_pixbuf_get_rowstride(baselineImage); + unsigned char* actualPixels = gdk_pixbuf_get_pixels(actualImage); + unsigned char* basePixels = gdk_pixbuf_get_pixels(baselineImage); + unsigned char* currentDiffPixel = diffBuffer; + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + unsigned char* actualPixel = actualPixels + (y * actualRowStride) + (x * numberOfChannels); + unsigned char* basePixel = basePixels + (y * baseRowStride) + (x * numberOfChannels); + + float red = (actualPixel[0] - basePixel[0]) / max<float>(255 - basePixel[0], basePixel[0]); + float green = (actualPixel[1] - basePixel[1]) / max<float>(255 - basePixel[1], basePixel[1]); + float blue = (actualPixel[2] - basePixel[2]) / max<float>(255 - basePixel[2], basePixel[2]); + float alpha = (actualPixel[3] - basePixel[3]) / max<float>(255 - basePixel[3], basePixel[3]); + float distance = sqrtf(red * red + green * green + blue * blue + alpha * alpha) / 2.0f; + + *currentDiffPixel++ = (unsigned char)(distance * 255.0f); + + if (distance >= 1.0f / 255.0f) { + count += 1.0f; + sum += distance; + maxDistance = max<float>(maxDistance, distance); + } + } + } + + // Compute the difference as a percentage combining both the number of + // different pixels and their difference amount i.e. the average distance + // over the entire image + float difference = 0; + if (count > 0.0f) + difference = 100.0f * sum / (height * width); + if (difference <= tolerance) + difference = 0; + else { + difference = roundf(difference * 100.0f) / 100.0f; + difference = max(difference, 0.01f); // round to 2 decimal places + *differenceImage = differenceImageFromDifferenceBuffer(diffBuffer, width, height); + } + + free(diffBuffer); + return difference; +} + +void printImage(GdkPixbuf* image) +{ + char* buffer; + gsize bufferSize; + GError* error = 0; + if (!gdk_pixbuf_save_to_buffer(image, &buffer, &bufferSize, "png", &error, NULL)) { + g_error_free(error); + return; // Don't bail out, as we can still use the percentage output. + } + + printf("Content-Length: %" G_GSIZE_FORMAT "\n", bufferSize); + fwrite(buffer, 1, bufferSize, stdout); +} + +void printImageDifferences(GdkPixbuf* baselineImage, GdkPixbuf* actualImage) +{ + GdkPixbuf* differenceImage = 0; + float difference = calculateDifference(baselineImage, actualImage, &differenceImage); + if (difference > 0.0f) { + if (differenceImage) { + printImage(differenceImage); + g_object_unref(differenceImage); + } + printf("diff: %01.2f%% failed\n", difference); + } else + printf("diff: %01.2f%% passed\n", difference); +} + +int main(int argc, char* argv[]) +{ + GError* error = 0; + GOptionContext* context = g_option_context_new("- compare two image files, printing their percentage difference and the difference image to stdout"); + g_option_context_add_main_entries(context, commandLineOptionEntries, 0); + if (!g_option_context_parse(context, &argc, &argv, &error)) { + printf("Option parsing failed: %s\n", error->message); + g_error_free(error); + return 1; + } + + GdkPixbuf* actualImage = 0; + GdkPixbuf* baselineImage = 0; + char buffer[2048]; + while (fgets(buffer, sizeof(buffer), stdin)) { + // Convert the first newline into a NUL character so that strtok doesn't produce it. + char* newLineCharacter = strchr(buffer, '\n'); + if (newLineCharacter) + *newLineCharacter = '\0'; + + if (!strncmp("Content-Length: ", buffer, 16)) { + gchar** tokens = g_strsplit(buffer, " ", 0); + if (!tokens[1]) { + g_strfreev(tokens); + printf("Error, image size must be specified..\n"); + return 1; + } + + long imageSize = strtol(tokens[1], 0, 10); + g_strfreev(tokens); + if (imageSize > 0 && !actualImage) { + if (!(actualImage = readPixbufFromStdin(imageSize))) { + printf("Error, could not read actual image.\n"); + return 1; + } + } else if (imageSize > 0 && !baselineImage) { + if (!(baselineImage = readPixbufFromStdin(imageSize))) { + printf("Error, could not read baseline image.\n"); + return 1; + } + } else { + printf("Error, image size must be specified..\n"); + return 1; + } + } + + if (actualImage && baselineImage) { + printImageDifferences(baselineImage, actualImage); + g_object_unref(actualImage); + g_object_unref(baselineImage); + actualImage = 0; + baselineImage = 0; + } + + fflush(stdout); + } + + return 0; +} diff --git a/Tools/ImageDiff/qt/ImageDiff.cpp b/Tools/ImageDiff/qt/ImageDiff.cpp new file mode 100644 index 000000000..816086aa6 --- /dev/null +++ b/Tools/ImageDiff/qt/ImageDiff.cpp @@ -0,0 +1,144 @@ +/* + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include <QtCore/qmath.h> + +#include <QApplication> +#include <QBuffer> +#include <QByteArray> +#include <QImage> +#include <QStringList> + +#include <stdio.h> + +int main(int argc, char* argv[]) +{ + QCoreApplication app(argc, argv); + + qreal tolerance = 0; // Tolerated percentage of error pixels. + + QStringList args = app.arguments(); + for (int i = 0; i < argc; ++i) + if (args[i] == "-t" || args[i] == "--tolerance") + tolerance = args[i + 1].toDouble(); + + char buffer[2048]; + QImage actualImage; + QImage baselineImage; + + while (fgets(buffer, sizeof(buffer), stdin)) { + // remove the CR + char* newLineCharacter = strchr(buffer, '\n'); + if (newLineCharacter) + *newLineCharacter = '\0'; + + if (!strncmp("Content-Length: ", buffer, 16)) { + strtok(buffer, " "); + int imageSize = strtol(strtok(0, " "), 0, 10); + + if (imageSize <= 0) { + fputs("error, image size must be specified.\n", stdout); + } else { + unsigned char buffer[2048]; + QBuffer data; + + // Read all the incoming chunks + data.open(QBuffer::WriteOnly); + while (imageSize > 0) { + size_t bytesToRead = qMin(imageSize, 2048); + size_t bytesRead = fread(buffer, 1, bytesToRead, stdin); + data.write(reinterpret_cast<const char*>(buffer), bytesRead); + imageSize -= static_cast<int>(bytesRead); + } + + // Convert into QImage + QImage decodedImage; + decodedImage.loadFromData(data.data(), "PNG"); + decodedImage = decodedImage.convertToFormat(QImage::Format_ARGB32); + + // Place it in the right place + if (actualImage.isNull()) + actualImage = decodedImage; + else + baselineImage = decodedImage; + } + } + + if (!actualImage.isNull() && !baselineImage.isNull()) { + + if (actualImage.size() != baselineImage.size()) { + fprintf(stdout, "diff: 100%% failed\n"); + fprintf(stderr, "error, test and reference image have different properties.\n"); + fflush(stderr); + fflush(stdout); + } else { + + int w = actualImage.width(); + int h = actualImage.height(); + QImage diffImage(w, h, QImage::Format_ARGB32); + + int errorCount = 0; + + for (int x = 0; x < w; ++x) { + for (int y = 0; y < h; ++y) { + QRgb pixel = actualImage.pixel(x, y); + QRgb basePixel = baselineImage.pixel(x, y); + qreal red = (qRed(pixel) - qRed(basePixel)) / static_cast<float>(qMax(255 - qRed(basePixel), qRed(basePixel))); + qreal green = (qGreen(pixel) - qGreen(basePixel)) / static_cast<float>(qMax(255 - qGreen(basePixel), qGreen(basePixel))); + qreal blue = (qBlue(pixel) - qBlue(basePixel)) / static_cast<float>(qMax(255 - qBlue(basePixel), qBlue(basePixel))); + qreal alpha = (qAlpha(pixel) - qAlpha(basePixel)) / static_cast<float>(qMax(255 - qAlpha(basePixel), qAlpha(basePixel))); + qreal distance = qSqrt(red * red + green * green + blue * blue + alpha * alpha) / 2.0f; + if (distance >= 1 / qreal(255)) { + errorCount++; + diffImage.setPixel(x, y, qRgb(255, 0, 0)); + } else + diffImage.setPixel(x, y, qRgba(qRed(basePixel), qGreen(basePixel), qBlue(basePixel), qAlpha(basePixel) * 0.5)); + } + } + + qreal difference = 0; + if (errorCount) + difference = 100 * errorCount / static_cast<qreal>(w * h); + + if (difference <= tolerance) + fprintf(stdout, "diff: %01.2f%% passed\n", difference); + else { + QBuffer buffer; + buffer.open(QBuffer::WriteOnly); + diffImage.save(&buffer, "PNG"); + buffer.close(); + const QByteArray &data = buffer.data(); + printf("Content-Length: %lu\n", static_cast<unsigned long>(data.length())); + + // We have to use the return value of fwrite to avoid "ignoring return value" gcc warning + // See https://bugs.webkit.org/show_bug.cgi?id=45384 for details. + if (fwrite(data.constData(), 1, data.length(), stdout)) {} + + fprintf(stdout, "diff: %01.2f%% failed\n", difference); + } + + fflush(stdout); + } + actualImage = QImage(); + baselineImage = QImage(); + } + } + + return 0; +} |