aboutsummaryrefslogtreecommitdiffhomepage
path: root/gm/path_stroke_with_zero_length.cpp
blob: 3cb789f0ac482160ab5944a43f0d6c6c9689d299 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
/*
 * Copyright 2015 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "SkAutoPixmapStorage.h"
#include "SkColorPriv.h"
#include "SkImage.h"
#include "SkParsePath.h"
#include "SkPath.h"
#include "SkSurface.h"
#include "gm.h"

// GM to test combinations of stroking zero length paths with different caps and other settings
// Variables:
// * Antialiasing: On, Off
// * Caps: Butt, Round, Square
// * Stroke width: 0, 0.9, 1, 1.1, 15, 25
// * Path form: M, ML, MLZ, MZ
// * Path contours: 1 or 2
// * Path verbs: Line, Quad, Cubic, Conic
//
// Each test is drawn to a 50x20 offscreen surface, and expected to produce some number (0 - 2) of
// visible pieces of cap geometry. These are counted by scanning horizontally for peaks (blobs).

static bool draw_path_cell(SkCanvas* canvas, SkImage* img, int expectedCaps) {
    // Draw the image
    canvas->drawImage(img, 0, 0);

    int w = img->width(), h = img->height();

    // Read the pixels back
    SkImageInfo info = SkImageInfo::MakeN32Premul(w, h);
    SkAutoPixmapStorage pmap;
    pmap.alloc(info);
    if (!img->readPixels(pmap, 0, 0)) {
        return false;
    }

    // To account for rasterization differences, we scan the middle two rows [y, y+1] of the image
    SkASSERT(h % 2 == 0);
    int y = (h - 1) / 2;

    bool inBlob = false;
    int numBlobs = 0;
    for (int x = 0; x < w; ++x) {
        // We drew white-on-black. We can look for any non-zero value. Just check red.
        // And we care if either row is non-zero, so just add them to simplify everything.
        uint32_t v = SkGetPackedR32(*pmap.addr32(x, y)) + SkGetPackedR32(*pmap.addr32(x, y + 1));

        if (!inBlob && v) {
            ++numBlobs;
        }
        inBlob = SkToBool(v);
    }

    SkPaint outline;
    outline.setStyle(SkPaint::kStroke_Style);
    if (numBlobs == expectedCaps) {
        outline.setColor(0xFF007F00); // Green
    } else if (numBlobs > expectedCaps) {
        outline.setColor(0xFF7F7F00); // Yellow -- more geometry than expected
    } else {
        outline.setColor(0xFF7F0000); // Red -- missing some geometry
    }

    canvas->drawRect(SkRect::MakeWH(w, h), outline);
    return numBlobs == expectedCaps;
}

static const SkPaint::Cap kCaps[] = {
    SkPaint::kButt_Cap,
    SkPaint::kRound_Cap,
    SkPaint::kSquare_Cap
};

static const SkScalar kWidths[] = { 0.0f, 0.9f, 1.0f, 1.1f, 15.0f, 25.0f };

// Full set of path structures for single contour case (each primitive with and without a close)
static const char* kAllVerbs[] = {
    nullptr,
    "z ",
    "l 0 0 ",
    "l 0 0 z ",
    "q 0 0 0 0 ",
    "q 0 0 0 0 z ",
    "c 0 0 0 0 0 0 ",
    "c 0 0 0 0 0 0 z ",
    "a 0 0 0 0 0 0 0 ",
    "a 0 0 0 0 0 0 0 z "
};

// Reduced set of path structures for double contour case, to keep total number of cases down
static const char* kSomeVerbs[] = {
    nullptr,
    "z ",
    "l 0 0 ",
    "l 0 0 z ",
    "q 0 0 0 0 ",
    "q 0 0 0 0 z ",
};

static const int kCellWidth = 50;
static const int kCellHeight = 20;
static const int kCellPad = 2;

static const int kNumRows = SK_ARRAY_COUNT(kCaps) * SK_ARRAY_COUNT(kWidths);
static const int kNumColumns = SK_ARRAY_COUNT(kAllVerbs);
static const int kTotalWidth = kNumColumns * (kCellWidth + kCellPad) + kCellPad;
static const int kTotalHeight = kNumRows * (kCellHeight + kCellPad) + kCellPad;

static const int kDblContourNumColums = SK_ARRAY_COUNT(kSomeVerbs) * SK_ARRAY_COUNT(kSomeVerbs);
static const int kDblContourTotalWidth = kDblContourNumColums * (kCellWidth + kCellPad) + kCellPad;

// 50% transparent versions of the colors used for positive/negative triage icons on gold.skia.org
static const SkColor kFailureRed = 0x7FE7298A;
static const SkColor kSuccessGreen = 0x7F1B9E77;

static void draw_zero_length_capped_paths(SkCanvas* canvas, bool aa) {
    canvas->translate(kCellPad, kCellPad);

    SkImageInfo info = canvas->imageInfo().makeWH(kCellWidth, kCellHeight);
    auto surface = canvas->makeSurface(info);
    if (!surface) {
        surface = SkSurface::MakeRasterN32Premul(kCellWidth, kCellHeight);
    }

    SkPaint paint;
    paint.setColor(SK_ColorWHITE);
    paint.setAntiAlias(aa);
    paint.setStyle(SkPaint::kStroke_Style);

    int numFailedTests = 0;
    for (auto cap : kCaps) {
        for (auto width : kWidths) {
            paint.setStrokeCap(cap);
            paint.setStrokeWidth(width);
            canvas->save();

            for (auto verb : kAllVerbs) {
                SkString pathStr;
                pathStr.appendf("M %f %f ", (kCellWidth - 1) * 0.5f, (kCellHeight - 1) * 0.5f);
                if (verb) {
                    pathStr.append(verb);
                }

                SkPath path;
                SkParsePath::FromSVGString(pathStr.c_str(), &path);

                surface->getCanvas()->clear(SK_ColorTRANSPARENT);
                surface->getCanvas()->drawPath(path, paint);
                auto img = surface->makeImageSnapshot();

                // All cases should draw one cap, except for butt capped, and dangling moves
                // (without a verb or close), which shouldn't draw anything.
                int expectedCaps = ((SkPaint::kButt_Cap == cap) || !verb) ? 0 : 1;

                if (!draw_path_cell(canvas, img.get(), expectedCaps)) {
                    ++numFailedTests;
                }
                canvas->translate(kCellWidth + kCellPad, 0);
            }
            canvas->restore();
            canvas->translate(0, kCellHeight + kCellPad);
        }
    }

    canvas->drawColor(numFailedTests > 0 ? kFailureRed : kSuccessGreen);
}

DEF_SIMPLE_GM_BG(zero_length_paths_aa, canvas, kTotalWidth, kTotalHeight, SK_ColorBLACK) {
    draw_zero_length_capped_paths(canvas, true);
}

DEF_SIMPLE_GM_BG(zero_length_paths_bw, canvas, kTotalWidth, kTotalHeight, SK_ColorBLACK) {
    draw_zero_length_capped_paths(canvas, false);
}

static void draw_zero_length_capped_paths_dbl_contour(SkCanvas* canvas, bool aa) {
    canvas->translate(kCellPad, kCellPad);

    SkImageInfo info = canvas->imageInfo().makeWH(kCellWidth, kCellHeight);
    auto surface = canvas->makeSurface(info);
    if (!surface) {
        surface = SkSurface::MakeRasterN32Premul(kCellWidth, kCellHeight);
    }

    SkPaint paint;
    paint.setColor(SK_ColorWHITE);
    paint.setAntiAlias(aa);
    paint.setStyle(SkPaint::kStroke_Style);

    int numFailedTests = 0;
    for (auto cap : kCaps) {
        for (auto width : kWidths) {
            paint.setStrokeCap(cap);
            paint.setStrokeWidth(width);
            canvas->save();

            for (auto firstVerb : kSomeVerbs) {
                for (auto secondVerb : kSomeVerbs) {
                    int expectedCaps = 0;

                    SkString pathStr;
                    pathStr.append("M 9.5 9.5 ");
                    if (firstVerb) {
                        pathStr.append(firstVerb);
                        ++expectedCaps;
                    }
                    pathStr.append("M 40.5 9.5 ");
                    if (secondVerb) {
                        pathStr.append(secondVerb);
                        ++expectedCaps;
                    }

                    SkPath path;
                    SkParsePath::FromSVGString(pathStr.c_str(), &path);

                    surface->getCanvas()->clear(SK_ColorTRANSPARENT);
                    surface->getCanvas()->drawPath(path, paint);
                    auto img = surface->makeImageSnapshot();

                    if (SkPaint::kButt_Cap == cap) {
                        expectedCaps = 0;
                    }

                    if (!draw_path_cell(canvas, img.get(), expectedCaps)) {
                        ++numFailedTests;
                    }
                    canvas->translate(kCellWidth + kCellPad, 0);
                }
            }
            canvas->restore();
            canvas->translate(0, kCellHeight + kCellPad);
        }
    }

    canvas->drawColor(numFailedTests > 0 ? kFailureRed : kSuccessGreen);
}

DEF_SIMPLE_GM_BG(zero_length_paths_dbl_aa, canvas, kDblContourTotalWidth, kTotalHeight,
                 SK_ColorBLACK) {
    draw_zero_length_capped_paths_dbl_contour(canvas, true);
}

DEF_SIMPLE_GM_BG(zero_length_paths_dbl_bw, canvas, kDblContourTotalWidth, kTotalHeight,
                 SK_ColorBLACK) {
    draw_zero_length_capped_paths_dbl_contour(canvas, false);
}