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
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
|
/*
* Copyright 2014 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "Test.h"
#include "RecordTestUtils.h"
#include "SkDebugCanvas.h"
#include "SkDropShadowImageFilter.h"
#include "SkImagePriv.h"
#include "SkRecord.h"
#include "SkRecordDraw.h"
#include "SkRecordOpts.h"
#include "SkRecorder.h"
#include "SkRecords.h"
#include "SkSurface.h"
static const int W = 1920, H = 1080;
class JustOneDraw : public SkPicture::AbortCallback {
public:
JustOneDraw() : fCalls(0) {}
bool abort() override { return fCalls++ > 0; }
private:
int fCalls;
};
DEF_TEST(RecordDraw_LazySaves, r) {
// Record two commands.
SkRecord record;
SkRecorder recorder(&record, W, H);
REPORTER_ASSERT(r, 0 == record.count());
recorder.save();
REPORTER_ASSERT(r, 0 == record.count()); // the save was not recorded (yet)
recorder.drawColor(SK_ColorRED);
REPORTER_ASSERT(r, 1 == record.count());
recorder.scale(2, 2);
REPORTER_ASSERT(r, 3 == record.count()); // now we see the save
recorder.restore();
REPORTER_ASSERT(r, 4 == record.count());
assert_type<SkRecords::DrawPaint>(r, record, 0);
assert_type<SkRecords::Save> (r, record, 1);
assert_type<SkRecords::SetMatrix>(r, record, 2);
assert_type<SkRecords::Restore> (r, record, 3);
recorder.save();
recorder.save();
recorder.restore();
recorder.restore();
REPORTER_ASSERT(r, 4 == record.count());
}
DEF_TEST(RecordDraw_Abort, r) {
// Record two commands.
SkRecord record;
SkRecorder recorder(&record, W, H);
recorder.drawRect(SkRect::MakeWH(200, 300), SkPaint());
recorder.clipRect(SkRect::MakeWH(100, 200));
SkRecord rerecord;
SkRecorder canvas(&rerecord, W, H);
JustOneDraw callback;
SkRecordDraw(record, &canvas, NULL, NULL, 0, NULL/*bbh*/, &callback);
REPORTER_ASSERT(r, 1 == count_instances_of_type<SkRecords::DrawRect>(rerecord));
REPORTER_ASSERT(r, 0 == count_instances_of_type<SkRecords::ClipRect>(rerecord));
}
DEF_TEST(RecordDraw_Unbalanced, r) {
SkRecord record;
SkRecorder recorder(&record, W, H);
recorder.save(); // We won't balance this, but SkRecordDraw will for us.
recorder.scale(2, 2);
SkRecord rerecord;
SkRecorder canvas(&rerecord, W, H);
SkRecordDraw(record, &canvas, NULL, NULL, 0, NULL/*bbh*/, NULL/*callback*/);
int save_count = count_instances_of_type<SkRecords::Save>(rerecord);
int restore_count = count_instances_of_type<SkRecords::Save>(rerecord);
REPORTER_ASSERT(r, save_count == restore_count);
}
DEF_TEST(RecordDraw_SetMatrixClobber, r) {
// Set up an SkRecord that just scales by 2x,3x.
SkRecord scaleRecord;
SkRecorder scaleCanvas(&scaleRecord, W, H);
SkMatrix scale;
scale.setScale(2, 3);
scaleCanvas.setMatrix(scale);
// Set up an SkRecord with an initial +20, +20 translate.
SkRecord translateRecord;
SkRecorder translateCanvas(&translateRecord, W, H);
SkMatrix translate;
translate.setTranslate(20, 20);
translateCanvas.setMatrix(translate);
SkRecordDraw(scaleRecord, &translateCanvas, NULL, NULL, 0, NULL/*bbh*/, NULL/*callback*/);
REPORTER_ASSERT(r, 4 == translateRecord.count());
assert_type<SkRecords::SetMatrix>(r, translateRecord, 0);
assert_type<SkRecords::Save> (r, translateRecord, 1);
assert_type<SkRecords::SetMatrix>(r, translateRecord, 2);
assert_type<SkRecords::Restore> (r, translateRecord, 3);
// When we look at translateRecord now, it should have its first +20,+20 translate,
// then a 2x,3x scale that's been concatted with that +20,+20 translate.
const SkRecords::SetMatrix* setMatrix;
setMatrix = assert_type<SkRecords::SetMatrix>(r, translateRecord, 0);
REPORTER_ASSERT(r, setMatrix->matrix == translate);
setMatrix = assert_type<SkRecords::SetMatrix>(r, translateRecord, 2);
SkMatrix expected = scale;
expected.postConcat(translate);
REPORTER_ASSERT(r, setMatrix->matrix == expected);
}
struct TestBBH : public SkBBoxHierarchy {
void insert(const SkRect boundsArray[], int N) override {
fEntries.setCount(N);
for (int i = 0; i < N; i++) {
Entry e = { (unsigned)i, boundsArray[i] };
fEntries[i] = e;
}
}
void search(const SkRect& query, SkTDArray<unsigned>* results) const override {}
size_t bytesUsed() const override { return 0; }
SkRect getRootBound() const override { return SkRect::MakeEmpty(); }
struct Entry {
unsigned opIndex;
SkRect bounds;
};
SkTDArray<Entry> fEntries;
};
// Like a==b, with a little slop recognizing that float equality can be weird.
static bool sloppy_rect_eq(SkRect a, SkRect b) {
SkRect inset(a), outset(a);
inset.inset(1, 1);
outset.outset(1, 1);
return outset.contains(b) && !inset.contains(b);
}
// This test is not meant to make total sense yet. It's testing the status quo
// of SkRecordFillBounds(), which itself doesn't make total sense yet.
DEF_TEST(RecordDraw_BBH, r) {
SkRecord record;
SkRecorder recorder(&record, W, H);
recorder.save();
recorder.clipRect(SkRect::MakeWH(400, 500));
recorder.scale(2, 2);
recorder.drawRect(SkRect::MakeWH(320, 240), SkPaint());
recorder.restore();
TestBBH bbh;
SkRecordFillBounds(SkRect::MakeWH(SkIntToScalar(W), SkIntToScalar(H)), record, &bbh);
REPORTER_ASSERT(r, bbh.fEntries.count() == 5);
for (int i = 0; i < bbh.fEntries.count(); i++) {
REPORTER_ASSERT(r, bbh.fEntries[i].opIndex == (unsigned)i);
REPORTER_ASSERT(r, sloppy_rect_eq(SkRect::MakeWH(400, 480), bbh.fEntries[i].bounds));
}
}
// A regression test for crbug.com/409110.
DEF_TEST(RecordDraw_TextBounds, r) {
SkRecord record;
SkRecorder recorder(&record, W, H);
// Two Chinese characters in UTF-8.
const char text[] = { '\xe6', '\xbc', '\xa2', '\xe5', '\xad', '\x97' };
const size_t bytes = SK_ARRAY_COUNT(text);
const SkScalar xpos[] = { 10, 20 };
recorder.drawPosTextH(text, bytes, xpos, 30, SkPaint());
const SkPoint pos[] = { {40, 50}, {60, 70} };
recorder.drawPosText(text, bytes, pos, SkPaint());
TestBBH bbh;
SkRecordFillBounds(SkRect::MakeWH(SkIntToScalar(W), SkIntToScalar(H)), record, &bbh);
REPORTER_ASSERT(r, bbh.fEntries.count() == 2);
// We can make these next assertions confidently because SkRecordFillBounds
// builds its bounds by overestimating font metrics in a platform-independent way.
// If that changes, these tests will need to be more flexible.
REPORTER_ASSERT(r, sloppy_rect_eq(bbh.fEntries[0].bounds, SkRect::MakeLTRB(0, 0, 140, 60)));
REPORTER_ASSERT(r, sloppy_rect_eq(bbh.fEntries[1].bounds, SkRect::MakeLTRB(0, 20, 180, 100)));
}
// Base test to ensure start/stop range is respected
DEF_TEST(RecordDraw_PartialStartStop, r) {
static const int kWidth = 10, kHeight = 10;
SkRect r1 = { 0, 0, kWidth, kHeight };
SkRect r2 = { 0, 0, kWidth, kHeight/2 };
SkRect r3 = { 0, 0, kWidth/2, kHeight };
SkPaint p;
SkRecord record;
SkRecorder recorder(&record, kWidth, kHeight);
recorder.drawRect(r1, p);
recorder.drawRect(r2, p);
recorder.drawRect(r3, p);
SkRecord rerecord;
SkRecorder canvas(&rerecord, kWidth, kHeight);
SkRecordPartialDraw(record, &canvas, NULL, 0, 1, 2, SkMatrix::I()); // replay just drawRect of r2
REPORTER_ASSERT(r, 1 == count_instances_of_type<SkRecords::DrawRect>(rerecord));
int index = find_first_instances_of_type<SkRecords::DrawRect>(rerecord);
const SkRecords::DrawRect* drawRect = assert_type<SkRecords::DrawRect>(r, rerecord, index);
REPORTER_ASSERT(r, drawRect->rect == r2);
}
// A regression test for crbug.com/415468 and skbug.com/2957.
//
// This also now serves as a regression test for crbug.com/418417. We used to adjust the
// bounds for the saveLayer, clip, and restore to be greater than the bounds of the picture.
// (We were applying the saveLayer paint to the bounds after restore, which makes no sense.)
DEF_TEST(RecordDraw_SaveLayerAffectsClipBounds, r) {
SkRecord record;
SkRecorder recorder(&record, 50, 50);
// We draw a rectangle with a long drop shadow. We used to not update the clip
// bounds based on SaveLayer paints, so the drop shadow could be cut off.
SkPaint paint;
paint.setImageFilter(SkDropShadowImageFilter::Create(20, 0, 0, 0, SK_ColorBLACK,
SkDropShadowImageFilter::kDrawShadowAndForeground_ShadowMode))->unref();
recorder.saveLayer(NULL, &paint);
recorder.clipRect(SkRect::MakeWH(20, 40));
recorder.drawRect(SkRect::MakeWH(20, 40), SkPaint());
recorder.restore();
// Under the original bug, the right edge value of the drawRect would be 20 less than asserted
// here because we intersected it with a clip that had not been adjusted for the drop shadow.
//
// The second bug showed up as adjusting the picture bounds (0,0,50,50) by the drop shadow too.
// The saveLayer, clipRect, and restore bounds were incorrectly (0,0,70,50).
TestBBH bbh;
SkRecordFillBounds(SkRect::MakeWH(50, 50), record, &bbh);
REPORTER_ASSERT(r, bbh.fEntries.count() == 4);
REPORTER_ASSERT(r, sloppy_rect_eq(bbh.fEntries[0].bounds, SkRect::MakeLTRB(0, 0, 50, 50)));
REPORTER_ASSERT(r, sloppy_rect_eq(bbh.fEntries[1].bounds, SkRect::MakeLTRB(0, 0, 50, 50)));
REPORTER_ASSERT(r, sloppy_rect_eq(bbh.fEntries[2].bounds, SkRect::MakeLTRB(0, 0, 40, 40)));
REPORTER_ASSERT(r, sloppy_rect_eq(bbh.fEntries[3].bounds, SkRect::MakeLTRB(0, 0, 50, 50)));
}
// When a saveLayer provides an explicit bound and has a complex paint (e.g., one that
// affects transparent black), that bound should serve to shrink the area of the required
// backing store.
DEF_TEST(RecordDraw_SaveLayerBoundsAffectsClipBounds, r) {
SkRecord record;
SkRecorder recorder(&record, 50, 50);
SkPaint p;
p.setXfermodeMode(SkXfermode::kSrc_Mode);
SkRect bounds = SkRect::MakeLTRB(10, 10, 40, 40);
recorder.saveLayer(&bounds, &p);
recorder.drawRect(SkRect::MakeLTRB(20, 20, 30, 30), SkPaint());
recorder.restore();
TestBBH bbh;
SkRecordFillBounds(SkRect::MakeWH(50, 50), record, &bbh);
REPORTER_ASSERT(r, bbh.fEntries.count() == 3);
if (!SkCanvas::Internal_Private_GetIgnoreSaveLayerBounds()) {
REPORTER_ASSERT(r, sloppy_rect_eq(bbh.fEntries[0].bounds, SkRect::MakeLTRB(10, 10, 40, 40)));
REPORTER_ASSERT(r, sloppy_rect_eq(bbh.fEntries[1].bounds, SkRect::MakeLTRB(20, 20, 30, 30)));
REPORTER_ASSERT(r, sloppy_rect_eq(bbh.fEntries[2].bounds, SkRect::MakeLTRB(10, 10, 40, 40)));
}
}
DEF_TEST(RecordDraw_drawImage, r){
class SkCanvasMock : public SkCanvas {
public:
SkCanvasMock(int width, int height) : SkCanvas(width, height) {
this->resetTestValues();
}
void onDrawImage(const SkImage* image, SkScalar left, SkScalar top,
const SkPaint* paint) override {
fDrawImageCalled = true;
}
void onDrawImageRect(const SkImage* image, const SkRect* src, const SkRect& dst,
const SkPaint* paint) override {
fDrawImageRectCalled = true;
}
void resetTestValues() {
fDrawImageCalled = fDrawImageRectCalled = false;
}
bool fDrawImageCalled;
bool fDrawImageRectCalled;
};
SkAutoTUnref<SkSurface> surface(SkSurface::NewRasterN32Premul(10, 10));
surface->getCanvas()->clear(SK_ColorGREEN);
SkAutoTUnref<SkImage> image(surface->newImageSnapshot());
SkCanvasMock canvas(10, 10);
{
SkRecord record;
SkRecorder recorder(&record, 10, 10);
recorder.drawImage(image, 0, 0);
SkRecordDraw(record, &canvas, NULL, NULL, 0, NULL, 0);
}
REPORTER_ASSERT(r, canvas.fDrawImageCalled);
canvas.resetTestValues();
{
SkRecord record;
SkRecorder recorder(&record, 10, 10);
recorder.drawImageRect(image, 0, SkRect::MakeWH(10, 10));
SkRecordDraw(record, &canvas, NULL, NULL, 0, NULL, 0);
}
REPORTER_ASSERT(r, canvas.fDrawImageRectCalled);
}
|