1
2/*
3 * Copyright 2012 Google Inc.
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
9
10#include "SkColorPriv.h"
11#include "SkDebugCanvas.h"
12#include "SkDrawCommand.h"
13#include "SkDrawFilter.h"
14#include "SkDevice.h"
15#include "SkXfermode.h"
16
17SkDebugCanvas::SkDebugCanvas(int windowWidth, int windowHeight)
18        : INHERITED(windowWidth, windowHeight)
19        , fPicture(NULL)
20        , fWindowSize(SkISize::Make(windowWidth, windowHeight))
21        , fFilter(false)
22        , fMegaVizMode(false)
23        , fIndex(0)
24        , fOverdrawViz(false)
25        , fOverdrawFilter(NULL)
26        , fOverrideTexFiltering(false)
27        , fTexOverrideFilter(NULL)
28        , fOutstandingSaveCount(0) {
29    fUserMatrix.reset();
30
31    // SkPicturePlayback uses the base-class' quickReject calls to cull clipped
32    // operations. This can lead to problems in the debugger which expects all
33    // the operations in the captured skp to appear in the debug canvas. To
34    // circumvent this we create a wide open clip here (an empty clip rect
35    // is not sufficient).
36    // Internally, the SkRect passed to clipRect is converted to an SkIRect and
37    // rounded out. The following code creates a nearly maximal rect that will
38    // not get collapsed by the coming conversions (Due to precision loss the
39    // inset has to be surprisingly large).
40    SkIRect largeIRect = SkIRect::MakeLargest();
41    largeIRect.inset(1024, 1024);
42    SkRect large = SkRect::Make(largeIRect);
43#ifdef SK_DEBUG
44    large.roundOut(&largeIRect);
45    SkASSERT(!largeIRect.isEmpty());
46#endif
47    // call the base class' version to avoid adding a draw command
48    this->INHERITED::onClipRect(large, SkRegion::kReplace_Op, kHard_ClipEdgeStyle);
49}
50
51SkDebugCanvas::~SkDebugCanvas() {
52    fCommandVector.deleteAll();
53    SkSafeUnref(fOverdrawFilter);
54    SkSafeUnref(fTexOverrideFilter);
55}
56
57void SkDebugCanvas::addDrawCommand(SkDrawCommand* command) {
58    command->setOffset(this->getOpID());
59    fCommandVector.push(command);
60}
61
62void SkDebugCanvas::draw(SkCanvas* canvas) {
63    if (!fCommandVector.isEmpty()) {
64        this->drawTo(canvas, fCommandVector.count() - 1);
65    }
66}
67
68void SkDebugCanvas::applyUserTransform(SkCanvas* canvas) {
69    canvas->concat(fUserMatrix);
70}
71
72int SkDebugCanvas::getCommandAtPoint(int x, int y, int index) {
73    SkBitmap bitmap;
74    bitmap.allocPixels(SkImageInfo::MakeN32Premul(1, 1));
75
76    SkCanvas canvas(bitmap);
77    canvas.translate(SkIntToScalar(-x), SkIntToScalar(-y));
78    this->applyUserTransform(&canvas);
79
80    int layer = 0;
81    SkColor prev = bitmap.getColor(0,0);
82    for (int i = 0; i < index; i++) {
83        if (fCommandVector[i]->isVisible()) {
84            fCommandVector[i]->execute(&canvas);
85        }
86        if (prev != bitmap.getColor(0,0)) {
87            layer = i;
88        }
89        prev = bitmap.getColor(0,0);
90    }
91    return layer;
92}
93
94class OverdrawXfermode : public SkXfermode {
95public:
96    virtual SkPMColor xferColor(SkPMColor src, SkPMColor dst) const SK_OVERRIDE {
97        // This table encodes the color progression of the overdraw visualization
98        static const SkPMColor gTable[] = {
99            SkPackARGB32(0x00, 0x00, 0x00, 0x00),
100            SkPackARGB32(0xFF, 128, 158, 255),
101            SkPackARGB32(0xFF, 170, 185, 212),
102            SkPackARGB32(0xFF, 213, 195, 170),
103            SkPackARGB32(0xFF, 255, 192, 127),
104            SkPackARGB32(0xFF, 255, 185, 85),
105            SkPackARGB32(0xFF, 255, 165, 42),
106            SkPackARGB32(0xFF, 255, 135, 0),
107            SkPackARGB32(0xFF, 255,  95, 0),
108            SkPackARGB32(0xFF, 255,  50, 0),
109            SkPackARGB32(0xFF, 255,  0, 0)
110        };
111
112        for (size_t i = 0; i < SK_ARRAY_COUNT(gTable)-1; ++i) {
113            if (gTable[i] == dst) {
114                return gTable[i+1];
115            }
116        }
117
118        return gTable[SK_ARRAY_COUNT(gTable)-1];
119    }
120
121    virtual Factory getFactory() const SK_OVERRIDE { return NULL; }
122#ifndef SK_IGNORE_TO_STRING
123    virtual void toString(SkString* str) const { str->set("OverdrawXfermode"); }
124#endif
125};
126
127class SkOverdrawFilter : public SkDrawFilter {
128public:
129    SkOverdrawFilter() {
130        fXferMode = SkNEW(OverdrawXfermode);
131    }
132
133    virtual ~SkOverdrawFilter() {
134        delete fXferMode;
135    }
136
137    virtual bool filter(SkPaint* p, Type) SK_OVERRIDE {
138        p->setXfermode(fXferMode);
139        return true;
140    }
141
142protected:
143    SkXfermode* fXferMode;
144
145private:
146    typedef SkDrawFilter INHERITED;
147};
148
149// SkTexOverrideFilter modifies every paint to use the specified
150// texture filtering mode
151class SkTexOverrideFilter : public SkDrawFilter {
152public:
153    SkTexOverrideFilter() : fFilterLevel(SkPaint::kNone_FilterLevel) {
154    }
155
156    void setFilterLevel(SkPaint::FilterLevel filterLevel) {
157        fFilterLevel = filterLevel;
158    }
159
160    virtual bool filter(SkPaint* p, Type) SK_OVERRIDE {
161        p->setFilterLevel(fFilterLevel);
162        return true;
163    }
164
165protected:
166    SkPaint::FilterLevel fFilterLevel;
167
168private:
169    typedef SkDrawFilter INHERITED;
170};
171
172class SkDebugClipVisitor : public SkCanvas::ClipVisitor {
173public:
174    SkDebugClipVisitor(SkCanvas* canvas) : fCanvas(canvas) {}
175
176    virtual void clipRect(const SkRect& r, SkRegion::Op, bool doAA) SK_OVERRIDE {
177        SkPaint p;
178        p.setColor(SK_ColorRED);
179        p.setStyle(SkPaint::kStroke_Style);
180        p.setAntiAlias(doAA);
181        fCanvas->drawRect(r, p);
182    }
183    virtual void clipRRect(const SkRRect& rr, SkRegion::Op, bool doAA) SK_OVERRIDE {
184        SkPaint p;
185        p.setColor(SK_ColorGREEN);
186        p.setStyle(SkPaint::kStroke_Style);
187        p.setAntiAlias(doAA);
188        fCanvas->drawRRect(rr, p);
189    }
190    virtual void clipPath(const SkPath& path, SkRegion::Op, bool doAA) SK_OVERRIDE {
191        SkPaint p;
192        p.setColor(SK_ColorBLUE);
193        p.setStyle(SkPaint::kStroke_Style);
194        p.setAntiAlias(doAA);
195        fCanvas->drawPath(path, p);
196    }
197
198protected:
199    SkCanvas* fCanvas;
200
201private:
202    typedef SkCanvas::ClipVisitor INHERITED;
203};
204
205// set up the saveLayer commands so that the active ones
206// return true in their 'active' method
207void SkDebugCanvas::markActiveCommands(int index) {
208    fActiveLayers.rewind();
209    fActiveCulls.rewind();
210
211    for (int i = 0; i < fCommandVector.count(); ++i) {
212        fCommandVector[i]->setActive(false);
213    }
214
215    for (int i = 0; i < index; ++i) {
216        SkDrawCommand::Action result = fCommandVector[i]->action();
217        if (SkDrawCommand::kPushLayer_Action == result) {
218            fActiveLayers.push(fCommandVector[i]);
219        } else if (SkDrawCommand::kPopLayer_Action == result) {
220            fActiveLayers.pop();
221        } else if (SkDrawCommand::kPushCull_Action == result) {
222            fActiveCulls.push(fCommandVector[i]);
223        } else if (SkDrawCommand::kPopCull_Action == result) {
224            fActiveCulls.pop();
225        }
226    }
227
228    for (int i = 0; i < fActiveLayers.count(); ++i) {
229        fActiveLayers[i]->setActive(true);
230    }
231
232    for (int i = 0; i < fActiveCulls.count(); ++i) {
233        fActiveCulls[i]->setActive(true);
234    }
235}
236
237void SkDebugCanvas::drawTo(SkCanvas* canvas, int index) {
238    SkASSERT(!fCommandVector.isEmpty());
239    SkASSERT(index < fCommandVector.count());
240    int i = 0;
241
242    bool pathOpsMode = getAllowSimplifyClip();
243    canvas->setAllowSimplifyClip(pathOpsMode);
244    // This only works assuming the canvas and device are the same ones that
245    // were previously drawn into because they need to preserve all saves
246    // and restores.
247    // The visibility filter also requires a full re-draw - otherwise we can
248    // end up drawing the filter repeatedly.
249    if (fIndex < index && !fFilter && !fMegaVizMode && !pathOpsMode) {
250        i = fIndex + 1;
251    } else {
252        for (int j = 0; j < fOutstandingSaveCount; j++) {
253            canvas->restore();
254        }
255        canvas->clear(SK_ColorTRANSPARENT);
256        canvas->resetMatrix();
257        SkRect rect = SkRect::MakeWH(SkIntToScalar(fWindowSize.fWidth),
258                                     SkIntToScalar(fWindowSize.fHeight));
259        canvas->clipRect(rect, SkRegion::kReplace_Op);
260        this->applyUserTransform(canvas);
261        fOutstandingSaveCount = 0;
262    }
263
264    // The setting of the draw filter has to go here (rather than in
265    // SkRasterWidget) due to the canvas restores this class performs.
266    // Since the draw filter is stored in the layer stack if we
267    // call setDrawFilter on anything but the root layer odd things happen.
268    if (fOverdrawViz) {
269        if (NULL == fOverdrawFilter) {
270            fOverdrawFilter = new SkOverdrawFilter;
271        }
272
273        if (fOverdrawFilter != canvas->getDrawFilter()) {
274            canvas->setDrawFilter(fOverdrawFilter);
275        }
276    } else if (fOverrideTexFiltering) {
277        if (NULL == fTexOverrideFilter) {
278            fTexOverrideFilter = new SkTexOverrideFilter;
279        }
280
281        if (fTexOverrideFilter != canvas->getDrawFilter()) {
282            canvas->setDrawFilter(fTexOverrideFilter);
283        }
284    } else {
285        canvas->setDrawFilter(NULL);
286    }
287
288    if (fMegaVizMode) {
289        this->markActiveCommands(index);
290    }
291
292    for (; i <= index; i++) {
293        if (i == index && fFilter) {
294            canvas->clear(0xAAFFFFFF);
295        }
296
297        if (fCommandVector[i]->isVisible()) {
298            if (fMegaVizMode && fCommandVector[i]->active()) {
299                // "active" commands execute their visualization behaviors:
300                //     All active saveLayers get replaced with saves so all draws go to the
301                //     visible canvas.
302                //     All active culls draw their cull box
303                fCommandVector[i]->vizExecute(canvas);
304            } else {
305                fCommandVector[i]->execute(canvas);
306            }
307
308            fCommandVector[i]->trackSaveState(&fOutstandingSaveCount);
309        }
310    }
311
312    if (fMegaVizMode) {
313        SkRect r = SkRect::MakeWH(SkIntToScalar(fWindowSize.fWidth),
314                                  SkIntToScalar(fWindowSize.fHeight));
315        r.outset(SK_Scalar1, SK_Scalar1);
316
317        canvas->save();
318        // nuke the CTM
319        canvas->resetMatrix();
320        // turn off clipping
321        canvas->clipRect(r, SkRegion::kReplace_Op);
322
323        // visualize existing clips
324        SkDebugClipVisitor visitor(canvas);
325
326        canvas->replayClips(&visitor);
327
328        canvas->restore();
329    }
330    if (pathOpsMode) {
331        this->resetClipStackData();
332        const SkClipStack* clipStack = canvas->getClipStack();
333        SkClipStack::Iter iter(*clipStack, SkClipStack::Iter::kBottom_IterStart);
334        const SkClipStack::Element* element;
335        SkPath devPath;
336        while ((element = iter.next())) {
337            SkClipStack::Element::Type type = element->getType();
338            SkPath operand;
339            if (type != SkClipStack::Element::kEmpty_Type) {
340               element->asPath(&operand);
341            }
342            SkRegion::Op elementOp = element->getOp();
343            this->addClipStackData(devPath, operand, elementOp);
344            if (elementOp == SkRegion::kReplace_Op) {
345                devPath = operand;
346            } else {
347                Op(devPath, operand, (SkPathOp) elementOp, &devPath);
348            }
349        }
350        this->lastClipStackData(devPath);
351    }
352    fMatrix = canvas->getTotalMatrix();
353    if (!canvas->getClipDeviceBounds(&fClip)) {
354        fClip.setEmpty();
355    }
356    fIndex = index;
357}
358
359void SkDebugCanvas::deleteDrawCommandAt(int index) {
360    SkASSERT(index < fCommandVector.count());
361    delete fCommandVector[index];
362    fCommandVector.remove(index);
363}
364
365SkDrawCommand* SkDebugCanvas::getDrawCommandAt(int index) {
366    SkASSERT(index < fCommandVector.count());
367    return fCommandVector[index];
368}
369
370void SkDebugCanvas::setDrawCommandAt(int index, SkDrawCommand* command) {
371    SkASSERT(index < fCommandVector.count());
372    delete fCommandVector[index];
373    fCommandVector[index] = command;
374}
375
376SkTDArray<SkString*>* SkDebugCanvas::getCommandInfo(int index) {
377    SkASSERT(index < fCommandVector.count());
378    return fCommandVector[index]->Info();
379}
380
381bool SkDebugCanvas::getDrawCommandVisibilityAt(int index) {
382    SkASSERT(index < fCommandVector.count());
383    return fCommandVector[index]->isVisible();
384}
385
386const SkTDArray <SkDrawCommand*>& SkDebugCanvas::getDrawCommands() const {
387    return fCommandVector;
388}
389
390SkTDArray <SkDrawCommand*>& SkDebugCanvas::getDrawCommands() {
391    return fCommandVector;
392}
393
394// TODO(chudy): Free command string memory.
395SkTArray<SkString>* SkDebugCanvas::getDrawCommandsAsStrings() const {
396    SkTArray<SkString>* commandString = new SkTArray<SkString>(fCommandVector.count());
397    if (!fCommandVector.isEmpty()) {
398        for (int i = 0; i < fCommandVector.count(); i ++) {
399            commandString->push_back() = fCommandVector[i]->toString();
400        }
401    }
402    return commandString;
403}
404
405SkTDArray<size_t>* SkDebugCanvas::getDrawCommandOffsets() const {
406    SkTDArray<size_t>* commandOffsets = new SkTDArray<size_t>;
407    if (!fCommandVector.isEmpty()) {
408        for (int i = 0; i < fCommandVector.count(); i ++) {
409            *commandOffsets->push() = fCommandVector[i]->offset();
410        }
411    }
412    return commandOffsets;
413}
414
415void SkDebugCanvas::overrideTexFiltering(bool overrideTexFiltering, SkPaint::FilterLevel level) {
416    if (NULL == fTexOverrideFilter) {
417        fTexOverrideFilter = new SkTexOverrideFilter;
418    }
419
420    fOverrideTexFiltering = overrideTexFiltering;
421    fTexOverrideFilter->setFilterLevel(level);
422}
423
424void SkDebugCanvas::clear(SkColor color) {
425    this->addDrawCommand(new SkClearCommand(color));
426}
427
428void SkDebugCanvas::onClipPath(const SkPath& path, SkRegion::Op op, ClipEdgeStyle edgeStyle) {
429    this->addDrawCommand(new SkClipPathCommand(path, op, kSoft_ClipEdgeStyle == edgeStyle));
430}
431
432void SkDebugCanvas::onClipRect(const SkRect& rect, SkRegion::Op op, ClipEdgeStyle edgeStyle) {
433    this->addDrawCommand(new SkClipRectCommand(rect, op, kSoft_ClipEdgeStyle == edgeStyle));
434}
435
436void SkDebugCanvas::onClipRRect(const SkRRect& rrect, SkRegion::Op op, ClipEdgeStyle edgeStyle) {
437    this->addDrawCommand(new SkClipRRectCommand(rrect, op, kSoft_ClipEdgeStyle == edgeStyle));
438}
439
440void SkDebugCanvas::onClipRegion(const SkRegion& region, SkRegion::Op op) {
441    this->addDrawCommand(new SkClipRegionCommand(region, op));
442}
443
444void SkDebugCanvas::didConcat(const SkMatrix& matrix) {
445    switch (matrix.getType()) {
446        case SkMatrix::kTranslate_Mask:
447            this->addDrawCommand(new SkTranslateCommand(matrix.getTranslateX(),
448                                                        matrix.getTranslateY()));
449            break;
450        case SkMatrix::kScale_Mask:
451            this->addDrawCommand(new SkScaleCommand(matrix.getScaleX(),
452                                                    matrix.getScaleY()));
453            break;
454        default:
455            this->addDrawCommand(new SkConcatCommand(matrix));
456            break;
457    }
458
459    this->INHERITED::didConcat(matrix);
460}
461
462void SkDebugCanvas::drawBitmap(const SkBitmap& bitmap, SkScalar left,
463                               SkScalar top, const SkPaint* paint = NULL) {
464    this->addDrawCommand(new SkDrawBitmapCommand(bitmap, left, top, paint));
465}
466
467void SkDebugCanvas::drawBitmapRectToRect(const SkBitmap& bitmap,
468                                         const SkRect* src, const SkRect& dst,
469                                         const SkPaint* paint,
470                                         SkCanvas::DrawBitmapRectFlags flags) {
471    this->addDrawCommand(new SkDrawBitmapRectCommand(bitmap, src, dst, paint, flags));
472}
473
474void SkDebugCanvas::drawBitmapMatrix(const SkBitmap& bitmap,
475                                     const SkMatrix& matrix, const SkPaint* paint) {
476    this->addDrawCommand(new SkDrawBitmapMatrixCommand(bitmap, matrix, paint));
477}
478
479void SkDebugCanvas::drawBitmapNine(const SkBitmap& bitmap,
480        const SkIRect& center, const SkRect& dst, const SkPaint* paint) {
481    this->addDrawCommand(new SkDrawBitmapNineCommand(bitmap, center, dst, paint));
482}
483
484void SkDebugCanvas::drawData(const void* data, size_t length) {
485    this->addDrawCommand(new SkDrawDataCommand(data, length));
486}
487
488void SkDebugCanvas::beginCommentGroup(const char* description) {
489    this->addDrawCommand(new SkBeginCommentGroupCommand(description));
490}
491
492void SkDebugCanvas::addComment(const char* kywd, const char* value) {
493    this->addDrawCommand(new SkCommentCommand(kywd, value));
494}
495
496void SkDebugCanvas::endCommentGroup() {
497    this->addDrawCommand(new SkEndCommentGroupCommand());
498}
499
500void SkDebugCanvas::drawOval(const SkRect& oval, const SkPaint& paint) {
501    this->addDrawCommand(new SkDrawOvalCommand(oval, paint));
502}
503
504void SkDebugCanvas::drawPaint(const SkPaint& paint) {
505    this->addDrawCommand(new SkDrawPaintCommand(paint));
506}
507
508void SkDebugCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
509    this->addDrawCommand(new SkDrawPathCommand(path, paint));
510}
511
512void SkDebugCanvas::onDrawPicture(const SkPicture* picture,
513                                  const SkMatrix* matrix,
514                                  const SkPaint* paint) {
515    this->addDrawCommand(new SkDrawPictureCommand(picture, matrix, paint));
516}
517
518void SkDebugCanvas::drawPoints(PointMode mode, size_t count,
519                               const SkPoint pts[], const SkPaint& paint) {
520    this->addDrawCommand(new SkDrawPointsCommand(mode, count, pts, paint));
521}
522
523void SkDebugCanvas::onDrawPosText(const void* text, size_t byteLength, const SkPoint pos[],
524                                  const SkPaint& paint) {
525    this->addDrawCommand(new SkDrawPosTextCommand(text, byteLength, pos, paint));
526}
527
528void SkDebugCanvas::onDrawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[],
529                                   SkScalar constY, const SkPaint& paint) {
530    this->addDrawCommand(
531        new SkDrawPosTextHCommand(text, byteLength, xpos, constY, paint));
532}
533
534void SkDebugCanvas::drawRect(const SkRect& rect, const SkPaint& paint) {
535    // NOTE(chudy): Messing up when renamed to DrawRect... Why?
536    addDrawCommand(new SkDrawRectCommand(rect, paint));
537}
538
539void SkDebugCanvas::drawRRect(const SkRRect& rrect, const SkPaint& paint) {
540    this->addDrawCommand(new SkDrawRRectCommand(rrect, paint));
541}
542
543void SkDebugCanvas::onDrawDRRect(const SkRRect& outer, const SkRRect& inner,
544                                 const SkPaint& paint) {
545    this->addDrawCommand(new SkDrawDRRectCommand(outer, inner, paint));
546}
547
548void SkDebugCanvas::drawSprite(const SkBitmap& bitmap, int left, int top,
549                               const SkPaint* paint = NULL) {
550    this->addDrawCommand(new SkDrawSpriteCommand(bitmap, left, top, paint));
551}
552
553void SkDebugCanvas::onDrawText(const void* text, size_t byteLength, SkScalar x, SkScalar y,
554                               const SkPaint& paint) {
555    this->addDrawCommand(new SkDrawTextCommand(text, byteLength, x, y, paint));
556}
557
558void SkDebugCanvas::onDrawTextOnPath(const void* text, size_t byteLength, const SkPath& path,
559                                     const SkMatrix* matrix, const SkPaint& paint) {
560    this->addDrawCommand(
561        new SkDrawTextOnPathCommand(text, byteLength, path, matrix, paint));
562}
563
564void SkDebugCanvas::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
565                                   const SkPaint& paint) {
566    this->addDrawCommand(new SkDrawTextBlobCommand(blob, x, y, paint));
567}
568
569void SkDebugCanvas::drawVertices(VertexMode vmode, int vertexCount,
570        const SkPoint vertices[], const SkPoint texs[], const SkColor colors[],
571        SkXfermode*, const uint16_t indices[], int indexCount,
572        const SkPaint& paint) {
573    this->addDrawCommand(new SkDrawVerticesCommand(vmode, vertexCount, vertices,
574                         texs, colors, NULL, indices, indexCount, paint));
575}
576
577void SkDebugCanvas::onPushCull(const SkRect& cullRect) {
578    this->addDrawCommand(new SkPushCullCommand(cullRect));
579}
580
581void SkDebugCanvas::onPopCull() {
582    this->addDrawCommand(new SkPopCullCommand());
583}
584
585void SkDebugCanvas::willRestore() {
586    this->addDrawCommand(new SkRestoreCommand());
587    this->INHERITED::willRestore();
588}
589
590void SkDebugCanvas::willSave() {
591    this->addDrawCommand(new SkSaveCommand());
592    this->INHERITED::willSave();
593}
594
595SkCanvas::SaveLayerStrategy SkDebugCanvas::willSaveLayer(const SkRect* bounds, const SkPaint* paint,
596                                                         SaveFlags flags) {
597    this->addDrawCommand(new SkSaveLayerCommand(bounds, paint, flags));
598    this->INHERITED::willSaveLayer(bounds, paint, flags);
599    // No need for a full layer.
600    return kNoLayer_SaveLayerStrategy;
601}
602
603void SkDebugCanvas::didSetMatrix(const SkMatrix& matrix) {
604    this->addDrawCommand(new SkSetMatrixCommand(matrix));
605    this->INHERITED::didSetMatrix(matrix);
606}
607
608void SkDebugCanvas::toggleCommand(int index, bool toggle) {
609    SkASSERT(index < fCommandVector.count());
610    fCommandVector[index]->setVisible(toggle);
611}
612
613static const char* gFillTypeStrs[] = {
614    "kWinding_FillType",
615    "kEvenOdd_FillType",
616    "kInverseWinding_FillType",
617    "kInverseEvenOdd_FillType"
618};
619
620static const char* gOpStrs[] = {
621    "kDifference_PathOp",
622    "kIntersect_PathOp",
623    "kUnion_PathOp",
624    "kXor_PathOp",
625    "kReverseDifference_PathOp",
626};
627
628static const char kHTML4SpaceIndent[] = "&nbsp;&nbsp;&nbsp;&nbsp;";
629
630void SkDebugCanvas::outputScalar(SkScalar num) {
631    if (num == (int) num) {
632        fClipStackData.appendf("%d", (int) num);
633    } else {
634        SkString str;
635        str.printf("%1.9g", num);
636        int width = (int) str.size();
637        const char* cStr = str.c_str();
638        while (cStr[width - 1] == '0') {
639            --width;
640        }
641        str.resize(width);
642        fClipStackData.appendf("%sf", str.c_str());
643    }
644}
645
646void SkDebugCanvas::outputPointsCommon(const SkPoint* pts, int count) {
647    for (int index = 0; index < count; ++index) {
648        this->outputScalar(pts[index].fX);
649        fClipStackData.appendf(", ");
650        this->outputScalar(pts[index].fY);
651        if (index + 1 < count) {
652            fClipStackData.appendf(", ");
653        }
654    }
655}
656
657void SkDebugCanvas::outputPoints(const SkPoint* pts, int count) {
658    this->outputPointsCommon(pts, count);
659    fClipStackData.appendf(");<br>");
660}
661
662void SkDebugCanvas::outputConicPoints(const SkPoint* pts, SkScalar weight) {
663    this->outputPointsCommon(pts, 2);
664    fClipStackData.appendf(", ");
665    this->outputScalar(weight);
666    fClipStackData.appendf(");<br>");
667}
668
669void SkDebugCanvas::addPathData(const SkPath& path, const char* pathName) {
670    SkPath::RawIter iter(path);
671    SkPath::FillType fillType = path.getFillType();
672    fClipStackData.appendf("%sSkPath %s;<br>", kHTML4SpaceIndent, pathName);
673    fClipStackData.appendf("%s%s.setFillType(SkPath::%s);<br>", kHTML4SpaceIndent, pathName,
674            gFillTypeStrs[fillType]);
675    iter.setPath(path);
676    uint8_t verb;
677    SkPoint pts[4];
678    while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
679        switch (verb) {
680            case SkPath::kMove_Verb:
681                fClipStackData.appendf("%s%s.moveTo(", kHTML4SpaceIndent, pathName);
682                this->outputPoints(&pts[0], 1);
683                continue;
684            case SkPath::kLine_Verb:
685                fClipStackData.appendf("%s%s.lineTo(", kHTML4SpaceIndent, pathName);
686                this->outputPoints(&pts[1], 1);
687                break;
688            case SkPath::kQuad_Verb:
689                fClipStackData.appendf("%s%s.quadTo(", kHTML4SpaceIndent, pathName);
690                this->outputPoints(&pts[1], 2);
691                break;
692            case SkPath::kConic_Verb:
693                fClipStackData.appendf("%s%s.conicTo(", kHTML4SpaceIndent, pathName);
694                this->outputConicPoints(&pts[1], iter.conicWeight());
695                break;
696            case SkPath::kCubic_Verb:
697                fClipStackData.appendf("%s%s.cubicTo(", kHTML4SpaceIndent, pathName);
698                this->outputPoints(&pts[1], 3);
699                break;
700            case SkPath::kClose_Verb:
701                fClipStackData.appendf("%s%s.close();<br>", kHTML4SpaceIndent, pathName);
702                break;
703            default:
704                SkDEBUGFAIL("bad verb");
705                return;
706        }
707    }
708}
709
710void SkDebugCanvas::addClipStackData(const SkPath& devPath, const SkPath& operand,
711                                     SkRegion::Op elementOp) {
712    if (elementOp == SkRegion::kReplace_Op) {
713        if (!lastClipStackData(devPath)) {
714            fSaveDevPath = operand;
715        }
716        fCalledAddStackData = false;
717    } else {
718        fClipStackData.appendf("<br>static void test(skiatest::Reporter* reporter,"
719            " const char* filename) {<br>");
720        addPathData(fCalledAddStackData ? devPath : fSaveDevPath, "path");
721        addPathData(operand, "pathB");
722        fClipStackData.appendf("%stestPathOp(reporter, path, pathB, %s, filename);<br>",
723            kHTML4SpaceIndent, gOpStrs[elementOp]);
724        fClipStackData.appendf("}<br>");
725        fCalledAddStackData = true;
726    }
727}
728
729bool SkDebugCanvas::lastClipStackData(const SkPath& devPath) {
730    if (fCalledAddStackData) {
731        fClipStackData.appendf("<br>");
732        addPathData(devPath, "pathOut");
733        return true;
734    }
735    return false;
736}
737