1/*
2 * Copyright 2014 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 "Test.h"
9
10#include "../include/core/SkCanvas.h"
11#include "../include/core/SkPicture.h"
12#include "../include/core/SkStream.h"
13#include "../include/core/SkString.h"
14#include "../include/record/SkRecording.h"
15#include "../include/core/SkPictureRecorder.h"
16#include <cstring>
17
18// Verify that replay of a recording into a clipped canvas
19// produces the correct bitmap.
20// This arose from http://crbug.com/401593 which has
21// https://code.google.com/p/skia/issues/detail?id=1291 as its root cause.
22
23
24namespace {
25
26class Drawer {
27 public:
28    explicit Drawer()
29            : fImageInfo(SkImageInfo::MakeN32Premul(200,100))
30    {
31        fCircleBM.allocPixels( SkImageInfo::MakeN32Premul(100,100) );
32        SkCanvas canvas(fCircleBM);
33        canvas.clear(0xffffffff);
34        SkPaint circlePaint;
35        circlePaint.setColor(0xff000000);
36        canvas.drawCircle(50,50,50,circlePaint);
37    }
38
39    const SkImageInfo& imageInfo() const { return fImageInfo; }
40
41    void draw(SkCanvas* canvas, const SkRect& clipRect, SkXfermode::Mode mode) const {
42        SkPaint greenPaint;
43        greenPaint.setColor(0xff008000);
44        SkPaint blackPaint;
45        blackPaint.setColor(0xff000000);
46        SkPaint whitePaint;
47        whitePaint.setColor(0xffffffff);
48        SkPaint layerPaint;
49        layerPaint.setColor(0xff000000);
50        layerPaint.setXfermodeMode(mode);
51        SkRect canvasRect(SkRect::MakeWH(SkIntToScalar(fImageInfo.width()),SkIntToScalar(fImageInfo.height())));
52
53        canvas->clipRect(clipRect);
54        canvas->clear(0xff000000);
55
56        canvas->saveLayer(NULL,&blackPaint);
57            canvas->drawRect(canvasRect,greenPaint);
58            canvas->saveLayer(NULL,&layerPaint);
59                canvas->drawBitmapRect(fCircleBM,SkRect::MakeXYWH(20,20,60,60),&blackPaint);
60            canvas->restore();
61        canvas->restore();
62    }
63
64 private:
65    const SkImageInfo fImageInfo;
66    SkBitmap fCircleBM;
67};
68
69class RecordingStrategy {
70 public:
71    virtual ~RecordingStrategy() {}
72    virtual void init(const SkImageInfo&) = 0;
73    virtual const SkBitmap& recordAndReplay(const Drawer& drawer,
74                                            const SkRect& intoClip,
75                                            SkXfermode::Mode) = 0;
76};
77
78class BitmapBackedCanvasStrategy : public RecordingStrategy {
79    // This version just draws into a bitmap-backed canvas.
80 public:
81    BitmapBackedCanvasStrategy() {}
82
83    virtual void init(const SkImageInfo& imageInfo) {
84        fBitmap.allocPixels(imageInfo);
85    }
86
87    virtual const SkBitmap& recordAndReplay(const Drawer& drawer,
88                                            const SkRect& intoClip,
89                                            SkXfermode::Mode mode) {
90        SkCanvas canvas(fBitmap);
91        canvas.clear(0xffffffff);
92        // Note that the scene is drawn just into the clipped region!
93        canvas.clipRect(intoClip);
94        drawer.draw(&canvas, intoClip, mode); // Shouild be canvas-wide...
95        return fBitmap;
96    }
97
98 private:
99    SkBitmap fBitmap;
100};
101
102class DeprecatedRecorderStrategy : public RecordingStrategy {
103    // This version draws the entire scene into an SkPictureRecorder,
104    // using the deprecated recording backend.
105    // Then it then replays the scene through a clip rectangle.
106    // This backend proved to be buggy.
107 public:
108    DeprecatedRecorderStrategy() {}
109
110    virtual void init(const SkImageInfo& imageInfo) {
111        fBitmap.allocPixels(imageInfo);
112        fWidth = imageInfo.width();
113        fHeight= imageInfo.height();
114    }
115
116    virtual const SkBitmap& recordAndReplay(const Drawer& drawer,
117                                            const SkRect& intoClip,
118                                            SkXfermode::Mode mode) {
119        SkTileGridFactory::TileGridInfo tileGridInfo = { {100,100}, {0,0}, {0,0} };
120        SkTileGridFactory factory(tileGridInfo);
121        SkPictureRecorder recorder;
122        SkRect canvasRect(SkRect::MakeWH(SkIntToScalar(fWidth),SkIntToScalar(fHeight)));
123        SkCanvas* canvas = recorder.DEPRECATED_beginRecording( SkIntToScalar(fWidth), SkIntToScalar(fHeight), &factory);
124        drawer.draw(canvas, canvasRect, mode);
125        SkAutoTDelete<SkPicture> picture(recorder.endRecording());
126
127        SkCanvas replayCanvas(fBitmap);
128        replayCanvas.clear(0xffffffff);
129        replayCanvas.clipRect(intoClip);
130        picture->playback(&replayCanvas);
131
132        return fBitmap;
133    }
134
135 private:
136    SkBitmap fBitmap;
137    int fWidth;
138    int fHeight;
139};
140
141class NewRecordingStrategy : public RecordingStrategy {
142    // This version draws the entire scene into an SkPictureRecorder,
143    // using the new recording backend.
144    // Then it then replays the scene through a clip rectangle.
145    // This backend proved to be buggy.
146 public:
147    NewRecordingStrategy() {}
148
149    virtual void init(const SkImageInfo& imageInfo) {
150        fBitmap.allocPixels(imageInfo);
151        fWidth = imageInfo.width();
152        fHeight= imageInfo.height();
153    }
154
155    virtual const SkBitmap& recordAndReplay(const Drawer& drawer,
156                                            const SkRect& intoClip,
157                                            SkXfermode::Mode mode) {
158        SkTileGridFactory::TileGridInfo tileGridInfo = { {100,100}, {0,0}, {0,0} };
159        SkTileGridFactory factory(tileGridInfo);
160        SkPictureRecorder recorder;
161        SkRect canvasRect(SkRect::MakeWH(SkIntToScalar(fWidth),SkIntToScalar(fHeight)));
162        SkCanvas* canvas = recorder.EXPERIMENTAL_beginRecording( SkIntToScalar(fWidth), SkIntToScalar(fHeight), &factory);
163
164        drawer.draw(canvas, canvasRect, mode);
165        SkAutoTDelete<SkPicture> picture(recorder.endRecording());
166
167        SkCanvas replayCanvas(fBitmap);
168        replayCanvas.clear(0xffffffff);
169        replayCanvas.clipRect(intoClip);
170        picture->playback(&replayCanvas);
171        return fBitmap;
172    }
173
174 private:
175    SkBitmap fBitmap;
176    int fWidth;
177    int fHeight;
178};
179
180}
181
182
183
184DEF_TEST(SkRecordingAccuracyXfermode, reporter) {
185#define FINEGRAIN 0
186
187    const Drawer drawer;
188
189    BitmapBackedCanvasStrategy golden; // This is the expected result.
190    DeprecatedRecorderStrategy deprecatedRecording;
191    NewRecordingStrategy newRecording;
192
193    golden.init(drawer.imageInfo());
194    deprecatedRecording.init(drawer.imageInfo());
195    newRecording.init(drawer.imageInfo());
196
197#if !FINEGRAIN
198    unsigned numErrors = 0;
199    SkString errors;
200#endif
201
202    for (int iMode = 0; iMode < int(SkXfermode::kLastMode) ; iMode++ ) {
203        const SkRect& clip = SkRect::MakeXYWH(100,0,100,100);
204        SkXfermode::Mode mode = SkXfermode::Mode(iMode);
205
206        const SkBitmap& goldenBM = golden.recordAndReplay(drawer, clip, mode);
207        const SkBitmap& deprecatedBM = deprecatedRecording.recordAndReplay(drawer, clip, mode);
208        const SkBitmap& newRecordingBM = newRecording.recordAndReplay(drawer, clip, mode);
209
210        size_t pixelsSize = goldenBM.getSize();
211        REPORTER_ASSERT( reporter, pixelsSize == deprecatedBM.getSize() );
212        REPORTER_ASSERT( reporter, pixelsSize == newRecordingBM.getSize() );
213
214        // The pixel arrays should match.
215#if FINEGRAIN
216        REPORTER_ASSERT_MESSAGE( reporter,
217                                 0==memcmp( goldenBM.getPixels(), deprecatedBM.getPixels(), pixelsSize ),
218                                 "Tiled bitmap is wrong");
219        REPORTER_ASSERT_MESSAGE( reporter,
220                                 0==memcmp( goldenBM.getPixels(), recordingBM.getPixels(), pixelsSize ),
221                                 "SkRecorder bitmap is wrong");
222#else
223        if ( memcmp( goldenBM.getPixels(), deprecatedBM.getPixels(), pixelsSize ) ) {
224            numErrors++;
225            SkString str;
226            str.printf("For SkXfermode %d %s:    Deprecated recorder bitmap is wrong\n", iMode, SkXfermode::ModeName(mode));
227            errors.append(str);
228        }
229        if ( memcmp( goldenBM.getPixels(), newRecordingBM.getPixels(), pixelsSize ) ) {
230            numErrors++;
231            SkString str;
232            str.printf("For SkXfermode %d %s:    SkPictureRecorder bitmap is wrong\n", iMode, SkXfermode::ModeName(mode));
233            errors.append(str);
234        }
235#endif
236    }
237#if !FINEGRAIN
238    REPORTER_ASSERT_MESSAGE( reporter, 0==numErrors, errors.c_str() );
239#endif
240}
241