Replace use of OwnArrayPtr<Foo> with std::unique_ptr<Foo[]> in Tools
[WebKit-https.git] / Tools / ImageDiff / efl / ImageDiff.cpp
1 /*
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
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
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.
19  *
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.
30  */
31
32 #include "config.h"
33
34 #include <Ecore.h>
35 #include <Ecore_Evas.h>
36 #include <Evas.h>
37 #include <algorithm>
38 #include <cmath>
39 #include <cstdio>
40 #include <cstdlib>
41 #include <getopt.h>
42 #include <sys/stat.h>
43 #include <sys/types.h>
44 #include <unistd.h>
45 #include <wtf/OwnPtr.h>
46 #include <wtf/PassOwnPtr.h>
47 #include <wtf/PassRefPtr.h>
48 #include <wtf/StdLibExtras.h>
49 #include <wtf/efl/RefPtrEfl.h>
50
51 enum PixelComponent {
52     Red,
53     Green,
54     Blue,
55     Alpha
56 };
57
58 static OwnPtr<Ecore_Evas> gEcoreEvas;
59 static double gTolerance = 0;
60
61 static void abortWithErrorMessage(const char* errorMessage);
62
63 static unsigned char* pixelFromImageData(unsigned char* imageData, int rowStride, int x, int y)
64 {
65     return imageData + (y * rowStride) + (x << 2);
66 }
67
68 static Evas_Object* differenceImageFromDifferenceBuffer(Evas* evas, unsigned char* buffer, int width, int height)
69 {
70     Evas_Object* image = evas_object_image_filled_add(evas);
71     if (!image)
72         abortWithErrorMessage("could not create difference image");
73
74     evas_object_image_size_set(image, width, height);
75     evas_object_image_colorspace_set(image, EVAS_COLORSPACE_ARGB8888);
76
77     unsigned char* diffPixels = static_cast<unsigned char*>(evas_object_image_data_get(image, EINA_TRUE));
78     const int rowStride = evas_object_image_stride_get(image);
79     for (int x = 0; x < width; x++) {
80         for (int y = 0; y < height; y++) {
81             unsigned char* diffPixel = pixelFromImageData(diffPixels, rowStride, x, y);
82             diffPixel[Red] = diffPixel[Green] = diffPixel[Blue] = *buffer++;
83             diffPixel[Alpha] = 0xff;
84         }
85     }
86
87     evas_object_image_data_set(image, diffPixels);
88
89     return image;
90 }
91
92 static float computeDistanceBetweenPixelComponents(unsigned char actualComponent, unsigned char baseComponent)
93 {
94     return (actualComponent - baseComponent) / std::max<float>(255 - baseComponent, baseComponent);
95 }
96
97 static float computeDistanceBetweenPixelComponents(unsigned char* actualPixel, unsigned char* basePixel, PixelComponent component)
98 {
99     return computeDistanceBetweenPixelComponents(actualPixel[component], basePixel[component]);
100 }
101
102 static float calculatePixelDifference(unsigned char* basePixel, unsigned char* actualPixel)
103 {
104     const float red = computeDistanceBetweenPixelComponents(actualPixel, basePixel, Red);
105     const float green = computeDistanceBetweenPixelComponents(actualPixel, basePixel, Green);
106     const float blue = computeDistanceBetweenPixelComponents(actualPixel, basePixel, Blue);
107     const float alpha = computeDistanceBetweenPixelComponents(actualPixel, basePixel, Alpha);
108     return sqrtf(red * red + green * green + blue * blue + alpha * alpha) / 2.0f;
109 }
110
111 static float calculateDifference(Evas_Object* baselineImage, Evas_Object* actualImage, RefPtr<Evas_Object>& differenceImage)
112 {
113     int width, height, baselineWidth, baselineHeight;
114     evas_object_image_size_get(actualImage, &width, &height);
115     evas_object_image_size_get(baselineImage, &baselineWidth, &baselineHeight);
116
117     if (width != baselineWidth || height != baselineHeight) {
118         printf("Error, test and reference image have different sizes.\n");
119         return 100; // Completely different.
120     }
121
122     auto diffBuffer = std::make_unique<unsigned char[]>(width * height);
123     if (!diffBuffer)
124         abortWithErrorMessage("could not create difference buffer");
125
126     const int actualRowStride = evas_object_image_stride_get(actualImage);
127     const int baseRowStride = evas_object_image_stride_get(baselineImage);
128     unsigned numberOfDifferentPixels = 0;
129     float totalDistance = 0;
130     float maxDistance = 0;
131     unsigned char* actualPixels = static_cast<unsigned char*>(evas_object_image_data_get(actualImage, EINA_FALSE));
132     unsigned char* basePixels = static_cast<unsigned char*>(evas_object_image_data_get(baselineImage, EINA_FALSE));
133     unsigned char* currentDiffPixel = diffBuffer.get();
134
135     for (int x = 0; x < width; x++) {
136         for (int y = 0; y < height; y++) {
137             unsigned char* actualPixel = pixelFromImageData(actualPixels, actualRowStride, x, y);
138             unsigned char* basePixel = pixelFromImageData(basePixels, baseRowStride, x, y);
139
140             const float distance = calculatePixelDifference(basePixel, actualPixel);
141             *currentDiffPixel++ = static_cast<unsigned char>(distance * 255.0f);
142
143             if (distance >= 1.0f / 255.0f) {
144                 ++numberOfDifferentPixels;
145                 totalDistance += distance;
146                 maxDistance = std::max<float>(maxDistance, distance);
147             }
148         }
149     }
150
151     // When using evas_object_image_data_get(), a complementary evas_object_data_set() must be
152     // issued to balance the reference count, even if the image hasn't been changed.
153     evas_object_image_data_set(baselineImage, basePixels);
154     evas_object_image_data_set(actualImage, actualPixels);
155
156     // Compute the difference as a percentage combining both the number of
157     // different pixels and their difference amount i.e. the average distance
158     // over the entire image
159     float difference = 0;
160     if (numberOfDifferentPixels)
161         difference = 100.0f * totalDistance / (height * width);
162     if (difference <= gTolerance)
163         difference = 0;
164     else {
165         difference = roundf(difference * 100.0f) / 100.0f;
166         difference = std::max(difference, 0.01f); // round to 2 decimal places
167
168         differenceImage = adoptRef(differenceImageFromDifferenceBuffer(evas_object_evas_get(baselineImage), diffBuffer.get(), width, height));
169     }
170
171     return difference;
172 }
173
174 static int getTemporaryFile(char *fileName, size_t fileNameLength)
175 {
176     char* tempDirectory = getenv("TMPDIR");
177     if (!tempDirectory)
178         tempDirectory = getenv("TEMP");
179
180     if (tempDirectory)
181         snprintf(fileName, fileNameLength, "%s/ImageDiffXXXXXX.png", tempDirectory);
182     else {
183 #if __linux__
184         strcpy(fileName, "/dev/shm/ImageDiffXXXXXX.png");
185         const int fileDescriptor = mkstemps(fileName, sizeof(".png") - 1);
186         if (fileDescriptor >= 0)
187             return fileDescriptor;
188 #endif // __linux__
189
190         strcpy(fileName, "ImageDiffXXXXXX.png");
191     }
192
193     return mkstemps(fileName, sizeof(".png") - 1);
194 }
195
196 static void printImage(Evas_Object* image)
197 {
198     char fileName[PATH_MAX];
199
200     const int tempImageFd = getTemporaryFile(fileName, PATH_MAX);
201     if (tempImageFd == -1)
202         abortWithErrorMessage("could not create temporary file");
203
204     evas_render(evas_object_evas_get(image));
205
206     if (evas_object_image_save(image, fileName, 0, 0)) {
207         struct stat fileInfo;
208         if (!stat(fileName, &fileInfo)) {
209             printf("Content-Length: %ld\n", fileInfo.st_size);
210             fflush(stdout);
211
212             unsigned char buffer[2048];
213             ssize_t bytesRead;
214             while ((bytesRead = read(tempImageFd, buffer, sizeof(buffer))) > 0) {
215                 ssize_t bytesWritten = 0;
216                 ssize_t count;
217                 do {
218                     if ((count = write(1, buffer + bytesWritten, bytesRead - bytesWritten)) <= 0)
219                         break;
220                     bytesWritten += count;
221                 } while (bytesWritten < bytesRead);
222             }
223         }
224     }
225     close(tempImageFd);
226     unlink(fileName);
227 }
228
229 static void printImageDifferences(Evas_Object* baselineImage, Evas_Object* actualImage)
230 {
231     RefPtr<Evas_Object> differenceImage;
232     const float difference = calculateDifference(baselineImage, actualImage, differenceImage);
233
234     if (difference > 0.0f) {
235         if (differenceImage)
236             printImage(differenceImage.get());
237
238         printf("diff: %01.2f%% failed\n", difference);
239     } else
240         printf("diff: %01.2f%% passed\n", difference);
241 }
242
243 static void resizeEcoreEvasIfNeeded(Evas_Object* image)
244 {
245     int newWidth, newHeight;
246     evas_object_image_size_get(image, &newWidth, &newHeight);
247
248     int currentWidth, currentHeight;
249     ecore_evas_screen_geometry_get(gEcoreEvas.get(), 0, 0, &currentWidth, &currentHeight);
250
251     if (newWidth > currentWidth)
252         currentWidth = newWidth;
253     if (newHeight > currentHeight)
254         currentHeight = newHeight;
255
256     ecore_evas_resize(gEcoreEvas.get(), currentWidth, currentHeight);
257 }
258
259 static PassRefPtr<Evas_Object> readImageFromStdin(Evas* evas, long imageSize)
260 {
261     auto imageBuffer = std::make_unique<unsigned char[]>(imageSize);
262     if (!imageBuffer)
263         abortWithErrorMessage("cannot allocate image");
264
265     const size_t bytesRead = fread(imageBuffer.get(), 1, imageSize, stdin);
266     if (!bytesRead)
267         return PassRefPtr<Evas_Object>();
268
269     Evas_Object* image = evas_object_image_filled_add(evas);
270     evas_object_image_colorspace_set(image, EVAS_COLORSPACE_ARGB8888);
271     evas_object_image_memfile_set(image, imageBuffer.get(), bytesRead, 0, 0);
272
273     resizeEcoreEvasIfNeeded(image);
274
275     return adoptRef(image);
276 }
277
278 static bool parseCommandLineOptions(int argc, char** argv)
279 {
280     static const option options[] = {
281         { "tolerance", required_argument, 0, 't' },
282         { 0, 0, 0, 0 }
283     };
284     int option;
285
286     while ((option = getopt_long(argc, (char* const*)argv, "t:", options, 0)) != -1) {
287         switch (option) {
288         case 't':
289             gTolerance = atof(optarg);
290             break;
291         case '?':
292         case ':':
293             return false;
294         }
295     }
296
297     return true;
298 }
299
300 static void shutdownEfl()
301 {
302     ecore_evas_shutdown();
303     ecore_shutdown();
304     evas_shutdown();
305 }
306
307 static void abortWithErrorMessage(const char* errorMessage)
308 {
309     shutdownEfl();
310
311     printf("Error, %s.\n", errorMessage);
312     exit(EXIT_FAILURE);
313 }
314
315 static Evas* initEfl()
316 {
317     evas_init();
318     ecore_init();
319     ecore_evas_init();
320
321     gEcoreEvas = adoptPtr(ecore_evas_buffer_new(1, 1));
322     Evas* evas = ecore_evas_get(gEcoreEvas.get());
323     if (!evas)
324         abortWithErrorMessage("could not create Ecore_Evas buffer");
325
326     return evas;
327 }
328
329 int main(int argc, char* argv[])
330 {
331     if (!parseCommandLineOptions(argc, argv))
332         return EXIT_FAILURE;
333
334     Evas* evas = initEfl();
335
336     RefPtr<Evas_Object> actualImage;
337     RefPtr<Evas_Object> baselineImage;
338
339     char buffer[2048];
340     while (fgets(buffer, sizeof(buffer), stdin)) {
341         char* contentLengthStart = strstr(buffer, "Content-Length: ");
342         if (!contentLengthStart)
343             continue;
344         long imageSize;
345         if (sscanf(contentLengthStart, "Content-Length: %ld", &imageSize) == 1) {
346             if (imageSize <= 0)
347                 abortWithErrorMessage("image size must be specified");
348
349             if (!actualImage)
350                 actualImage = readImageFromStdin(evas, imageSize);
351             else if (!baselineImage) {
352                 baselineImage = readImageFromStdin(evas, imageSize);
353
354                 printImageDifferences(baselineImage.get(), actualImage.get());
355
356                 actualImage.clear();
357                 baselineImage.clear();
358             }
359         }
360
361         fflush(stdout);
362     }
363
364     gEcoreEvas.clear(); // Make sure ecore_evas_free is called before the EFL are shut down
365
366     shutdownEfl();
367     return EXIT_SUCCESS;
368 }