blob: 6adfd5a9be1d19b1161cb24e05eb26045ad486ee [file] [log] [blame]
scroggo@google.comd614c6a2012-09-14 17:26:37 +00001/*
2 * Copyright 2012 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7#include "Test.h"
reed@google.com21b519d2012-10-02 17:42:15 +00008#include "SkCanvas.h"
reed@google.comfe7b1ed2012-11-29 21:00:39 +00009#include "SkColorPriv.h"
10#include "SkData.h"
reed@google.com21b519d2012-10-02 17:42:15 +000011#include "SkPaint.h"
scroggo@google.comd614c6a2012-09-14 17:26:37 +000012#include "SkPicture.h"
reed@google.com21b519d2012-10-02 17:42:15 +000013#include "SkRandom.h"
reed@google.com72aa79c2013-01-24 18:27:42 +000014#include "SkRRect.h"
reed@google.comfe7b1ed2012-11-29 21:00:39 +000015#include "SkShader.h"
scroggo@google.comd614c6a2012-09-14 17:26:37 +000016#include "SkStream.h"
17
reed@google.comfe7b1ed2012-11-29 21:00:39 +000018#include "SkPictureUtils.h"
19
20static void make_bm(SkBitmap* bm, int w, int h, SkColor color, bool immutable) {
21 bm->setConfig(SkBitmap::kARGB_8888_Config, w, h);
22 bm->allocPixels();
23 bm->eraseColor(color);
24 if (immutable) {
25 bm->setImmutable();
26 }
27}
28
29typedef void (*DrawBitmapProc)(SkCanvas*, const SkBitmap&, const SkPoint&);
30
31static void drawbitmap_proc(SkCanvas* canvas, const SkBitmap& bm,
32 const SkPoint& pos) {
33 canvas->drawBitmap(bm, pos.fX, pos.fY, NULL);
34}
35
36static void drawbitmaprect_proc(SkCanvas* canvas, const SkBitmap& bm,
37 const SkPoint& pos) {
38 SkRect r = {
39 0, 0, SkIntToScalar(bm.width()), SkIntToScalar(bm.height())
40 };
41 r.offset(pos.fX, pos.fY);
42 canvas->drawBitmapRectToRect(bm, NULL, r, NULL);
43}
44
45static void drawshader_proc(SkCanvas* canvas, const SkBitmap& bm,
46 const SkPoint& pos) {
47 SkRect r = {
48 0, 0, SkIntToScalar(bm.width()), SkIntToScalar(bm.height())
49 };
50 r.offset(pos.fX, pos.fY);
51
52 SkShader* s = SkShader::CreateBitmapShader(bm,
53 SkShader::kClamp_TileMode,
54 SkShader::kClamp_TileMode);
55 SkPaint paint;
56 paint.setShader(s)->unref();
57 canvas->drawRect(r, paint);
reed@google.com72aa79c2013-01-24 18:27:42 +000058 canvas->drawOval(r, paint);
59 SkRRect rr;
60 rr.setRectXY(r, 10, 10);
61 canvas->drawRRect(rr, paint);
reed@google.comfe7b1ed2012-11-29 21:00:39 +000062}
63
64// Return a picture with the bitmaps drawn at the specified positions.
65static SkPicture* record_bitmaps(const SkBitmap bm[], const SkPoint pos[],
66 int count, DrawBitmapProc proc) {
67 SkPicture* pic = new SkPicture;
68 SkCanvas* canvas = pic->beginRecording(1000, 1000);
69 for (int i = 0; i < count; ++i) {
70 proc(canvas, bm[i], pos[i]);
71 }
72 pic->endRecording();
73 return pic;
74}
75
jvanverth@google.comc490f802013-03-04 13:56:38 +000076static void rand_rect(SkRect* rect, SkMWCRandom& rand, SkScalar W, SkScalar H) {
reed@google.comfe7b1ed2012-11-29 21:00:39 +000077 rect->fLeft = rand.nextRangeScalar(-W, 2*W);
78 rect->fTop = rand.nextRangeScalar(-H, 2*H);
79 rect->fRight = rect->fLeft + rand.nextRangeScalar(0, W);
80 rect->fBottom = rect->fTop + rand.nextRangeScalar(0, H);
81
82 // we integralize rect to make our tests more predictable, since Gather is
83 // a little sloppy.
84 SkIRect ir;
85 rect->round(&ir);
86 rect->set(ir);
87}
88
89// Allocate result to be large enough to hold subset, and then draw the picture
90// into it, offsetting by subset's top/left corner.
91static void draw(SkPicture* pic, const SkRect& subset, SkBitmap* result) {
92 SkIRect ir;
93 subset.roundOut(&ir);
94 int w = ir.width();
95 int h = ir.height();
96 make_bm(result, w, h, 0, false);
97
98 SkCanvas canvas(*result);
99 canvas.translate(-SkIntToScalar(ir.left()), -SkIntToScalar(ir.top()));
100 canvas.drawPicture(*pic);
101}
102
103template <typename T> int find_index(const T* array, T elem, int count) {
104 for (int i = 0; i < count; ++i) {
105 if (array[i] == elem) {
106 return i;
107 }
108 }
109 return -1;
110}
111
112// Return true if 'ref' is found in array[]
113static bool find(SkPixelRef const * const * array, SkPixelRef const * ref, int count) {
114 return find_index<const SkPixelRef*>(array, ref, count) >= 0;
115}
116
117// Look at each pixel in bm, and if its color appears in colors[], find the
118// corresponding value in refs[] and append that ref into array, skipping
119// duplicates of the same value.
120static void gather_from_colors(const SkBitmap& bm, SkPixelRef* const refs[],
121 int count, SkTDArray<SkPixelRef*>* array) {
122 // Since we only want to return unique values in array, when we scan we just
123 // set a bit for each index'd color found. In practice we only have a few
124 // distinct colors, so we just use an int's bits as our array. Hence the
125 // assert that count <= number-of-bits-in-our-int.
126 SkASSERT((unsigned)count <= 32);
127 uint32_t bitarray = 0;
128
129 SkAutoLockPixels alp(bm);
130
131 for (int y = 0; y < bm.height(); ++y) {
132 for (int x = 0; x < bm.width(); ++x) {
133 SkPMColor pmc = *bm.getAddr32(x, y);
134 // the only good case where the color is not found would be if
135 // the color is transparent, meaning no bitmap was drawn in that
136 // pixel.
137 if (pmc) {
bsalomon@google.comc3d753e2013-01-08 17:24:44 +0000138 uint32_t index = SkGetPackedR32(pmc);
reed@google.comfe7b1ed2012-11-29 21:00:39 +0000139 SkASSERT(SkGetPackedG32(pmc) == index);
140 SkASSERT(SkGetPackedB32(pmc) == index);
bsalomon@google.com5f429b02013-01-08 18:42:20 +0000141 SkASSERT(static_cast<int>(index) < count);
reed@google.comfe7b1ed2012-11-29 21:00:39 +0000142 bitarray |= 1 << index;
143 }
144 }
145 }
146
147 for (int i = 0; i < count; ++i) {
148 if (bitarray & (1 << i)) {
149 *array->append() = refs[i];
150 }
151 }
152}
153
154static void test_gatherpixelrefs(skiatest::Reporter* reporter) {
155 const int IW = 8;
156 const int IH = IW;
157 const SkScalar W = SkIntToScalar(IW);
158 const SkScalar H = W;
159
160 static const int N = 4;
161 SkBitmap bm[N];
reed@google.comfe7b1ed2012-11-29 21:00:39 +0000162 SkPixelRef* refs[N];
163
164 const SkPoint pos[] = {
165 { 0, 0 }, { W, 0 }, { 0, H }, { W, H }
166 };
167
168 // Our convention is that the color components contain the index of their
169 // corresponding bitmap/pixelref
170 for (int i = 0; i < N; ++i) {
171 make_bm(&bm[i], IW, IH, SkColorSetARGB(0xFF, i, i, i), true);
172 refs[i] = bm[i].pixelRef();
173 }
174
175 static const DrawBitmapProc procs[] = {
176 drawbitmap_proc, drawbitmaprect_proc, drawshader_proc
177 };
178
jvanverth@google.comc490f802013-03-04 13:56:38 +0000179 SkMWCRandom rand;
reed@google.comfe7b1ed2012-11-29 21:00:39 +0000180 for (size_t k = 0; k < SK_ARRAY_COUNT(procs); ++k) {
181 SkAutoTUnref<SkPicture> pic(record_bitmaps(bm, pos, N, procs[k]));
182
183 // quick check for a small piece of each quadrant, which should just
184 // contain 1 bitmap.
185 for (size_t i = 0; i < SK_ARRAY_COUNT(pos); ++i) {
186 SkRect r;
187 r.set(2, 2, W - 2, H - 2);
188 r.offset(pos[i].fX, pos[i].fY);
189 SkAutoDataUnref data(SkPictureUtils::GatherPixelRefs(pic, r));
190 REPORTER_ASSERT(reporter, data);
191 int count = data->size() / sizeof(SkPixelRef*);
192 REPORTER_ASSERT(reporter, 1 == count);
193 REPORTER_ASSERT(reporter, *(SkPixelRef**)data->data() == refs[i]);
194 }
195
196 // Test a bunch of random (mostly) rects, and compare the gather results
197 // with a deduced list of refs by looking at the colors drawn.
198 for (int j = 0; j < 100; ++j) {
199 SkRect r;
200 rand_rect(&r, rand, 2*W, 2*H);
201
202 SkBitmap result;
203 draw(pic, r, &result);
204 SkTDArray<SkPixelRef*> array;
205
206 SkData* data = SkPictureUtils::GatherPixelRefs(pic, r);
207 size_t dataSize = data ? data->size() : 0;
208 int gatherCount = dataSize / sizeof(SkPixelRef*);
209 SkASSERT(gatherCount * sizeof(SkPixelRef*) == dataSize);
210 SkPixelRef** gatherRefs = data ? (SkPixelRef**)(data->data()) : NULL;
211 SkAutoDataUnref adu(data);
212
213 gather_from_colors(result, refs, N, &array);
skia.committer@gmail.comc3d7d902012-11-30 02:01:24 +0000214
reed@google.comfe7b1ed2012-11-29 21:00:39 +0000215 /*
216 * GatherPixelRefs is conservative, so it can return more bitmaps
217 * that we actually can see (usually because of conservative bounds
218 * inflation for antialiasing). Thus our check here is only that
219 * Gather didn't miss any that we actually saw. Even that isn't
220 * a strict requirement on Gather, which is meant to be quick and
221 * only mostly-correct, but at the moment this test should work.
222 */
223 for (int i = 0; i < array.count(); ++i) {
224 bool found = find(gatherRefs, array[i], gatherCount);
225 REPORTER_ASSERT(reporter, found);
226#if 0
227 // enable this block of code to debug failures, as it will rerun
228 // the case that failed.
229 if (!found) {
230 SkData* data = SkPictureUtils::GatherPixelRefs(pic, r);
231 size_t dataSize = data ? data->size() : 0;
232 }
233#endif
234 }
235 }
236 }
237}
238
scroggo@google.comd614c6a2012-09-14 17:26:37 +0000239#ifdef SK_DEBUG
240// Ensure that deleting SkPicturePlayback does not assert. Asserts only fire in debug mode, so only
241// run in debug mode.
242static void test_deleting_empty_playback() {
243 SkPicture picture;
244 // Creates an SkPictureRecord
245 picture.beginRecording(0, 0);
246 // Turns that into an SkPicturePlayback
247 picture.endRecording();
248 // Deletes the old SkPicturePlayback, and creates a new SkPictureRecord
249 picture.beginRecording(0, 0);
250}
251
252// Ensure that serializing an empty picture does not assert. Likewise only runs in debug mode.
253static void test_serializing_empty_picture() {
254 SkPicture picture;
255 picture.beginRecording(0, 0);
256 picture.endRecording();
257 SkDynamicMemoryWStream stream;
258 picture.serialize(&stream);
259}
260#endif
261
jvanverth@google.comc490f802013-03-04 13:56:38 +0000262static void rand_op(SkCanvas* canvas, SkMWCRandom& rand) {
reed@google.com21b519d2012-10-02 17:42:15 +0000263 SkPaint paint;
264 SkRect rect = SkRect::MakeWH(50, 50);
265
266 SkScalar unit = rand.nextUScalar1();
267 if (unit <= 0.3) {
268// SkDebugf("save\n");
269 canvas->save();
270 } else if (unit <= 0.6) {
271// SkDebugf("restore\n");
272 canvas->restore();
273 } else if (unit <= 0.9) {
274// SkDebugf("clip\n");
275 canvas->clipRect(rect);
276 } else {
277// SkDebugf("draw\n");
278 canvas->drawPaint(paint);
279 }
280}
281
sugoi@google.com54f0d1b2013-02-27 19:17:41 +0000282static void test_peephole() {
jvanverth@google.comc490f802013-03-04 13:56:38 +0000283 SkMWCRandom rand;
reed@google.com21b519d2012-10-02 17:42:15 +0000284
285 for (int j = 0; j < 100; j++) {
jvanverth@google.comc490f802013-03-04 13:56:38 +0000286 SkMWCRandom rand2(rand); // remember the seed
reed@google.com21b519d2012-10-02 17:42:15 +0000287
288 SkPicture picture;
289 SkCanvas* canvas = picture.beginRecording(100, 100);
290
291 for (int i = 0; i < 1000; ++i) {
292 rand_op(canvas, rand);
293 }
294 picture.endRecording();
jvanverth@google.comc490f802013-03-04 13:56:38 +0000295
296 rand = rand2;
reed@google.com21b519d2012-10-02 17:42:15 +0000297 }
298
299 {
300 SkPicture picture;
301 SkCanvas* canvas = picture.beginRecording(100, 100);
302 SkRect rect = SkRect::MakeWH(50, 50);
skia.committer@gmail.com52c24372012-10-03 02:01:13 +0000303
reed@google.com21b519d2012-10-02 17:42:15 +0000304 for (int i = 0; i < 100; ++i) {
305 canvas->save();
306 }
307 while (canvas->getSaveCount() > 1) {
308 canvas->clipRect(rect);
309 canvas->restore();
310 }
311 picture.endRecording();
312 }
313}
314
scroggo@google.com4b90b112012-12-04 15:08:56 +0000315#ifndef SK_DEBUG
316// Only test this is in release mode. We deliberately crash in debug mode, since a valid caller
317// should never do this.
318static void test_bad_bitmap() {
319 // This bitmap has a width and height but no pixels. As a result, attempting to record it will
320 // fail.
321 SkBitmap bm;
322 bm.setConfig(SkBitmap::kARGB_8888_Config, 100, 100);
323 SkPicture picture;
324 SkCanvas* recordingCanvas = picture.beginRecording(100, 100);
325 recordingCanvas->drawBitmap(bm, 0, 0);
326 picture.endRecording();
327
328 SkCanvas canvas;
329 canvas.drawPicture(picture);
330}
331#endif
332
scroggo@google.com7c9d5392012-12-10 15:40:55 +0000333#include "SkData.h"
334#include "SkImageRef_GlobalPool.h"
335// Class to test SkPixelRef::onRefEncodedData, since there are currently no implementations in skia.
336class SkDataImageRef : public SkImageRef_GlobalPool {
337
338public:
339 SkDataImageRef(SkMemoryStream* stream)
340 : SkImageRef_GlobalPool(stream, SkBitmap::kNo_Config) {
341 SkASSERT(stream != NULL);
342 fData = stream->copyToData();
343 this->setImmutable();
344 }
345
346 ~SkDataImageRef() {
347 fData->unref();
348 }
349
350 virtual SkData* onRefEncodedData() SK_OVERRIDE {
351 fData->ref();
352 return fData;
353 }
354
355private:
356 SkData* fData;
357};
358
359#include "SkImageEncoder.h"
360
361static bool PNGEncodeBitmapToStream(SkWStream* wStream, const SkBitmap& bm) {
362 return SkImageEncoder::EncodeStream(wStream, bm, SkImageEncoder::kPNG_Type, 100);
363}
364
365static SkData* serialized_picture_from_bitmap(const SkBitmap& bitmap) {
366 SkPicture picture;
367 SkCanvas* canvas = picture.beginRecording(bitmap.width(), bitmap.height());
368 canvas->drawBitmap(bitmap, 0, 0);
369 SkDynamicMemoryWStream wStream;
370 picture.serialize(&wStream, &PNGEncodeBitmapToStream);
371 return wStream.copyToData();
372}
373
374static void test_bitmap_with_encoded_data(skiatest::Reporter* reporter) {
375 // Create a bitmap that will be encoded.
376 SkBitmap original;
377 make_bm(&original, 100, 100, SK_ColorBLUE, true);
378 SkDynamicMemoryWStream wStream;
379 if (!SkImageEncoder::EncodeStream(&wStream, original, SkImageEncoder::kPNG_Type, 100)) {
380 return;
381 }
382 SkAutoDataUnref data(wStream.copyToData());
383 SkMemoryStream memStream;
384 memStream.setData(data);
385
386 // Use the encoded bitmap as the data for an image ref.
387 SkBitmap bm;
388 SkAutoTUnref<SkDataImageRef> imageRef(SkNEW_ARGS(SkDataImageRef, (&memStream)));
389 imageRef->getInfo(&bm);
390 bm.setPixelRef(imageRef);
391
392 // Write both bitmaps to pictures, and ensure that the resulting data streams are the same.
393 // Flattening original will follow the old path of performing an encode, while flattening bm
394 // will use the already encoded data.
395 SkAutoDataUnref picture1(serialized_picture_from_bitmap(original));
396 SkAutoDataUnref picture2(serialized_picture_from_bitmap(bm));
397 REPORTER_ASSERT(reporter, picture1->equals(picture2));
398}
399
junov@chromium.org94f20dc2013-01-28 21:04:44 +0000400static void test_clone_empty(skiatest::Reporter* reporter) {
401 // This is a regression test for crbug.com/172062
402 // Before the fix, we used to crash accessing a null pointer when we
403 // had a picture with no paints. This test passes by not crashing.
404 {
405 SkPicture picture;
406 picture.beginRecording(1, 1);
407 picture.endRecording();
408 SkPicture* destPicture = picture.clone();
409 REPORTER_ASSERT(reporter, NULL != destPicture);
410 destPicture->unref();
411 }
412 {
413 // Test without call to endRecording
414 SkPicture picture;
415 picture.beginRecording(1, 1);
416 SkPicture* destPicture = picture.clone();
417 REPORTER_ASSERT(reporter, NULL != destPicture);
418 destPicture->unref();
419 }
420}
421
scroggo@google.comd614c6a2012-09-14 17:26:37 +0000422static void TestPicture(skiatest::Reporter* reporter) {
423#ifdef SK_DEBUG
424 test_deleting_empty_playback();
425 test_serializing_empty_picture();
scroggo@google.com4b90b112012-12-04 15:08:56 +0000426#else
427 test_bad_bitmap();
scroggo@google.comd614c6a2012-09-14 17:26:37 +0000428#endif
sugoi@google.com54f0d1b2013-02-27 19:17:41 +0000429 test_peephole();
reed@google.comfe7b1ed2012-11-29 21:00:39 +0000430 test_gatherpixelrefs(reporter);
scroggo@google.com7c9d5392012-12-10 15:40:55 +0000431 test_bitmap_with_encoded_data(reporter);
junov@chromium.org94f20dc2013-01-28 21:04:44 +0000432 test_clone_empty(reporter);
scroggo@google.comd614c6a2012-09-14 17:26:37 +0000433}
434
435#include "TestClassDef.h"
reed@google.com21b519d2012-10-02 17:42:15 +0000436DEFINE_TESTCLASS("Pictures", PictureTestClass, TestPicture)