render_pictures_main.cpp revision a3f882c4756bd8b9b6449dcc60b6d884ee0cc8ed
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(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.");
35DEFINE_string(writeJsonSummaryPath, "", "File to write a JSON summary of image results to. "
36              "TODO(epoger): Currently, this only works if --writePath is also specified.");
37DEFINE_string2(writePath, w, "", "Directory to write the rendered images.");
38DEFINE_bool(writeWholeImage, false, "In tile mode, write the entire rendered image to a "
39            "file, instead of an image for each tile.");
40DEFINE_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
44DEFINE_bool(bench_record, false, "If true, drop into an infinite loop of recording the picture.");
45
46static 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 */
58struct Format {
59    SkImageDecoder::Format  fFormat;
60    const char*             fSuffix;
61};
62static 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 */
76static 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 */
88static SkString gInputFileName;
89
90/**
91 *  Number to be appended to the image file name so that it is unique.
92 */
93static 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 */
102static 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 */
117static 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 */
140static 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
212static inline int getByte(uint32_t value, int index) {
213    SkASSERT(0 <= index && index < 4);
214    return (value >> (index * 8)) & 0xFF;
215}
216
217static 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
222namespace {
223class AutoRestoreBbhType {
224public:
225    AutoRestoreBbhType() {
226        fRenderer = NULL;
227        fSavedBbhType = sk_tools::PictureRenderer::kNone_BBoxHierarchyType;
228    }
229
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
237    ~AutoRestoreBbhType() {
238        if (NULL != fRenderer) {
239            fRenderer->setBBoxHierarchyType(fSavedBbhType);
240        }
241    }
242
243private:
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 */
257static 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
381static 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
410int tool_main(int argc, char** argv);
411int 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
495int main(int argc, char * const argv[]) {
496    return tool_main(argc, (char**) argv);
497}
498#endif
499