summaryrefslogtreecommitdiff
path: root/Tools/ImageDiff
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@digia.com>2013-09-13 12:51:20 +0200
committerThe Qt Project <gerrit-noreply@qt-project.org>2013-09-19 20:50:05 +0200
commitd441d6f39bb846989d95bcf5caf387b42414718d (patch)
treee367e64a75991c554930278175d403c072de6bb8 /Tools/ImageDiff
parent0060b2994c07842f4c59de64b5e3e430525c4b90 (diff)
downloadqtwebkit-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.txt17
-rw-r--r--Tools/ImageDiff/ImageDiff.pro14
-rw-r--r--Tools/ImageDiff/PlatformEfl.cmake19
-rw-r--r--Tools/ImageDiff/efl/ImageDiff.cpp368
-rw-r--r--Tools/ImageDiff/gtk/ImageDiff.cpp236
-rw-r--r--Tools/ImageDiff/qt/ImageDiff.cpp144
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, &currentWidth, &currentHeight);
+
+ 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;
+}