1
2/*
3 * Copyright 2013 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#include "ppapi/cpp/completion_callback.h"
10#include "ppapi/cpp/graphics_2d.h"
11#include "ppapi/cpp/image_data.h"
12#include "ppapi/cpp/instance.h"
13#include "ppapi/cpp/module.h"
14#include "ppapi/cpp/point.h"
15#include "ppapi/cpp/rect.h"
16#include "ppapi/cpp/var.h"
17
18#include "SkBase64.h"
19#include "SkBitmap.h"
20#include "SkCanvas.h"
21#include "SkColor.h"
22#include "SkDebugger.h"
23#include "SkGraphics.h"
24#include "SkStream.h"
25#include "SkString.h"
26
27class SkiaInstance;
28
29// Used by SkDebugf
30SkiaInstance* gPluginInstance;
31
32void FlushCallback(void* data, int32_t result);
33
34// Skia's subclass of pp::Instance, our interface with the browser.
35class SkiaInstance : public pp::Instance {
36public:
37    explicit SkiaInstance(PP_Instance instance)
38        : pp::Instance(instance)
39        , fCanvas(NULL)
40        , fFlushLoopRunning(false)
41        , fFlushPending(false)
42
43    {
44        gPluginInstance = this;
45        SkGraphics::Init();
46    }
47
48    virtual ~SkiaInstance() {
49        SkGraphics::Term();
50        gPluginInstance = NULL;
51    }
52
53    virtual void HandleMessage(const pp::Var& var_message) {
54        // Receive a message from javascript.
55        if (var_message.is_string()) {
56            SkString msg(var_message.AsString().c_str());
57            if (msg.startsWith("init")) {
58            } else if (msg.startsWith("LoadSKP")) {
59                size_t startIndex = strlen("LoadSKP");
60                size_t dataSize = msg.size()/sizeof(char) - startIndex;
61                SkBase64 decodedData;
62                decodedData.decode(msg.c_str() + startIndex, dataSize);
63                size_t decodedSize = 3 * (dataSize / 4);
64                SkDebugf("Got size: %d\n", decodedSize);
65                if (!decodedData.getData()) {
66                    SkDebugf("Failed to decode SKP\n");
67                    return;
68                }
69                SkMemoryStream pictureStream(decodedData.getData(), decodedSize);
70                SkPicture* picture = SkPicture::CreateFromStream(&pictureStream);
71                if (NULL == picture) {
72                    SkDebugf("Failed to create SKP.\n");
73                    return;
74                }
75                fDebugger.loadPicture(picture);
76                picture->unref();
77
78                // Set up the command list.
79                SkTArray<SkString>* commands = fDebugger.getDrawCommandsAsStrings();
80                PostMessage("ClearCommands");
81                for (int i = 0; i < commands->count(); ++i) {
82                    SkString addCommand("AddCommand:");
83                    addCommand.append((*commands)[i]);
84                    PostMessage(addCommand.c_str());
85                }
86                PostMessage("UpdateCommands");
87
88                // Set the overview text.
89                SkString overviewText;
90                fDebugger.getOverviewText(NULL, 0.0, &overviewText, 1);
91                overviewText.prepend("SetOverview:");
92                PostMessage(overviewText.c_str());
93
94                // Draw the SKP.
95                if (!fFlushLoopRunning) {
96                    Paint();
97                }
98            } else if (msg.startsWith("CommandSelected:")) {
99                size_t startIndex = strlen("CommandSelected:");
100                int index = atoi(msg.c_str() + startIndex);
101                fDebugger.setIndex(index);
102                if (!fFlushLoopRunning) {
103                    Paint();
104                }
105            } else if (msg.startsWith("Rewind")) {
106                fCanvas->clear(SK_ColorWHITE);
107                fDebugger.setIndex(0);
108                if (!fFlushLoopRunning) {
109                    Paint();
110                }
111            } else if (msg.startsWith("StepBack")) {
112                fCanvas->clear(SK_ColorWHITE);
113                int currentIndex = fDebugger.index();
114                if (currentIndex > 1) {
115                    fDebugger.setIndex(currentIndex - 1);
116                    if (!fFlushLoopRunning) {
117                        Paint();
118                    }
119                }
120            } else if (msg.startsWith("Pause")) {
121                // TODO(borenet)
122            } else if (msg.startsWith("StepForward")) {
123                int currentIndex = fDebugger.index();
124                if (currentIndex < fDebugger.getSize() -1) {
125                    fDebugger.setIndex(currentIndex + 1);
126                    if (!fFlushLoopRunning) {
127                        Paint();
128                    }
129                }
130            } else if (msg.startsWith("Play")) {
131                fDebugger.setIndex(fDebugger.getSize() - 1);
132                if (!fFlushLoopRunning) {
133                    Paint();
134                }
135            }
136        }
137    }
138
139    void Paint() {
140        if (!fImage.is_null()) {
141            fDebugger.draw(fCanvas);
142            fDeviceContext.PaintImageData(fImage, pp::Point(0, 0));
143            if (!fFlushPending) {
144                fFlushPending = true;
145                fDeviceContext.Flush(pp::CompletionCallback(&FlushCallback, this));
146            } else {
147                SkDebugf("A flush is pending... Skipping flush.\n");
148            }
149        } else {
150            SkDebugf("No pixels to write to!\n");
151        }
152    }
153
154    virtual void DidChangeView(const pp::Rect& position, const pp::Rect& clip) {
155        if (position.size().width() == fWidth &&
156            position.size().height() == fHeight) {
157            return;  // We don't care about the position, only the size.
158        }
159        fWidth = position.size().width();
160        fHeight = position.size().height();
161
162        fDeviceContext = pp::Graphics2D(this, pp::Size(fWidth, fHeight), false);
163        if (!BindGraphics(fDeviceContext)) {
164            SkDebugf("Couldn't bind the device context\n");
165            return;
166        }
167        fImage = pp::ImageData(this,
168                               PP_IMAGEDATAFORMAT_BGRA_PREMUL,
169                               pp::Size(fWidth, fHeight), false);
170        const SkImageInfo info = SkImageInfo::MakeN32Premul(fWidth, fHeight);
171        fBitmap.installPixels(info, fImage.data(), info.minRowBytes());
172        if (fCanvas) {
173            delete fCanvas;
174        }
175        fCanvas = new SkCanvas(fBitmap);
176        fCanvas->clear(SK_ColorWHITE);
177        if (!fFlushLoopRunning) {
178            Paint();
179        }
180    }
181
182    void OnFlush() {
183        fFlushLoopRunning = true;
184        fFlushPending = false;
185        Paint();
186    }
187
188private:
189    pp::Graphics2D fDeviceContext;
190    pp::ImageData fImage;
191    int fWidth;
192    int fHeight;
193
194    SkBitmap fBitmap;
195    SkCanvas* fCanvas;
196    SkDebugger fDebugger;
197
198    bool fFlushLoopRunning;
199    bool fFlushPending;
200};
201
202void FlushCallback(void* data, int32_t result) {
203    static_cast<SkiaInstance*>(data)->OnFlush();
204}
205
206class SkiaModule : public pp::Module {
207public:
208    SkiaModule() : pp::Module() {}
209    virtual ~SkiaModule() {}
210
211    virtual pp::Instance* CreateInstance(PP_Instance instance) {
212        return new SkiaInstance(instance);
213    }
214};
215
216namespace pp {
217Module* CreateModule() {
218    return new SkiaModule();
219}
220}  // namespace pp
221