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