• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2011 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 "SkPictureRecord.h"
9 #include "SkBBoxHierarchy.h"
10 #include "SkDevice.h"
11 #include "SkPatchUtils.h"
12 #include "SkPictureStateTree.h"
13 #include "SkPixelRef.h"
14 #include "SkRRect.h"
15 #include "SkTextBlob.h"
16 #include "SkTSearch.h"
17 
18 #define HEAP_BLOCK_SIZE 4096
19 
20 // If SK_RECORD_LITERAL_PICTURES is defined, record our inputs as literally as possible.
21 // Otherwise, we can be clever and record faster equivalents.  kBeClever is normally true.
22 static const bool kBeClever =
23 #ifdef SK_RECORD_LITERAL_PICTURES
24     false;
25 #else
26     true;
27 #endif
28 
29 enum {
30     // just need a value that save or getSaveCount would never return
31     kNoInitialSave = -1,
32 };
33 
34 // A lot of basic types get stored as a uint32_t: bools, ints, paint indices, etc.
35 static int const kUInt32Size = 4;
36 
37 static const uint32_t kSaveSize = kUInt32Size;
38 static const uint32_t kSaveLayerNoBoundsSize = 4 * kUInt32Size;
39 static const uint32_t kSaveLayerWithBoundsSize = 4 * kUInt32Size + sizeof(SkRect);
40 
SkPictureRecord(const SkISize & dimensions,uint32_t flags)41 SkPictureRecord::SkPictureRecord(const SkISize& dimensions, uint32_t flags)
42     : INHERITED(dimensions.width(), dimensions.height())
43     , fBoundingHierarchy(NULL)
44     , fStateTree(NULL)
45     , fFlattenableHeap(HEAP_BLOCK_SIZE)
46     , fPaints(&fFlattenableHeap)
47     , fRecordFlags(flags)
48     , fOptsEnabled(kBeClever) {
49 
50     fBitmapHeap = SkNEW(SkBitmapHeap);
51     fFlattenableHeap.setBitmapStorage(fBitmapHeap);
52 
53     fFirstSavedLayerIndex = kNoSavedLayerIndex;
54     fInitialSaveCount = kNoInitialSave;
55 }
56 
~SkPictureRecord()57 SkPictureRecord::~SkPictureRecord() {
58     SkSafeUnref(fBitmapHeap);
59     SkSafeUnref(fBoundingHierarchy);
60     SkSafeUnref(fStateTree);
61     fFlattenableHeap.setBitmapStorage(NULL);
62     fPictureRefs.unrefAll();
63     fTextBlobRefs.unrefAll();
64 }
65 
66 ///////////////////////////////////////////////////////////////////////////////
67 
68 // Return the offset of the paint inside a given op's byte stream. A zero
69 // return value means there is no paint (and you really shouldn't be calling
70 // this method)
getPaintOffset(DrawType op,size_t opSize)71 static inline size_t getPaintOffset(DrawType op, size_t opSize) {
72     // These offsets are where the paint would be if the op size doesn't overflow
73     static const uint8_t gPaintOffsets[] = {
74         0,  // UNUSED - no paint
75         0,  // CLIP_PATH - no paint
76         0,  // CLIP_REGION - no paint
77         0,  // CLIP_RECT - no paint
78         0,  // CLIP_RRECT - no paint
79         0,  // CONCAT - no paint
80         1,  // DRAW_BITMAP - right after op code
81         1,  // DRAW_BITMAP_MATRIX - right after op code
82         1,  // DRAW_BITMAP_NINE - right after op code
83         1,  // DRAW_BITMAP_RECT_TO_RECT - right after op code
84         0,  // DRAW_CLEAR - no paint
85         0,  // DRAW_DATA - no paint
86         1,  // DRAW_OVAL - right after op code
87         1,  // DRAW_PAINT - right after op code
88         1,  // DRAW_PATH - right after op code
89         0,  // DRAW_PICTURE - no paint
90         1,  // DRAW_POINTS - right after op code
91         1,  // DRAW_POS_TEXT - right after op code
92         1,  // DRAW_POS_TEXT_TOP_BOTTOM - right after op code
93         1,  // DRAW_POS_TEXT_H - right after op code
94         1,  // DRAW_POS_TEXT_H_TOP_BOTTOM - right after op code
95         1,  // DRAW_RECT - right after op code
96         1,  // DRAW_RRECT - right after op code
97         1,  // DRAW_SPRITE - right after op code
98         1,  // DRAW_TEXT - right after op code
99         1,  // DRAW_TEXT_ON_PATH - right after op code
100         1,  // DRAW_TEXT_TOP_BOTTOM - right after op code
101         1,  // DRAW_VERTICES - right after op code
102         0,  // RESTORE - no paint
103         0,  // ROTATE - no paint
104         0,  // SAVE - no paint
105         0,  // SAVE_LAYER - see below - this paint's location varies
106         0,  // SCALE - no paint
107         0,  // SET_MATRIX - no paint
108         0,  // SKEW - no paint
109         0,  // TRANSLATE - no paint
110         0,  // NOOP - no paint
111         0,  // BEGIN_GROUP - no paint
112         0,  // COMMENT - no paint
113         0,  // END_GROUP - no paint
114         1,  // DRAWDRRECT - right after op code
115         0,  // PUSH_CULL - no paint
116         0,  // POP_CULL - no paint
117         1,  // DRAW_PATCH - right after op code
118         1,  // DRAW_PICTURE_MATRIX_PAINT - right after op code
119         1,  // DRAW_TEXT_BLOB- right after op code
120     };
121 
122     SK_COMPILE_ASSERT(sizeof(gPaintOffsets) == LAST_DRAWTYPE_ENUM + 1,
123                       need_to_be_in_sync);
124     SkASSERT((unsigned)op <= (unsigned)LAST_DRAWTYPE_ENUM);
125 
126     int overflow = 0;
127     if (0 != (opSize & ~MASK_24) || opSize == MASK_24) {
128         // This op's size overflows so an extra uint32_t will be written
129         // after the op code
130         overflow = sizeof(uint32_t);
131     }
132 
133     if (SAVE_LAYER == op) {
134         static const uint32_t kSaveLayerNoBoundsPaintOffset = 2 * kUInt32Size;
135         static const uint32_t kSaveLayerWithBoundsPaintOffset = 2 * kUInt32Size + sizeof(SkRect);
136 
137         if (kSaveLayerNoBoundsSize == opSize) {
138             return kSaveLayerNoBoundsPaintOffset + overflow;
139         } else {
140             SkASSERT(kSaveLayerWithBoundsSize == opSize);
141             return kSaveLayerWithBoundsPaintOffset + overflow;
142         }
143     }
144 
145     SkASSERT(0 != gPaintOffsets[op]);   // really shouldn't be calling this method
146     return gPaintOffsets[op] * sizeof(uint32_t) + overflow;
147 }
148 
willSave()149 void SkPictureRecord::willSave() {
150     // record the offset to us, making it non-positive to distinguish a save
151     // from a clip entry.
152     fRestoreOffsetStack.push(-(int32_t)fWriter.bytesWritten());
153     this->recordSave();
154 
155     this->INHERITED::willSave();
156 }
157 
recordSave()158 void SkPictureRecord::recordSave() {
159     fContentInfo.onSave();
160 
161     // op only
162     size_t size = kSaveSize;
163     size_t initialOffset = this->addDraw(SAVE, &size);
164 
165     this->validate(initialOffset, size);
166 }
167 
willSaveLayer(const SkRect * bounds,const SkPaint * paint,SaveFlags flags)168 SkCanvas::SaveLayerStrategy SkPictureRecord::willSaveLayer(const SkRect* bounds,
169                                                            const SkPaint* paint, SaveFlags flags) {
170     // record the offset to us, making it non-positive to distinguish a save
171     // from a clip entry.
172     fRestoreOffsetStack.push(-(int32_t)fWriter.bytesWritten());
173     this->recordSaveLayer(bounds, paint, flags);
174     if (kNoSavedLayerIndex == fFirstSavedLayerIndex) {
175         fFirstSavedLayerIndex = fRestoreOffsetStack.count();
176     }
177 
178     this->INHERITED::willSaveLayer(bounds, paint, flags);
179     /*  No need for a (potentially very big) layer which we don't actually need
180         at this time (and may not be able to afford since during record our
181         clip starts out the size of the picture, which is often much larger
182         than the size of the actual device we'll use during playback).
183      */
184     return kNoLayer_SaveLayerStrategy;
185 }
186 
recordSaveLayer(const SkRect * bounds,const SkPaint * paint,SaveFlags flags)187 void SkPictureRecord::recordSaveLayer(const SkRect* bounds, const SkPaint* paint,
188                                       SaveFlags flags) {
189     fContentInfo.onSaveLayer();
190 
191     // op + bool for 'bounds'
192     size_t size = 2 * kUInt32Size;
193     if (bounds) {
194         size += sizeof(*bounds); // + rect
195     }
196     // + paint index + flags
197     size += 2 * kUInt32Size;
198 
199     SkASSERT(kSaveLayerNoBoundsSize == size || kSaveLayerWithBoundsSize == size);
200 
201     size_t initialOffset = this->addDraw(SAVE_LAYER, &size);
202     this->addRectPtr(bounds);
203     SkASSERT(initialOffset+getPaintOffset(SAVE_LAYER, size) == fWriter.bytesWritten());
204     this->addPaintPtr(paint);
205     this->addInt(flags);
206 
207     this->validate(initialOffset, size);
208 }
209 
isDrawingToLayer() const210 bool SkPictureRecord::isDrawingToLayer() const {
211     return fFirstSavedLayerIndex != kNoSavedLayerIndex;
212 }
213 
214 /*
215  * Read the op code from 'offset' in 'writer'.
216  */
217 #ifdef SK_DEBUG
peek_op(SkWriter32 * writer,size_t offset)218 static DrawType peek_op(SkWriter32* writer, size_t offset) {
219     return (DrawType)(writer->readTAt<uint32_t>(offset) >> 24);
220 }
221 #endif
222 
223 /*
224  * Read the op code from 'offset' in 'writer' and extract the size too.
225  */
peek_op_and_size(SkWriter32 * writer,size_t offset,uint32_t * size)226 static DrawType peek_op_and_size(SkWriter32* writer, size_t offset, uint32_t* size) {
227     uint32_t peek = writer->readTAt<uint32_t>(offset);
228 
229     uint32_t op;
230     UNPACK_8_24(peek, op, *size);
231     if (MASK_24 == *size) {
232         // size required its own slot right after the op code
233         *size = writer->readTAt<uint32_t>(offset + kUInt32Size);
234     }
235     return (DrawType) op;
236 }
237 
238 // Is the supplied paint simply a color?
is_simple(const SkPaint & p)239 static bool is_simple(const SkPaint& p) {
240     intptr_t orAccum = (intptr_t)p.getPathEffect()  |
241                        (intptr_t)p.getShader()      |
242                        (intptr_t)p.getXfermode()    |
243                        (intptr_t)p.getMaskFilter()  |
244                        (intptr_t)p.getColorFilter() |
245                        (intptr_t)p.getRasterizer()  |
246                        (intptr_t)p.getLooper()      |
247                        (intptr_t)p.getImageFilter();
248     return 0 == orAccum;
249 }
250 
251 // CommandInfos are fed to the 'match' method and filled in with command
252 // information.
253 struct CommandInfo {
254     DrawType fActualOp;
255     uint32_t fOffset;
256     uint32_t fSize;
257 };
258 
259 /*
260  * Attempt to match the provided pattern of commands starting at 'offset'
261  * in the byte stream and stopping at the end of the stream. Upon success,
262  * return true with all the pattern information filled out in the result
263  * array (i.e., actual ops, offsets and sizes).
264  * Note this method skips any NOOPs seen in the stream
265  */
match(SkWriter32 * writer,uint32_t offset,int * pattern,CommandInfo * result,int numCommands)266 static bool match(SkWriter32* writer, uint32_t offset,
267                   int* pattern, CommandInfo* result, int numCommands) {
268     SkASSERT(offset < writer->bytesWritten());
269 
270     uint32_t curOffset = offset;
271     uint32_t curSize = 0;
272     int numMatched;
273     for (numMatched = 0; numMatched < numCommands && curOffset < writer->bytesWritten(); ++numMatched) {
274         DrawType op = peek_op_and_size(writer, curOffset, &curSize);
275         while (NOOP == op) {
276             curOffset += curSize;
277             if (curOffset >= writer->bytesWritten()) {
278                 return false;
279             }
280             op = peek_op_and_size(writer, curOffset, &curSize);
281         }
282 
283         if (kDRAW_BITMAP_FLAVOR == pattern[numMatched]) {
284             if (DRAW_BITMAP != op && DRAW_BITMAP_MATRIX != op &&
285                 DRAW_BITMAP_NINE != op && DRAW_BITMAP_RECT_TO_RECT != op) {
286                 return false;
287             }
288         } else if (op != pattern[numMatched]) {
289             return false;
290         }
291 
292         result[numMatched].fActualOp = op;
293         result[numMatched].fOffset = curOffset;
294         result[numMatched].fSize = curSize;
295 
296         curOffset += curSize;
297     }
298 
299     if (numMatched != numCommands) {
300         return false;
301     }
302 
303     if (curOffset < writer->bytesWritten()) {
304         // Something else between the last command and the end of the stream
305         return false;
306     }
307 
308     return true;
309 }
310 
311 // temporarily here to make code review easier
312 static bool merge_savelayer_paint_into_drawbitmp(SkWriter32* writer,
313                                                  SkPaintDictionary* paintDict,
314                                                  const CommandInfo& saveLayerInfo,
315                                                  const CommandInfo& dbmInfo);
316 
317 /*
318  * Restore has just been called (but not recorded), look back at the
319  * matching save* and see if we are in the configuration:
320  *   SAVE_LAYER
321  *       DRAW_BITMAP|DRAW_BITMAP_MATRIX|DRAW_BITMAP_NINE|DRAW_BITMAP_RECT_TO_RECT
322  *   RESTORE
323  * where the saveLayer's color can be moved into the drawBitmap*'s paint
324  */
remove_save_layer1(SkWriter32 * writer,int32_t offset,SkPaintDictionary * paintDict)325 static bool remove_save_layer1(SkWriter32* writer, int32_t offset,
326                                SkPaintDictionary* paintDict) {
327     // back up to the save block
328     // TODO: add a stack to track save*/restore offsets rather than searching backwards
329     while (offset > 0) {
330         offset = writer->readTAt<uint32_t>(offset);
331     }
332 
333     int pattern[] = { SAVE_LAYER, kDRAW_BITMAP_FLAVOR, /* RESTORE */ };
334     CommandInfo result[SK_ARRAY_COUNT(pattern)];
335 
336     if (!match(writer, -offset, pattern, result, SK_ARRAY_COUNT(pattern))) {
337         return false;
338     }
339 
340     if (kSaveLayerWithBoundsSize == result[0].fSize) {
341         // The saveLayer's bound can offset where the dbm is drawn
342         return false;
343     }
344 
345     return merge_savelayer_paint_into_drawbitmp(writer, paintDict,
346                                                 result[0], result[1]);
347 }
348 
349 /*
350  * Convert the command code located at 'offset' to a NOOP. Leave the size
351  * field alone so the NOOP can be skipped later.
352  */
convert_command_to_noop(SkWriter32 * writer,uint32_t offset)353 static void convert_command_to_noop(SkWriter32* writer, uint32_t offset) {
354     uint32_t command = writer->readTAt<uint32_t>(offset);
355     writer->overwriteTAt(offset, (command & MASK_24) | (NOOP << 24));
356 }
357 
358 /*
359  * Attempt to merge the saveLayer's paint into the drawBitmap*'s paint.
360  * Return true on success; false otherwise.
361  */
merge_savelayer_paint_into_drawbitmp(SkWriter32 * writer,SkPaintDictionary * paintDict,const CommandInfo & saveLayerInfo,const CommandInfo & dbmInfo)362 static bool merge_savelayer_paint_into_drawbitmp(SkWriter32* writer,
363                                                  SkPaintDictionary* paintDict,
364                                                  const CommandInfo& saveLayerInfo,
365                                                  const CommandInfo& dbmInfo) {
366     SkASSERT(SAVE_LAYER == saveLayerInfo.fActualOp);
367     SkASSERT(DRAW_BITMAP == dbmInfo.fActualOp ||
368              DRAW_BITMAP_MATRIX == dbmInfo.fActualOp ||
369              DRAW_BITMAP_NINE == dbmInfo.fActualOp ||
370              DRAW_BITMAP_RECT_TO_RECT == dbmInfo.fActualOp);
371 
372     size_t dbmPaintOffset = getPaintOffset(dbmInfo.fActualOp, dbmInfo.fSize);
373     size_t slPaintOffset = getPaintOffset(SAVE_LAYER, saveLayerInfo.fSize);
374 
375     // we have a match, now we need to get the paints involved
376     uint32_t dbmPaintId = writer->readTAt<uint32_t>(dbmInfo.fOffset + dbmPaintOffset);
377     uint32_t saveLayerPaintId = writer->readTAt<uint32_t>(saveLayerInfo.fOffset + slPaintOffset);
378 
379     if (0 == saveLayerPaintId) {
380         // In this case the saveLayer/restore isn't needed at all - just kill the saveLayer
381         // and signal the caller (by returning true) to not add the RESTORE op
382         convert_command_to_noop(writer, saveLayerInfo.fOffset);
383         return true;
384     }
385 
386     if (0 == dbmPaintId) {
387         // In this case just make the DBM* use the saveLayer's paint, kill the saveLayer
388         // and signal the caller (by returning true) to not add the RESTORE op
389         convert_command_to_noop(writer, saveLayerInfo.fOffset);
390         writer->overwriteTAt(dbmInfo.fOffset + dbmPaintOffset, saveLayerPaintId);
391         return true;
392     }
393 
394     SkAutoTDelete<SkPaint> saveLayerPaint(paintDict->unflatten(saveLayerPaintId));
395     if (NULL == saveLayerPaint.get() || !is_simple(*saveLayerPaint)) {
396         return false;
397     }
398 
399     // For this optimization we only fold the saveLayer and drawBitmapRect
400     // together if the saveLayer's draw is simple (i.e., no fancy effects) and
401     // and the only difference in the colors is that the saveLayer's can have
402     // an alpha while the drawBitmapRect's is opaque.
403     // TODO: it should be possible to fold them together even if they both
404     // have different non-255 alphas
405     SkColor layerColor = saveLayerPaint->getColor() | 0xFF000000; // force opaque
406 
407     SkAutoTDelete<SkPaint> dbmPaint(paintDict->unflatten(dbmPaintId));
408     if (NULL == dbmPaint.get() || dbmPaint->getColor() != layerColor || !is_simple(*dbmPaint)) {
409         return false;
410     }
411 
412     SkColor newColor = SkColorSetA(dbmPaint->getColor(),
413                                    SkColorGetA(saveLayerPaint->getColor()));
414     dbmPaint->setColor(newColor);
415 
416     const SkFlatData* data = paintDict->findAndReturnFlat(*dbmPaint);
417     if (NULL == data) {
418         return false;
419     }
420 
421     // kill the saveLayer and alter the DBMR2R's paint to be the modified one
422     convert_command_to_noop(writer, saveLayerInfo.fOffset);
423     writer->overwriteTAt(dbmInfo.fOffset + dbmPaintOffset, data->index());
424     return true;
425 }
426 
427 /*
428  * Restore has just been called (but not recorded), look back at the
429  * matching save* and see if we are in the configuration:
430  *   SAVE_LAYER (with NULL == bounds)
431  *      SAVE
432  *         CLIP_RECT
433  *         DRAW_BITMAP|DRAW_BITMAP_MATRIX|DRAW_BITMAP_NINE|DRAW_BITMAP_RECT_TO_RECT
434  *      RESTORE
435  *   RESTORE
436  * where the saveLayer's color can be moved into the drawBitmap*'s paint
437  */
remove_save_layer2(SkWriter32 * writer,int32_t offset,SkPaintDictionary * paintDict)438 static bool remove_save_layer2(SkWriter32* writer, int32_t offset,
439                                SkPaintDictionary* paintDict) {
440     // back up to the save block
441     // TODO: add a stack to track save*/restore offsets rather than searching backwards
442     while (offset > 0) {
443         offset = writer->readTAt<uint32_t>(offset);
444     }
445 
446     int pattern[] = { SAVE_LAYER, SAVE, CLIP_RECT, kDRAW_BITMAP_FLAVOR, RESTORE, /* RESTORE */ };
447     CommandInfo result[SK_ARRAY_COUNT(pattern)];
448 
449     if (!match(writer, -offset, pattern, result, SK_ARRAY_COUNT(pattern))) {
450         return false;
451     }
452 
453     if (kSaveLayerWithBoundsSize == result[0].fSize) {
454         // The saveLayer's bound can offset where the dbm is drawn
455         return false;
456     }
457 
458     return merge_savelayer_paint_into_drawbitmp(writer, paintDict,
459                                                 result[0], result[3]);
460 }
461 
is_drawing_op(DrawType op)462 static bool is_drawing_op(DrawType op) {
463 
464     // FIXME: yuck. convert to a lookup table?
465     return (op > CONCAT && op < ROTATE)
466             || DRAW_DRRECT == op
467             || DRAW_PATCH == op
468             || DRAW_PICTURE_MATRIX_PAINT == op
469             || DRAW_TEXT_BLOB == op;
470 }
471 
472 /*
473  *  Restore has just been called (but not recorded), so look back at the
474  *  matching save(), and see if we can eliminate the pair of them, due to no
475  *  intervening matrix/clip calls.
476  *
477  *  If so, update the writer and return true, in which case we won't even record
478  *  the restore() call. If we still need the restore(), return false.
479  */
collapse_save_clip_restore(SkWriter32 * writer,int32_t offset,SkPaintDictionary * paintDict)480 static bool collapse_save_clip_restore(SkWriter32* writer, int32_t offset,
481                                        SkPaintDictionary* paintDict) {
482     int32_t restoreOffset = (int32_t)writer->bytesWritten();
483 
484     // back up to the save block
485     while (offset > 0) {
486         offset = writer->readTAt<uint32_t>(offset);
487     }
488 
489     // now offset points to a save
490     offset = -offset;
491     uint32_t opSize;
492     DrawType op = peek_op_and_size(writer, offset, &opSize);
493     if (SAVE_LAYER == op) {
494         // not ready to cull these out yet (mrr)
495         return false;
496     }
497     SkASSERT(SAVE == op);
498     SkASSERT(kSaveSize == opSize);
499 
500     // Walk forward until we get back to either a draw-verb (abort) or we hit
501     // our restore (success).
502     int32_t saveOffset = offset;
503 
504     offset += opSize;
505     while (offset < restoreOffset) {
506         op = peek_op_and_size(writer, offset, &opSize);
507         if (is_drawing_op(op) || (SAVE_LAYER == op)) {
508             // drawing verb, abort
509             return false;
510         }
511         offset += opSize;
512     }
513 
514     writer->rewindToOffset(saveOffset);
515     return true;
516 }
517 
518 typedef bool (*PictureRecordOptProc)(SkWriter32* writer, int32_t offset,
519                                      SkPaintDictionary* paintDict);
520 enum PictureRecordOptType {
521     kRewind_OptType,  // Optimization rewinds the command stream
522     kCollapseSaveLayer_OptType,  // Optimization eliminates a save/restore pair
523 };
524 
525 enum PictureRecordOptFlags {
526     kSkipIfBBoxHierarchy_Flag  = 0x1,  // Optimization should be skipped if the
527                                        // SkPicture has a bounding box hierarchy.
528     kRescindLastSave_Flag      = 0x2,
529     kRescindLastSaveLayer_Flag = 0x4,
530 };
531 
532 struct PictureRecordOpt {
533     PictureRecordOptProc fProc;
534     PictureRecordOptType fType;
535     unsigned fFlags;
536 };
537 /*
538  * A list of the optimizations that are tried upon seeing a restore
539  * TODO: add a real API for such optimizations
540  *       Add the ability to fire optimizations on any op (not just RESTORE)
541  */
542 static const PictureRecordOpt gPictureRecordOpts[] = {
543     // 'collapse_save_clip_restore' is skipped if there is a BBoxHierarchy
544     // because it is redundant with the state traversal optimization in
545     // SkPictureStateTree, and applying the optimization introduces significant
546     // record time overhead because it requires rewinding contents that were
547     // recorded into the BBoxHierarchy.
548     { collapse_save_clip_restore, kRewind_OptType,
549                                                 kSkipIfBBoxHierarchy_Flag|kRescindLastSave_Flag },
550     { remove_save_layer1,         kCollapseSaveLayer_OptType, kRescindLastSaveLayer_Flag },
551     { remove_save_layer2,         kCollapseSaveLayer_OptType, kRescindLastSaveLayer_Flag }
552 };
553 
554 // This is called after an optimization has been applied to the command stream
555 // in order to adjust the contents and state of the bounding box hierarchy and
556 // state tree to reflect the optimization.
apply_optimization_to_bbh(PictureRecordOptType opt,SkPictureStateTree * stateTree,SkBBoxHierarchy * boundingHierarchy)557 static void apply_optimization_to_bbh(PictureRecordOptType opt, SkPictureStateTree* stateTree,
558                                       SkBBoxHierarchy* boundingHierarchy) {
559     switch (opt) {
560     case kCollapseSaveLayer_OptType:
561         if (stateTree) {
562             stateTree->saveCollapsed();
563         }
564         break;
565     case kRewind_OptType:
566         if (boundingHierarchy) {
567             boundingHierarchy->rewindInserts();
568         }
569         // Note: No need to touch the state tree for this to work correctly.
570         // Unused branches do not burden the playback, and pruning the tree
571         // would be O(N^2), so it is best to leave it alone.
572         break;
573     default:
574         SkASSERT(0);
575     }
576 }
577 
willRestore()578 void SkPictureRecord::willRestore() {
579     // FIXME: SkDeferredCanvas needs to be refactored to respect
580     // save/restore balancing so that the following test can be
581     // turned on permanently.
582 #if 0
583     SkASSERT(fRestoreOffsetStack.count() > 1);
584 #endif
585 
586     // check for underflow
587     if (fRestoreOffsetStack.count() == 0) {
588         return;
589     }
590 
591     if (fRestoreOffsetStack.count() == fFirstSavedLayerIndex) {
592         fFirstSavedLayerIndex = kNoSavedLayerIndex;
593     }
594 
595     size_t opt = 0;
596     if (fOptsEnabled) {
597         for (opt = 0; opt < SK_ARRAY_COUNT(gPictureRecordOpts); ++opt) {
598             if (0 != (gPictureRecordOpts[opt].fFlags & kSkipIfBBoxHierarchy_Flag)
599                 && fBoundingHierarchy) {
600                 continue;
601             }
602             if ((*gPictureRecordOpts[opt].fProc)(&fWriter, fRestoreOffsetStack.top(), &fPaints)) {
603                 // Some optimization fired so don't add the RESTORE
604                 apply_optimization_to_bbh(gPictureRecordOpts[opt].fType,
605                                           fStateTree, fBoundingHierarchy);
606                 if (gPictureRecordOpts[opt].fFlags & kRescindLastSave_Flag) {
607                     fContentInfo.rescindLastSave();
608                 } else if (gPictureRecordOpts[opt].fFlags & kRescindLastSaveLayer_Flag) {
609                     fContentInfo.rescindLastSaveLayer();
610                 }
611                 break;
612             }
613         }
614     }
615 
616     if (!fOptsEnabled || SK_ARRAY_COUNT(gPictureRecordOpts) == opt) {
617         // No optimization fired so add the RESTORE
618         this->recordRestore();
619     }
620 
621     fRestoreOffsetStack.pop();
622 
623     this->INHERITED::willRestore();
624 }
625 
recordRestore(bool fillInSkips)626 void SkPictureRecord::recordRestore(bool fillInSkips) {
627     fContentInfo.onRestore();
628 
629     if (fillInSkips) {
630         this->fillRestoreOffsetPlaceholdersForCurrentStackLevel((uint32_t)fWriter.bytesWritten());
631     }
632     size_t size = 1 * kUInt32Size; // RESTORE consists solely of 1 op code
633     size_t initialOffset = this->addDraw(RESTORE, &size);
634     this->validate(initialOffset, size);
635 }
636 
recordTranslate(const SkMatrix & m)637 void SkPictureRecord::recordTranslate(const SkMatrix& m) {
638     SkASSERT(SkMatrix::kTranslate_Mask == m.getType());
639 
640     // op + dx + dy
641     size_t size = 1 * kUInt32Size + 2 * sizeof(SkScalar);
642     size_t initialOffset = this->addDraw(TRANSLATE, &size);
643     this->addScalar(m.getTranslateX());
644     this->addScalar(m.getTranslateY());
645     this->validate(initialOffset, size);
646 }
647 
recordScale(const SkMatrix & m)648 void SkPictureRecord::recordScale(const SkMatrix& m) {
649     SkASSERT(SkMatrix::kScale_Mask == m.getType());
650 
651     // op + sx + sy
652     size_t size = 1 * kUInt32Size + 2 * sizeof(SkScalar);
653     size_t initialOffset = this->addDraw(SCALE, &size);
654     this->addScalar(m.getScaleX());
655     this->addScalar(m.getScaleY());
656     this->validate(initialOffset, size);
657 }
658 
didConcat(const SkMatrix & matrix)659 void SkPictureRecord::didConcat(const SkMatrix& matrix) {
660     switch (matrix.getType()) {
661         case SkMatrix::kTranslate_Mask:
662             this->recordTranslate(matrix);
663             break;
664         case SkMatrix::kScale_Mask:
665             this->recordScale(matrix);
666             break;
667         default:
668             this->recordConcat(matrix);
669             break;
670     }
671     this->INHERITED::didConcat(matrix);
672 }
673 
recordConcat(const SkMatrix & matrix)674 void SkPictureRecord::recordConcat(const SkMatrix& matrix) {
675     this->validate(fWriter.bytesWritten(), 0);
676     // op + matrix
677     size_t size = kUInt32Size + matrix.writeToMemory(NULL);
678     size_t initialOffset = this->addDraw(CONCAT, &size);
679     this->addMatrix(matrix);
680     this->validate(initialOffset, size);
681 }
682 
didSetMatrix(const SkMatrix & matrix)683 void SkPictureRecord::didSetMatrix(const SkMatrix& matrix) {
684     this->validate(fWriter.bytesWritten(), 0);
685     // op + matrix
686     size_t size = kUInt32Size + matrix.writeToMemory(NULL);
687     size_t initialOffset = this->addDraw(SET_MATRIX, &size);
688     this->addMatrix(matrix);
689     this->validate(initialOffset, size);
690     this->INHERITED::didSetMatrix(matrix);
691 }
692 
regionOpExpands(SkRegion::Op op)693 static bool regionOpExpands(SkRegion::Op op) {
694     switch (op) {
695         case SkRegion::kUnion_Op:
696         case SkRegion::kXOR_Op:
697         case SkRegion::kReverseDifference_Op:
698         case SkRegion::kReplace_Op:
699             return true;
700         case SkRegion::kIntersect_Op:
701         case SkRegion::kDifference_Op:
702             return false;
703         default:
704             SkDEBUGFAIL("unknown region op");
705             return false;
706     }
707 }
708 
fillRestoreOffsetPlaceholdersForCurrentStackLevel(uint32_t restoreOffset)709 void SkPictureRecord::fillRestoreOffsetPlaceholdersForCurrentStackLevel(uint32_t restoreOffset) {
710     int32_t offset = fRestoreOffsetStack.top();
711     while (offset > 0) {
712         uint32_t peek = fWriter.readTAt<uint32_t>(offset);
713         fWriter.overwriteTAt(offset, restoreOffset);
714         offset = peek;
715     }
716 
717 #ifdef SK_DEBUG
718     // assert that the final offset value points to a save verb
719     uint32_t opSize;
720     DrawType drawOp = peek_op_and_size(&fWriter, -offset, &opSize);
721     SkASSERT(SAVE == drawOp || SAVE_LAYER == drawOp);
722 #endif
723 }
724 
beginRecording()725 void SkPictureRecord::beginRecording() {
726     // we have to call this *after* our constructor, to ensure that it gets
727     // recorded. This is balanced by restoreToCount() call from endRecording,
728     // which in-turn calls our overridden restore(), so those get recorded too.
729     fInitialSaveCount = this->save();
730 }
731 
endRecording()732 void SkPictureRecord::endRecording() {
733     SkASSERT(kNoInitialSave != fInitialSaveCount);
734     this->restoreToCount(fInitialSaveCount);
735 }
736 
recordRestoreOffsetPlaceholder(SkRegion::Op op)737 size_t SkPictureRecord::recordRestoreOffsetPlaceholder(SkRegion::Op op) {
738     if (fRestoreOffsetStack.isEmpty()) {
739         return -1;
740     }
741 
742     // The RestoreOffset field is initially filled with a placeholder
743     // value that points to the offset of the previous RestoreOffset
744     // in the current stack level, thus forming a linked list so that
745     // the restore offsets can be filled in when the corresponding
746     // restore command is recorded.
747     int32_t prevOffset = fRestoreOffsetStack.top();
748 
749     if (regionOpExpands(op)) {
750         // Run back through any previous clip ops, and mark their offset to
751         // be 0, disabling their ability to trigger a jump-to-restore, otherwise
752         // they could hide this clips ability to expand the clip (i.e. go from
753         // empty to non-empty).
754         this->fillRestoreOffsetPlaceholdersForCurrentStackLevel(0);
755 
756         // Reset the pointer back to the previous clip so that subsequent
757         // restores don't overwrite the offsets we just cleared.
758         prevOffset = 0;
759     }
760 
761     size_t offset = fWriter.bytesWritten();
762     this->addInt(prevOffset);
763     fRestoreOffsetStack.top() = SkToU32(offset);
764     return offset;
765 }
766 
onClipRect(const SkRect & rect,SkRegion::Op op,ClipEdgeStyle edgeStyle)767 void SkPictureRecord::onClipRect(const SkRect& rect, SkRegion::Op op, ClipEdgeStyle edgeStyle) {
768     this->recordClipRect(rect, op, kSoft_ClipEdgeStyle == edgeStyle);
769     this->INHERITED::onClipRect(rect, op, edgeStyle);
770 }
771 
recordClipRect(const SkRect & rect,SkRegion::Op op,bool doAA)772 size_t SkPictureRecord::recordClipRect(const SkRect& rect, SkRegion::Op op, bool doAA) {
773     // id + rect + clip params
774     size_t size = 1 * kUInt32Size + sizeof(rect) + 1 * kUInt32Size;
775     // recordRestoreOffsetPlaceholder doesn't always write an offset
776     if (!fRestoreOffsetStack.isEmpty()) {
777         // + restore offset
778         size += kUInt32Size;
779     }
780     size_t initialOffset = this->addDraw(CLIP_RECT, &size);
781     this->addRect(rect);
782     this->addInt(ClipParams_pack(op, doAA));
783     size_t offset = this->recordRestoreOffsetPlaceholder(op);
784 
785     this->validate(initialOffset, size);
786     return offset;
787 }
788 
onClipRRect(const SkRRect & rrect,SkRegion::Op op,ClipEdgeStyle edgeStyle)789 void SkPictureRecord::onClipRRect(const SkRRect& rrect, SkRegion::Op op, ClipEdgeStyle edgeStyle) {
790     this->recordClipRRect(rrect, op, kSoft_ClipEdgeStyle == edgeStyle);
791     this->INHERITED::onClipRRect(rrect, op, edgeStyle);
792 }
793 
recordClipRRect(const SkRRect & rrect,SkRegion::Op op,bool doAA)794 size_t SkPictureRecord::recordClipRRect(const SkRRect& rrect, SkRegion::Op op, bool doAA) {
795     // op + rrect + clip params
796     size_t size = 1 * kUInt32Size + SkRRect::kSizeInMemory + 1 * kUInt32Size;
797     // recordRestoreOffsetPlaceholder doesn't always write an offset
798     if (!fRestoreOffsetStack.isEmpty()) {
799         // + restore offset
800         size += kUInt32Size;
801     }
802     size_t initialOffset = this->addDraw(CLIP_RRECT, &size);
803     this->addRRect(rrect);
804     this->addInt(ClipParams_pack(op, doAA));
805     size_t offset = recordRestoreOffsetPlaceholder(op);
806     this->validate(initialOffset, size);
807     return offset;
808 }
809 
onClipPath(const SkPath & path,SkRegion::Op op,ClipEdgeStyle edgeStyle)810 void SkPictureRecord::onClipPath(const SkPath& path, SkRegion::Op op, ClipEdgeStyle edgeStyle) {
811     int pathID = this->addPathToHeap(path);
812     this->recordClipPath(pathID, op, kSoft_ClipEdgeStyle == edgeStyle);
813     this->INHERITED::onClipPath(path, op, edgeStyle);
814 }
815 
recordClipPath(int pathID,SkRegion::Op op,bool doAA)816 size_t SkPictureRecord::recordClipPath(int pathID, SkRegion::Op op, bool doAA) {
817     // op + path index + clip params
818     size_t size = 3 * kUInt32Size;
819     // recordRestoreOffsetPlaceholder doesn't always write an offset
820     if (!fRestoreOffsetStack.isEmpty()) {
821         // + restore offset
822         size += kUInt32Size;
823     }
824     size_t initialOffset = this->addDraw(CLIP_PATH, &size);
825     this->addInt(pathID);
826     this->addInt(ClipParams_pack(op, doAA));
827     size_t offset = recordRestoreOffsetPlaceholder(op);
828     this->validate(initialOffset, size);
829     return offset;
830 }
831 
onClipRegion(const SkRegion & region,SkRegion::Op op)832 void SkPictureRecord::onClipRegion(const SkRegion& region, SkRegion::Op op) {
833     this->recordClipRegion(region, op);
834     this->INHERITED::onClipRegion(region, op);
835 }
836 
recordClipRegion(const SkRegion & region,SkRegion::Op op)837 size_t SkPictureRecord::recordClipRegion(const SkRegion& region, SkRegion::Op op) {
838     // op + clip params + region
839     size_t size = 2 * kUInt32Size + region.writeToMemory(NULL);
840     // recordRestoreOffsetPlaceholder doesn't always write an offset
841     if (!fRestoreOffsetStack.isEmpty()) {
842         // + restore offset
843         size += kUInt32Size;
844     }
845     size_t initialOffset = this->addDraw(CLIP_REGION, &size);
846     this->addRegion(region);
847     this->addInt(ClipParams_pack(op, false));
848     size_t offset = this->recordRestoreOffsetPlaceholder(op);
849 
850     this->validate(initialOffset, size);
851     return offset;
852 }
853 
clear(SkColor color)854 void SkPictureRecord::clear(SkColor color) {
855     // op + color
856     size_t size = 2 * kUInt32Size;
857     size_t initialOffset = this->addDraw(DRAW_CLEAR, &size);
858     this->addInt(color);
859     this->validate(initialOffset, size);
860 }
861 
drawPaint(const SkPaint & paint)862 void SkPictureRecord::drawPaint(const SkPaint& paint) {
863     // op + paint index
864     size_t size = 2 * kUInt32Size;
865     size_t initialOffset = this->addDraw(DRAW_PAINT, &size);
866     SkASSERT(initialOffset+getPaintOffset(DRAW_PAINT, size) == fWriter.bytesWritten());
867     this->addPaint(paint);
868     this->validate(initialOffset, size);
869 }
870 
drawPoints(PointMode mode,size_t count,const SkPoint pts[],const SkPaint & paint)871 void SkPictureRecord::drawPoints(PointMode mode, size_t count, const SkPoint pts[],
872                                  const SkPaint& paint) {
873     fContentInfo.onDrawPoints(count, paint);
874 
875     // op + paint index + mode + count + point data
876     size_t size = 4 * kUInt32Size + count * sizeof(SkPoint);
877     size_t initialOffset = this->addDraw(DRAW_POINTS, &size);
878     SkASSERT(initialOffset+getPaintOffset(DRAW_POINTS, size) == fWriter.bytesWritten());
879     this->addPaint(paint);
880 
881     this->addInt(mode);
882     this->addInt(SkToInt(count));
883     fWriter.writeMul4(pts, count * sizeof(SkPoint));
884     this->validate(initialOffset, size);
885 }
886 
drawOval(const SkRect & oval,const SkPaint & paint)887 void SkPictureRecord::drawOval(const SkRect& oval, const SkPaint& paint) {
888     // op + paint index + rect
889     size_t size = 2 * kUInt32Size + sizeof(oval);
890     size_t initialOffset = this->addDraw(DRAW_OVAL, &size);
891     SkASSERT(initialOffset+getPaintOffset(DRAW_OVAL, size) == fWriter.bytesWritten());
892     this->addPaint(paint);
893     this->addRect(oval);
894     this->validate(initialOffset, size);
895 }
896 
drawRect(const SkRect & rect,const SkPaint & paint)897 void SkPictureRecord::drawRect(const SkRect& rect, const SkPaint& paint) {
898     // op + paint index + rect
899     size_t size = 2 * kUInt32Size + sizeof(rect);
900     size_t initialOffset = this->addDraw(DRAW_RECT, &size);
901     SkASSERT(initialOffset+getPaintOffset(DRAW_RECT, size) == fWriter.bytesWritten());
902     this->addPaint(paint);
903     this->addRect(rect);
904     this->validate(initialOffset, size);
905 }
906 
drawRRect(const SkRRect & rrect,const SkPaint & paint)907 void SkPictureRecord::drawRRect(const SkRRect& rrect, const SkPaint& paint) {
908     if (rrect.isRect() && kBeClever) {
909         this->SkPictureRecord::drawRect(rrect.getBounds(), paint);
910     } else if (rrect.isOval() && kBeClever) {
911         this->SkPictureRecord::drawOval(rrect.getBounds(), paint);
912     } else {
913         // op + paint index + rrect
914         size_t size = 2 * kUInt32Size + SkRRect::kSizeInMemory;
915         size_t initialOffset = this->addDraw(DRAW_RRECT, &size);
916         SkASSERT(initialOffset+getPaintOffset(DRAW_RRECT, size) == fWriter.bytesWritten());
917         this->addPaint(paint);
918         this->addRRect(rrect);
919         this->validate(initialOffset, size);
920     }
921 }
922 
onDrawDRRect(const SkRRect & outer,const SkRRect & inner,const SkPaint & paint)923 void SkPictureRecord::onDrawDRRect(const SkRRect& outer, const SkRRect& inner,
924                                    const SkPaint& paint) {
925     // op + paint index + rrects
926     size_t size = 2 * kUInt32Size + SkRRect::kSizeInMemory * 2;
927     size_t initialOffset = this->addDraw(DRAW_DRRECT, &size);
928     SkASSERT(initialOffset+getPaintOffset(DRAW_DRRECT, size) == fWriter.bytesWritten());
929     this->addPaint(paint);
930     this->addRRect(outer);
931     this->addRRect(inner);
932     this->validate(initialOffset, size);
933 }
934 
drawPath(const SkPath & path,const SkPaint & paint)935 void SkPictureRecord::drawPath(const SkPath& path, const SkPaint& paint) {
936     fContentInfo.onDrawPath(path, paint);
937 
938     // op + paint index + path index
939     size_t size = 3 * kUInt32Size;
940     size_t initialOffset = this->addDraw(DRAW_PATH, &size);
941     SkASSERT(initialOffset+getPaintOffset(DRAW_PATH, size) == fWriter.bytesWritten());
942     this->addPaint(paint);
943     this->addPath(path);
944     this->validate(initialOffset, size);
945 }
946 
drawBitmap(const SkBitmap & bitmap,SkScalar left,SkScalar top,const SkPaint * paint=NULL)947 void SkPictureRecord::drawBitmap(const SkBitmap& bitmap, SkScalar left, SkScalar top,
948                                  const SkPaint* paint = NULL) {
949     if (bitmap.drawsNothing() && kBeClever) {
950         return;
951     }
952 
953     // op + paint index + bitmap index + left + top
954     size_t size = 3 * kUInt32Size + 2 * sizeof(SkScalar);
955     size_t initialOffset = this->addDraw(DRAW_BITMAP, &size);
956     SkASSERT(initialOffset+getPaintOffset(DRAW_BITMAP, size) == fWriter.bytesWritten());
957     this->addPaintPtr(paint);
958     this->addBitmap(bitmap);
959     this->addScalar(left);
960     this->addScalar(top);
961     this->validate(initialOffset, size);
962 }
963 
drawBitmapRectToRect(const SkBitmap & bitmap,const SkRect * src,const SkRect & dst,const SkPaint * paint,DrawBitmapRectFlags flags)964 void SkPictureRecord::drawBitmapRectToRect(const SkBitmap& bitmap, const SkRect* src,
965                                            const SkRect& dst, const SkPaint* paint,
966                                            DrawBitmapRectFlags flags) {
967     if (bitmap.drawsNothing() && kBeClever) {
968         return;
969     }
970 
971     // id + paint index + bitmap index + bool for 'src' + flags
972     size_t size = 5 * kUInt32Size;
973     if (src) {
974         size += sizeof(*src);   // + rect
975     }
976     size += sizeof(dst);        // + rect
977 
978     size_t initialOffset = this->addDraw(DRAW_BITMAP_RECT_TO_RECT, &size);
979     SkASSERT(initialOffset+getPaintOffset(DRAW_BITMAP_RECT_TO_RECT, size)
980              == fWriter.bytesWritten());
981     this->addPaintPtr(paint);
982     this->addBitmap(bitmap);
983     this->addRectPtr(src);  // may be null
984     this->addRect(dst);
985     this->addInt(flags);
986     this->validate(initialOffset, size);
987 }
988 
drawBitmapMatrix(const SkBitmap & bitmap,const SkMatrix & matrix,const SkPaint * paint)989 void SkPictureRecord::drawBitmapMatrix(const SkBitmap& bitmap, const SkMatrix& matrix,
990                                        const SkPaint* paint) {
991     if (bitmap.drawsNothing() && kBeClever) {
992         return;
993     }
994 
995     // id + paint index + bitmap index + matrix
996     size_t size = 3 * kUInt32Size + matrix.writeToMemory(NULL);
997     size_t initialOffset = this->addDraw(DRAW_BITMAP_MATRIX, &size);
998     SkASSERT(initialOffset+getPaintOffset(DRAW_BITMAP_MATRIX, size) == fWriter.bytesWritten());
999     this->addPaintPtr(paint);
1000     this->addBitmap(bitmap);
1001     this->addMatrix(matrix);
1002     this->validate(initialOffset, size);
1003 }
1004 
drawBitmapNine(const SkBitmap & bitmap,const SkIRect & center,const SkRect & dst,const SkPaint * paint)1005 void SkPictureRecord::drawBitmapNine(const SkBitmap& bitmap, const SkIRect& center,
1006                                      const SkRect& dst, const SkPaint* paint) {
1007     if (bitmap.drawsNothing() && kBeClever) {
1008         return;
1009     }
1010 
1011     // op + paint index + bitmap id + center + dst rect
1012     size_t size = 3 * kUInt32Size + sizeof(center) + sizeof(dst);
1013     size_t initialOffset = this->addDraw(DRAW_BITMAP_NINE, &size);
1014     SkASSERT(initialOffset+getPaintOffset(DRAW_BITMAP_NINE, size) == fWriter.bytesWritten());
1015     this->addPaintPtr(paint);
1016     this->addBitmap(bitmap);
1017     this->addIRect(center);
1018     this->addRect(dst);
1019     this->validate(initialOffset, size);
1020 }
1021 
drawSprite(const SkBitmap & bitmap,int left,int top,const SkPaint * paint=NULL)1022 void SkPictureRecord::drawSprite(const SkBitmap& bitmap, int left, int top,
1023                                  const SkPaint* paint = NULL) {
1024     if (bitmap.drawsNothing() && kBeClever) {
1025         return;
1026     }
1027 
1028     // op + paint index + bitmap index + left + top
1029     size_t size = 5 * kUInt32Size;
1030     size_t initialOffset = this->addDraw(DRAW_SPRITE, &size);
1031     SkASSERT(initialOffset+getPaintOffset(DRAW_SPRITE, size) == fWriter.bytesWritten());
1032     this->addPaintPtr(paint);
1033     this->addBitmap(bitmap);
1034     this->addInt(left);
1035     this->addInt(top);
1036     this->validate(initialOffset, size);
1037 }
1038 
ComputeFontMetricsTopBottom(const SkPaint & paint,SkScalar topbot[2])1039 void SkPictureRecord::ComputeFontMetricsTopBottom(const SkPaint& paint, SkScalar topbot[2]) {
1040     SkPaint::FontMetrics metrics;
1041     paint.getFontMetrics(&metrics);
1042     SkRect bounds;
1043     // construct a rect so we can see any adjustments from the paint.
1044     // we use 0,1 for left,right, just so the rect isn't empty
1045     bounds.set(0, metrics.fTop, SK_Scalar1, metrics.fBottom);
1046     (void)paint.computeFastBounds(bounds, &bounds);
1047     topbot[0] = bounds.fTop;
1048     topbot[1] = bounds.fBottom;
1049 }
1050 
addFontMetricsTopBottom(const SkPaint & paint,const SkFlatData & flat,SkScalar minY,SkScalar maxY)1051 void SkPictureRecord::addFontMetricsTopBottom(const SkPaint& paint, const SkFlatData& flat,
1052                                               SkScalar minY, SkScalar maxY) {
1053     WriteTopBot(paint, flat);
1054     this->addScalar(flat.topBot()[0] + minY);
1055     this->addScalar(flat.topBot()[1] + maxY);
1056 }
1057 
onDrawText(const void * text,size_t byteLength,SkScalar x,SkScalar y,const SkPaint & paint)1058 void SkPictureRecord::onDrawText(const void* text, size_t byteLength, SkScalar x, SkScalar y,
1059                                  const SkPaint& paint) {
1060     bool fast = !paint.isVerticalText() && paint.canComputeFastBounds() && kBeClever;
1061 
1062     // op + paint index + length + 'length' worth of chars + x + y
1063     size_t size = 3 * kUInt32Size + SkAlign4(byteLength) + 2 * sizeof(SkScalar);
1064     if (fast) {
1065         size += 2 * sizeof(SkScalar); // + top & bottom
1066     }
1067 
1068     DrawType op = fast ? DRAW_TEXT_TOP_BOTTOM : DRAW_TEXT;
1069     size_t initialOffset = this->addDraw(op, &size);
1070     SkASSERT(initialOffset+getPaintOffset(op, size) == fWriter.bytesWritten());
1071     const SkFlatData* flatPaintData = addPaint(paint);
1072     SkASSERT(flatPaintData);
1073     this->addText(text, byteLength);
1074     this->addScalar(x);
1075     this->addScalar(y);
1076     if (fast) {
1077         this->addFontMetricsTopBottom(paint, *flatPaintData, y, y);
1078     }
1079     this->validate(initialOffset, size);
1080 }
1081 
onDrawPosText(const void * text,size_t byteLength,const SkPoint pos[],const SkPaint & paint)1082 void SkPictureRecord::onDrawPosText(const void* text, size_t byteLength, const SkPoint pos[],
1083                                     const SkPaint& paint) {
1084     int points = paint.countText(text, byteLength);
1085     if (0 == points)
1086         return;
1087 
1088     bool canUseDrawH = true;
1089     SkScalar minY = pos[0].fY;
1090     SkScalar maxY = pos[0].fY;
1091     // check if the caller really should have used drawPosTextH()
1092     {
1093         const SkScalar firstY = pos[0].fY;
1094         for (int index = 1; index < points; index++) {
1095             if (pos[index].fY != firstY) {
1096                 canUseDrawH = false;
1097                 if (pos[index].fY < minY) {
1098                     minY = pos[index].fY;
1099                 } else if (pos[index].fY > maxY) {
1100                     maxY = pos[index].fY;
1101                 }
1102             }
1103         }
1104     }
1105 
1106     bool fastBounds = !paint.isVerticalText() && paint.canComputeFastBounds() && kBeClever;
1107     bool fast = canUseDrawH && fastBounds && kBeClever;
1108 
1109     // op + paint index + length + 'length' worth of data + num points
1110     size_t size = 3 * kUInt32Size + SkAlign4(byteLength) + 1 * kUInt32Size;
1111     if (canUseDrawH) {
1112         if (fast) {
1113             size += 2 * sizeof(SkScalar); // + top & bottom
1114         }
1115         // + y-pos + actual x-point data
1116         size += sizeof(SkScalar) + points * sizeof(SkScalar);
1117     } else {
1118         // + x&y point data
1119         size += points * sizeof(SkPoint);
1120         if (fastBounds) {
1121             size += 2 * sizeof(SkScalar); // + top & bottom
1122         }
1123     }
1124 
1125     DrawType op;
1126     if (fast) {
1127         op = DRAW_POS_TEXT_H_TOP_BOTTOM;
1128     } else if (canUseDrawH) {
1129         op = DRAW_POS_TEXT_H;
1130     } else if (fastBounds) {
1131         op = DRAW_POS_TEXT_TOP_BOTTOM;
1132     } else {
1133         op = DRAW_POS_TEXT;
1134     }
1135     size_t initialOffset = this->addDraw(op, &size);
1136     SkASSERT(initialOffset+getPaintOffset(op, size) == fWriter.bytesWritten());
1137     const SkFlatData* flatPaintData = this->addPaint(paint);
1138     SkASSERT(flatPaintData);
1139     this->addText(text, byteLength);
1140     this->addInt(points);
1141 
1142     if (canUseDrawH) {
1143         if (fast) {
1144             this->addFontMetricsTopBottom(paint, *flatPaintData, pos[0].fY, pos[0].fY);
1145         }
1146         this->addScalar(pos[0].fY);
1147         SkScalar* xptr = (SkScalar*)fWriter.reserve(points * sizeof(SkScalar));
1148         for (int index = 0; index < points; index++)
1149             *xptr++ = pos[index].fX;
1150     } else {
1151         fWriter.writeMul4(pos, points * sizeof(SkPoint));
1152         if (fastBounds) {
1153             this->addFontMetricsTopBottom(paint, *flatPaintData, minY, maxY);
1154         }
1155     }
1156     this->validate(initialOffset, size);
1157 }
1158 
onDrawPosTextH(const void * text,size_t byteLength,const SkScalar xpos[],SkScalar constY,const SkPaint & paint)1159 void SkPictureRecord::onDrawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[],
1160                                      SkScalar constY, const SkPaint& paint) {
1161     const SkFlatData* flatPaintData = this->getFlatPaintData(paint);
1162     this->drawPosTextHImpl(text, byteLength, xpos, constY, paint, flatPaintData);
1163 }
1164 
drawPosTextHImpl(const void * text,size_t byteLength,const SkScalar xpos[],SkScalar constY,const SkPaint & paint,const SkFlatData * flatPaintData)1165 void SkPictureRecord::drawPosTextHImpl(const void* text, size_t byteLength,
1166                           const SkScalar xpos[], SkScalar constY,
1167                           const SkPaint& paint, const SkFlatData* flatPaintData) {
1168     int points = paint.countText(text, byteLength);
1169     if (0 == points && kBeClever) {
1170         return;
1171     }
1172 
1173     bool fast = !paint.isVerticalText() && paint.canComputeFastBounds() && kBeClever;
1174 
1175     // op + paint index + length + 'length' worth of data + num points
1176     size_t size = 3 * kUInt32Size + SkAlign4(byteLength) + 1 * kUInt32Size;
1177     if (fast) {
1178         size += 2 * sizeof(SkScalar); // + top & bottom
1179     }
1180     // + y + the actual points
1181     size += 1 * kUInt32Size + points * sizeof(SkScalar);
1182     size_t initialOffset = this->addDraw(fast ? DRAW_POS_TEXT_H_TOP_BOTTOM : DRAW_POS_TEXT_H,
1183                                          &size);
1184     SkASSERT(flatPaintData);
1185     this->addFlatPaint(flatPaintData);
1186 
1187     this->addText(text, byteLength);
1188     this->addInt(points);
1189 
1190     if (fast) {
1191         this->addFontMetricsTopBottom(paint, *flatPaintData, constY, constY);
1192     }
1193     this->addScalar(constY);
1194     fWriter.writeMul4(xpos, points * sizeof(SkScalar));
1195     this->validate(initialOffset, size);
1196 }
1197 
onDrawTextOnPath(const void * text,size_t byteLength,const SkPath & path,const SkMatrix * matrix,const SkPaint & paint)1198 void SkPictureRecord::onDrawTextOnPath(const void* text, size_t byteLength, const SkPath& path,
1199                                        const SkMatrix* matrix, const SkPaint& paint) {
1200     // op + paint index + length + 'length' worth of data + path index + matrix
1201     const SkMatrix& m = matrix ? *matrix : SkMatrix::I();
1202     size_t size = 3 * kUInt32Size + SkAlign4(byteLength) + kUInt32Size + m.writeToMemory(NULL);
1203     size_t initialOffset = this->addDraw(DRAW_TEXT_ON_PATH, &size);
1204     SkASSERT(initialOffset+getPaintOffset(DRAW_TEXT_ON_PATH, size) == fWriter.bytesWritten());
1205     this->addPaint(paint);
1206     this->addText(text, byteLength);
1207     this->addPath(path);
1208     this->addMatrix(m);
1209     this->validate(initialOffset, size);
1210 }
1211 
onDrawTextBlob(const SkTextBlob * blob,SkScalar x,SkScalar y,const SkPaint & paint)1212 void SkPictureRecord::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
1213                                      const SkPaint& paint) {
1214 
1215     // op + paint index + blob index + x/y
1216     size_t size = 3 * kUInt32Size + 2 * sizeof(SkScalar);
1217     size_t initialOffset = this->addDraw(DRAW_TEXT_BLOB, &size);
1218     SkASSERT(initialOffset + getPaintOffset(DRAW_TEXT_BLOB, size) == fWriter.bytesWritten());
1219 
1220     this->addPaint(paint);
1221     this->addTextBlob(blob);
1222     this->addScalar(x);
1223     this->addScalar(y);
1224 
1225     this->validate(initialOffset, size);
1226 }
1227 
onDrawPicture(const SkPicture * picture,const SkMatrix * matrix,const SkPaint * paint)1228 void SkPictureRecord::onDrawPicture(const SkPicture* picture, const SkMatrix* matrix,
1229                                     const SkPaint* paint) {
1230     // op + picture index
1231     size_t size = 2 * kUInt32Size;
1232     size_t initialOffset;
1233 
1234     if (NULL == matrix && NULL == paint) {
1235         initialOffset = this->addDraw(DRAW_PICTURE, &size);
1236         this->addPicture(picture);
1237     } else {
1238         const SkMatrix& m = matrix ? *matrix : SkMatrix::I();
1239         size += m.writeToMemory(NULL) + kUInt32Size;    // matrix + paint
1240         initialOffset = this->addDraw(DRAW_PICTURE_MATRIX_PAINT, &size);
1241         SkASSERT(initialOffset + getPaintOffset(DRAW_PICTURE_MATRIX_PAINT, size)
1242                  == fWriter.bytesWritten());
1243         this->addPaintPtr(paint);
1244         this->addMatrix(m);
1245         this->addPicture(picture);
1246     }
1247     this->validate(initialOffset, size);
1248 }
1249 
drawVertices(VertexMode vmode,int vertexCount,const SkPoint vertices[],const SkPoint texs[],const SkColor colors[],SkXfermode * xfer,const uint16_t indices[],int indexCount,const SkPaint & paint)1250 void SkPictureRecord::drawVertices(VertexMode vmode, int vertexCount,
1251                           const SkPoint vertices[], const SkPoint texs[],
1252                           const SkColor colors[], SkXfermode* xfer,
1253                           const uint16_t indices[], int indexCount,
1254                           const SkPaint& paint) {
1255     uint32_t flags = 0;
1256     if (texs) {
1257         flags |= DRAW_VERTICES_HAS_TEXS;
1258     }
1259     if (colors) {
1260         flags |= DRAW_VERTICES_HAS_COLORS;
1261     }
1262     if (indexCount > 0) {
1263         flags |= DRAW_VERTICES_HAS_INDICES;
1264     }
1265     if (xfer) {
1266         SkXfermode::Mode mode;
1267         if (xfer->asMode(&mode) && SkXfermode::kModulate_Mode != mode) {
1268             flags |= DRAW_VERTICES_HAS_XFER;
1269         }
1270     }
1271 
1272     // op + paint index + flags + vmode + vCount + vertices
1273     size_t size = 5 * kUInt32Size + vertexCount * sizeof(SkPoint);
1274     if (flags & DRAW_VERTICES_HAS_TEXS) {
1275         size += vertexCount * sizeof(SkPoint);  // + uvs
1276     }
1277     if (flags & DRAW_VERTICES_HAS_COLORS) {
1278         size += vertexCount * sizeof(SkColor);  // + vert colors
1279     }
1280     if (flags & DRAW_VERTICES_HAS_INDICES) {
1281         // + num indices + indices
1282         size += 1 * kUInt32Size + SkAlign4(indexCount * sizeof(uint16_t));
1283     }
1284     if (flags & DRAW_VERTICES_HAS_XFER) {
1285         size += kUInt32Size;    // mode enum
1286     }
1287 
1288     size_t initialOffset = this->addDraw(DRAW_VERTICES, &size);
1289     SkASSERT(initialOffset+getPaintOffset(DRAW_VERTICES, size) == fWriter.bytesWritten());
1290     this->addPaint(paint);
1291     this->addInt(flags);
1292     this->addInt(vmode);
1293     this->addInt(vertexCount);
1294     this->addPoints(vertices, vertexCount);
1295     if (flags & DRAW_VERTICES_HAS_TEXS) {
1296         this->addPoints(texs, vertexCount);
1297     }
1298     if (flags & DRAW_VERTICES_HAS_COLORS) {
1299         fWriter.writeMul4(colors, vertexCount * sizeof(SkColor));
1300     }
1301     if (flags & DRAW_VERTICES_HAS_INDICES) {
1302         this->addInt(indexCount);
1303         fWriter.writePad(indices, indexCount * sizeof(uint16_t));
1304     }
1305     if (flags & DRAW_VERTICES_HAS_XFER) {
1306         SkXfermode::Mode mode = SkXfermode::kModulate_Mode;
1307         (void)xfer->asMode(&mode);
1308         this->addInt(mode);
1309     }
1310     this->validate(initialOffset, size);
1311 }
1312 
onDrawPatch(const SkPoint cubics[12],const SkColor colors[4],const SkPoint texCoords[4],SkXfermode * xmode,const SkPaint & paint)1313 void SkPictureRecord::onDrawPatch(const SkPoint cubics[12], const SkColor colors[4],
1314                                   const SkPoint texCoords[4], SkXfermode* xmode,
1315                                   const SkPaint& paint) {
1316     // op + paint index + patch 12 control points + flag + patch 4 colors + 4 texture coordinates
1317     size_t size = 2 * kUInt32Size + SkPatchUtils::kNumCtrlPts * sizeof(SkPoint) + kUInt32Size;
1318     uint32_t flag = 0;
1319     if (colors) {
1320         flag |= DRAW_VERTICES_HAS_COLORS;
1321         size += SkPatchUtils::kNumCorners * sizeof(SkColor);
1322     }
1323     if (texCoords) {
1324         flag |= DRAW_VERTICES_HAS_TEXS;
1325         size += SkPatchUtils::kNumCorners * sizeof(SkPoint);
1326     }
1327     if (xmode) {
1328         SkXfermode::Mode mode;
1329         if (xmode->asMode(&mode) && SkXfermode::kModulate_Mode != mode) {
1330             flag |= DRAW_VERTICES_HAS_XFER;
1331             size += kUInt32Size;
1332         }
1333     }
1334 
1335     size_t initialOffset = this->addDraw(DRAW_PATCH, &size);
1336     SkASSERT(initialOffset+getPaintOffset(DRAW_PATCH, size) == fWriter.bytesWritten());
1337     this->addPaint(paint);
1338     this->addPatch(cubics);
1339     this->addInt(flag);
1340 
1341     // write optional parameters
1342     if (colors) {
1343         fWriter.write(colors, SkPatchUtils::kNumCorners * sizeof(SkColor));
1344     }
1345     if (texCoords) {
1346         fWriter.write(texCoords, SkPatchUtils::kNumCorners * sizeof(SkPoint));
1347     }
1348     if (flag & DRAW_VERTICES_HAS_XFER) {
1349         SkXfermode::Mode mode = SkXfermode::kModulate_Mode;
1350         xmode->asMode(&mode);
1351         this->addInt(mode);
1352     }
1353     this->validate(initialOffset, size);
1354 }
1355 
drawData(const void * data,size_t length)1356 void SkPictureRecord::drawData(const void* data, size_t length) {
1357     // op + length + 'length' worth of data
1358     size_t size = 2 * kUInt32Size + SkAlign4(length);
1359     size_t initialOffset = this->addDraw(DRAW_DATA, &size);
1360     this->addInt(SkToInt(length));
1361     fWriter.writePad(data, length);
1362     this->validate(initialOffset, size);
1363 }
1364 
beginCommentGroup(const char * description)1365 void SkPictureRecord::beginCommentGroup(const char* description) {
1366     // op/size + length of string + \0 terminated chars
1367     size_t length = strlen(description);
1368     size_t size = 2 * kUInt32Size + SkAlign4(length + 1);
1369     size_t initialOffset = this->addDraw(BEGIN_COMMENT_GROUP, &size);
1370     fWriter.writeString(description, length);
1371     this->validate(initialOffset, size);
1372 }
1373 
addComment(const char * kywd,const char * value)1374 void SkPictureRecord::addComment(const char* kywd, const char* value) {
1375     // op/size + 2x length of string + 2x \0 terminated chars
1376     size_t kywdLen = strlen(kywd);
1377     size_t valueLen = strlen(value);
1378     size_t size = 3 * kUInt32Size + SkAlign4(kywdLen + 1) + SkAlign4(valueLen + 1);
1379     size_t initialOffset = this->addDraw(COMMENT, &size);
1380     fWriter.writeString(kywd, kywdLen);
1381     fWriter.writeString(value, valueLen);
1382     this->validate(initialOffset, size);
1383 }
1384 
endCommentGroup()1385 void SkPictureRecord::endCommentGroup() {
1386     // op/size
1387     size_t size = 1 * kUInt32Size;
1388     size_t initialOffset = this->addDraw(END_COMMENT_GROUP, &size);
1389     this->validate(initialOffset, size);
1390 }
1391 
1392 // [op/size] [rect] [skip offset]
1393 static const uint32_t kPushCullOpSize = 2 * kUInt32Size + sizeof(SkRect);
onPushCull(const SkRect & cullRect)1394 void SkPictureRecord::onPushCull(const SkRect& cullRect) {
1395     size_t size = kPushCullOpSize;
1396     size_t initialOffset = this->addDraw(PUSH_CULL, &size);
1397     // PUSH_CULL's size should stay constant (used to rewind).
1398     SkASSERT(size == kPushCullOpSize);
1399 
1400     this->addRect(cullRect);
1401     fCullOffsetStack.push(SkToU32(fWriter.bytesWritten()));
1402     this->addInt(0);
1403     this->validate(initialOffset, size);
1404 }
1405 
onPopCull()1406 void SkPictureRecord::onPopCull() {
1407     SkASSERT(!fCullOffsetStack.isEmpty());
1408 
1409     uint32_t cullSkipOffset = fCullOffsetStack.top();
1410     fCullOffsetStack.pop();
1411 
1412     // Collapse empty push/pop pairs.
1413     if ((size_t)(cullSkipOffset + kUInt32Size) == fWriter.bytesWritten() && kBeClever) {
1414         SkASSERT(fWriter.bytesWritten() >= kPushCullOpSize);
1415         SkASSERT(PUSH_CULL == peek_op(&fWriter, fWriter.bytesWritten() - kPushCullOpSize));
1416         fWriter.rewindToOffset(fWriter.bytesWritten() - kPushCullOpSize);
1417         return;
1418     }
1419 
1420     // op only
1421     size_t size = kUInt32Size;
1422     size_t initialOffset = this->addDraw(POP_CULL, &size);
1423 
1424     // update the cull skip offset to point past this op.
1425     fWriter.overwriteTAt<uint32_t>(cullSkipOffset, SkToU32(fWriter.bytesWritten()));
1426 
1427     this->validate(initialOffset, size);
1428 }
1429 
1430 ///////////////////////////////////////////////////////////////////////////////
1431 
onNewSurface(const SkImageInfo & info,const SkSurfaceProps &)1432 SkSurface* SkPictureRecord::onNewSurface(const SkImageInfo& info, const SkSurfaceProps&) {
1433     return NULL;
1434 }
1435 
addBitmap(const SkBitmap & bitmap)1436 int SkPictureRecord::addBitmap(const SkBitmap& bitmap) {
1437     const int index = fBitmapHeap->insert(bitmap);
1438     // In debug builds, a bad return value from insert() will crash, allowing for debugging. In
1439     // release builds, the invalid value will be recorded so that the reader will know that there
1440     // was a problem.
1441     SkASSERT(index != SkBitmapHeap::INVALID_SLOT);
1442     this->addInt(index);
1443     return index;
1444 }
1445 
addMatrix(const SkMatrix & matrix)1446 void SkPictureRecord::addMatrix(const SkMatrix& matrix) {
1447     fWriter.writeMatrix(matrix);
1448 }
1449 
getFlatPaintData(const SkPaint & paint)1450 const SkFlatData* SkPictureRecord::getFlatPaintData(const SkPaint& paint) {
1451     return fPaints.findAndReturnFlat(paint);
1452 }
1453 
addPaintPtr(const SkPaint * paint)1454 const SkFlatData* SkPictureRecord::addPaintPtr(const SkPaint* paint) {
1455     fContentInfo.onAddPaintPtr(paint);
1456 
1457     const SkFlatData* data = paint ? getFlatPaintData(*paint) : NULL;
1458     this->addFlatPaint(data);
1459     return data;
1460 }
1461 
addFlatPaint(const SkFlatData * flatPaint)1462 void SkPictureRecord::addFlatPaint(const SkFlatData* flatPaint) {
1463     int index = flatPaint ? flatPaint->index() : 0;
1464     this->addInt(index);
1465 }
1466 
addPathToHeap(const SkPath & path)1467 int SkPictureRecord::addPathToHeap(const SkPath& path) {
1468     if (NULL == fPathHeap) {
1469         fPathHeap.reset(SkNEW(SkPathHeap));
1470     }
1471 #ifdef SK_DEDUP_PICTURE_PATHS
1472     return fPathHeap->insert(path);
1473 #else
1474     return fPathHeap->append(path);
1475 #endif
1476 }
1477 
addPath(const SkPath & path)1478 void SkPictureRecord::addPath(const SkPath& path) {
1479     this->addInt(this->addPathToHeap(path));
1480 }
1481 
addPatch(const SkPoint cubics[12])1482 void SkPictureRecord::addPatch(const SkPoint cubics[12]) {
1483     fWriter.write(cubics, SkPatchUtils::kNumCtrlPts * sizeof(SkPoint));
1484 }
1485 
addPicture(const SkPicture * picture)1486 void SkPictureRecord::addPicture(const SkPicture* picture) {
1487     int index = fPictureRefs.find(picture);
1488     if (index < 0) {    // not found
1489         index = fPictureRefs.count();
1490         *fPictureRefs.append() = picture;
1491         picture->ref();
1492     }
1493     // follow the convention of recording a 1-based index
1494     this->addInt(index + 1);
1495 }
1496 
addPoint(const SkPoint & point)1497 void SkPictureRecord::addPoint(const SkPoint& point) {
1498     fWriter.writePoint(point);
1499 }
1500 
addPoints(const SkPoint pts[],int count)1501 void SkPictureRecord::addPoints(const SkPoint pts[], int count) {
1502     fWriter.writeMul4(pts, count * sizeof(SkPoint));
1503 }
1504 
addNoOp()1505 void SkPictureRecord::addNoOp() {
1506     size_t size = kUInt32Size; // op
1507     this->addDraw(NOOP, &size);
1508 }
1509 
addRect(const SkRect & rect)1510 void SkPictureRecord::addRect(const SkRect& rect) {
1511     fWriter.writeRect(rect);
1512 }
1513 
addRectPtr(const SkRect * rect)1514 void SkPictureRecord::addRectPtr(const SkRect* rect) {
1515     if (fWriter.writeBool(rect != NULL)) {
1516         fWriter.writeRect(*rect);
1517     }
1518 }
1519 
addIRect(const SkIRect & rect)1520 void SkPictureRecord::addIRect(const SkIRect& rect) {
1521     fWriter.write(&rect, sizeof(rect));
1522 }
1523 
addIRectPtr(const SkIRect * rect)1524 void SkPictureRecord::addIRectPtr(const SkIRect* rect) {
1525     if (fWriter.writeBool(rect != NULL)) {
1526         *(SkIRect*)fWriter.reserve(sizeof(SkIRect)) = *rect;
1527     }
1528 }
1529 
addRRect(const SkRRect & rrect)1530 void SkPictureRecord::addRRect(const SkRRect& rrect) {
1531     fWriter.writeRRect(rrect);
1532 }
1533 
addRegion(const SkRegion & region)1534 void SkPictureRecord::addRegion(const SkRegion& region) {
1535     fWriter.writeRegion(region);
1536 }
1537 
addText(const void * text,size_t byteLength)1538 void SkPictureRecord::addText(const void* text, size_t byteLength) {
1539     fContentInfo.onDrawText();
1540     addInt(SkToInt(byteLength));
1541     fWriter.writePad(text, byteLength);
1542 }
1543 
addTextBlob(const SkTextBlob * blob)1544 void SkPictureRecord::addTextBlob(const SkTextBlob *blob) {
1545     int index = fTextBlobRefs.count();
1546     *fTextBlobRefs.append() = blob;
1547     blob->ref();
1548     // follow the convention of recording a 1-based index
1549     this->addInt(index + 1);
1550 }
1551 
1552 ///////////////////////////////////////////////////////////////////////////////
1553 
1554