render_pictures_main.cpp revision 5fb2ce38b3dcb8e60e9e112df23c9d42456d7069
1/* 2 * Copyright 2012 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#include "LazyDecodeBitmap.h" 9#include "CopyTilesRenderer.h" 10#include "SkBitmap.h" 11#include "SkDevice.h" 12#include "SkCommandLineFlags.h" 13#include "SkGraphics.h" 14#include "SkImageDecoder.h" 15#include "SkImageEncoder.h" 16#include "SkMath.h" 17#include "SkOSFile.h" 18#include "SkPicture.h" 19#include "SkStream.h" 20#include "SkString.h" 21#include "PictureRenderer.h" 22#include "PictureRenderingFlags.h" 23#include "picture_utils.h" 24 25// Flags used by this file, alphabetically: 26DEFINE_int32(clone, 0, "Clone the picture n times before rendering."); 27DECLARE_bool(deferImageDecoding); 28DEFINE_int32(maxComponentDiff, 256, "Maximum diff on a component, 0 - 256. Components that differ " 29 "by more than this amount are considered errors, though all diffs are reported. " 30 "Requires --validate."); 31DECLARE_string(readPath); 32DEFINE_bool(writeChecksumBasedFilenames, false, 33 "When writing out images, use checksum-based filenames."); 34DEFINE_bool(writeEncodedImages, false, "Any time the skp contains an encoded image, write it to a " 35 "file rather than decoding it. Requires writePath to be set. Skips drawing the full " 36 "skp to a file. Not compatible with deferImageDecoding."); 37DEFINE_string(writeJsonSummaryPath, "", "File to write a JSON summary of image results to. " 38 "TODO(epoger): Currently, this only works if --writePath is also specified. " 39 "See https://code.google.com/p/skia/issues/detail?id=2043 ."); 40DEFINE_string2(writePath, w, "", "Directory to write the rendered images."); 41DEFINE_bool(writeWholeImage, false, "In tile mode, write the entire rendered image to a " 42 "file, instead of an image for each tile."); 43DEFINE_bool(validate, false, "Verify that the rendered image contains the same pixels as " 44 "the picture rendered in simple mode. When used in conjunction with --bbh, results " 45 "are validated against the picture rendered in the same mode, but without the bbh."); 46 47DEFINE_bool(bench_record, false, "If true, drop into an infinite loop of recording the picture."); 48 49DEFINE_bool(preprocess, false, "If true, perform device specific preprocessing before rendering."); 50 51//////////////////////////////////////////////////////////////////////////////////////////////////// 52 53/** 54 * Table for translating from format of data to a suffix. 55 */ 56struct Format { 57 SkImageDecoder::Format fFormat; 58 const char* fSuffix; 59}; 60static const Format gFormats[] = { 61 { SkImageDecoder::kBMP_Format, ".bmp" }, 62 { SkImageDecoder::kGIF_Format, ".gif" }, 63 { SkImageDecoder::kICO_Format, ".ico" }, 64 { SkImageDecoder::kJPEG_Format, ".jpg" }, 65 { SkImageDecoder::kPNG_Format, ".png" }, 66 { SkImageDecoder::kWBMP_Format, ".wbmp" }, 67 { SkImageDecoder::kWEBP_Format, ".webp" }, 68 { SkImageDecoder::kUnknown_Format, "" }, 69}; 70 71/** 72 * Get an appropriate suffix for an image format. 73 */ 74static const char* get_suffix_from_format(SkImageDecoder::Format format) { 75 for (size_t i = 0; i < SK_ARRAY_COUNT(gFormats); i++) { 76 if (gFormats[i].fFormat == format) { 77 return gFormats[i].fSuffix; 78 } 79 } 80 return ""; 81} 82 83/** 84 * Base name for an image file created from the encoded data in an skp. 85 */ 86static SkString gInputFileName; 87 88/** 89 * Number to be appended to the image file name so that it is unique. 90 */ 91static uint32_t gImageNo; 92 93/** 94 * Set up the name for writing encoded data to a file. 95 * Sets gInputFileName to name, minus any extension ".*" 96 * Sets gImageNo to 0, so images from file "X.skp" will 97 * look like "X_<gImageNo>.<suffix>", beginning with 0 98 * for each new skp. 99 */ 100static void reset_image_file_base_name(const SkString& name) { 101 gImageNo = 0; 102 // Remove ".skp" 103 const char* cName = name.c_str(); 104 const char* dot = strrchr(cName, '.'); 105 if (dot != NULL) { 106 gInputFileName.set(cName, dot - cName); 107 } else { 108 gInputFileName.set(name); 109 } 110} 111 112/** 113 * Write the raw encoded bitmap data to a file. 114 */ 115static bool write_image_to_file(const void* buffer, size_t size, SkBitmap* bitmap) { 116 SkASSERT(!FLAGS_writePath.isEmpty()); 117 SkMemoryStream memStream(buffer, size); 118 SkString outPath; 119 SkImageDecoder::Format format = SkImageDecoder::GetStreamFormat(&memStream); 120 SkString name = SkStringPrintf("%s_%d%s", gInputFileName.c_str(), gImageNo++, 121 get_suffix_from_format(format)); 122 SkString dir(FLAGS_writePath[0]); 123 sk_tools::make_filepath(&outPath, dir, name); 124 SkFILEWStream fileStream(outPath.c_str()); 125 if (!(fileStream.isValid() && fileStream.write(buffer, size))) { 126 SkDebugf("Failed to write encoded data to \"%s\"\n", outPath.c_str()); 127 } 128 // Put in a dummy bitmap. 129 return SkImageDecoder::DecodeStream(&memStream, bitmap, SkBitmap::kNo_Config, 130 SkImageDecoder::kDecodeBounds_Mode); 131} 132 133//////////////////////////////////////////////////////////////////////////////////////////////////// 134 135/** 136 * Called only by render_picture(). 137 */ 138static bool render_picture_internal(const SkString& inputPath, const SkString* outputDir, 139 sk_tools::PictureRenderer& renderer, 140 SkBitmap** out) { 141 SkString inputFilename; 142 sk_tools::get_basename(&inputFilename, inputPath); 143 SkString outputDirString; 144 if (NULL != outputDir && outputDir->size() > 0 && !FLAGS_writeEncodedImages) { 145 outputDirString.set(*outputDir); 146 } 147 148 SkFILEStream inputStream; 149 inputStream.setPath(inputPath.c_str()); 150 if (!inputStream.isValid()) { 151 SkDebugf("Could not open file %s\n", inputPath.c_str()); 152 return false; 153 } 154 155 SkPicture::InstallPixelRefProc proc; 156 if (FLAGS_deferImageDecoding) { 157 proc = &sk_tools::LazyDecodeBitmap; 158 } else if (FLAGS_writeEncodedImages) { 159 SkASSERT(!FLAGS_writePath.isEmpty()); 160 reset_image_file_base_name(inputFilename); 161 proc = &write_image_to_file; 162 } else { 163 proc = &SkImageDecoder::DecodeMemory; 164 } 165 166 SkDebugf("deserializing... %s\n", inputPath.c_str()); 167 168 SkPicture* picture = SkPicture::CreateFromStream(&inputStream, proc); 169 170 if (NULL == picture) { 171 SkDebugf("Could not read an SkPicture from %s\n", inputPath.c_str()); 172 return false; 173 } 174 175 while (FLAGS_bench_record) { 176 SkPictureRecorder recorder; 177 picture->draw(recorder.beginRecording(picture->width(), picture->height(), NULL, 0)); 178 SkAutoTUnref<SkPicture> other(recorder.endRecording()); 179 } 180 181 for (int i = 0; i < FLAGS_clone; ++i) { 182 SkPicture* clone = picture->clone(); 183 SkDELETE(picture); 184 picture = clone; 185 } 186 187 SkDebugf("drawing... [%i %i] %s\n", picture->width(), picture->height(), 188 inputPath.c_str()); 189 190 renderer.init(picture, &outputDirString, &inputFilename, FLAGS_writeChecksumBasedFilenames); 191 192 if (FLAGS_preprocess) { 193 if (NULL != renderer.getCanvas()) { 194 renderer.getCanvas()->EXPERIMENTAL_optimize(renderer.getPicture()); 195 } 196 } 197 198 renderer.setup(); 199 200 bool success = renderer.render(out); 201 if (!success) { 202 SkDebugf("Failed to render %s\n", inputFilename.c_str()); 203 } 204 205 renderer.end(); 206 207 SkDELETE(picture); 208 return success; 209} 210 211static inline int getByte(uint32_t value, int index) { 212 SkASSERT(0 <= index && index < 4); 213 return (value >> (index * 8)) & 0xFF; 214} 215 216static int MaxByteDiff(uint32_t v1, uint32_t v2) { 217 return SkMax32(SkMax32(abs(getByte(v1, 0) - getByte(v2, 0)), abs(getByte(v1, 1) - getByte(v2, 1))), 218 SkMax32(abs(getByte(v1, 2) - getByte(v2, 2)), abs(getByte(v1, 3) - getByte(v2, 3)))); 219} 220 221class AutoRestoreBbhType { 222public: 223 AutoRestoreBbhType() { 224 fRenderer = NULL; 225 fSavedBbhType = sk_tools::PictureRenderer::kNone_BBoxHierarchyType; 226 } 227 228 void set(sk_tools::PictureRenderer* renderer, 229 sk_tools::PictureRenderer::BBoxHierarchyType bbhType) { 230 fRenderer = renderer; 231 fSavedBbhType = renderer->getBBoxHierarchyType(); 232 renderer->setBBoxHierarchyType(bbhType); 233 } 234 235 ~AutoRestoreBbhType() { 236 if (NULL != fRenderer) { 237 fRenderer->setBBoxHierarchyType(fSavedBbhType); 238 } 239 } 240 241private: 242 sk_tools::PictureRenderer* fRenderer; 243 sk_tools::PictureRenderer::BBoxHierarchyType fSavedBbhType; 244}; 245 246/** 247 * Render the SKP file(s) within inputPath, writing their bitmap images into outputDir. 248 * 249 * @param inputPath path to an individual SKP file, or a directory of SKP files 250 * @param outputDir if not NULL, write the image(s) generated into this directory 251 * @param renderer PictureRenderer to use to render the SKPs 252 * @param jsonSummaryPtr if not NULL, add the image(s) generated to this summary 253 */ 254static bool render_picture(const SkString& inputPath, const SkString* outputDir, 255 sk_tools::PictureRenderer& renderer, 256 sk_tools::ImageResultsSummary *jsonSummaryPtr) { 257 int diffs[256] = {0}; 258 SkBitmap* bitmap = NULL; 259 renderer.setJsonSummaryPtr(jsonSummaryPtr); 260 bool success = render_picture_internal(inputPath, 261 FLAGS_writeWholeImage ? NULL : outputDir, 262 renderer, 263 FLAGS_validate || FLAGS_writeWholeImage ? &bitmap : NULL); 264 265 if (!success || ((FLAGS_validate || FLAGS_writeWholeImage) && bitmap == NULL)) { 266 SkDebugf("Failed to draw the picture.\n"); 267 SkDELETE(bitmap); 268 return false; 269 } 270 271 if (FLAGS_validate) { 272 SkBitmap* referenceBitmap = NULL; 273 sk_tools::PictureRenderer* referenceRenderer; 274 // If the renderer uses a BBoxHierarchy, then the reference renderer 275 // will be the same renderer, without the bbh. 276 AutoRestoreBbhType arbbh; 277 if (sk_tools::PictureRenderer::kNone_BBoxHierarchyType != 278 renderer.getBBoxHierarchyType()) { 279 referenceRenderer = &renderer; 280 referenceRenderer->ref(); // to match auto unref below 281 arbbh.set(referenceRenderer, sk_tools::PictureRenderer::kNone_BBoxHierarchyType); 282 } else { 283 referenceRenderer = SkNEW(sk_tools::SimplePictureRenderer); 284 } 285 SkAutoTUnref<sk_tools::PictureRenderer> aurReferenceRenderer(referenceRenderer); 286 287 success = render_picture_internal(inputPath, NULL, *referenceRenderer, 288 &referenceBitmap); 289 290 if (!success || NULL == referenceBitmap || NULL == referenceBitmap->getPixels()) { 291 SkDebugf("Failed to draw the reference picture.\n"); 292 SkDELETE(bitmap); 293 SkDELETE(referenceBitmap); 294 return false; 295 } 296 297 if (success && (bitmap->width() != referenceBitmap->width())) { 298 SkDebugf("Expected image width: %i, actual image width %i.\n", 299 referenceBitmap->width(), bitmap->width()); 300 SkDELETE(bitmap); 301 SkDELETE(referenceBitmap); 302 return false; 303 } 304 if (success && (bitmap->height() != referenceBitmap->height())) { 305 SkDebugf("Expected image height: %i, actual image height %i", 306 referenceBitmap->height(), bitmap->height()); 307 SkDELETE(bitmap); 308 SkDELETE(referenceBitmap); 309 return false; 310 } 311 312 for (int y = 0; success && y < bitmap->height(); y++) { 313 for (int x = 0; success && x < bitmap->width(); x++) { 314 int diff = MaxByteDiff(*referenceBitmap->getAddr32(x, y), 315 *bitmap->getAddr32(x, y)); 316 SkASSERT(diff >= 0 && diff <= 255); 317 diffs[diff]++; 318 319 if (diff > FLAGS_maxComponentDiff) { 320 SkDebugf("Expected pixel at (%i %i) exceedds maximum " 321 "component diff of %i: 0x%x, actual 0x%x\n", 322 x, y, FLAGS_maxComponentDiff, 323 *referenceBitmap->getAddr32(x, y), 324 *bitmap->getAddr32(x, y)); 325 SkDELETE(bitmap); 326 SkDELETE(referenceBitmap); 327 return false; 328 } 329 } 330 } 331 SkDELETE(referenceBitmap); 332 333 for (int i = 1; i <= 255; ++i) { 334 if(diffs[i] > 0) { 335 SkDebugf("Number of pixels with max diff of %i is %i\n", i, diffs[i]); 336 } 337 } 338 } 339 340 if (FLAGS_writeWholeImage) { 341 sk_tools::force_all_opaque(*bitmap); 342 343 // TODO(epoger): It would be better for the filename (without outputDir) to be passed in 344 // here, and used both for the checksum file and writing into outputDir. 345 SkString inputFilename, outputPath; 346 sk_tools::get_basename(&inputFilename, inputPath); 347 sk_tools::make_filepath(&outputPath, *outputDir, inputFilename); 348 sk_tools::replace_char(&outputPath, '.', '_'); 349 outputPath.append(".png"); 350 351 if (NULL != jsonSummaryPtr) { 352 SkString outputFileBasename; 353 sk_tools::get_basename(&outputFileBasename, outputPath); 354 jsonSummaryPtr->add(inputFilename.c_str(), outputFileBasename.c_str(), *bitmap); 355 } 356 357 if (NULL != outputDir) { 358 if (!SkImageEncoder::EncodeFile(outputPath.c_str(), *bitmap, 359 SkImageEncoder::kPNG_Type, 100)) { 360 SkDebugf("Failed to draw the picture.\n"); 361 success = false; 362 } 363 } 364 } 365 SkDELETE(bitmap); 366 367 return success; 368} 369 370 371static int process_input(const char* input, const SkString* outputDir, 372 sk_tools::PictureRenderer& renderer, 373 sk_tools::ImageResultsSummary *jsonSummaryPtr) { 374 SkOSFile::Iter iter(input, "skp"); 375 SkString inputFilename; 376 int failures = 0; 377 SkDebugf("process_input, %s\n", input); 378 if (iter.next(&inputFilename)) { 379 do { 380 SkString inputPath; 381 SkString inputAsSkString(input); 382 sk_tools::make_filepath(&inputPath, inputAsSkString, inputFilename); 383 if (!render_picture(inputPath, outputDir, renderer, jsonSummaryPtr)) { 384 ++failures; 385 } 386 } while(iter.next(&inputFilename)); 387 } else if (SkStrEndsWith(input, ".skp")) { 388 SkString inputPath(input); 389 if (!render_picture(inputPath, outputDir, renderer, jsonSummaryPtr)) { 390 ++failures; 391 } 392 } else { 393 SkString warning; 394 warning.printf("Warning: skipping %s\n", input); 395 SkDebugf(warning.c_str()); 396 } 397 return failures; 398} 399 400int tool_main(int argc, char** argv); 401int tool_main(int argc, char** argv) { 402 SkCommandLineFlags::SetUsage("Render .skp files."); 403 SkCommandLineFlags::Parse(argc, argv); 404 405 if (FLAGS_readPath.isEmpty()) { 406 SkDebugf(".skp files or directories are required.\n"); 407 exit(-1); 408 } 409 410 if (FLAGS_maxComponentDiff < 0 || FLAGS_maxComponentDiff > 256) { 411 SkDebugf("--maxComponentDiff must be between 0 and 256\n"); 412 exit(-1); 413 } 414 415 if (FLAGS_maxComponentDiff != 256 && !FLAGS_validate) { 416 SkDebugf("--maxComponentDiff requires --validate\n"); 417 exit(-1); 418 } 419 420 if (FLAGS_clone < 0) { 421 SkDebugf("--clone must be >= 0. Was %i\n", FLAGS_clone); 422 exit(-1); 423 } 424 425 if (FLAGS_writeEncodedImages) { 426 if (FLAGS_writePath.isEmpty()) { 427 SkDebugf("--writeEncodedImages requires --writePath\n"); 428 exit(-1); 429 } 430 if (FLAGS_deferImageDecoding) { 431 SkDebugf("--writeEncodedImages is not compatible with --deferImageDecoding\n"); 432 exit(-1); 433 } 434 } 435 436 SkString errorString; 437 SkAutoTUnref<sk_tools::PictureRenderer> renderer(parseRenderer(errorString, 438 kRender_PictureTool)); 439 if (errorString.size() > 0) { 440 SkDebugf("%s\n", errorString.c_str()); 441 } 442 443 if (renderer.get() == NULL) { 444 exit(-1); 445 } 446 447 SkAutoGraphics ag; 448 449 SkString outputDir; 450 if (FLAGS_writePath.count() == 1) { 451 outputDir.set(FLAGS_writePath[0]); 452 } 453 sk_tools::ImageResultsSummary jsonSummary; 454 sk_tools::ImageResultsSummary* jsonSummaryPtr = NULL; 455 if (FLAGS_writeJsonSummaryPath.count() == 1) { 456 jsonSummaryPtr = &jsonSummary; 457 } 458 459 int failures = 0; 460 for (int i = 0; i < FLAGS_readPath.count(); i ++) { 461 failures += process_input(FLAGS_readPath[i], &outputDir, *renderer.get(), jsonSummaryPtr); 462 } 463 if (failures != 0) { 464 SkDebugf("Failed to render %i pictures.\n", failures); 465 return 1; 466 } 467#if SK_SUPPORT_GPU 468#if GR_CACHE_STATS 469 if (renderer->isUsingGpuDevice()) { 470 GrContext* ctx = renderer->getGrContext(); 471 ctx->printCacheStats(); 472#ifdef SK_DEVELOPER 473 ctx->dumpFontCache(); 474#endif 475 } 476#endif 477#endif 478 if (FLAGS_writeJsonSummaryPath.count() == 1) { 479 jsonSummary.writeToFile(FLAGS_writeJsonSummaryPath[0]); 480 } 481 return 0; 482} 483 484#if !defined SK_BUILD_FOR_IOS 485int main(int argc, char * const argv[]) { 486 return tool_main(argc, (char**) argv); 487} 488#endif 489