/* HTML canvas element rendering context binding using duktape and libdom * * Copyright 2020 Daniel Silverstone * * This file is part of NetSurf, http://www.netsurf-browser.org/ * * Released under the terms of the MIT License, * http://www.opensource.org/licenses/mit-license */ class CanvasRenderingContext2D { private struct dom_html_element *canvas; private struct bitmap *bitmap; private int width; private int height; private size_t stride; prologue %{ /* prologue */ #include "desktop/gui_internal.h" #include "desktop/gui_table.h" #include "netsurf/bitmap.h" #include "utils/corestrings.h" /* It's a smidge naughty of us to read * this particular header, but we're needing * to redraw the node we represent */ #include "content/handlers/html/private.h" static void redraw_node(dom_node *node) { struct box *box = NULL; html_content *htmlc = NULL; dom_exception exc; dom_document *doc; exc = dom_node_get_user_data(node, corestring_dom___ns_key_box_node_data, &box); if (exc != DOM_NO_ERR || box == NULL) { return; } exc = dom_node_get_owner_document(node, &doc); if (exc != DOM_NO_ERR || doc == NULL) { return; } exc = dom_node_get_user_data(doc, corestring_dom___ns_key_html_content_data, &htmlc); if (exc != DOM_NO_ERR || htmlc == NULL) { dom_node_unref(doc); return; } html__redraw_a_box(htmlc, box); dom_node_unref(doc); } /* prologue ends */ %}; }; init CanvasRenderingContext2D(struct dom_html_element *canvas) %{ struct bitmap *bitmap; dom_exception exc; assert(canvas != NULL); priv->canvas = canvas; dom_node_ref(canvas); exc = dom_node_get_user_data(canvas, corestring_dom___ns_key_canvas_node_data, &bitmap); assert(exc == DOM_NO_ERR); assert(bitmap != NULL); priv->bitmap = bitmap; priv->width = guit->bitmap->get_width(bitmap); priv->height = guit->bitmap->get_height(bitmap); priv->stride = guit->bitmap->get_rowstride(bitmap); %} fini CanvasRenderingContext2D() %{ dom_node_unref(priv->canvas); %} getter CanvasRenderingContext2D::canvas() %{ dukky_push_node(ctx, (dom_node *)priv->canvas); return 1; %} method CanvasRenderingContext2D::createImageData() %{ /* Can be called either with width and height, or with a reference * imagedata object */ image_data_private_t *idpriv; int width, height; if (duk_get_top(ctx) == 2) { width = duk_to_int(ctx, 0); height = duk_to_int(ctx, 1); } else if (dukky_instanceof(ctx, 0, PROTO_NAME(IMAGEDATA))) { duk_get_prop_string(ctx, 0, dukky_magic_string_private); idpriv = duk_get_pointer(ctx, -1); width = idpriv->width; height = idpriv->height; duk_pop(ctx); } else { duk_push_null(ctx); return 1; } duk_push_int(ctx, width); duk_push_int(ctx, height); if (dukky_create_object(ctx, PROTO_NAME(IMAGEDATA), 2) != DUK_EXEC_SUCCESS) { return duk_error(ctx, DUK_ERR_ERROR, "Unable to create ImageData"); } return 1; %} method CanvasRenderingContext2D::getImageData() %{ /* called with x, y, width, height */ int x = duk_get_int(ctx, 0); int y = duk_get_int(ctx, 1); int width = duk_get_int(ctx, 2); int height = duk_get_int(ctx, 3); image_data_private_t *idpriv; uint8_t *bitmap_base; if (width < 1 || height < 1 || (x + width) > priv->width || (y + height) > priv->height) { return duk_error(ctx, DUK_ERR_RANGE_ERROR, "invalid (%d,%d) (%dx%d)", x, y, width, height); } duk_push_int(ctx, width); duk_push_int(ctx, height); if (dukky_create_object(ctx, PROTO_NAME(IMAGEDATA), 2) != DUK_EXEC_SUCCESS) { return duk_error(ctx, DUK_ERR_ERROR, "Unable to create ImageData"); } /* ... imgdata */ duk_get_prop_string(ctx, -1, dukky_magic_string_private); idpriv = duk_get_pointer(ctx, -1); duk_pop(ctx); /* We now have access to the imagedata private, so we need to copy * the pixel range out of ourselves */ bitmap_base = guit->bitmap->get_buffer(priv->bitmap); for (int yy = y; yy < (y+height); ++yy) { uint8_t *src_base = bitmap_base + (priv->stride * yy); uint8_t *dst_base = idpriv->data + (width * 4); memcpy(dst_base + (x * 4), src_base + (x * 4), width * 4); } return 1; %} method CanvasRenderingContext2D::putImageData() %{ /* imgdata, x, y[, clipx, clipy, clipw, cliph] */ /* If provided, the clip coordinates are within the input image data */ /* We pretend the image is placed at x,y within ourselves, and then we * copy the clip rectangle (defaults to whole image) */ image_data_private_t *idpriv; int x = duk_to_int(ctx, 1); int y = duk_to_int(ctx, 2); int clipx = 0; int clipy = 0; int clipw = 0; int cliph = 0; uint8_t *bitmap_base; if (!dukky_instanceof(ctx, 0, PROTO_NAME(IMAGEDATA))) { return duk_generic_error(ctx, "Expected ImageData as first argument"); } duk_get_prop_string(ctx, 0, dukky_magic_string_private); idpriv = duk_get_pointer(ctx, -1); duk_pop(ctx); if (duk_get_top(ctx) < 7) { /* Clipping data not provided */ clipw = idpriv->width; cliph = idpriv->height; } else { clipx = duk_to_int(ctx, 3); clipy = duk_to_int(ctx, 4); clipw = duk_to_int(ctx, 5); cliph = duk_to_int(ctx, 6); } if (x < 0 || y < 0 || /* Not positioning negative */ (x + clipx + clipw) > priv->width || /* RHS not beyond bounds */ (y + clipy + cliph) > priv->height || /* bottom not beyond bounds */ clipx < 0 || clipy < 0 || /* Input in range */ (clipx + clipw) > idpriv->width || /* Input in range */ (clipy + cliph) > idpriv->height) { /* Input in range */ return duk_error(ctx, DUK_ERR_RANGE_ERROR, "invalid inputs"); } bitmap_base = guit->bitmap->get_buffer(priv->bitmap); for (int yy = clipy; yy < (clipy + cliph); yy++) { uint8_t *dst_row = bitmap_base + ((y + yy) * priv->stride); uint8_t *src_row = idpriv->data + (yy * idpriv->width * 4); memcpy(dst_row + ((x + clipx) * 4), src_row + (clipx * 4), clipw * 4); } guit->bitmap->modified(priv->bitmap); redraw_node((dom_node *)(priv->canvas)); return 0; %}