OLD | NEW |
| (Empty) |
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 "SkPictureRecorder.h" | |
20 #include "SkStream.h" | |
21 #include "SkString.h" | |
22 | |
23 #include "image_expectations.h" | |
24 #include "PictureRenderer.h" | |
25 #include "PictureRenderingFlags.h" | |
26 #include "picture_utils.h" | |
27 | |
28 #include <stdlib.h> | |
29 | |
30 // Flags used by this file, alphabetically: | |
31 DEFINE_bool(bench_record, false, "If true, drop into an infinite loop of recordi
ng the picture."); | |
32 DECLARE_bool(deferImageDecoding); | |
33 DEFINE_string(descriptions, "", "one or more key=value pairs to add to the descr
iptions section " | |
34 "of the JSON summary."); | |
35 DEFINE_string(imageBaseGSUrl, "", "The Google Storage image base URL the images
are stored in."); | |
36 DEFINE_int32(maxComponentDiff, 256, "Maximum diff on a component, 0 - 256. Compo
nents that differ " | |
37 "by more than this amount are considered errors, though all diffs a
re reported. " | |
38 "Requires --validate."); | |
39 DEFINE_string(mismatchPath, "", "Write images for tests that failed due to " | |
40 "pixel mismatches into this directory."); | |
41 #if GR_GPU_STATS | |
42 DEFINE_bool(gpuStats, false, "Only meaningful with gpu configurations. " | |
43 "Report some GPU call statistics."); | |
44 #endif | |
45 DEFINE_bool(mpd, false, "If true, use MultiPictureDraw for rendering."); | |
46 DEFINE_string(readJsonSummaryPath, "", "JSON file to read image expectations fro
m."); | |
47 DECLARE_string(readPath); | |
48 DEFINE_bool(writeChecksumBasedFilenames, false, | |
49 "When writing out images, use checksum-based filenames."); | |
50 DEFINE_bool(writeEncodedImages, false, "Any time the skp contains an encoded ima
ge, write it to a " | |
51 "file rather than decoding it. Requires writePath to be set. Skips d
rawing the full " | |
52 "skp to a file. Not compatible with deferImageDecoding."); | |
53 DEFINE_string(writeJsonSummaryPath, "", "File to write a JSON summary of image r
esults to."); | |
54 DEFINE_string2(writePath, w, "", "Directory to write the rendered images into.")
; | |
55 DEFINE_bool(writeWholeImage, false, "In tile mode, write the entire rendered ima
ge to a " | |
56 "file, instead of an image for each tile."); | |
57 DEFINE_bool(validate, false, "Verify that the rendered image contains the same p
ixels as " | |
58 "the picture rendered in simple mode. When used in conjunction with
--bbh, results " | |
59 "are validated against the picture rendered in the same mode, but wi
thout the bbh."); | |
60 | |
61 ////////////////////////////////////////////////////////////////////////////////
//////////////////// | |
62 | |
63 /** | |
64 * Table for translating from format of data to a suffix. | |
65 */ | |
66 struct Format { | |
67 SkImageDecoder::Format fFormat; | |
68 const char* fSuffix; | |
69 }; | |
70 static const Format gFormats[] = { | |
71 { SkImageDecoder::kBMP_Format, ".bmp" }, | |
72 { SkImageDecoder::kGIF_Format, ".gif" }, | |
73 { SkImageDecoder::kICO_Format, ".ico" }, | |
74 { SkImageDecoder::kJPEG_Format, ".jpg" }, | |
75 { SkImageDecoder::kPNG_Format, ".png" }, | |
76 { SkImageDecoder::kWBMP_Format, ".wbmp" }, | |
77 { SkImageDecoder::kWEBP_Format, ".webp" }, | |
78 { SkImageDecoder::kUnknown_Format, "" }, | |
79 }; | |
80 | |
81 /** | |
82 * Get an appropriate suffix for an image format. | |
83 */ | |
84 static const char* get_suffix_from_format(SkImageDecoder::Format format) { | |
85 for (size_t i = 0; i < SK_ARRAY_COUNT(gFormats); i++) { | |
86 if (gFormats[i].fFormat == format) { | |
87 return gFormats[i].fSuffix; | |
88 } | |
89 } | |
90 return ""; | |
91 } | |
92 | |
93 /** | |
94 * Base name for an image file created from the encoded data in an skp. | |
95 */ | |
96 static SkString gInputFileName; | |
97 | |
98 /** | |
99 * Number to be appended to the image file name so that it is unique. | |
100 */ | |
101 static uint32_t gImageNo; | |
102 | |
103 /** | |
104 * Set up the name for writing encoded data to a file. | |
105 * Sets gInputFileName to name, minus any extension ".*" | |
106 * Sets gImageNo to 0, so images from file "X.skp" will | |
107 * look like "X_<gImageNo>.<suffix>", beginning with 0 | |
108 * for each new skp. | |
109 */ | |
110 static void reset_image_file_base_name(const SkString& name) { | |
111 gImageNo = 0; | |
112 // Remove ".skp" | |
113 const char* cName = name.c_str(); | |
114 const char* dot = strrchr(cName, '.'); | |
115 if (dot != nullptr) { | |
116 gInputFileName.set(cName, dot - cName); | |
117 } else { | |
118 gInputFileName.set(name); | |
119 } | |
120 } | |
121 | |
122 /** | |
123 * Write the raw encoded bitmap data to a file. | |
124 */ | |
125 static bool write_image_to_file(const void* buffer, size_t size, SkBitmap* bitma
p) { | |
126 SkASSERT(!FLAGS_writePath.isEmpty()); | |
127 SkMemoryStream memStream(buffer, size); | |
128 SkString outPath; | |
129 SkImageDecoder::Format format = SkImageDecoder::GetStreamFormat(&memStream); | |
130 SkString name = SkStringPrintf("%s_%d%s", gInputFileName.c_str(), gImageNo++
, | |
131 get_suffix_from_format(format)); | |
132 SkString dir(FLAGS_writePath[0]); | |
133 outPath = SkOSPath::Join(dir.c_str(), name.c_str()); | |
134 SkFILEWStream fileStream(outPath.c_str()); | |
135 if (!(fileStream.isValid() && fileStream.write(buffer, size))) { | |
136 SkDebugf("Failed to write encoded data to \"%s\"\n", outPath.c_str()); | |
137 } | |
138 // Put in a dummy bitmap. | |
139 return SkImageDecoder::DecodeStream(&memStream, bitmap, kUnknown_SkColorType
, | |
140 SkImageDecoder::kDecodeBounds_Mode); | |
141 } | |
142 | |
143 ////////////////////////////////////////////////////////////////////////////////
//////////////////// | |
144 | |
145 /** | |
146 * Called only by render_picture(). | |
147 */ | |
148 static bool render_picture_internal(const SkString& inputPath, const SkString* w
ritePath, | |
149 const SkString* mismatchPath, | |
150 sk_tools::PictureRenderer& renderer, | |
151 SkBitmap** out) { | |
152 SkString inputFilename = SkOSPath::Basename(inputPath.c_str()); | |
153 SkString writePathString; | |
154 if (writePath && writePath->size() > 0 && !FLAGS_writeEncodedImages) { | |
155 writePathString.set(*writePath); | |
156 } | |
157 SkString mismatchPathString; | |
158 if (mismatchPath && mismatchPath->size() > 0) { | |
159 mismatchPathString.set(*mismatchPath); | |
160 } | |
161 | |
162 SkFILEStream inputStream; | |
163 inputStream.setPath(inputPath.c_str()); | |
164 if (!inputStream.isValid()) { | |
165 SkDebugf("Could not open file %s\n", inputPath.c_str()); | |
166 return false; | |
167 } | |
168 | |
169 SkPicture::InstallPixelRefProc proc; | |
170 if (FLAGS_deferImageDecoding) { | |
171 proc = &sk_tools::LazyDecodeBitmap; | |
172 } else if (FLAGS_writeEncodedImages) { | |
173 SkASSERT(!FLAGS_writePath.isEmpty()); | |
174 reset_image_file_base_name(inputFilename); | |
175 proc = &write_image_to_file; | |
176 } else { | |
177 proc = &SkImageDecoder::DecodeMemory; | |
178 } | |
179 | |
180 SkDebugf("deserializing... %s\n", inputPath.c_str()); | |
181 | |
182 SkAutoTUnref<SkPicture> picture(SkPicture::CreateFromStream(&inputStream, pr
oc)); | |
183 | |
184 if (nullptr == picture) { | |
185 SkDebugf("Could not read an SkPicture from %s\n", inputPath.c_str()); | |
186 return false; | |
187 } | |
188 | |
189 while (FLAGS_bench_record) { | |
190 SkPictureRecorder recorder; | |
191 picture->playback(recorder.beginRecording(picture->cullRect().width(), | |
192 picture->cullRect().height(), | |
193 nullptr, 0)); | |
194 SkAutoTUnref<SkPicture> other(recorder.endRecording()); | |
195 } | |
196 | |
197 SkDebugf("drawing... [%f %f %f %f] %s\n", | |
198 picture->cullRect().fLeft, picture->cullRect().fTop, | |
199 picture->cullRect().fRight, picture->cullRect().fBottom, | |
200 inputPath.c_str()); | |
201 | |
202 renderer.init(picture, &writePathString, &mismatchPathString, &inputFilename
, | |
203 FLAGS_writeChecksumBasedFilenames, FLAGS_mpd); | |
204 | |
205 renderer.setup(); | |
206 renderer.enableWrites(); | |
207 | |
208 bool success = renderer.render(out); | |
209 if (!success) { | |
210 SkDebugf("Failed to render %s\n", inputFilename.c_str()); | |
211 } | |
212 | |
213 renderer.end(); | |
214 | |
215 return success; | |
216 } | |
217 | |
218 static inline int getByte(uint32_t value, int index) { | |
219 SkASSERT(0 <= index && index < 4); | |
220 return (value >> (index * 8)) & 0xFF; | |
221 } | |
222 | |
223 static int MaxByteDiff(uint32_t v1, uint32_t v2) { | |
224 return SkMax32(SkMax32(SkTAbs(getByte(v1, 0) - getByte(v2, 0)), SkTAbs(getBy
te(v1, 1) - getByte(v2, 1))), | |
225 SkMax32(SkTAbs(getByte(v1, 2) - getByte(v2, 2)), SkTAbs(getBy
te(v1, 3) - getByte(v2, 3)))); | |
226 } | |
227 | |
228 class AutoRestoreBbhType { | |
229 public: | |
230 AutoRestoreBbhType() { | |
231 fRenderer = nullptr; | |
232 fSavedBbhType = sk_tools::PictureRenderer::kNone_BBoxHierarchyType; | |
233 } | |
234 | |
235 void set(sk_tools::PictureRenderer* renderer, | |
236 sk_tools::PictureRenderer::BBoxHierarchyType bbhType) { | |
237 fRenderer = renderer; | |
238 fSavedBbhType = renderer->getBBoxHierarchyType(); | |
239 renderer->setBBoxHierarchyType(bbhType); | |
240 } | |
241 | |
242 ~AutoRestoreBbhType() { | |
243 if (fRenderer) { | |
244 fRenderer->setBBoxHierarchyType(fSavedBbhType); | |
245 } | |
246 } | |
247 | |
248 private: | |
249 sk_tools::PictureRenderer* fRenderer; | |
250 sk_tools::PictureRenderer::BBoxHierarchyType fSavedBbhType; | |
251 }; | |
252 | |
253 /** | |
254 * Render the SKP file(s) within inputPath. | |
255 * | |
256 * @param inputPath path to an individual SKP file, or a directory of SKP files | |
257 * @param writePath if not nullptr, write all image(s) generated into this direc
tory | |
258 * @param mismatchPath if not nullptr, write any image(s) not matching expectati
ons into this directory | |
259 * @param renderer PictureRenderer to use to render the SKPs | |
260 * @param jsonSummaryPtr if not nullptr, add the image(s) generated to this summ
ary | |
261 */ | |
262 static bool render_picture(const SkString& inputPath, const SkString* writePath, | |
263 const SkString* mismatchPath, sk_tools::PictureRender
er& renderer, | |
264 sk_tools::ImageResultsAndExpectations *jsonSummaryPtr
) { | |
265 int diffs[256] = {0}; | |
266 SkBitmap* bitmap = nullptr; | |
267 renderer.setJsonSummaryPtr(jsonSummaryPtr); | |
268 bool success = render_picture_internal(inputPath, | |
269 FLAGS_writeWholeImage ? nullptr : writePath, | |
270 FLAGS_writeWholeImage ? nullptr : mismatchPath, | |
271 renderer, | |
272 FLAGS_validate || FLAGS_writeWholeImage ? &bitmap : nullptr); | |
273 | |
274 if (!success || ((FLAGS_validate || FLAGS_writeWholeImage) && bitmap == null
ptr)) { | |
275 SkDebugf("Failed to draw the picture.\n"); | |
276 delete bitmap; | |
277 return false; | |
278 } | |
279 | |
280 if (FLAGS_validate) { | |
281 SkBitmap* referenceBitmap = nullptr; | |
282 sk_tools::PictureRenderer* referenceRenderer; | |
283 // If the renderer uses a BBoxHierarchy, then the reference renderer | |
284 // will be the same renderer, without the bbh. | |
285 AutoRestoreBbhType arbbh; | |
286 if (sk_tools::PictureRenderer::kNone_BBoxHierarchyType != | |
287 renderer.getBBoxHierarchyType()) { | |
288 referenceRenderer = &renderer; | |
289 referenceRenderer->ref(); // to match auto unref below | |
290 arbbh.set(referenceRenderer, sk_tools::PictureRenderer::kNone_BBoxHi
erarchyType); | |
291 } else { | |
292 #if SK_SUPPORT_GPU | |
293 referenceRenderer = new sk_tools::SimplePictureRenderer(renderer.get
GrContextOptions()); | |
294 #else | |
295 referenceRenderer = new sk_tools::SimplePictureRenderer; | |
296 #endif | |
297 } | |
298 SkAutoTUnref<sk_tools::PictureRenderer> aurReferenceRenderer(referenceRe
nderer); | |
299 | |
300 success = render_picture_internal(inputPath, nullptr, nullptr, *referenc
eRenderer, | |
301 &referenceBitmap); | |
302 | |
303 if (!success || nullptr == referenceBitmap || nullptr == referenceBitmap
->getPixels()) { | |
304 SkDebugf("Failed to draw the reference picture.\n"); | |
305 delete bitmap; | |
306 delete referenceBitmap; | |
307 return false; | |
308 } | |
309 | |
310 if (success && (bitmap->width() != referenceBitmap->width())) { | |
311 SkDebugf("Expected image width: %i, actual image width %i.\n", | |
312 referenceBitmap->width(), bitmap->width()); | |
313 delete bitmap; | |
314 delete referenceBitmap; | |
315 return false; | |
316 } | |
317 if (success && (bitmap->height() != referenceBitmap->height())) { | |
318 SkDebugf("Expected image height: %i, actual image height %i", | |
319 referenceBitmap->height(), bitmap->height()); | |
320 delete bitmap; | |
321 delete referenceBitmap; | |
322 return false; | |
323 } | |
324 | |
325 for (int y = 0; success && y < bitmap->height(); y++) { | |
326 for (int x = 0; success && x < bitmap->width(); x++) { | |
327 int diff = MaxByteDiff(*referenceBitmap->getAddr32(x, y), | |
328 *bitmap->getAddr32(x, y)); | |
329 SkASSERT(diff >= 0 && diff <= 255); | |
330 diffs[diff]++; | |
331 | |
332 if (diff > FLAGS_maxComponentDiff) { | |
333 SkDebugf("Expected pixel at (%i %i) exceedds maximum " | |
334 "component diff of %i: 0x%x, actual 0x%x\n", | |
335 x, y, FLAGS_maxComponentDiff, | |
336 *referenceBitmap->getAddr32(x, y), | |
337 *bitmap->getAddr32(x, y)); | |
338 delete bitmap; | |
339 delete referenceBitmap; | |
340 return false; | |
341 } | |
342 } | |
343 } | |
344 delete referenceBitmap; | |
345 | |
346 for (int i = 1; i <= 255; ++i) { | |
347 if(diffs[i] > 0) { | |
348 SkDebugf("Number of pixels with max diff of %i is %i\n", i, diff
s[i]); | |
349 } | |
350 } | |
351 } | |
352 | |
353 if (FLAGS_writeWholeImage) { | |
354 sk_tools::force_all_opaque(*bitmap); | |
355 | |
356 SkString inputFilename = SkOSPath::Basename(inputPath.c_str()); | |
357 SkString outputFilename(inputFilename); | |
358 sk_tools::replace_char(&outputFilename, '.', '_'); | |
359 outputFilename.append(".png"); | |
360 | |
361 if (jsonSummaryPtr) { | |
362 sk_tools::ImageDigest imageDigest(*bitmap); | |
363 jsonSummaryPtr->add(inputFilename.c_str(), outputFilename.c_str(), i
mageDigest); | |
364 if ((mismatchPath) && !mismatchPath->isEmpty() && | |
365 !jsonSummaryPtr->getExpectation(inputFilename.c_str()).matches(i
mageDigest)) { | |
366 success &= sk_tools::write_bitmap_to_disk(*bitmap, *mismatchPath
, nullptr, | |
367 outputFilename); | |
368 } | |
369 } | |
370 | |
371 if ((writePath) && !writePath->isEmpty()) { | |
372 success &= sk_tools::write_bitmap_to_disk(*bitmap, *writePath, nullp
tr, outputFilename); | |
373 } | |
374 } | |
375 delete bitmap; | |
376 | |
377 return success; | |
378 } | |
379 | |
380 | |
381 static int process_input(const char* input, const SkString* writePath, | |
382 const SkString* mismatchPath, sk_tools::PictureRenderer
& renderer, | |
383 sk_tools::ImageResultsAndExpectations *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 = SkOSPath::Join(input, inputFilename.c_str()); | |
391 if (!render_picture(inputPath, writePath, mismatchPath, renderer, js
onSummaryPtr)) { | |
392 ++failures; | |
393 } | |
394 } while(iter.next(&inputFilename)); | |
395 } else if (SkStrEndsWith(input, ".skp")) { | |
396 SkString inputPath(input); | |
397 if (!render_picture(inputPath, writePath, mismatchPath, renderer, jsonSu
mmaryPtr)) { | |
398 ++failures; | |
399 } | |
400 } else { | |
401 SkString warning; | |
402 warning.printf("Warning: skipping %s\n", input); | |
403 SkDebugf("%s", warning.c_str()); | |
404 } | |
405 return failures; | |
406 } | |
407 | |
408 int tool_main(int argc, char** argv); | |
409 int tool_main(int argc, char** argv) { | |
410 SkCommandLineFlags::SetUsage("Render .skp files."); | |
411 SkCommandLineFlags::Parse(argc, argv); | |
412 | |
413 if (FLAGS_readPath.isEmpty()) { | |
414 SkDebugf(".skp files or directories are required.\n"); | |
415 exit(-1); | |
416 } | |
417 | |
418 if (FLAGS_maxComponentDiff < 0 || FLAGS_maxComponentDiff > 256) { | |
419 SkDebugf("--maxComponentDiff must be between 0 and 256\n"); | |
420 exit(-1); | |
421 } | |
422 | |
423 if (FLAGS_maxComponentDiff != 256 && !FLAGS_validate) { | |
424 SkDebugf("--maxComponentDiff requires --validate\n"); | |
425 exit(-1); | |
426 } | |
427 | |
428 if (FLAGS_writeEncodedImages) { | |
429 if (FLAGS_writePath.isEmpty()) { | |
430 SkDebugf("--writeEncodedImages requires --writePath\n"); | |
431 exit(-1); | |
432 } | |
433 if (FLAGS_deferImageDecoding) { | |
434 SkDebugf("--writeEncodedImages is not compatible with --deferImageDe
coding\n"); | |
435 exit(-1); | |
436 } | |
437 } | |
438 | |
439 SkString errorString; | |
440 SkAutoTUnref<sk_tools::PictureRenderer> renderer(parseRenderer(errorString, | |
441 kRender_Pictu
reTool)); | |
442 if (errorString.size() > 0) { | |
443 SkDebugf("%s\n", errorString.c_str()); | |
444 } | |
445 | |
446 if (renderer.get() == nullptr) { | |
447 exit(-1); | |
448 } | |
449 | |
450 SkAutoGraphics ag; | |
451 | |
452 SkString writePath; | |
453 if (FLAGS_writePath.count() == 1) { | |
454 writePath.set(FLAGS_writePath[0]); | |
455 } | |
456 SkString mismatchPath; | |
457 if (FLAGS_mismatchPath.count() == 1) { | |
458 mismatchPath.set(FLAGS_mismatchPath[0]); | |
459 } | |
460 sk_tools::ImageResultsAndExpectations jsonSummary; | |
461 sk_tools::ImageResultsAndExpectations* jsonSummaryPtr = nullptr; | |
462 if (FLAGS_writeJsonSummaryPath.count() == 1) { | |
463 jsonSummaryPtr = &jsonSummary; | |
464 if (FLAGS_readJsonSummaryPath.count() == 1) { | |
465 SkASSERT(jsonSummary.readExpectationsFile(FLAGS_readJsonSummaryPath[
0])); | |
466 } | |
467 } | |
468 | |
469 int failures = 0; | |
470 for (int i = 0; i < FLAGS_readPath.count(); i ++) { | |
471 failures += process_input(FLAGS_readPath[i], &writePath, &mismatchPath,
*renderer.get(), | |
472 jsonSummaryPtr); | |
473 } | |
474 if (failures != 0) { | |
475 SkDebugf("Failed to render %i pictures.\n", failures); | |
476 return 1; | |
477 } | |
478 #if GR_CACHE_STATS && SK_SUPPORT_GPU | |
479 if (renderer->isUsingGpuDevice()) { | |
480 GrContext* ctx = renderer->getGrContext(); | |
481 ctx->printCacheStats(); | |
482 } | |
483 #endif | |
484 | |
485 #if GR_GPU_STATS && SK_SUPPORT_GPU | |
486 if (FLAGS_gpuStats && renderer->isUsingGpuDevice()) { | |
487 renderer->getGrContext()->printGpuStats(); | |
488 } | |
489 #endif | |
490 | |
491 if (FLAGS_writeJsonSummaryPath.count() == 1) { | |
492 // If there were any descriptions on the command line, insert them now. | |
493 for (int i=0; i<FLAGS_descriptions.count(); i++) { | |
494 SkTArray<SkString> tokens; | |
495 SkStrSplit(FLAGS_descriptions[i], "=", &tokens); | |
496 SkASSERT(tokens.count() == 2); | |
497 jsonSummary.addDescription(tokens[0].c_str(), tokens[1].c_str()); | |
498 } | |
499 if (FLAGS_imageBaseGSUrl.count() == 1) { | |
500 jsonSummary.setImageBaseGSUrl(FLAGS_imageBaseGSUrl[0]); | |
501 } | |
502 jsonSummary.writeToFile(FLAGS_writeJsonSummaryPath[0]); | |
503 } | |
504 return 0; | |
505 } | |
506 | |
507 #if !defined SK_BUILD_FOR_IOS | |
508 int main(int argc, char * const argv[]) { | |
509 return tool_main(argc, (char**) argv); | |
510 } | |
511 #endif | |
OLD | NEW |