blob: 427ce4b8f31f8a9cb3b002884f0e68c575794f15 [file] [log] [blame]
joshualitt7f6a1e02016-01-22 11:21:43 -08001/*
2 * Copyright 2016 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 "GrCaps.h"
9#include "GrContextFactory.h"
10#include "SkCanvas.h"
11#include "SkCommandLineFlags.h"
joshualitt136f5172016-02-02 11:07:39 -080012#include "SkDebugCanvas.h"
joshualitt609d9792016-01-27 11:07:23 -080013#include "SkJSONCanvas.h"
joshualitt29e5a892016-02-04 06:08:33 -080014#include "SkJSONCPP.h"
joshualitt9a4e1882016-01-27 07:03:29 -080015#include "SkPicture.h"
joshualitt792345f2016-02-02 13:02:33 -080016#include "SkPictureRecorder.h"
17#include "SkPixelSerializer.h"
joshualitt7f6a1e02016-01-22 11:21:43 -080018#include "SkStream.h"
19#include "SkSurface.h"
20
joshualittcdad12f2016-02-08 07:08:21 -080021#include "UrlDataManager.h"
22
joshualitt7f6a1e02016-01-22 11:21:43 -080023#include <sys/socket.h>
joshualitt7f6a1e02016-01-22 11:21:43 -080024#include <microhttpd.h>
ethannicholas546d6652016-02-16 11:03:04 -080025#include "png.h"
joshualitt7f6a1e02016-01-22 11:21:43 -080026
27// To get image decoders linked in we have to do the below magic
28#include "SkForceLinking.h"
29#include "SkImageDecoder.h"
30__SK_FORCE_IMAGE_DECODER_LINKING;
31
jcgregorio21ab1202016-01-28 06:24:19 -080032DEFINE_string(source, "https://debugger.skia.org", "Where to load the web UI from.");
joshualitt873d6242016-02-08 13:57:44 -080033DEFINE_string(faviconDir, "tools/skiaserve", "The directory of the favicon");
jcgregorio21ab1202016-01-28 06:24:19 -080034DEFINE_int32(port, 8888, "The port to listen on.");
joshualitt7f6a1e02016-01-22 11:21:43 -080035
36// TODO probably want to make this configurable
37static const int kImageWidth = 1920;
38static const int kImageHeight = 1080;
39
joshualitt7f6a1e02016-01-22 11:21:43 -080040// TODO move to template file
41SkString generateTemplate(SkString source) {
42 SkString debuggerTemplate;
43 debuggerTemplate.appendf(
44 "<!DOCTYPE html>\n"
45 "<html>\n"
46 "<head>\n"
47 " <title>SkDebugger</title>\n"
48 " <meta charset=\"utf-8\" />\n"
49 " <meta http-equiv=\"X-UA-Compatible\" content=\"IE=egde,chrome=1\">\n"
50 " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n"
51 " <script src=\"%s/res/js/core.js\" type=\"text/javascript\" charset=\"utf-8\"></script>\n"
52 " <link href=\"%s/res/vul/elements.html\" rel=\"import\" />\n"
53 "</head>\n"
54 "<body class=\"fullbleed layout vertical\">\n"
55 " <debugger-app-sk>This is the app."
56 " </debugger-app-sk>\n"
57 "</body>\n"
58 "</html>", source.c_str(), source.c_str());
59 return debuggerTemplate;
60
61}
62
joshualitt9a4e1882016-01-27 07:03:29 -080063struct UploadContext {
joshualitt609d9792016-01-27 11:07:23 -080064 SkDynamicMemoryWStream fStream;
65 MHD_PostProcessor* fPostProcessor;
joshualitt9a4e1882016-01-27 07:03:29 -080066 MHD_Connection* connection;
67};
68
69struct Request {
ethannicholas3ff5d8c2016-02-22 08:59:57 -080070 Request(SkString rootUrl)
71 : fUploadContext(nullptr)
72 , fUrlDataManager(rootUrl)
73 , fGPUEnabled(false) {}
74
joshualitt9a4e1882016-01-27 07:03:29 -080075 UploadContext* fUploadContext;
joshualitt609d9792016-01-27 11:07:23 -080076 SkAutoTUnref<SkPicture> fPicture;
joshualitt136f5172016-02-02 11:07:39 -080077 SkAutoTUnref<SkDebugCanvas> fDebugCanvas;
ethannicholasde8e54c2016-02-12 10:09:34 -080078 SkAutoTDelete<GrContextFactory> fContextFactory;
ethannicholas272af6e2016-02-17 10:30:27 -080079 SkAutoTUnref<SkSurface> fSurface;
joshualittcdad12f2016-02-08 07:08:21 -080080 UrlDataManager fUrlDataManager;
ethannicholas3ff5d8c2016-02-22 08:59:57 -080081 bool fGPUEnabled;
joshualitt9a4e1882016-01-27 07:03:29 -080082};
83
ethannicholas546d6652016-02-16 11:03:04 -080084static void write_png_callback(png_structp png_ptr, png_bytep data, png_size_t length) {
85 SkWStream* out = (SkWStream*) png_get_io_ptr(png_ptr);
86 out->write(data, length);
87}
88
89static void write_png(const png_bytep rgba, png_uint_32 width, png_uint_32 height, SkWStream& out) {
90 png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
91 SkASSERT(png != nullptr);
92 png_infop info_ptr = png_create_info_struct(png);
93 SkASSERT(info_ptr != nullptr);
94 if (setjmp(png_jmpbuf(png))) {
95 SkFAIL("png encode error");
96 }
ethannicholascecbbe22016-02-17 11:49:43 -080097 png_set_IHDR(png, info_ptr, width, height, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
ethannicholas546d6652016-02-16 11:03:04 -080098 PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
99 png_set_compression_level(png, 1);
100 png_bytepp rows = (png_bytepp) sk_malloc_throw(height * sizeof(png_byte*));
ethannicholascecbbe22016-02-17 11:49:43 -0800101 png_bytep pixels = (png_bytep) sk_malloc_throw(width * height * 3);
ethannicholas546d6652016-02-16 11:03:04 -0800102 for (png_size_t y = 0; y < height; ++y) {
ethannicholascecbbe22016-02-17 11:49:43 -0800103 const png_bytep src = rgba + y * width * 4;
104 rows[y] = pixels + y * width * 3;
105 // convert from RGBA to RGB
106 for (png_size_t x = 0; x < width; ++x) {
107 rows[y][x * 3] = src[x * 4];
108 rows[y][x * 3 + 1] = src[x * 4 + 1];
109 rows[y][x * 3 + 2] = src[x * 4 + 2];
110 }
ethannicholas546d6652016-02-16 11:03:04 -0800111 }
112 png_set_filter(png, 0, PNG_NO_FILTERS);
113 png_set_rows(png, info_ptr, &rows[0]);
114 png_set_write_fn(png, &out, write_png_callback, NULL);
115 png_write_png(png, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
116 png_destroy_write_struct(&png, NULL);
117 sk_free(rows);
118}
119
ethannicholas3ca1e1a2016-02-18 10:22:34 -0800120SkBitmap* getBitmapFromCanvas(SkCanvas* canvas) {
121 SkBitmap* bmp = new SkBitmap();
122 SkImageInfo info = SkImageInfo::Make(kImageWidth, kImageHeight, kRGBA_8888_SkColorType,
123 kOpaque_SkAlphaType);
124 bmp->setInfo(info);
125 if (!canvas->readPixels(bmp, 0, 0)) {
ethannicholas272af6e2016-02-17 10:30:27 -0800126 fprintf(stderr, "Can't read pixels\n");
127 return nullptr;
128 }
ethannicholas3ca1e1a2016-02-18 10:22:34 -0800129 return bmp;
130}
131
132SkData* writeCanvasToPng(SkCanvas* canvas) {
133 // capture pixels
134 SkAutoTDelete<SkBitmap> bmp(getBitmapFromCanvas(canvas));
135 SkASSERT(bmp);
ethannicholas272af6e2016-02-17 10:30:27 -0800136
joshualitt609d9792016-01-27 11:07:23 -0800137 // write to png
ethannicholas546d6652016-02-16 11:03:04 -0800138 SkDynamicMemoryWStream buffer;
ethannicholas3ca1e1a2016-02-18 10:22:34 -0800139 write_png((const png_bytep) bmp->getPixels(), bmp->width(), bmp->height(), buffer);
ethannicholas546d6652016-02-16 11:03:04 -0800140 return buffer.copyToData();
joshualitt136f5172016-02-02 11:07:39 -0800141}
142
joshualitt6b3cf732016-02-17 11:20:26 -0800143SkCanvas* getCanvasFromRequest(Request* request) {
ethannicholasde8e54c2016-02-12 10:09:34 -0800144 GrContextFactory* factory = request->fContextFactory;
145 SkGLContext* gl = factory->getContextInfo(GrContextFactory::kNative_GLContextType,
146 GrContextFactory::kNone_GLContextOptions).fGLContext;
147 gl->makeCurrent();
148 SkASSERT(request->fDebugCanvas);
ethannicholas272af6e2016-02-17 10:30:27 -0800149 SkCanvas* target = request->fSurface->getCanvas();
joshualitt6b3cf732016-02-17 11:20:26 -0800150 return target;
151}
152
ethannicholas3ca1e1a2016-02-18 10:22:34 -0800153void drawToCanvas(Request* request, int n) {
joshualitt6b3cf732016-02-17 11:20:26 -0800154 SkCanvas* target = getCanvasFromRequest(request);
ethannicholas272af6e2016-02-17 10:30:27 -0800155 request->fDebugCanvas->drawTo(target, n);
ethannicholas3ca1e1a2016-02-18 10:22:34 -0800156}
157
158SkData* drawToPng(Request* request, int n) {
159 drawToCanvas(request, n);
160 return writeCanvasToPng(getCanvasFromRequest(request));
joshualitt609d9792016-01-27 11:07:23 -0800161}
162
ethannicholas85fca852016-02-19 08:40:59 -0800163SkSurface* createCPUSurface() {
joshualitt29e5a892016-02-04 06:08:33 -0800164 SkImageInfo info = SkImageInfo::Make(kImageWidth, kImageHeight, kN32_SkColorType,
165 kPremul_SkAlphaType);
166 return SkSurface::NewRaster(info);
167}
168
ethannicholas85fca852016-02-19 08:40:59 -0800169SkSurface* createGPUSurface(Request* request) {
170 GrContext* context = request->fContextFactory->get(GrContextFactory::kNative_GLContextType,
171 GrContextFactory::kNone_GLContextOptions);
172 int maxRTSize = context->caps()->maxRenderTargetSize();
173 SkImageInfo info = SkImageInfo::Make(SkTMin(kImageWidth, maxRTSize),
174 SkTMin(kImageHeight, maxRTSize),
175 kN32_SkColorType, kPremul_SkAlphaType);
176 uint32_t flags = 0;
177 SkSurfaceProps props(flags, SkSurfaceProps::kLegacyFontHost_InitType);
178 SkSurface* surface = SkSurface::NewRenderTarget(context, SkSurface::kNo_Budgeted, info, 0,
179 &props);
180 return surface;
181}
182
joshualitt9a4e1882016-01-27 07:03:29 -0800183static const size_t kBufferSize = 1024;
184
185static int process_upload_data(void* cls, enum MHD_ValueKind kind,
186 const char* key, const char* filename,
187 const char* content_type, const char* transfer_encoding,
188 const char* data, uint64_t off, size_t size) {
189 struct UploadContext* uc = reinterpret_cast<UploadContext*>(cls);
190
191 if (0 != size) {
joshualitt609d9792016-01-27 11:07:23 -0800192 uc->fStream.write(data, size);
joshualitt9a4e1882016-01-27 07:03:29 -0800193 }
194 return MHD_YES;
195}
196
jcgregorio12d47ce2016-02-10 14:10:37 -0800197// SendOK just sends an empty response with a 200 OK status code.
198static int SendOK(MHD_Connection* connection) {
199 const char* data = "";
200
201 MHD_Response* response = MHD_create_response_from_buffer(strlen(data),
202 (void*)data,
203 MHD_RESPMEM_PERSISTENT);
204 int ret = MHD_queue_response(connection, 200, response);
205 MHD_destroy_response(response);
206 return ret;
207}
208
ethannicholas85fca852016-02-19 08:40:59 -0800209static int SendError(MHD_Connection* connection, const char* msg) {
210 MHD_Response* response = MHD_create_response_from_buffer(strlen(msg),
211 (void*) msg,
212 MHD_RESPMEM_PERSISTENT);
213 int ret = MHD_queue_response(connection, 500, response);
214 MHD_destroy_response(response);
215 return ret;
216}
217
joshualitt792345f2016-02-02 13:02:33 -0800218static int SendData(MHD_Connection* connection, const SkData* data, const char* type,
219 bool setContentDisposition = false, const char* dispositionString = nullptr) {
joshualittccfdaa52016-01-27 07:40:29 -0800220 MHD_Response* response = MHD_create_response_from_buffer(data->size(),
221 const_cast<void*>(data->data()),
222 MHD_RESPMEM_MUST_COPY);
joshualitt609d9792016-01-27 11:07:23 -0800223 MHD_add_response_header(response, "Content-Type", type);
joshualittccfdaa52016-01-27 07:40:29 -0800224
joshualitt792345f2016-02-02 13:02:33 -0800225 if (setContentDisposition) {
226 MHD_add_response_header(response, "Content-Disposition", dispositionString);
227 }
228
joshualittccfdaa52016-01-27 07:40:29 -0800229 int ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
230 MHD_destroy_response(response);
231 return ret;
232}
233
ethannicholas3ff5d8c2016-02-22 08:59:57 -0800234static int SendJSON(MHD_Connection* connection, Request* request, int n) {
235 SkCanvas* canvas = getCanvasFromRequest(request);
236 SkDebugCanvas* debugCanvas = request->fDebugCanvas;
237 UrlDataManager* urlDataManager = &request->fUrlDataManager;
joshualitt6b3cf732016-02-17 11:20:26 -0800238 Json::Value root = debugCanvas->toJSON(*urlDataManager, n, canvas);
ethannicholas3ff5d8c2016-02-22 08:59:57 -0800239 root["mode"] = Json::Value(request->fGPUEnabled ? "gpu" : "cpu");
joshualitt609d9792016-01-27 11:07:23 -0800240 SkDynamicMemoryWStream stream;
joshualittdb6a2542016-02-11 07:09:51 -0800241 stream.writeText(Json::FastWriter().write(root).c_str());
joshualitt609d9792016-01-27 11:07:23 -0800242
243 SkAutoTUnref<SkData> data(stream.copyToData());
244 return SendData(connection, data, "application/json");
245}
246
247static int SendTemplate(MHD_Connection* connection, bool redirect = false,
248 const char* redirectUrl = nullptr) {
jcgregorio21ab1202016-01-28 06:24:19 -0800249 SkString debuggerTemplate = generateTemplate(SkString(FLAGS_source[0]));
joshualittccfdaa52016-01-27 07:40:29 -0800250
251 MHD_Response* response = MHD_create_response_from_buffer(
252 debuggerTemplate.size(),
253 (void*) const_cast<char*>(debuggerTemplate.c_str()),
254 MHD_RESPMEM_MUST_COPY);
jcgregorio6a2046e2016-01-28 05:31:31 -0800255 MHD_add_response_header (response, "Access-Control-Allow-Origin", "*");
joshualittccfdaa52016-01-27 07:40:29 -0800256
jcgregorio6f17bc52016-01-27 11:44:38 -0800257 int status = MHD_HTTP_OK;
258
joshualitt609d9792016-01-27 11:07:23 -0800259 if (redirect) {
260 MHD_add_response_header (response, "Location", redirectUrl);
jcgregorio6f17bc52016-01-27 11:44:38 -0800261 status = MHD_HTTP_SEE_OTHER;
joshualitt609d9792016-01-27 11:07:23 -0800262 }
263
jcgregorio6f17bc52016-01-27 11:44:38 -0800264 int ret = MHD_queue_response(connection, status, response);
joshualittccfdaa52016-01-27 07:40:29 -0800265 MHD_destroy_response(response);
266 return ret;
267}
268
joshualitt483b9012016-02-02 07:16:24 -0800269class UrlHandler {
270public:
271 virtual ~UrlHandler() {}
272 virtual bool canHandle(const char* method, const char* url) = 0;
273 virtual int handle(Request* request, MHD_Connection* connection,
joshualitt136f5172016-02-02 11:07:39 -0800274 const char* url, const char* method,
joshualitt483b9012016-02-02 07:16:24 -0800275 const char* upload_data, size_t* upload_data_size) = 0;
276};
joshualittccfdaa52016-01-27 07:40:29 -0800277
joshualitt29e5a892016-02-04 06:08:33 -0800278class CmdHandler : public UrlHandler {
joshualitt483b9012016-02-02 07:16:24 -0800279public:
280 bool canHandle(const char* method, const char* url) override {
joshualitt136f5172016-02-02 11:07:39 -0800281 const char* kBasePath = "/cmd";
282 return 0 == strncmp(url, kBasePath, strlen(kBasePath));
joshualittccfdaa52016-01-27 07:40:29 -0800283 }
284
joshualitt483b9012016-02-02 07:16:24 -0800285 int handle(Request* request, MHD_Connection* connection,
joshualitt136f5172016-02-02 11:07:39 -0800286 const char* url, const char* method,
joshualitt483b9012016-02-02 07:16:24 -0800287 const char* upload_data, size_t* upload_data_size) override {
joshualitt136f5172016-02-02 11:07:39 -0800288 SkTArray<SkString> commands;
289 SkStrSplit(url, "/", &commands);
290
291 if (!request->fPicture.get() || commands.count() > 3) {
292 return MHD_NO;
joshualitt483b9012016-02-02 07:16:24 -0800293 }
joshualitt136f5172016-02-02 11:07:39 -0800294
joshualittdb6a2542016-02-11 07:09:51 -0800295 // /cmd or /cmd/N
296 if (0 == strcmp(method, MHD_HTTP_METHOD_GET)) {
297 int n;
298 if (commands.count() == 1) {
ethannicholas0a0520a2016-02-12 12:06:53 -0800299 n = request->fDebugCanvas->getSize() - 1;
joshualittdb6a2542016-02-11 07:09:51 -0800300 } else {
301 sscanf(commands[1].c_str(), "%d", &n);
302 }
ethannicholas3ff5d8c2016-02-22 08:59:57 -0800303 return SendJSON(connection, request, n);
joshualitt136f5172016-02-02 11:07:39 -0800304 }
305
306 // /cmd/N, for now only delete supported
307 if (commands.count() == 2 && 0 == strcmp(method, MHD_HTTP_METHOD_DELETE)) {
308 int n;
309 sscanf(commands[1].c_str(), "%d", &n);
310 request->fDebugCanvas->deleteDrawCommandAt(n);
jcgregorio12d47ce2016-02-10 14:10:37 -0800311 return SendOK(connection);
joshualitt136f5172016-02-02 11:07:39 -0800312 }
313
314 // /cmd/N/[0|1]
315 if (commands.count() == 3 && 0 == strcmp(method, MHD_HTTP_METHOD_POST)) {
316 int n, toggle;
317 sscanf(commands[1].c_str(), "%d", &n);
318 sscanf(commands[2].c_str(), "%d", &toggle);
319 request->fDebugCanvas->toggleCommand(n, toggle);
jcgregorio12d47ce2016-02-10 14:10:37 -0800320 return SendOK(connection);
joshualitt136f5172016-02-02 11:07:39 -0800321 }
322
joshualitt483b9012016-02-02 07:16:24 -0800323 return MHD_NO;
324 }
325};
326
327class ImgHandler : public UrlHandler {
328public:
329 bool canHandle(const char* method, const char* url) override {
330 static const char* kBasePath = "/img";
331 return 0 == strcmp(method, MHD_HTTP_METHOD_GET) &&
332 0 == strncmp(url, kBasePath, strlen(kBasePath));
joshualittccfdaa52016-01-27 07:40:29 -0800333 }
334
joshualitt483b9012016-02-02 07:16:24 -0800335 int handle(Request* request, MHD_Connection* connection,
joshualitt136f5172016-02-02 11:07:39 -0800336 const char* url, const char* method,
joshualitt483b9012016-02-02 07:16:24 -0800337 const char* upload_data, size_t* upload_data_size) override {
joshualitt136f5172016-02-02 11:07:39 -0800338 SkTArray<SkString> commands;
339 SkStrSplit(url, "/", &commands);
340
341 if (!request->fPicture.get() || commands.count() > 2) {
342 return MHD_NO;
joshualitt483b9012016-02-02 07:16:24 -0800343 }
joshualitta341b902016-02-02 07:37:21 -0800344
joshualitt136f5172016-02-02 11:07:39 -0800345 int n;
346 // /img or /img/N
347 if (commands.count() == 1) {
348 n = request->fDebugCanvas->getSize() - 1;
349 } else {
350 sscanf(commands[1].c_str(), "%d", &n);
351 }
352
ethannicholas3ca1e1a2016-02-18 10:22:34 -0800353 SkAutoTUnref<SkData> data(drawToPng(request, n));
joshualitt136f5172016-02-02 11:07:39 -0800354 return SendData(connection, data, "image/png");
joshualitt483b9012016-02-02 07:16:24 -0800355 }
356};
joshualittccfdaa52016-01-27 07:40:29 -0800357
ethannicholas3ca1e1a2016-02-18 10:22:34 -0800358class BreakHandler : public UrlHandler {
359public:
360 bool canHandle(const char* method, const char* url) override {
361 static const char* kBasePath = "/break";
362 return 0 == strcmp(method, MHD_HTTP_METHOD_GET) &&
363 0 == strncmp(url, kBasePath, strlen(kBasePath));
364 }
365
366 static SkColor GetPixel(Request* request, int x, int y) {
367 SkCanvas* canvas = getCanvasFromRequest(request);
368 canvas->flush();
369 SkAutoTDelete<SkBitmap> bitmap(getBitmapFromCanvas(canvas));
370 SkASSERT(bitmap);
371 bitmap->lockPixels();
372 uint8_t* start = ((uint8_t*) bitmap->getPixels()) + (y * kImageWidth + x) * 4;
373 SkColor result = SkColorSetARGB(start[3], start[0], start[1], start[2]);
374 bitmap->unlockPixels();
375 return result;
376 }
377
378 int handle(Request* request, MHD_Connection* connection,
379 const char* url, const char* method,
380 const char* upload_data, size_t* upload_data_size) override {
381 SkTArray<SkString> commands;
382 SkStrSplit(url, "/", &commands);
383
384 if (!request->fPicture.get() || commands.count() != 4) {
385 return MHD_NO;
386 }
387
388 // /break/<n>/<x>/<y>
389 int n;
390 sscanf(commands[1].c_str(), "%d", &n);
391 int x;
392 sscanf(commands[2].c_str(), "%d", &x);
393 int y;
394 sscanf(commands[3].c_str(), "%d", &y);
395
396 int count = request->fDebugCanvas->getSize();
397 SkASSERT(n < count);
398
399 SkCanvas* canvas = getCanvasFromRequest(request);
400 canvas->clear(SK_ColorWHITE);
401 int saveCount = canvas->save();
402 for (int i = 0; i <= n; ++i) {
403 request->fDebugCanvas->getDrawCommandAt(i)->execute(canvas);
404 }
405 SkColor target = GetPixel(request, x, y);
406 Json::Value response(Json::objectValue);
407 Json::Value startColor(Json::arrayValue);
408 startColor.append(Json::Value(SkColorGetR(target)));
409 startColor.append(Json::Value(SkColorGetG(target)));
410 startColor.append(Json::Value(SkColorGetB(target)));
411 startColor.append(Json::Value(SkColorGetA(target)));
412 response["startColor"] = startColor;
413 response["endColor"] = startColor;
414 response["endOp"] = Json::Value(n);
415 for (int i = n + 1; i < n + count; ++i) {
416 int index = i % count;
417 if (index == 0) {
418 // reset canvas for wraparound
419 canvas->restoreToCount(saveCount);
420 canvas->clear(SK_ColorWHITE);
421 saveCount = canvas->save();
422 }
423 request->fDebugCanvas->getDrawCommandAt(index)->execute(canvas);
424 SkColor current = GetPixel(request, x, y);
425 if (current != target) {
426 Json::Value endColor(Json::arrayValue);
427 endColor.append(Json::Value(SkColorGetR(current)));
428 endColor.append(Json::Value(SkColorGetG(current)));
429 endColor.append(Json::Value(SkColorGetB(current)));
430 endColor.append(Json::Value(SkColorGetA(current)));
431 response["endColor"] = endColor;
432 response["endOp"] = Json::Value(index);
433 break;
434 }
435 }
436 canvas->restoreToCount(saveCount);
437 SkDynamicMemoryWStream stream;
438 stream.writeText(Json::FastWriter().write(response).c_str());
439 SkAutoTUnref<SkData> data(stream.copyToData());
440 return SendData(connection, data, "application/json");
441 }
442};
443
ethannicholas0a0520a2016-02-12 12:06:53 -0800444/**
445 Updates the clip visualization alpha. On all subsequent /img requests, the clip will be drawn in
446 black with the specified alpha. 0 = no visible clip, 255 = fully opaque clip.
447 */
448class ClipAlphaHandler : public UrlHandler {
449public:
450 bool canHandle(const char* method, const char* url) override {
451 static const char* kBasePath = "/clipAlpha/";
jcgregorio3341d422016-02-16 10:31:07 -0800452 return 0 == strcmp(method, MHD_HTTP_METHOD_POST) &&
ethannicholas0a0520a2016-02-12 12:06:53 -0800453 0 == strncmp(url, kBasePath, strlen(kBasePath));
454 }
455
456 int handle(Request* request, MHD_Connection* connection,
457 const char* url, const char* method,
458 const char* upload_data, size_t* upload_data_size) override {
459 SkTArray<SkString> commands;
460 SkStrSplit(url, "/", &commands);
461
462 if (!request->fPicture.get() || commands.count() != 2) {
463 return MHD_NO;
464 }
465
466 int alpha;
467 sscanf(commands[1].c_str(), "%d", &alpha);
468
469 request->fDebugCanvas->setClipVizColor(SkColorSetARGB(alpha, 0, 0, 0));
470 return SendOK(connection);
471 }
472};
473
ethannicholas85fca852016-02-19 08:40:59 -0800474/**
475 Controls whether GPU rendering is enabled. Posting to /enableGPU/1 turns GPU on, /enableGPU/0
476 disables it.
477 */
478class EnableGPUHandler : public UrlHandler {
479public:
480 bool canHandle(const char* method, const char* url) override {
481 static const char* kBasePath = "/enableGPU/";
482 return 0 == strcmp(method, MHD_HTTP_METHOD_POST) &&
483 0 == strncmp(url, kBasePath, strlen(kBasePath));
484 }
485
486 int handle(Request* request, MHD_Connection* connection,
487 const char* url, const char* method,
488 const char* upload_data, size_t* upload_data_size) override {
489 SkTArray<SkString> commands;
490 SkStrSplit(url, "/", &commands);
491
492 if (commands.count() != 2) {
493 return MHD_NO;
494 }
495
496 int enable;
497 sscanf(commands[1].c_str(), "%d", &enable);
498
499 if (enable) {
500 SkSurface* surface = createGPUSurface(request);
501 if (surface) {
502 request->fSurface.reset(surface);
ethannicholas3ff5d8c2016-02-22 08:59:57 -0800503 request->fGPUEnabled = true;
ethannicholas85fca852016-02-19 08:40:59 -0800504 return SendOK(connection);
505 }
506 return SendError(connection, "Unable to create GPU surface");
507 }
508 request->fSurface.reset(createCPUSurface());
ethannicholas3ff5d8c2016-02-22 08:59:57 -0800509 request->fGPUEnabled = false;
ethannicholas85fca852016-02-19 08:40:59 -0800510 return SendOK(connection);
511 }
512};
513
joshualitt483b9012016-02-02 07:16:24 -0800514class PostHandler : public UrlHandler {
515public:
516 bool canHandle(const char* method, const char* url) override {
517 return 0 == strcmp(method, MHD_HTTP_METHOD_POST) &&
518 0 == strcmp(url, "/new");
joshualittccfdaa52016-01-27 07:40:29 -0800519 }
520
joshualitt483b9012016-02-02 07:16:24 -0800521 int handle(Request* request, MHD_Connection* connection,
joshualitt136f5172016-02-02 11:07:39 -0800522 const char* url, const char* method,
joshualitt483b9012016-02-02 07:16:24 -0800523 const char* upload_data, size_t* upload_data_size) override {
524 UploadContext* uc = request->fUploadContext;
joshualittccfdaa52016-01-27 07:40:29 -0800525
joshualitt483b9012016-02-02 07:16:24 -0800526 // New connection
527 if (!uc) {
528 // TODO make this a method on request
529 uc = new UploadContext;
530 uc->connection = connection;
531 uc->fPostProcessor = MHD_create_post_processor(connection, kBufferSize,
532 &process_upload_data, uc);
533 SkASSERT(uc->fPostProcessor);
joshualitt609d9792016-01-27 11:07:23 -0800534
joshualitt483b9012016-02-02 07:16:24 -0800535 request->fUploadContext = uc;
536 return MHD_YES;
537 }
538
539 // in process upload
540 if (0 != *upload_data_size) {
541 SkASSERT(uc->fPostProcessor);
542 MHD_post_process(uc->fPostProcessor, upload_data, *upload_data_size);
543 *upload_data_size = 0;
544 return MHD_YES;
545 }
546
547 // end of upload
548 MHD_destroy_post_processor(uc->fPostProcessor);
549 uc->fPostProcessor = nullptr;
550
joshualitt136f5172016-02-02 11:07:39 -0800551 // parse picture from stream
552 request->fPicture.reset(
553 SkPicture::CreateFromStream(request->fUploadContext->fStream.detachAsStream()));
554 if (!request->fPicture.get()) {
555 fprintf(stderr, "Could not create picture from stream.\n");
556 return MHD_NO;
joshualitt483b9012016-02-02 07:16:24 -0800557 }
558
joshualitt136f5172016-02-02 11:07:39 -0800559 // pour picture into debug canvas
560 request->fDebugCanvas.reset(new SkDebugCanvas(kImageWidth, kImageHeight));
561 request->fDebugCanvas->drawPicture(request->fPicture);
562
joshualitta341b902016-02-02 07:37:21 -0800563 // clear upload context
564 delete request->fUploadContext;
565 request->fUploadContext = nullptr;
566
joshualitt483b9012016-02-02 07:16:24 -0800567 return SendTemplate(connection, true, "/");
joshualittccfdaa52016-01-27 07:40:29 -0800568 }
joshualitt483b9012016-02-02 07:16:24 -0800569};
570
joshualitt792345f2016-02-02 13:02:33 -0800571class DownloadHandler : public UrlHandler {
572public:
573 bool canHandle(const char* method, const char* url) override {
574 return 0 == strcmp(method, MHD_HTTP_METHOD_GET) &&
575 0 == strcmp(url, "/download");
576 }
577
578 int handle(Request* request, MHD_Connection* connection,
579 const char* url, const char* method,
580 const char* upload_data, size_t* upload_data_size) override {
581 if (!request->fPicture.get()) {
582 return MHD_NO;
583 }
584
585 // TODO move to a function
586 // Playback into picture recorder
587 SkPictureRecorder recorder;
588 SkCanvas* canvas = recorder.beginRecording(kImageWidth, kImageHeight);
589
590 request->fDebugCanvas->draw(canvas);
591
592 SkAutoTUnref<SkPicture> picture(recorder.endRecording());
593
594 SkDynamicMemoryWStream outStream;
595
596 SkAutoTUnref<SkPixelSerializer> serializer(SkImageEncoder::CreatePixelSerializer());
597 picture->serialize(&outStream, serializer);
598
599 SkAutoTUnref<SkData> data(outStream.copyToData());
600
601 // TODO fancier name handling
602 return SendData(connection, data, "application/octet-stream", true,
603 "attachment; filename=something.skp;");
604 }
605};
606
joshualitt29e5a892016-02-04 06:08:33 -0800607class InfoHandler : public UrlHandler {
608public:
609 bool canHandle(const char* method, const char* url) override {
610 const char* kBaseName = "/info";
611 return 0 == strcmp(method, MHD_HTTP_METHOD_GET) &&
612 0 == strncmp(url, kBaseName, strlen(kBaseName));
613 }
614
615 int handle(Request* request, MHD_Connection* connection,
616 const char* url, const char* method,
617 const char* upload_data, size_t* upload_data_size) override {
618 SkTArray<SkString> commands;
619 SkStrSplit(url, "/", &commands);
620
621 if (!request->fPicture.get() || commands.count() > 2) {
622 return MHD_NO;
623 }
624
625 // drawTo
ethannicholas85fca852016-02-19 08:40:59 -0800626 SkAutoTUnref<SkSurface> surface(createCPUSurface());
joshualitt29e5a892016-02-04 06:08:33 -0800627 SkCanvas* canvas = surface->getCanvas();
628
629 int n;
630 // /info or /info/N
631 if (commands.count() == 1) {
632 n = request->fDebugCanvas->getSize() - 1;
633 } else {
634 sscanf(commands[1].c_str(), "%d", &n);
635 }
636
637 // TODO this is really slow and we should cache the matrix and clip
638 request->fDebugCanvas->drawTo(canvas, n);
639
640 // make some json
641 SkMatrix vm = request->fDebugCanvas->getCurrentMatrix();
642 SkIRect clip = request->fDebugCanvas->getCurrentClip();
643 Json::Value info(Json::objectValue);
644 info["ViewMatrix"] = SkJSONCanvas::MakeMatrix(vm);
645 info["ClipRect"] = SkJSONCanvas::MakeIRect(clip);
646
647 std::string json = Json::FastWriter().write(info);
648
649 // We don't want the null terminator so strlen is correct
650 SkAutoTUnref<SkData> data(SkData::NewWithCopy(json.c_str(), strlen(json.c_str())));
651 return SendData(connection, data, "application/json");
652 }
653};
654
joshualittcdad12f2016-02-08 07:08:21 -0800655class DataHandler : public UrlHandler {
656public:
657 bool canHandle(const char* method, const char* url) override {
658 static const char* kBaseUrl = "/data";
659 return 0 == strcmp(method, MHD_HTTP_METHOD_GET) &&
660 0 == strncmp(url, kBaseUrl, strlen(kBaseUrl));
661 }
662
663 int handle(Request* request, MHD_Connection* connection,
664 const char* url, const char* method,
665 const char* upload_data, size_t* upload_data_size) override {
666 SkTArray<SkString> commands;
667 SkStrSplit(url, "/", &commands);
668
669 if (!request->fPicture.get() || commands.count() != 2) {
670 return MHD_NO;
671 }
672
673 SkAutoTUnref<UrlDataManager::UrlData> urlData(
674 SkRef(request->fUrlDataManager.getDataFromUrl(SkString(url))));
675
676 if (urlData) {
677 return SendData(connection, urlData->fData.get(), urlData->fContentType.c_str());
678 }
679 return MHD_NO;
680 }
681};
joshualitt29e5a892016-02-04 06:08:33 -0800682
joshualitt873d6242016-02-08 13:57:44 -0800683class FaviconHandler : public UrlHandler {
684public:
685 bool canHandle(const char* method, const char* url) override {
686 return 0 == strcmp(method, MHD_HTTP_METHOD_GET) &&
687 0 == strcmp(url, "/favicon.ico");
688 }
689
690 int handle(Request* request, MHD_Connection* connection,
691 const char* url, const char* method,
692 const char* upload_data, size_t* upload_data_size) override {
693 SkString dir(FLAGS_faviconDir[0]);
694 dir.append("/favicon.ico");
695 FILE* ico = fopen(dir.c_str(), "r");
696
697 SkAutoTUnref<SkData> data(SkData::NewFromFILE(ico));
698 int ret = SendData(connection, data, "image/vnd.microsoft.icon");
699 fclose(ico);
700 return ret;
701 }
702};
703
704
joshualitt483b9012016-02-02 07:16:24 -0800705class RootHandler : public UrlHandler {
706public:
707 bool canHandle(const char* method, const char* url) override {
708 return 0 == strcmp(method, MHD_HTTP_METHOD_GET) &&
709 0 == strcmp(url, "/");
710 }
711
712 int handle(Request* request, MHD_Connection* connection,
joshualitt136f5172016-02-02 11:07:39 -0800713 const char* url, const char* method,
joshualitt483b9012016-02-02 07:16:24 -0800714 const char* upload_data, size_t* upload_data_size) override {
715 return SendTemplate(connection);
716 }
717};
joshualittccfdaa52016-01-27 07:40:29 -0800718
719class UrlManager {
720public:
721 UrlManager() {
722 // Register handlers
joshualitt483b9012016-02-02 07:16:24 -0800723 fHandlers.push_back(new RootHandler);
724 fHandlers.push_back(new PostHandler);
725 fHandlers.push_back(new ImgHandler);
ethannicholas0a0520a2016-02-12 12:06:53 -0800726 fHandlers.push_back(new ClipAlphaHandler);
ethannicholas85fca852016-02-19 08:40:59 -0800727 fHandlers.push_back(new EnableGPUHandler);
joshualitt29e5a892016-02-04 06:08:33 -0800728 fHandlers.push_back(new CmdHandler);
joshualitt483b9012016-02-02 07:16:24 -0800729 fHandlers.push_back(new InfoHandler);
joshualitt792345f2016-02-02 13:02:33 -0800730 fHandlers.push_back(new DownloadHandler);
joshualittcdad12f2016-02-08 07:08:21 -0800731 fHandlers.push_back(new DataHandler);
joshualitt873d6242016-02-08 13:57:44 -0800732 fHandlers.push_back(new FaviconHandler);
ethannicholas3ca1e1a2016-02-18 10:22:34 -0800733 fHandlers.push_back(new BreakHandler);
joshualitt483b9012016-02-02 07:16:24 -0800734 }
735
736 ~UrlManager() {
737 for (int i = 0; i < fHandlers.count(); i++) { delete fHandlers[i]; }
joshualittccfdaa52016-01-27 07:40:29 -0800738 }
739
740 // This is clearly not efficient for a large number of urls and handlers
741 int invoke(Request* request, MHD_Connection* connection, const char* url, const char* method,
742 const char* upload_data, size_t* upload_data_size) const {
743 for (int i = 0; i < fHandlers.count(); i++) {
joshualitt483b9012016-02-02 07:16:24 -0800744 if (fHandlers[i]->canHandle(method, url)) {
joshualitt136f5172016-02-02 11:07:39 -0800745 return fHandlers[i]->handle(request, connection, url, method, upload_data,
746 upload_data_size);
joshualittccfdaa52016-01-27 07:40:29 -0800747 }
748 }
749 return MHD_NO;
750 }
751
752private:
joshualitt483b9012016-02-02 07:16:24 -0800753 SkTArray<UrlHandler*> fHandlers;
joshualittccfdaa52016-01-27 07:40:29 -0800754};
755
756const UrlManager kUrlManager;
757
joshualitt7f6a1e02016-01-22 11:21:43 -0800758int answer_to_connection(void* cls, struct MHD_Connection* connection,
759 const char* url, const char* method, const char* version,
760 const char* upload_data, size_t* upload_data_size,
761 void** con_cls) {
joshualitt9a4e1882016-01-27 07:03:29 -0800762 SkDebugf("New %s request for %s using version %s\n", method, url, version);
joshualitt7f6a1e02016-01-22 11:21:43 -0800763
joshualitt9a4e1882016-01-27 07:03:29 -0800764 Request* request = reinterpret_cast<Request*>(cls);
joshualitt483b9012016-02-02 07:16:24 -0800765 int result = kUrlManager.invoke(request, connection, url, method, upload_data,
766 upload_data_size);
767 if (MHD_NO == result) {
joshualitt873d6242016-02-08 13:57:44 -0800768 fprintf(stderr, "Invalid method and / or url: %s %s\n", method, url);
joshualitt483b9012016-02-02 07:16:24 -0800769 }
770 return result;
joshualitt7f6a1e02016-01-22 11:21:43 -0800771}
772
773int skiaserve_main() {
joshualittcdad12f2016-02-08 07:08:21 -0800774 Request request(SkString("/data")); // This simple server has one request
ethannicholas85fca852016-02-19 08:40:59 -0800775
776 // create surface
777 GrContextOptions grContextOpts;
778 request.fContextFactory.reset(new GrContextFactory(grContextOpts));
779 request.fSurface.reset(createCPUSurface());
780
joshualitt7f6a1e02016-01-22 11:21:43 -0800781 struct MHD_Daemon* daemon;
jcgregorio21ab1202016-01-28 06:24:19 -0800782 // TODO Add option to bind this strictly to an address, e.g. localhost, for security.
783 daemon = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, FLAGS_port, nullptr, nullptr,
joshualitt9a4e1882016-01-27 07:03:29 -0800784 &answer_to_connection, &request,
785 MHD_OPTION_END);
joshualitt7f6a1e02016-01-22 11:21:43 -0800786 if (NULL == daemon) {
787 return 1;
788 }
789
790 getchar();
791 MHD_stop_daemon(daemon);
792 return 0;
793}
794
795#if !defined SK_BUILD_FOR_IOS
796int main(int argc, char** argv) {
797 SkCommandLineFlags::Parse(argc, argv);
798 return skiaserve_main();
799}
800#endif