2 * Copyright (C) 2009 Zan Dobersek <zandobersek@gmail.com>
3 * Copyright (C) 2010 Igalia S.L.
4 * Copyright (C) 2011 ProFUSION Embedded Systems
5 * Copyright (C) 2011 Samsung Electronics
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
17 * its contributors may be used to endorse or promote products derived
18 * from this software without specific prior written permission.
20 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
21 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
24 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 #include <Ecore_Evas.h>
43 #include <sys/types.h>
45 #include <wtf/OwnArrayPtr.h>
46 #include <wtf/OwnPtr.h>
47 #include <wtf/PassOwnPtr.h>
56 static OwnPtr<Ecore_Evas> gEcoreEvas;
57 static double gTolerance = 0;
59 static void abortWithErrorMessage(const char* errorMessage);
61 static unsigned char* pixelFromImageData(unsigned char* imageData, int rowStride, int x, int y)
63 return imageData + (y * rowStride) + (x << 2);
66 static Evas_Object* differenceImageFromDifferenceBuffer(Evas* evas, unsigned char* buffer, int width, int height)
68 Evas_Object* image = evas_object_image_filled_add(evas);
70 abortWithErrorMessage("could not create difference image");
72 evas_object_image_size_set(image, width, height);
73 evas_object_image_colorspace_set(image, EVAS_COLORSPACE_ARGB8888);
75 unsigned char* diffPixels = static_cast<unsigned char*>(evas_object_image_data_get(image, EINA_TRUE));
76 const int rowStride = evas_object_image_stride_get(image);
77 for (int x = 0; x < width; x++) {
78 for (int y = 0; y < height; y++) {
79 unsigned char* diffPixel = pixelFromImageData(diffPixels, rowStride, x, y);
80 diffPixel[Red] = diffPixel[Green] = diffPixel[Blue] = *buffer++;
81 diffPixel[Alpha] = 0xff;
88 static float computeDistanceBetweenPixelComponents(unsigned char actualComponent, unsigned char baseComponent)
90 return (actualComponent - baseComponent) / std::max<float>(255 - baseComponent, baseComponent);
93 static float computeDistanceBetweenPixelComponents(unsigned char* actualPixel, unsigned char* basePixel, PixelComponent component)
95 return computeDistanceBetweenPixelComponents(actualPixel[component], basePixel[component]);
98 static float calculatePixelDifference(unsigned char* basePixel, unsigned char* actualPixel)
100 const float red = computeDistanceBetweenPixelComponents(actualPixel, basePixel, Red);
101 const float green = computeDistanceBetweenPixelComponents(actualPixel, basePixel, Green);
102 const float blue = computeDistanceBetweenPixelComponents(actualPixel, basePixel, Blue);
103 const float alpha = computeDistanceBetweenPixelComponents(actualPixel, basePixel, Alpha);
104 return sqrtf(red * red + green * green + blue * blue + alpha * alpha) / 2.0f;
107 static float calculateDifference(const Evas_Object* baselineImage, const Evas_Object* actualImage, OwnPtr<Evas_Object>& differenceImage)
109 int width, height, baselineWidth, baselineHeight;
110 evas_object_image_size_get(actualImage, &width, &height);
111 evas_object_image_size_get(baselineImage, &baselineWidth, &baselineHeight);
113 if (width != baselineWidth || height != baselineHeight) {
114 printf("Error, test and reference image have different sizes.\n");
115 return 100; // Completely different.
118 OwnArrayPtr<unsigned char> diffBuffer = adoptArrayPtr(new unsigned char[width * height]);
120 abortWithErrorMessage("could not create difference buffer");
122 const int actualRowStride = evas_object_image_stride_get(actualImage);
123 const int baseRowStride = evas_object_image_stride_get(baselineImage);
124 unsigned numberOfDifferentPixels = 0;
125 float totalDistance = 0;
126 float maxDistance = 0;
127 unsigned char* actualPixels = static_cast<unsigned char*>(evas_object_image_data_get(actualImage, EINA_FALSE));
128 unsigned char* basePixels = static_cast<unsigned char*>(evas_object_image_data_get(baselineImage, EINA_FALSE));
129 unsigned char* currentDiffPixel = diffBuffer.get();
131 for (int x = 0; x < width; x++) {
132 for (int y = 0; y < height; y++) {
133 unsigned char* actualPixel = pixelFromImageData(actualPixels, actualRowStride, x, y);
134 unsigned char* basePixel = pixelFromImageData(basePixels, baseRowStride, x, y);
136 const float distance = calculatePixelDifference(basePixel, actualPixel);
137 *currentDiffPixel++ = static_cast<unsigned char>(distance * 255.0f);
139 if (distance >= 1.0f / 255.0f) {
140 ++numberOfDifferentPixels;
141 totalDistance += distance;
142 maxDistance = std::max<float>(maxDistance, distance);
147 // Compute the difference as a percentage combining both the number of
148 // different pixels and their difference amount i.e. the average distance
149 // over the entire image
150 float difference = 0;
151 if (numberOfDifferentPixels)
152 difference = 100.0f * totalDistance / (height * width);
153 if (difference <= gTolerance)
156 difference = roundf(difference * 100.0f) / 100.0f;
157 difference = std::max(difference, 0.01f); // round to 2 decimal places
159 differenceImage = adoptPtr(differenceImageFromDifferenceBuffer(evas_object_evas_get(baselineImage), diffBuffer.get(), width, height));
165 static int getTemporaryFile(char *fileName, size_t fileNameLength)
167 char* tempDirectory = getenv("TMPDIR");
169 tempDirectory = getenv("TEMP");
172 snprintf(fileName, fileNameLength, "%s/ImageDiffXXXXXX.png", tempDirectory);
175 strcpy(fileName, "/dev/shm/ImageDiffXXXXXX.png");
176 const int fileDescriptor = mkstemps(fileName, sizeof(".png") - 1);
177 if (fileDescriptor >= 0)
178 return fileDescriptor;
181 strcpy(fileName, "ImageDiffXXXXXX.png");
184 return mkstemps(fileName, sizeof(".png") - 1);
187 static void printImage(Evas_Object* image)
189 char fileName[PATH_MAX];
191 const int tempImageFd = getTemporaryFile(fileName, PATH_MAX);
192 if (tempImageFd == -1)
193 abortWithErrorMessage("could not create temporary file");
195 if (evas_object_image_save(image, fileName, 0, 0)) {
196 struct stat fileInfo;
197 if (!stat(fileName, &fileInfo)) {
198 printf("Content-Length: %ld\n", fileInfo.st_size);
201 unsigned char buffer[2048];
203 while ((bytesRead = read(tempImageFd, buffer, sizeof(buffer))) > 0)
204 write(1, buffer, bytesRead);
211 static void printImageDifferences(const Evas_Object* baselineImage, Evas_Object* actualImage)
213 OwnPtr<Evas_Object> differenceImage;
214 const float difference = calculateDifference(baselineImage, actualImage, differenceImage);
216 if (difference > 0.0f) {
218 printImage(differenceImage.get());
220 printf("diff: %01.2f%% failed\n", difference);
222 printf("diff: %01.2f%% passed\n", difference);
225 static void resizeEcoreEvasIfNeeded(Evas_Object* image)
227 int newWidth, newHeight;
228 evas_object_image_size_get(image, &newWidth, &newHeight);
230 int currentWidth, currentHeight;
231 ecore_evas_screen_geometry_get(gEcoreEvas.get(), 0, 0, ¤tWidth, ¤tHeight);
233 if (newWidth > currentWidth)
234 currentWidth = newWidth;
235 if (newHeight > currentHeight)
236 currentHeight = newHeight;
238 ecore_evas_resize(gEcoreEvas.get(), currentWidth, currentHeight);
241 static PassOwnPtr<Evas_Object> readImageFromStdin(Evas* evas, long imageSize)
243 OwnArrayPtr<unsigned char> imageBuffer = adoptArrayPtr(new unsigned char[imageSize]);
245 abortWithErrorMessage("cannot allocate image");
247 const size_t bytesRead = fread(imageBuffer.get(), 1, imageSize, stdin);
249 return PassOwnPtr<Evas_Object>();
251 Evas_Object* image = evas_object_image_filled_add(evas);
252 evas_object_image_memfile_set(image, imageBuffer.get(), bytesRead, 0, 0);
253 evas_object_image_colorspace_set(image, EVAS_COLORSPACE_ARGB8888);
255 resizeEcoreEvasIfNeeded(image);
257 return adoptPtr(image);
260 static bool parseCommandLineOptions(int argc, char** argv)
262 static const option options[] = {
263 { "tolerance", required_argument, 0, 't' },
268 while ((option = getopt_long(argc, (char* const*)argv, "t:", options, 0)) != -1) {
271 gTolerance = atof(optarg);
282 static void shutdownEfl()
284 ecore_evas_shutdown();
289 static void abortWithErrorMessage(const char* errorMessage)
293 printf("Error, %s.\n", errorMessage);
297 static Evas* initEfl()
303 gEcoreEvas = adoptPtr(ecore_evas_buffer_new(1, 1));
304 Evas* evas = ecore_evas_get(gEcoreEvas.get());
306 abortWithErrorMessage("could not create Ecore_Evas buffer");
311 int main(int argc, char* argv[])
313 if (!parseCommandLineOptions(argc, argv))
316 Evas* evas = initEfl();
318 OwnPtr<Evas_Object> actualImage;
319 OwnPtr<Evas_Object> baselineImage;
322 while (fgets(buffer, sizeof(buffer), stdin)) {
323 char* contentLengthStart = strstr(buffer, "Content-Length: ");
324 if (!contentLengthStart)
327 if (sscanf(contentLengthStart, "Content-Length: %ld", &imageSize) == 1) {
329 abortWithErrorMessage("image size must be specified");
332 actualImage = readImageFromStdin(evas, imageSize);
333 else if (!baselineImage) {
334 baselineImage = readImageFromStdin(evas, imageSize);
336 printImageDifferences(baselineImage.get(), actualImage.get());
339 baselineImage.clear();
346 gEcoreEvas.clear(); // Make sure ecore_evas_free is called before the EFL are shut down