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