1/* 2 * Copyright 2013 Google Inc. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8// TODO(djsollen): Rename this whole package (perhaps to "SkMultiDiffer"). 9// It's not just for "pdiff" (perceptual diffs)--it's a harness that allows 10// the execution of an arbitrary set of difference algorithms. 11// See http://skbug.com/2711 ('rename skpdiff') 12 13#include "SkTypes.h" 14 15#if SK_SUPPORT_OPENCL 16 17#define __NO_STD_VECTOR // Uses cl::vectpr instead of std::vectpr 18#define __NO_STD_STRING // Uses cl::STRING_CLASS instead of std::string 19#if defined(SK_BUILD_FOR_MAC) 20// Note that some macs don't have this header and it can be downloaded from the Khronos registry 21# include <OpenCL/cl.hpp> 22#else 23# include <CL/cl.hpp> 24#endif 25 26#endif 27 28#include "SkCommandLineFlags.h" 29#include "SkGraphics.h" 30#include "SkStream.h" 31#include "SkTDArray.h" 32#include "SkTaskGroup.h" 33 34#include "SkDifferentPixelsMetric.h" 35#include "SkDiffContext.h" 36#include "SkImageDiffer.h" 37#include "SkPMetric.h" 38#include "skpdiff_util.h" 39 40#include "SkForceLinking.h" 41__SK_FORCE_IMAGE_DECODER_LINKING; 42 43// Command line argument definitions go here 44DEFINE_bool2(list, l, false, "List out available differs"); 45DEFINE_string2(differs, d, "", "The names of the differs to use or all of them by default"); 46DEFINE_string2(folders, f, "", "Compare two folders with identical subfile names: <baseline folder> <test folder>"); 47DEFINE_string2(patterns, p, "", "Use two patterns to compare images: <baseline> <test>"); 48DEFINE_string2(output, o, "", "Writes a JSON summary of these diffs to file: <filepath>"); 49DEFINE_string(alphaDir, "", "If the differ can generate an alpha mask, write it into directory: <dirpath>"); 50DEFINE_string(rgbDiffDir, "", "If the differ can generate an image showing the RGB diff at each pixel, write it into directory: <dirpath>"); 51DEFINE_string(whiteDiffDir, "", "If the differ can generate an image showing every changed pixel in white, write it into directory: <dirpath>"); 52DEFINE_bool(jsonp, true, "Output JSON with padding"); 53DEFINE_string(csv, "", "Writes the output of these diffs to a csv file: <filepath>"); 54DEFINE_int32(threads, -1, "run N threads in parallel [default is derived from CPUs available]"); 55DEFINE_bool(longnames, false, "Output image names are a combination of baseline and test names"); 56 57#if SK_SUPPORT_OPENCL 58/// A callback for any OpenCL errors 59static void CL_CALLBACK error_notify(const char* errorInfo, const void* privateInfoSize, ::size_t cb, void* userData) { 60 SkDebugf("OpenCL error notify: %s\n", errorInfo); 61 exit(1); 62} 63 64/// Creates a device and context with OpenCL 65static bool init_device_and_context(cl::Device* device, cl::Context* context) { 66 // Query for a platform 67 cl::vector<cl::Platform> platformList; 68 cl::Platform::get(&platformList); 69 SkDebugf("The number of platforms is %u\n", platformList.size()); 70 71 // Print some information about the platform for debugging 72 cl::Platform& platform = platformList[0]; 73 cl::STRING_CLASS platformName; 74 platform.getInfo(CL_PLATFORM_NAME, &platformName); 75 SkDebugf("Platform index 0 is named %s\n", platformName.c_str()); 76 77 // Query for a device 78 cl::vector<cl::Device> deviceList; 79 platform.getDevices(CL_DEVICE_TYPE_ALL, &deviceList); 80 SkDebugf("The number of devices is %u\n", deviceList.size()); 81 82 // Print some information about the device for debugging 83 *device = deviceList[0]; 84 cl::STRING_CLASS deviceName; 85 device->getInfo(CL_DEVICE_NAME, &deviceName); 86 SkDebugf("Device index 0 is named %s\n", deviceName.c_str()); 87 88 // Create a CL context and check for all errors 89 cl_int contextErr = CL_SUCCESS; 90 *context = cl::Context(deviceList, NULL, error_notify, NULL, &contextErr); 91 if (contextErr != CL_SUCCESS) { 92 SkDebugf("Context creation failed: %s\n", cl_error_to_string(contextErr)); 93 return false; 94 } 95 96 return true; 97} 98 99static bool init_cl_diff(SkImageDiffer* differ) { 100 // Setup OpenCL 101 cl::Device device; 102 cl::Context context; 103 if (!init_device_and_context(&device, &context)) { 104 return false; 105 } 106 107 // Setup our differ of choice 108 SkCLImageDiffer* clDiffer = (SkCLImageDiffer*)differ; 109 return clDiffer->init(device(), context()); 110} 111#endif 112 113// TODO Find a better home for the diff registry. One possibility is to have the differs self 114// register. 115 116// List here every differ 117SkDifferentPixelsMetric gDiffPixel; 118SkPMetric gPDiff; 119 120// A null terminated array of pointer to every differ declared above 121SkImageDiffer* gDiffers[] = { &gDiffPixel, &gPDiff, NULL }; 122 123int tool_main(int argc, char * argv[]); 124int tool_main(int argc, char * argv[]) { 125 // Setup command line parsing 126 SkCommandLineFlags::SetUsage("Compare images using various metrics."); 127 SkCommandLineFlags::Parse(argc, argv); 128 129 // Needed by various Skia components 130 SkAutoGraphics ag; 131 SkTaskGroup::Enabler enabled; 132 133 if (FLAGS_list) { 134 SkDebugf("Available Metrics:\n"); 135 } 136 137 // Figure which differs the user chose, and optionally print them if the user requests it 138 SkTDArray<SkImageDiffer*> chosenDiffers; 139 for (int differIndex = 0; gDiffers[differIndex]; differIndex++) { 140 SkImageDiffer* differ = gDiffers[differIndex]; 141 if (FLAGS_list) { 142 SkDebugf(" %s", differ->getName()); 143 SkDebugf("\n"); 144 } 145 146 // Check if this differ was chosen by any of the flags. Initialize them if they were chosen. 147 if (FLAGS_differs.isEmpty()) { 148 // If no differs were chosen, they all get added 149 if (differ->requiresOpenCL()) { 150#if SK_SUPPORT_OPENCL 151 init_cl_diff(differ); 152 chosenDiffers.push(differ); 153#endif 154 } else { 155 chosenDiffers.push(differ); 156 } 157 } else { 158 for (int flagIndex = 0; flagIndex < FLAGS_differs.count(); flagIndex++) { 159 if (SkString(FLAGS_differs[flagIndex]).equals(differ->getName())) { 160 // Initialize OpenCL for the differ if it needs it and support was compiled in. 161 if (differ->requiresOpenCL()) { 162#if SK_SUPPORT_OPENCL 163 init_cl_diff(differ); 164 chosenDiffers.push(differ); 165#endif 166 } else { 167 chosenDiffers.push(differ); 168 } 169 break; 170 } 171 } 172 } 173 } 174 175 // Don't attempt to initialize the differ if we aren't going to use it 176 if (FLAGS_folders.isEmpty() && FLAGS_patterns.isEmpty()) { 177 return 0; 178 } 179 180 // Validate command line flags 181 if (!FLAGS_folders.isEmpty()) { 182 if (2 != FLAGS_folders.count()) { 183 SkDebugf("Folders flag expects two arguments: <baseline folder> <test folder>\n"); 184 return 1; 185 } 186 } 187 188 if (!FLAGS_patterns.isEmpty()) { 189 if (2 != FLAGS_patterns.count()) { 190 SkDebugf("Patterns flag expects two arguments: <baseline pattern> <test pattern>\n"); 191 return 1; 192 } 193 } 194 195 if (!FLAGS_csv.isEmpty()) { 196 if (1 != FLAGS_csv.count()) { 197 SkDebugf("csv flag expects one argument: <csv file>\n"); 198 return 1; 199 } 200 } 201 202 if (!FLAGS_alphaDir.isEmpty()) { 203 if (1 != FLAGS_alphaDir.count()) { 204 SkDebugf("alphaDir flag expects one argument: <directory>\n"); 205 return 1; 206 } 207 } 208 if (!FLAGS_rgbDiffDir.isEmpty()) { 209 if (1 != FLAGS_rgbDiffDir.count()) { 210 SkDebugf("rgbDiffDir flag expects one argument: <directory>\n"); 211 return 1; 212 } 213 } 214 215 if (!FLAGS_whiteDiffDir.isEmpty()) { 216 if (1 != FLAGS_whiteDiffDir.count()) { 217 SkDebugf("whiteDiffDir flag expects one argument: <directory>\n"); 218 return 1; 219 } 220 } 221 222 SkDiffContext ctx; 223 ctx.setDiffers(chosenDiffers); 224 ctx.setLongNames(FLAGS_longnames); 225 226 if (!FLAGS_alphaDir.isEmpty()) { 227 ctx.setAlphaMaskDir(SkString(FLAGS_alphaDir[0])); 228 } 229 if (!FLAGS_rgbDiffDir.isEmpty()) { 230 ctx.setRgbDiffDir(SkString(FLAGS_rgbDiffDir[0])); 231 } 232 if (!FLAGS_whiteDiffDir.isEmpty()) { 233 ctx.setWhiteDiffDir(SkString(FLAGS_whiteDiffDir[0])); 234 } 235 236 if (FLAGS_threads >= 0) { 237 ctx.setThreadCount(FLAGS_threads); 238 } 239 240 // Perform a folder diff if one is requested 241 if (!FLAGS_folders.isEmpty()) { 242 ctx.diffDirectories(FLAGS_folders[0], FLAGS_folders[1]); 243 } 244 245 // Perform a pattern diff if one is requested 246 if (!FLAGS_patterns.isEmpty()) { 247 ctx.diffPatterns(FLAGS_patterns[0], FLAGS_patterns[1]); 248 } 249 250 // Output to the file specified 251 if (!FLAGS_output.isEmpty()) { 252 SkFILEWStream outputStream(FLAGS_output[0]); 253 ctx.outputRecords(outputStream, FLAGS_jsonp); 254 } 255 256 if (!FLAGS_csv.isEmpty()) { 257 SkFILEWStream outputStream(FLAGS_csv[0]); 258 ctx.outputCsv(outputStream); 259 } 260 261 return 0; 262} 263 264#if !defined(SK_BUILD_FOR_IOS) 265int main(int argc, char * argv[]) { 266 return tool_main(argc, (char**) argv); 267} 268#endif 269