1 #include "gm.h" 2 #include "SkColorPriv.h" 3 #include "SkGraphics.h" 4 #include "SkImageDecoder.h" 5 #include "SkImageEncoder.h" 6 #include "SkPicture.h" 7 #include "SkStream.h" 8 #include "SkRefCnt.h" 9 10 #include "GrContext.h" 11 #include "SkGpuCanvas.h" 12 #include "SkGpuDevice.h" 13 #include "SkEGLContext.h" 14 #include "SkDevice.h" 15 16 #ifdef SK_SUPPORT_PDF 17 #include "SkPDFDevice.h" 18 #include "SkPDFDocument.h" 19 #endif 20 21 using namespace skiagm; 22 23 // need to explicitly declare this, or we get some weird infinite loop llist 24 template GMRegistry* GMRegistry::gHead; 25 26 class Iter { 27 public: 28 Iter() { 29 fReg = GMRegistry::Head(); 30 } 31 32 GM* next() { 33 if (fReg) { 34 GMRegistry::Factory fact = fReg->factory(); 35 fReg = fReg->next(); 36 return fact(0); 37 } 38 return NULL; 39 } 40 41 static int Count() { 42 const GMRegistry* reg = GMRegistry::Head(); 43 int count = 0; 44 while (reg) { 45 count += 1; 46 reg = reg->next(); 47 } 48 return count; 49 } 50 51 private: 52 const GMRegistry* fReg; 53 }; 54 55 static SkString make_name(const char shortName[], const char configName[]) { 56 SkString name(shortName); 57 name.appendf("_%s", configName); 58 return name; 59 } 60 61 static SkString make_filename(const char path[], 62 const char pathSuffix[], 63 const SkString& name, 64 const char suffix[]) { 65 SkString filename(path); 66 if (filename.endsWith("/")) { 67 filename.remove(filename.size() - 1, 1); 68 } 69 filename.append(pathSuffix); 70 filename.append("/"); 71 filename.appendf("%s.%s", name.c_str(), suffix); 72 return filename; 73 } 74 75 /* since PNG insists on unpremultiplying our alpha, we take no precision chances 76 and force all pixels to be 100% opaque, otherwise on compare we may not get 77 a perfect match. 78 */ 79 static void force_all_opaque(const SkBitmap& bitmap) { 80 SkAutoLockPixels lock(bitmap); 81 for (int y = 0; y < bitmap.height(); y++) { 82 for (int x = 0; x < bitmap.width(); x++) { 83 *bitmap.getAddr32(x, y) |= (SK_A32_MASK << SK_A32_SHIFT); 84 } 85 } 86 } 87 88 static bool write_bitmap(const SkString& path, const SkBitmap& bitmap) { 89 SkBitmap copy; 90 bitmap.copyTo(©, SkBitmap::kARGB_8888_Config); 91 force_all_opaque(copy); 92 return SkImageEncoder::EncodeFile(path.c_str(), copy, 93 SkImageEncoder::kPNG_Type, 100); 94 } 95 96 static inline SkPMColor compute_diff_pmcolor(SkPMColor c0, SkPMColor c1) { 97 int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1); 98 int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1); 99 int db = SkGetPackedB32(c0) - SkGetPackedB32(c1); 100 return SkPackARGB32(0xFF, SkAbs32(dr), SkAbs32(dg), SkAbs32(db)); 101 } 102 103 static void compute_diff(const SkBitmap& target, const SkBitmap& base, 104 SkBitmap* diff) { 105 SkAutoLockPixels alp(*diff); 106 107 const int w = target.width(); 108 const int h = target.height(); 109 for (int y = 0; y < h; y++) { 110 for (int x = 0; x < w; x++) { 111 SkPMColor c0 = *base.getAddr32(x, y); 112 SkPMColor c1 = *target.getAddr32(x, y); 113 SkPMColor d = 0; 114 if (c0 != c1) { 115 d = compute_diff_pmcolor(c0, c1); 116 } 117 *diff->getAddr32(x, y) = d; 118 } 119 } 120 } 121 122 static bool compare(const SkBitmap& target, const SkBitmap& base, 123 const SkString& name, const char* renderModeDescriptor, 124 SkBitmap* diff) { 125 SkBitmap copy; 126 const SkBitmap* bm = ⌖ 127 if (target.config() != SkBitmap::kARGB_8888_Config) { 128 target.copyTo(©, SkBitmap::kARGB_8888_Config); 129 bm = © 130 } 131 132 force_all_opaque(*bm); 133 134 const int w = bm->width(); 135 const int h = bm->height(); 136 if (w != base.width() || h != base.height()) { 137 SkDebugf( 138 "---- %s dimensions mismatch for %s base [%d %d] current [%d %d]\n", 139 renderModeDescriptor, name.c_str(), 140 base.width(), base.height(), w, h); 141 return false; 142 } 143 144 SkAutoLockPixels bmLock(*bm); 145 SkAutoLockPixels baseLock(base); 146 147 for (int y = 0; y < h; y++) { 148 for (int x = 0; x < w; x++) { 149 SkPMColor c0 = *base.getAddr32(x, y); 150 SkPMColor c1 = *bm->getAddr32(x, y); 151 if (c0 != c1) { 152 SkDebugf( 153 "----- %s pixel mismatch for %s at [%d %d] base 0x%08X current 0x%08X\n", 154 renderModeDescriptor, name.c_str(), x, y, c0, c1); 155 156 if (diff) { 157 diff->setConfig(SkBitmap::kARGB_8888_Config, w, h); 158 diff->allocPixels(); 159 compute_diff(*bm, base, diff); 160 } 161 return false; 162 } 163 } 164 } 165 166 // they're equal 167 return true; 168 } 169 170 static bool write_pdf(const SkString& path, const SkDynamicMemoryWStream& pdf) { 171 SkFILEWStream stream(path.c_str()); 172 return stream.write(pdf.getStream(), pdf.getOffset()); 173 } 174 175 enum Backend { 176 kRaster_Backend, 177 kGPU_Backend, 178 kPDF_Backend, 179 }; 180 181 struct ConfigData { 182 SkBitmap::Config fConfig; 183 Backend fBackend; 184 const char* fName; 185 }; 186 187 /// Returns true if processing should continue, false to skip the 188 /// remainder of this config for this GM. 189 //@todo thudson 22 April 2011 - could refactor this to take in 190 // a factory to generate the context, always call readPixels() 191 // (logically a noop for rasters, if wasted time), and thus collapse the 192 // GPU special case and also let this be used for SkPicture testing. 193 static void setup_bitmap(const ConfigData& gRec, SkISize& size, 194 SkBitmap* bitmap) { 195 bitmap->setConfig(gRec.fConfig, size.width(), size.height()); 196 bitmap->allocPixels(); 197 bitmap->eraseColor(0); 198 } 199 200 // Returns true if the test should continue, false if the test should 201 // halt. 202 static bool generate_image(GM* gm, const ConfigData& gRec, 203 GrContext* context, 204 SkBitmap& bitmap) { 205 SkISize size (gm->getISize()); 206 setup_bitmap(gRec, size, &bitmap); 207 SkCanvas canvas(bitmap); 208 209 if (gRec.fBackend == kRaster_Backend) { 210 gm->draw(&canvas); 211 } else { // GPU 212 if (NULL == context) { 213 return false; 214 } 215 SkGpuCanvas gc(context, 216 SkGpuDevice::Current3DApiRenderTarget()); 217 gc.setDevice(gc.createDevice(bitmap.config(), 218 bitmap.width(), 219 bitmap.height(), 220 bitmap.isOpaque(), 221 false))->unref(); 222 gm->draw(&gc); 223 gc.readPixels(&bitmap); // overwrite our previous allocation 224 } 225 return true; 226 } 227 228 static void generate_image_from_picture(GM* gm, const ConfigData& gRec, 229 SkPicture* pict, SkBitmap* bitmap) { 230 SkISize size = gm->getISize(); 231 setup_bitmap(gRec, size, bitmap); 232 SkCanvas canvas(*bitmap); 233 canvas.drawPicture(*pict); 234 } 235 236 static void generate_pdf(GM* gm, SkDynamicMemoryWStream& pdf) { 237 #ifdef SK_SUPPORT_PDF 238 SkISize size = gm->getISize(); 239 SkMatrix identity; 240 identity.reset(); 241 SkPDFDevice* dev = new SkPDFDevice(size, size, identity); 242 SkAutoUnref aur(dev); 243 244 SkCanvas c(dev); 245 gm->draw(&c); 246 247 SkPDFDocument doc; 248 doc.appendPage(dev); 249 doc.emitPDF(&pdf); 250 #endif 251 } 252 253 static bool write_reference_image(const ConfigData& gRec, 254 const char writePath [], 255 const char renderModeDescriptor [], 256 const SkString& name, 257 SkBitmap& bitmap, 258 SkDynamicMemoryWStream* pdf) { 259 SkString path; 260 bool success = false; 261 if (gRec.fBackend != kPDF_Backend) { 262 path = make_filename(writePath, renderModeDescriptor, name, "png"); 263 success = write_bitmap(path, bitmap); 264 } else if (pdf) { 265 path = make_filename(writePath, renderModeDescriptor, name, "pdf"); 266 success = write_pdf(path, *pdf); 267 } 268 if (!success) { 269 fprintf(stderr, "FAILED to write %s\n", path.c_str()); 270 } 271 return success; 272 } 273 274 static bool compare_to_reference_image(const char readPath [], 275 const SkString& name, 276 SkBitmap &bitmap, 277 const char diffPath [], 278 const char renderModeDescriptor []) { 279 SkString path = make_filename(readPath, "", name, "png"); 280 SkBitmap orig; 281 bool success = SkImageDecoder::DecodeFile(path.c_str(), &orig, 282 SkBitmap::kARGB_8888_Config, 283 SkImageDecoder::kDecodePixels_Mode, NULL); 284 if (success) { 285 SkBitmap diffBitmap; 286 success = compare(bitmap, orig, name, renderModeDescriptor, 287 diffPath ? &diffBitmap : NULL); 288 if (!success && diffPath) { 289 SkString diffName = make_filename(diffPath, "", name, ".diff.png"); 290 fprintf(stderr, "Writing %s\n", diffName.c_str()); 291 write_bitmap(diffName, diffBitmap); 292 } 293 } else { 294 fprintf(stderr, "FAILED to read %s\n", path.c_str()); 295 } 296 return success; 297 } 298 299 static bool handle_test_results(GM* gm, 300 const ConfigData& gRec, 301 const char writePath [], 302 const char readPath [], 303 const char diffPath [], 304 const char renderModeDescriptor [], 305 SkBitmap& bitmap, 306 SkDynamicMemoryWStream* pdf) { 307 SkString name = make_name(gm->shortName(), gRec.fName); 308 309 if (writePath) { 310 write_reference_image(gRec, writePath, renderModeDescriptor, 311 name, bitmap, pdf); 312 // TODO: Figure out a way to compare PDFs. 313 } else if (readPath && gRec.fBackend != kPDF_Backend) { 314 return compare_to_reference_image(readPath, name, bitmap, 315 diffPath, renderModeDescriptor); 316 } 317 return true; 318 } 319 320 static SkPicture* generate_new_picture(GM* gm) { 321 // Pictures are refcounted so must be on heap 322 SkPicture* pict = new SkPicture; 323 SkCanvas* cv = pict->beginRecording(1000, 1000); 324 gm->draw(cv); 325 pict->endRecording(); 326 327 return pict; 328 } 329 330 static SkPicture* stream_to_new_picture(const SkPicture& src) { 331 332 // To do in-memory commiunications with a stream, we need to: 333 // * create a dynamic memory stream 334 // * copy it into a buffer 335 // * create a read stream from it 336 // ?!?! 337 338 SkDynamicMemoryWStream storage; 339 src.serialize(&storage); 340 341 int streamSize = storage.getOffset(); 342 SkAutoMalloc dstStorage(streamSize); 343 void* dst = dstStorage.get(); 344 //char* dst = new char [streamSize]; 345 //@todo thudson 22 April 2011 when can we safely delete [] dst? 346 storage.copyTo(dst); 347 SkMemoryStream pictReadback(dst, streamSize); 348 SkPicture* retval = new SkPicture (&pictReadback); 349 return retval; 350 } 351 352 // Test: draw into a bitmap or pdf. 353 // Depending on flags, possibly compare to an expected image 354 // and possibly output a diff image if it fails to match. 355 static bool test_drawing(GM* gm, 356 const ConfigData& gRec, 357 const char writePath [], 358 const char readPath [], 359 const char diffPath [], 360 GrContext* context) { 361 SkBitmap bitmap; 362 SkDynamicMemoryWStream pdf; 363 364 if (gRec.fBackend == kRaster_Backend || 365 gRec.fBackend == kGPU_Backend) { 366 // Early exit if we can't generate the image, but this is 367 // expected in some cases, so don't report a test failure. 368 if (!generate_image(gm, gRec, context, bitmap)) { 369 return true; 370 } 371 } 372 // TODO: Figure out a way to compare PDFs. 373 if (gRec.fBackend == kPDF_Backend && writePath) { 374 generate_pdf(gm, pdf); 375 } 376 return handle_test_results(gm, gRec, writePath, readPath, diffPath, 377 "", bitmap, &pdf); 378 } 379 380 static bool test_picture_playback(GM* gm, 381 const ConfigData& gRec, 382 const char readPath [], 383 const char diffPath []) { 384 SkPicture* pict = generate_new_picture(gm); 385 SkAutoUnref aur(pict); 386 387 if (kRaster_Backend == gRec.fBackend) { 388 SkBitmap bitmap; 389 generate_image_from_picture(gm, gRec, pict, &bitmap); 390 return handle_test_results(gm, gRec, NULL, readPath, diffPath, 391 "-replay", bitmap, NULL); 392 } 393 return true; 394 } 395 396 static bool test_picture_serialization(GM* gm, 397 const ConfigData& gRec, 398 const char readPath [], 399 const char diffPath []) { 400 SkPicture* pict = generate_new_picture(gm); 401 SkAutoUnref aurp(pict); 402 SkPicture* repict = stream_to_new_picture(*pict); 403 SkAutoUnref aurr(repict); 404 405 if (kRaster_Backend == gRec.fBackend) { 406 SkBitmap bitmap; 407 generate_image_from_picture(gm, gRec, repict, &bitmap); 408 return handle_test_results(gm, gRec, NULL, readPath, diffPath, 409 "-serialize", bitmap, NULL); 410 } 411 return true; 412 } 413 414 static void usage(const char * argv0) { 415 SkDebugf("%s [-w writePath] [-r readPath] [-d diffPath]\n", argv0); 416 SkDebugf(" [--replay] [--serialize]\n"); 417 SkDebugf(" writePath: directory to write rendered images in.\n"); 418 SkDebugf( 419 " readPath: directory to read reference images from;\n" 420 " reports if any pixels mismatch between reference and new images\n"); 421 SkDebugf(" diffPath: directory to write difference images in.\n"); 422 SkDebugf(" --replay: exercise SkPicture replay.\n"); 423 SkDebugf( 424 " --serialize: exercise SkPicture serialization & deserialization.\n"); 425 } 426 427 static const ConfigData gRec[] = { 428 { SkBitmap::kARGB_8888_Config, kRaster_Backend, "8888" }, 429 { SkBitmap::kARGB_4444_Config, kRaster_Backend, "4444" }, 430 { SkBitmap::kRGB_565_Config, kRaster_Backend, "565" }, 431 { SkBitmap::kARGB_8888_Config, kGPU_Backend, "gpu" }, 432 #ifdef SK_SUPPORT_PDF 433 { SkBitmap::kARGB_8888_Config, kPDF_Backend, "pdf" }, 434 #endif 435 }; 436 437 int main(int argc, char * const argv[]) { 438 SkAutoGraphics ag; 439 440 const char* writePath = NULL; // if non-null, where we write the originals 441 const char* readPath = NULL; // if non-null, were we read from to compare 442 const char* diffPath = NULL; // if non-null, where we write our diffs (from compare) 443 444 bool doReplay = true; 445 bool doSerialize = false; 446 const char* const commandName = argv[0]; 447 char* const* stop = argv + argc; 448 for (++argv; argv < stop; ++argv) { 449 if (strcmp(*argv, "-w") == 0) { 450 argv++; 451 if (argv < stop && **argv) { 452 writePath = *argv; 453 } 454 } else if (strcmp(*argv, "-r") == 0) { 455 argv++; 456 if (argv < stop && **argv) { 457 readPath = *argv; 458 } 459 } else if (strcmp(*argv, "-d") == 0) { 460 argv++; 461 if (argv < stop && **argv) { 462 diffPath = *argv; 463 } 464 } else if (strcmp(*argv, "--noreplay") == 0) { 465 doReplay = false; 466 } else if (strcmp(*argv, "--serialize") == 0) { 467 doSerialize = true; 468 } else { 469 usage(commandName); 470 return -1; 471 } 472 } 473 if (argv != stop) { 474 usage(commandName); 475 return -1; 476 } 477 478 // setup a GL context for drawing offscreen 479 GrContext* context = NULL; 480 SkEGLContext eglContext; 481 if (eglContext.init(1024, 1024)) { 482 context = GrContext::CreateGLShaderContext(); 483 } 484 485 Iter iter; 486 GM* gm; 487 488 if (readPath) { 489 fprintf(stderr, "reading from %s\n", readPath); 490 } else if (writePath) { 491 fprintf(stderr, "writing to %s\n", writePath); 492 } 493 494 // Accumulate success of all tests so we can flag error in any 495 // one with the return value. 496 bool overallSuccess = true; 497 while ((gm = iter.next()) != NULL) { 498 SkISize size = gm->getISize(); 499 SkDebugf("drawing... %s [%d %d]\n", gm->shortName(), 500 size.width(), size.height()); 501 502 for (size_t i = 0; i < SK_ARRAY_COUNT(gRec); i++) { 503 bool testSuccess = test_drawing(gm, gRec[i], 504 writePath, readPath, diffPath, context); 505 overallSuccess &= testSuccess; 506 507 if (doReplay && testSuccess) { 508 testSuccess = test_picture_playback(gm, gRec[i], 509 readPath, diffPath); 510 overallSuccess &= testSuccess; 511 } 512 513 if (doSerialize && testSuccess) { 514 overallSuccess &= test_picture_serialization(gm, gRec[i], 515 readPath, diffPath); 516 } 517 } 518 SkDELETE(gm); 519 } 520 if (false == overallSuccess) { 521 return -1; 522 } 523 return 0; 524 } 525 526 /////////////////////////////////////////////////////////////////////////////// 527 528 using namespace skiagm; 529 530 GM::GM() {} 531 GM::~GM() {} 532 533 void GM::draw(SkCanvas* canvas) { 534 this->onDraw(canvas); 535 } 536