diff --git a/Makefile b/Makefile index 377e78e..9043f5d 100644 --- a/Makefile +++ b/Makefile @@ -131,6 +131,17 @@ else ifeq ($(SCENIC_LOCAL_TARGET),drm) else CFLAGS += -DSCENIC_GLES3 endif +else ifeq ($(SCENIC_LOCAL_TARGET),cairo) + LDFLAGS += `pkg-config --static --libs freetype2 cairo` + CFLAGS += `pkg-config --static --cflags freetype2 cairo` + LDFLAGS += -lm + CFLAGS ?= -O2 -Wall -Wextra -Wno-unused-parameter -pedantic + CFLAGS += -std=gnu99 + + DEVICE_SRCS += c_src/device/cairo.c + FONT_SRCS += c_src/font/cairo_font_ops.c + IMAGE_SRCS += c_src/image/cairo_image_ops.c + SCENIC_SRCS += c_src/scenic/ops/cairo_script_ops.c else $(info ------ no SCENIC_LOCAL_TARGET set ------) $(info If you get here, then you are probably using a custom Nerves system) diff --git a/c_src/device/cairo.c b/c_src/device/cairo.c new file mode 100644 index 0000000..64fa3d5 --- /dev/null +++ b/c_src/device/cairo.c @@ -0,0 +1,401 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cairo_ctx.h" +#include "comms.h" +#include "device.h" +#include "fontstash.h" +#include "ops/script_ops.h" + +const char* device = "/dev/fb0"; + +typedef struct { + int fd; + + struct fb_var_screeninfo var; + struct fb_fix_screeninfo fix; +} cairo_fb_t; + +cairo_fb_t g_cairo_fb = {0}; + +extern device_info_t g_device_info; +extern device_opts_t g_opts; + +// Support for 8 bits per pixel colors +unsigned short red[256], green[256], blue[256]; +struct fb_cmap map332 = {0, 256, red, green, blue, NULL}; +unsigned short red_b[256], green_b[256], blue_b[256]; +struct fb_cmap map_back = {0, 256, red_b, green_b, blue_b, NULL}; + +void make332map(struct fb_cmap *map) +{ + int rs, gs, bs, i; + int r = 8, g = 8, b = 4; + + map->red = red; + map->green = green; + map->blue = blue; + + rs = 256 / (r - 1); + gs = 256 / (g - 1); + bs = 256 / (b - 1); + + for (i = 0; i < 256; i++) { + map->red[i] = (rs * ((i / (g * b)) % r)) * 255; + map->green[i] = (gs * ((i / b) % g)) * 255; + map->blue[i] = (bs * ((i) % b)) * 255; + } +} + +void set8map(int fh, struct fb_cmap *map) +{ + if (ioctl(fh, FBIOPUTCMAP, map) < 0) { + log_error("cairo: Error putting colormap"); + } +} + +void get8map(int fh, struct fb_cmap *map) +{ + if (ioctl(fh, FBIOGETCMAP, map) < 0) { + log_error("cairo: Error getting colormap"); + } +} + +void set332map(int fh) +{ + make332map(&map332); + set8map(fh, &map332); +} + +int device_init(const device_opts_t* p_opts, + device_info_t* p_info, + driver_data_t* p_data) +{ + if (g_opts.debug_mode) { + log_info("cairo %s", __func__); + } + + if ((g_cairo_fb.fd = open(device, O_RDWR)) == -1) { + log_error("Failed to open device %s: %s", device, strerror(errno)); + return -1; + } + + if (ioctl(g_cairo_fb.fd, FBIOGET_VSCREENINFO, &g_cairo_fb.var)) { + log_error("Failed to get fb_var_screeninfo: %s", strerror(errno)); + return -1; + } + + if (ioctl(g_cairo_fb.fd, FBIOGET_FSCREENINFO, &g_cairo_fb.fix)) { + log_error("Failed to get fb_fix_screeninfo: %s", strerror(errno)); + return -1; + } + + scenic_cairo_ctx_t* p_ctx = calloc(1, sizeof(scenic_cairo_ctx_t)); + + FT_Error status = FT_Init_FreeType(&p_ctx->ft_library); + if (status != 0) { + log_error("cairo: FT_Init_FreeType: Error: %d", status); + close(g_cairo_fb.fd); + free(p_ctx); + + return -1; + } + + p_ctx->ratio = 1.0f; + p_ctx->dist_tolerance = 0.1f * p_ctx->ratio; + + size_t pix_count = p_opts->width * p_opts->height; + switch (g_cairo_fb.var.bits_per_pixel) + { + case 8: + p_ctx->fbbuff.c = (uint8_t*)malloc(pix_count * sizeof(uint8_t)); + break; + case 15: + case 16: + p_ctx->fbbuff.c = (uint8_t*)malloc(pix_count * sizeof(uint16_t)); + break; + case 24: + p_ctx->fbbuff.c = (uint8_t*)malloc(pix_count * 3 * sizeof(uint8_t)); + break; + case 32: + p_ctx->fbbuff.c = (uint8_t*)malloc(pix_count * sizeof(uint32_t)); + break; + default: + log_error("cairo: Unsupported video mode: %dbpp", g_cairo_fb.var.bits_per_pixel); + return -1; + } + + if (g_cairo_fb.var.bits_per_pixel == 8) { + get8map(g_cairo_fb.fd, &map_back); + set332map(g_cairo_fb.fd); + } + + p_info->width = p_opts->width; + p_info->height = p_opts->height; + + p_ctx->font_size = 10.0; // Cairo default + p_ctx->text_align = TEXT_ALIGN_LEFT; + p_ctx->text_base = TEXT_BASE_ALPHABETIC; + + p_ctx->clear_color = (color_rgba_t){ + // black opaque + .red = 0.0, + .green = 0.0, + .blue = 0.0, + .alpha = 1.0 + }; + + p_info->v_ctx = p_ctx; + + p_ctx->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, + p_info->width, p_info->height); + return 0; +} + +int device_close(device_info_t* p_info) +{ + if (g_opts.debug_mode) { + log_info("cairo %s", __func__); + } + + if (g_cairo_fb.var.bits_per_pixel == 8) { + set8map(g_cairo_fb.fd, &map_back); + } + + close(g_cairo_fb.fd); + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)p_info->v_ctx; + free(p_ctx->fbbuff.c); + cairo_surface_destroy(p_ctx->surface); + free(p_ctx); +} + +void device_poll() +{ +} + +void device_begin_render(driver_data_t* p_data) +{ + if (g_opts.debug_mode) { + log_info("cairo %s", __func__); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)p_data->v_ctx; + + p_ctx->cr = cairo_create(p_ctx->surface); + + // Paint surface to clear color + cairo_set_source_rgba(p_ctx->cr, + p_ctx->clear_color.red, + p_ctx->clear_color.green, + p_ctx->clear_color.blue, + p_ctx->clear_color.alpha); + cairo_paint(p_ctx->cr); +} + +void device_begin_cursor_render(driver_data_t* p_data) +{ + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)p_data->v_ctx; + cairo_translate(p_ctx->cr, p_data->cursor_pos[0], p_data->cursor_pos[1]); +} + +inline static uint8_t to_8_color(uint8_t r, uint8_t g, uint8_t b) +{ + return ((((r >> 5) & 7) << 5) | + (((g >> 5) & 7) << 2) | + ((b >> 6) & 3)); +} + +inline static uint16_t to_15_color(uint8_t r, uint8_t g, uint8_t b) +{ + return ((((r >> 3) & 31) << 10) | + (((g >> 3) & 31) << 5) | + ((b >> 3) & 31)); +} + +inline static uint16_t to_15_color_bgr(uint8_t r, uint8_t g, uint8_t b) +{ + return ((((b >> 3) & 31) << 10) | + (((g >> 3) & 31) << 5) | + ((r >> 3) & 31)); +} + +inline static uint16_t to_16_color(uint8_t r, uint8_t g, uint8_t b) +{ + return ((((r >> 3) & 31) << 11) | + (((g >> 2) & 63) << 5) | + ((b >> 3) & 31)); +} + +void render_cairo_surface_to_fb(scenic_cairo_ctx_t* p_ctx) +{ + cairo_surface_flush(p_ctx->surface); + uint8_t* cairo_buff = cairo_image_surface_get_data(p_ctx->surface); + uint32_t width = cairo_image_surface_get_width(p_ctx->surface); + uint32_t height = cairo_image_surface_get_height(p_ctx->surface); + + bool is_bgr555 = ((g_cairo_fb.var.red.offset == 0 && + g_cairo_fb.var.green.offset == 5 && + g_cairo_fb.var.blue.offset == 10)) + ? true + : false; + + size_t pix_count = width * height; + + int cpp = 0; + switch (g_cairo_fb.var.bits_per_pixel) + { + case 8: + cpp = 1; + for (uint32_t i = 0, j = 0; i < pix_count; i++, j += 4) { + p_ctx->fbbuff.c[i] = to_8_color(cairo_buff[j+2], + cairo_buff[j+1], + cairo_buff[j+0]); + } + break; + case 15: + cpp = 2; + if (is_bgr555) { + for (uint32_t i = 0, j = 0; i < pix_count; i++, j += 4) { + p_ctx->fbbuff.s[i] = to_15_color_bgr(cairo_buff[j+2], + cairo_buff[j+1], + cairo_buff[j+0]); + } + } else { + for (uint32_t i = 0, j = 0; i < pix_count; i++, j += 4) { + p_ctx->fbbuff.s[i] = to_15_color(cairo_buff[j+2], + cairo_buff[j+1], + cairo_buff[j+0]); + } + } + break; + case 16: + cpp = 2; + if (is_bgr555) { + for (uint32_t i = 0, j = 0; i < pix_count; i++, j += 4) { + p_ctx->fbbuff.s[i] = to_15_color_bgr(cairo_buff[j+2], + cairo_buff[j+1], + cairo_buff[j+0]); + } + } else { + for (uint32_t i = 0, j = 0; i < pix_count; i++, j += 4) { + p_ctx->fbbuff.s[i] = to_16_color(cairo_buff[j+2], + cairo_buff[j+1], + cairo_buff[j+0]); + } + } + break; + case 24: + cpp = 3; + for (uint32_t i = 0, j = 0; i < (cpp * pix_count); i += 3, j += 4) { + p_ctx->fbbuff.c[i+0] = cairo_buff[j+0]; + p_ctx->fbbuff.c[i+1] = cairo_buff[j+1]; + p_ctx->fbbuff.c[i+2] = cairo_buff[j+2]; + } + break; + case 32: + cpp = 4; + for (uint32_t i = 0, j = 0; i < pix_count; i++, j += 4) { + p_ctx->fbbuff.i[i] = ((cairo_buff[j+2] << 16)) | + (cairo_buff[j+1] << 8) | + (cairo_buff[j+0]); + } + break; + } + + uint32_t x_stride = (g_cairo_fb.fix.line_length * 8) / g_cairo_fb.var.bits_per_pixel; + + uint32_t pic_xs = g_device_info.width; + uint32_t pic_ys = g_device_info.height; + uint32_t scr_xs = x_stride; + uint32_t scr_ys = g_cairo_fb.var.yres; + + uint32_t xc = (pic_xs > scr_xs) ? scr_xs : pic_xs; + uint32_t yc = (pic_ys > scr_ys) ? scr_ys : pic_ys; + + uint32_t x_offs = (pic_xs < g_cairo_fb.var.xres) + ? (g_cairo_fb.var.xres - pic_xs) / 2 + : 0; + uint32_t y_offs = (pic_ys < g_cairo_fb.var.yres) + ? (g_cairo_fb.var.yres - pic_ys) / 2 + : 0; + + size_t fb_size = scr_xs * scr_ys * cpp; + uint8_t* fb = mmap(NULL, fb_size, PROT_WRITE | PROT_READ, MAP_SHARED, g_cairo_fb.fd, 0); + + if (fb == MAP_FAILED) { + log_error("cairo: failed to mmap fb"); + return; + } + + uint8_t* p_fb = fb + (y_offs * scr_xs + x_offs) * cpp; + uint8_t* p_image = p_ctx->fbbuff.c; + + for (uint32_t i = 0; i < yc; i++, p_fb += scr_xs * cpp, p_image += pic_xs * cpp) + memcpy(p_fb, p_image, xc * cpp); + + munmap(fb, fb_size); +} + +void device_end_render(driver_data_t* p_data) +{ + if (g_opts.debug_mode) { + log_info("cairo %s", __func__); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)p_data->v_ctx; + render_cairo_surface_to_fb(p_ctx); + + cairo_destroy(p_ctx->cr); +} + +void device_clear_color(float red, float green, float blue, float alpha) +{ + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)g_device_info.v_ctx; + p_ctx->clear_color = (color_rgba_t){ + .red = red, + .green = green, + .blue = blue, + .alpha = alpha + }; +} + +char* device_gl_error() +{ + return NULL; +} + +void pattern_stack_push(scenic_cairo_ctx_t* p_ctx) +{ + pattern_stack_t* ptr = (pattern_stack_t*)malloc(sizeof(pattern_stack_t)); + + ptr->pattern = p_ctx->pattern; + + if (!p_ctx->pattern_stack_head) { + ptr->next = NULL; + p_ctx->pattern_stack_head = ptr; + } else { + ptr->next = p_ctx->pattern_stack_head; + p_ctx->pattern_stack_head = ptr; + } +} + +void pattern_stack_pop(scenic_cairo_ctx_t* p_ctx) +{ + pattern_stack_t* ptr = p_ctx->pattern_stack_head; + + if (!ptr) { + log_error("pattern stack underflow"); + } else { + p_ctx->pattern = ptr->pattern; + p_ctx->pattern_stack_head = p_ctx->pattern_stack_head->next; + free(ptr); + } +} diff --git a/c_src/device/cairo_ctx.h b/c_src/device/cairo_ctx.h new file mode 100644 index 0000000..086db76 --- /dev/null +++ b/c_src/device/cairo_ctx.h @@ -0,0 +1,60 @@ +#pragma once + +#include +#include +#include FT_FREETYPE_H +#include "ops/script_ops.h" + +typedef struct { + cairo_pattern_t* fill; + cairo_pattern_t* stroke; +} fill_stroke_pattern_t; + +typedef struct pattern_stack_t_ { + fill_stroke_pattern_t pattern; + struct pattern_stack_t_* next; +} pattern_stack_t; + +typedef struct { + int id; + cairo_surface_t* surface; + cairo_pattern_t* pattern; +} image_pattern_data_t; + +typedef struct { + int id; + cairo_font_face_t* font_face; +} font_data_t; + +typedef struct { + color_rgba_t clear_color; + FT_Library ft_library; + float font_size; + text_align_t text_align; + text_base_t text_base; + union { + u_int8_t *c; + u_int16_t *s; + u_int32_t *i; + } fbbuff; + cairo_surface_t* surface; + cairo_t* cr; + pattern_stack_t* pattern_stack_head; + fill_stroke_pattern_t pattern; + int images_count; + int images_used; + int highest_image_id; + image_pattern_data_t* images; + int fonts_count; + int fonts_used; + int highest_font_id; + font_data_t* fonts; + float dist_tolerance; + float ratio; +} scenic_cairo_ctx_t; + +void pattern_stack_push(scenic_cairo_ctx_t* p_ctx); +void pattern_stack_pop(scenic_cairo_ctx_t* p_ctx); + +image_pattern_data_t* find_image_pattern(scenic_cairo_ctx_t* p_ctx, int id); +font_data_t* find_font(scenic_cairo_ctx_t* p_ctx, int id); diff --git a/c_src/font/cairo_font_ops.c b/c_src/font/cairo_font_ops.c new file mode 100644 index 0000000..2960b7b --- /dev/null +++ b/c_src/font/cairo_font_ops.c @@ -0,0 +1,80 @@ +#include "cairo_ctx.h" +#include "font_ops.h" + +#include + +static int maxi(int a, int b) { return a > b ? a : b; } + +static +font_data_t* alloc_font_data(scenic_cairo_ctx_t* p_ctx, cairo_font_face_t* font_face) +{ + font_data_t* font = NULL; + int i; + + for (i = 0; i < p_ctx->fonts_used; i++) { + if (p_ctx->fonts[i].id == 0) { + font = &p_ctx->fonts[i]; + break; + } + } + + if (!font) { + if (p_ctx->fonts_used + 1 > p_ctx->fonts_count) { + font_data_t* fonts; + int fonts_count = maxi(p_ctx->fonts_count + 1, 4) + p_ctx->fonts_used / 2; // 1.5x over allocation + fonts = (font_data_t*)realloc(p_ctx->fonts, sizeof(font_data_t) * fonts_count); + if (!fonts) return NULL; + p_ctx->fonts = fonts; + p_ctx->fonts_count = fonts_count; + } + font = &p_ctx->fonts[p_ctx->fonts_used++]; + } + + memset(font, 0, sizeof(*font)); + font->id = ++p_ctx->highest_font_id; + font->font_face = font_face; + + return font; +} + +font_data_t* find_font(scenic_cairo_ctx_t* p_ctx, int id) +{ + int i; + for (i = 0; i < p_ctx->fonts_used; i++) { + if (p_ctx->fonts[i].id == id) { + return &p_ctx->fonts[i]; + } + } + return NULL; +} + +int32_t font_ops_create(void* v_ctx, font_t* p_font, uint32_t size) +{ + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + const char* name = p_font->id.p_data; + unsigned char* data = p_font->blob.p_data; + + FT_Face ft_face; + FT_Error ft_status = FT_New_Memory_Face(p_ctx->ft_library, data, size, 0, &ft_face); + + if (ft_status != 0) { + log_error("cairo: FT_New_Memory_Face: Error: %d", ft_status); + return -1; + } + + cairo_font_face_t* font_face = cairo_ft_font_face_create_for_ft_face(ft_face, 0); + cairo_status_t status = cairo_font_face_set_user_data(font_face, NULL, + ft_face, + (cairo_destroy_func_t) FT_Done_Face); + if (status) { + log_error("cairo: Failed to create font face: %d", status); + cairo_font_face_destroy(font_face); + FT_Done_Face(ft_face); + return -1; + } + + font_data_t* font_data = alloc_font_data(p_ctx, font_face); + if (!font_data) return -1; + + return font_data->id; +} diff --git a/c_src/image/cairo_image_ops.c b/c_src/image/cairo_image_ops.c new file mode 100644 index 0000000..03cd5c4 --- /dev/null +++ b/c_src/image/cairo_image_ops.c @@ -0,0 +1,137 @@ +#include +#include +#include + +#include "cairo_ctx.h" +#include "comms.h" +#include "image_ops.h" + +static int maxi(int a, int b) { return a > b ? a : b; } + +static +image_pattern_data_t* alloc_image_pattern(scenic_cairo_ctx_t* p_ctx) +{ + image_pattern_data_t* ipd = NULL; + int i; + + for (i = 0; i < p_ctx->images_used; i++) { + if (p_ctx->images[i].id == 0) { + ipd = &p_ctx->images[i]; + break; + } + } + + if (ipd == NULL) { + if (p_ctx->images_used + 1 > p_ctx->images_count) { + image_pattern_data_t* images; + int images_count = maxi(p_ctx->images_count + 1, 4) + p_ctx->images_used / 2; // 1.5x over allocation + images = (image_pattern_data_t*)realloc(p_ctx->images, sizeof(image_pattern_data_t) * images_count); + if (images == NULL) return NULL; + p_ctx->images = images; + p_ctx->images_count = images_count; + } + ipd = &p_ctx->images[p_ctx->images_used++]; + } + + memset(ipd, 0, sizeof(*ipd)); + ipd->id = ++p_ctx->highest_image_id; + + return ipd; +} + +image_pattern_data_t* find_image_pattern(scenic_cairo_ctx_t* p_ctx, int id) +{ + int i; + for (i = 0; i < p_ctx->images_used; i++) { + if (p_ctx->images[i].id == id) { + return &p_ctx->images[i]; + } + } + return NULL; +} + +static +int delete_image_pattern(scenic_cairo_ctx_t* p_ctx, int id) +{ + int i; + for (i = 0; i < p_ctx->images_used; i++) { + if (p_ctx->images[i].id == id) { + memset(&p_ctx->images[i], 0, sizeof(p_ctx->images[i])); + return 1; + } + } + return 0; +} + +static +uint32_t convert_rgba_to_argb(uint32_t pixel) +{ + return ((pixel & 0xFFFFFF00) >> 8) | ((pixel & 0xFF) << 24); +} + +int32_t image_ops_create(void* v_ctx, + uint32_t width, uint32_t height, + void* p_pixels) +{ + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + + cairo_format_t format = CAIRO_FORMAT_ARGB32; + int stride = cairo_format_stride_for_width(format, width); + size_t num_pixels = width * height; + uint32_t* rgba_pixels = (uint32_t*)p_pixels; + uint32_t* argb_pixels = malloc(num_pixels * sizeof(uint32_t)); + + if (!argb_pixels) return 0; + + for(size_t i = 0; i < num_pixels; ++i) { + argb_pixels[i] = convert_rgba_to_argb(htonl(rgba_pixels[i])); + } + + image_pattern_data_t* image_data = alloc_image_pattern(p_ctx); + if (!image_data) return 0; + + image_data->surface + = cairo_image_surface_create_for_data((uint8_t*)argb_pixels, + format, + width, height, stride); + + image_data->pattern = cairo_pattern_create_for_surface(image_data->surface); + cairo_pattern_set_extend(image_data->pattern, CAIRO_EXTEND_REPEAT); + + static cairo_user_data_key_t dummy_key; + cairo_surface_set_user_data(image_data->surface, &dummy_key, argb_pixels, free); + + return image_data->id; +} + +void image_ops_update(void* v_ctx, int32_t image_id, void* p_pixels) +{ + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + image_pattern_data_t* image_data = find_image_pattern(p_ctx, image_id); + if (!image_data) return; + + uint32_t width = cairo_image_surface_get_width(image_data->surface); + uint32_t height = cairo_image_surface_get_height(image_data->surface); + size_t num_pixels = width * height; + uint32_t* rgba_pixels = (uint32_t*)p_pixels; + uint32_t* argb_pixels = (uint32_t*)cairo_image_surface_get_data(image_data->surface); + + for(size_t i = 0; i < num_pixels; ++i) { + argb_pixels[i] = convert_rgba_to_argb(htonl(rgba_pixels[i])); + } + + cairo_pattern_destroy(image_data->pattern); + image_data->pattern = cairo_pattern_create_for_surface(image_data->surface); + cairo_pattern_set_extend(image_data->pattern, CAIRO_EXTEND_REPEAT); +} + +void image_ops_delete(void* v_ctx, int32_t image_id) +{ + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + image_pattern_data_t* image_data = find_image_pattern(p_ctx, image_id); + if (!image_data) return; + + cairo_surface_destroy(image_data->surface); + cairo_pattern_destroy(image_data->pattern); + delete_image_pattern(p_ctx, image_id); +} diff --git a/c_src/image/image_ops.h b/c_src/image/image_ops.h index 4a3cdd5..684f65c 100644 --- a/c_src/image/image_ops.h +++ b/c_src/image/image_ops.h @@ -3,5 +3,5 @@ #include int32_t image_ops_create(void* v_ctx, uint32_t width, uint32_t height, void* p_pixels); -void image_ops_update(void* v_ctx, uint32_t image_id, void* p_pixels); -void image_ops_delete(void* v_ctx, uint32_t image_id); +void image_ops_update(void* v_ctx, int32_t image_id, void* p_pixels); +void image_ops_delete(void* v_ctx, int32_t image_id); diff --git a/c_src/image/nvg_image_ops.c b/c_src/image/nvg_image_ops.c index b97353d..d28c22a 100644 --- a/c_src/image/nvg_image_ops.c +++ b/c_src/image/nvg_image_ops.c @@ -10,13 +10,13 @@ int32_t image_ops_create(void* v_ctx, uint32_t width, uint32_t height, void* p_p return nvgCreateImageRGBA(p_ctx, width, height, REPEAT_XY, p_pixels); } -void image_ops_update(void* v_ctx, uint32_t image_id, void* p_pixels) +void image_ops_update(void* v_ctx, int32_t image_id, void* p_pixels) { NVGcontext* p_ctx = (NVGcontext*)v_ctx; nvgUpdateImage(p_ctx, image_id, p_pixels); } -void image_ops_delete(void* v_ctx, uint32_t image_id) +void image_ops_delete(void* v_ctx, int32_t image_id) { NVGcontext* p_ctx = (NVGcontext*)v_ctx; nvgDeleteImage(p_ctx, image_id); diff --git a/c_src/scenic/ops/cairo_script_ops.c b/c_src/scenic/ops/cairo_script_ops.c new file mode 100644 index 0000000..b2dbd96 --- /dev/null +++ b/c_src/scenic/ops/cairo_script_ops.c @@ -0,0 +1,990 @@ +#include +#include + +#include "cairo_ctx.h" +#include "comms.h" +#include "font.h" +#include "font_ops.h" +#include "image.h" +#include "script.h" +#include "script_ops.h" + +extern device_opts_t g_opts; + +static const char* log_prefix = "cairo"; + +void set_fill_pattern(scenic_cairo_ctx_t* p_ctx, cairo_pattern_t* pattern) +{ + p_ctx->pattern.fill = pattern; +} + +void set_stroke_pattern(scenic_cairo_ctx_t* p_ctx, cairo_pattern_t* pattern) +{ + p_ctx->pattern.stroke = pattern; +} + +void do_fill_stroke(scenic_cairo_ctx_t* p_ctx, bool fill, bool stroke) +{ + if (fill && stroke) { + cairo_set_source(p_ctx->cr, p_ctx->pattern.fill); + cairo_fill_preserve(p_ctx->cr); + } + else if (fill) { + cairo_set_source(p_ctx->cr, p_ctx->pattern.fill); + cairo_fill(p_ctx->cr); + } + + if (stroke) { + cairo_set_source(p_ctx->cr, p_ctx->pattern.stroke); + cairo_stroke(p_ctx->cr); + } +} + +void script_ops_draw_line(void* v_ctx, + coordinates_t a, + coordinates_t b, + bool stroke) +{ + if (g_opts.debug_mode) { + log_script_ops_draw_line(log_prefix, __func__, log_level_info, + a, b, stroke); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + cairo_move_to(p_ctx->cr, a.x, a.y); + cairo_line_to(p_ctx->cr, b.x, b.y); + + if (stroke) { + cairo_set_source(p_ctx->cr, p_ctx->pattern.stroke); + cairo_stroke(p_ctx->cr); + } +} + +void script_ops_draw_triangle(void* v_ctx, + coordinates_t a, + coordinates_t b, + coordinates_t c, + bool fill, bool stroke) +{ + if (g_opts.debug_mode) { + log_script_ops_draw_triangle(log_prefix, __func__, log_level_info, + a, b, c, fill, stroke); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + cairo_move_to(p_ctx->cr, a.x, a.y); + cairo_line_to(p_ctx->cr, b.x, b.y); + cairo_line_to(p_ctx->cr, c.x, c.y); + cairo_close_path(p_ctx->cr); + + do_fill_stroke(p_ctx, fill, stroke); +} + +void script_ops_draw_quad(void* v_ctx, + coordinates_t a, + coordinates_t b, + coordinates_t c, + coordinates_t d, + bool fill, bool stroke) +{ + if (g_opts.debug_mode) { + log_script_ops_draw_quad(log_prefix, __func__, log_level_info, + a, b, c, d, fill, stroke); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + cairo_move_to(p_ctx->cr, a.x, a.y); + cairo_line_to(p_ctx->cr, b.x, b.y); + cairo_line_to(p_ctx->cr, c.x, c.y); + cairo_line_to(p_ctx->cr, d.x, d.y); + cairo_close_path(p_ctx->cr); + + do_fill_stroke(p_ctx, fill, stroke); +} + +void script_ops_draw_rect(void* v_ctx, + float w, + float h, + bool fill, bool stroke) +{ + if (g_opts.debug_mode) { + log_script_ops_draw_rect(log_prefix, __func__, log_level_info, + w, h, fill, stroke); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + + cairo_rectangle(p_ctx->cr, 0, 0, w, h); + + do_fill_stroke(p_ctx, fill, stroke); +} + +void script_ops_draw_rrect(void* v_ctx, + float w, + float h, + float radius, + bool fill, bool stroke) +{ + if (g_opts.debug_mode) { + log_script_ops_draw_rrect(log_prefix, __func__, log_level_info, + w, h, radius, fill, stroke); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + cairo_arc(p_ctx->cr, 0 + radius, 0 + radius, radius, 2 * (M_PI/2), 3 * (M_PI/2)); + cairo_arc(p_ctx->cr, w - radius, 0 + radius, radius, 3 * (M_PI/2), 4 * (M_PI/2)); + cairo_arc(p_ctx->cr, w - radius, h - radius, radius, 0 * (M_PI/2), 1 * (M_PI/2)); + cairo_arc(p_ctx->cr, 0 + radius, h - radius, radius, 1 * (M_PI/2), 2 * (M_PI/2)); + cairo_close_path(p_ctx->cr); + + do_fill_stroke(p_ctx, fill, stroke); +} + +void script_ops_draw_arc(void* v_ctx, + float radius, + float radians, + bool fill, bool stroke) +{ + if (g_opts.debug_mode) { + log_script_ops_draw_arc(log_prefix, __func__, log_level_info, + radius, radians, fill, stroke); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + if (radians > 0) + cairo_arc(p_ctx->cr, 0, 0, radius, 0, radians); + else + cairo_arc_negative(p_ctx->cr, 0, 0, radius, 0, radians); + + do_fill_stroke(p_ctx, fill, stroke); +} + +void script_ops_draw_sector(void* v_ctx, + float radius, + float radians, + bool fill, bool stroke) +{ + if (g_opts.debug_mode) { + log_script_ops_draw_sector(log_prefix, __func__, log_level_info, + radius, radians, fill, stroke); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + cairo_move_to(p_ctx->cr, 0, 0); + cairo_line_to(p_ctx->cr, radius, 0); + + if (radians > 0) + cairo_arc(p_ctx->cr, 0, 0, radius, 0, radians); + else + cairo_arc_negative(p_ctx->cr, 0, 0, radius, 0, radians); + + cairo_close_path(p_ctx->cr); + + do_fill_stroke(p_ctx, fill, stroke); +} + +void script_ops_draw_circle(void* v_ctx, + float radius, + bool fill, bool stroke) +{ + if (g_opts.debug_mode) { + log_script_ops_draw_circle(log_prefix, __func__, log_level_info, + radius, fill, stroke); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + + cairo_arc(p_ctx->cr, 0, 0, radius, 0, 2 * M_PI); + + do_fill_stroke(p_ctx, fill, stroke); +} + +void script_ops_draw_ellipse(void* v_ctx, + float radius0, + float radius1, + bool fill, bool stroke) +{ + if (g_opts.debug_mode) { + log_script_ops_draw_ellipse(log_prefix, __func__, log_level_info, + radius0, radius1, fill, stroke); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + + // Implementation based on nvgEllipse() + float kappa90 = 0.5522847493f; // Length proportional to radius of a cubic bezier handle for 90deg arcs. + + float cx = 0; + float cy = 0; + float rx = radius0; + float ry = radius1; + cairo_move_to(p_ctx->cr, cx-rx, cy); + cairo_curve_to(p_ctx->cr, cx-rx, cy+ry*kappa90, cx-rx*kappa90, cy+ry, cx, cy+ry); + cairo_curve_to(p_ctx->cr, cx+rx*kappa90, cy+ry, cx+rx, cy+ry*kappa90, cx+rx, cy); + cairo_curve_to(p_ctx->cr, cx+rx, cy-ry*kappa90, cx+rx*kappa90, cy-ry, cx, cy-ry); + cairo_curve_to(p_ctx->cr, cx-rx*kappa90, cy-ry, cx-rx, cy-ry*kappa90, cx-rx, cy); + cairo_close_path(p_ctx->cr); + + do_fill_stroke(p_ctx, fill, stroke); +} + +void script_ops_draw_text(void* v_ctx, + uint32_t size, + const char* text) +{ + if (g_opts.debug_mode) { + log_script_ops_draw_text(log_prefix, __func__, log_level_info, + size, text); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + + cairo_glyph_t* glyphs = NULL; + int glyph_count; + cairo_text_cluster_t* clusters = NULL; + int cluster_count; + cairo_text_cluster_flags_t cluster_flags; + + cairo_scaled_font_t* scaled_font = cairo_get_scaled_font(p_ctx->cr); + cairo_status_t status = cairo_scaled_font_text_to_glyphs(scaled_font, + 0, 0, + text, size, + &glyphs, &glyph_count, + &clusters, &cluster_count, + &cluster_flags); + cairo_font_extents_t font_extents; + cairo_scaled_font_extents(scaled_font, &font_extents); + + cairo_text_extents_t text_extents; + cairo_scaled_font_glyph_extents(scaled_font, glyphs, glyph_count, &text_extents); + + float align_offset; + switch (p_ctx->text_align) { + case TEXT_ALIGN_LEFT: + align_offset = 0; + break; + case TEXT_ALIGN_CENTER: + align_offset = -(text_extents.width / 2); + break; + case TEXT_ALIGN_RIGHT: + align_offset = -(text_extents.width); + break; + } + + float base_offset; + switch (p_ctx->text_base) { + case TEXT_BASE_TOP: + base_offset = font_extents.ascent; + break; + case TEXT_BASE_MIDDLE: + base_offset = -((font_extents.descent - font_extents.ascent) / 2); + break; + case TEXT_BASE_ALPHABETIC: + base_offset = 0; + break; + case TEXT_BASE_BOTTOM: + base_offset = -(font_extents.descent); + break; + } + + cairo_translate(p_ctx->cr, align_offset, base_offset); + cairo_set_source(p_ctx->cr, p_ctx->pattern.fill); + + if (status == CAIRO_STATUS_SUCCESS) { + int glyph_index = 0; + int byte_index = 0; + for (int i = 0; i < cluster_count; i++) { + cairo_text_cluster_t* cluster = &clusters[i]; + cairo_glyph_t* cluster_glyphs = &glyphs[glyph_index]; + + cairo_scaled_font_glyph_extents(scaled_font, cluster_glyphs, cluster->num_glyphs, &text_extents); + cairo_show_glyphs(p_ctx->cr, cluster_glyphs, cluster->num_glyphs); + + glyph_index += cluster->num_glyphs; + byte_index += cluster->num_bytes; + } + } else { + log_error("%s: cairo_scaled_font_text_to_glyphs: error %d", __func__, status); + } + + p_ctx->text_align = TEXT_ALIGN_LEFT; + p_ctx->text_base = TEXT_BASE_ALPHABETIC; +} + +static void draw_sprite(scenic_cairo_ctx_t* p_ctx, + cairo_surface_t *surface, + const sprite_t sprite) +{ + cairo_save(p_ctx->cr); + + cairo_set_source_surface(p_ctx->cr, surface, + sprite.dx - sprite.sx, sprite.dy - sprite.sy); + + cairo_rectangle(p_ctx->cr, sprite.dx, sprite.dy, sprite.dw, sprite.dh); + cairo_scale(p_ctx->cr, sprite.dw / sprite.sw, sprite.dh / sprite.sh); + cairo_fill(p_ctx->cr); + + cairo_restore(p_ctx->cr); +} + +void script_ops_draw_sprites(void* v_ctx, + sid_t id, + uint32_t count, + const sprite_t* sprites) +{ + if (g_opts.debug_mode) { + log_script_ops_draw_sprites(log_prefix, __func__, log_level_info, + id, count, sprites); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + + // get the mapped image_id for this driver_id + image_t* p_image = get_image(id); + if (!p_image) return; + + image_pattern_data_t* image_data = find_image_pattern(p_ctx, p_image->image_id); + + for (int i = 0; i < count; i++) { + draw_sprite(p_ctx, image_data->surface, sprites[i]); + } +} + +void script_ops_begin_path(void* v_ctx) +{ + if (g_opts.debug_mode) { + log_script_ops_begin_path(log_prefix, __func__, log_level_info); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + cairo_new_path(p_ctx->cr); +} + +void script_ops_close_path(void* v_ctx) +{ + if (g_opts.debug_mode) { + log_script_ops_close_path(log_prefix, __func__, log_level_info); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + cairo_close_path(p_ctx->cr); +} + +void script_ops_fill_path(void* v_ctx) +{ + if (g_opts.debug_mode) { + log_script_ops_fill_path(log_prefix, __func__, log_level_info); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + cairo_set_source(p_ctx->cr, p_ctx->pattern.fill); + cairo_fill(p_ctx->cr); +} + +void script_ops_stroke_path(void* v_ctx) +{ + if (g_opts.debug_mode) { + log_script_ops_stroke_path(log_prefix, __func__, log_level_info); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + cairo_set_source(p_ctx->cr, p_ctx->pattern.stroke); + cairo_stroke(p_ctx->cr); +} + +void script_ops_move_to(void* v_ctx, + coordinates_t a) +{ + if (g_opts.debug_mode) { + log_script_ops_move_to(log_prefix, __func__, log_level_info, + a); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + cairo_move_to(p_ctx->cr, a.x, a.y); +} + +void script_ops_line_to(void* v_ctx, + coordinates_t a) +{ + if (g_opts.debug_mode) { + log_script_ops_line_to(log_prefix, __func__, log_level_info, + a); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + cairo_line_to(p_ctx->cr, a.x, a.y); +} + +static bool ptEquals(coordinates_t a, coordinates_t b, float tolerance) +{ + coordinates_t d = {(b.x-a.x), (b.y-a.y)}; + return d.x*d.x + d.y*d.y < tolerance*tolerance; +} + +static float distPtSeg(coordinates_t a, coordinates_t p, coordinates_t q) +{ + coordinates_t pq = {(q.x-p.x), (q.y-p.y)}; + coordinates_t d = {(a.x-p.x), (a.y-p.y)}; + float e = pq.x*pq.x + pq.y*pq.y; + float t = pq.x*d.x + pq.y*d.y; + if (e > 0) t /= e; + if (t < 0) t = 0; + else if (t > 1) t = 1; + d.x = p.x + t*pq.x - a.x; + d.y = p.y + t*pq.y - a.y; + return d.x*d.x + d.y*d.y; +} + +static float normalize(coordinates_t* a) +{ + float d = sqrtf((a->x)*(a->x) + (a->y)*(a->y)); + if (d > 1e-6f) { + float id = 1.0f / d; + a->x *= id; + a->y *= id; + } + return d; +} + +static float cross(coordinates_t d0, coordinates_t d1) +{ + return d1.x*d0.y - d0.x*d1.y; +} + +void script_ops_arc_to(void* v_ctx, + coordinates_t a, + coordinates_t b, + float radius) +{ + if (g_opts.debug_mode) { + log_script_ops_arc_to(log_prefix, __func__, log_level_info, + a, b, radius); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + if (!cairo_has_current_point(p_ctx->cr)) { + return; + } + + double px, py; + cairo_get_current_point(p_ctx->cr, &px, &py); + coordinates_t p = {px, py}; + + if (ptEquals(p, a, p_ctx->dist_tolerance) || + ptEquals(a, b, p_ctx->dist_tolerance) || + distPtSeg(a, p, b) < p_ctx->dist_tolerance * p_ctx->dist_tolerance || + radius < p_ctx->dist_tolerance) { + cairo_line_to(p_ctx->cr, a.x, a.y); + return; + } + + coordinates_t d0 = {(p.x-a.x), (p.y-a.y)}; + coordinates_t d1 = {(b.x-a.x), (b.y-a.y)}; + normalize(&d0); + normalize(&d1); + + float angle = acosf(d0.x*d1.x + d0.y*d1.y); + float diameter = radius / tanf(angle/2.0f); + + if (diameter > 10000.0f) { + cairo_line_to(p_ctx->cr, a.x, a.y); + return; + } + + coordinates_t c; + float a0, a1; + if (cross(d0, d1) > 0.0f) { + c.x = a.x + d0.x*diameter + d0.y*radius; + c.y = a.y + d0.y*diameter + -d0.x*radius; + a0 = atan2f(d0.x, -d0.y); + a1 = atan2f(-d1.x, d1.y); + cairo_arc(p_ctx->cr, c.x, c.y, radius, a0, a1); + } else { + c.x = a.x + d0.x*diameter + -d0.y*radius; + c.y = a.y + d0.y*diameter + d0.x*radius; + a0 = atan2f(-d0.x, d0.y); + a1 = atan2f(d1.x, -d1.y); + cairo_arc_negative(p_ctx->cr, c.x, c.y, radius, a0, a1); + } +} + +void script_ops_bezier_to(void* v_ctx, + coordinates_t c0, + coordinates_t c1, + coordinates_t a) +{ + if (g_opts.debug_mode) { + log_script_ops_bezier_to(log_prefix, __func__, log_level_info, + c0, c1, a); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + cairo_curve_to(p_ctx->cr, c0.x, c0.y, c1.x, c1.y, a.x, a.y); +} + +void script_ops_quadratic_to(void* v_ctx, + coordinates_t c, + coordinates_t a) +{ + if (g_opts.debug_mode) { + log_script_ops_quadratic_to(log_prefix, __func__, log_level_info, + c, a); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + if (!cairo_has_current_point(p_ctx->cr)) { + return; + } + + double x0, y0; + cairo_get_current_point(p_ctx->cr, &x0, &y0); + cairo_curve_to(p_ctx->cr, + x0 + 2.0f / 3.0f * (c.x - x0), y0 + 2.0f /3.0f * (c.y - y0), + a.x + 2.0f / 3.0f * (c.x - a.x), a.y + 2.0f / 3.0f * (c.y - a.y), + a.x, a.y); +} + +void script_ops_arc(void *v_ctx, + coordinates_t c, + float r, + float a0, float a1, + sweep_dir_t sweep_dir) +{ + if (g_opts.debug_mode) { + log_script_ops_arc(log_prefix, __func__, log_level_info, + c, r, a0, a1, sweep_dir); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + if (sweep_dir == SWEEP_DIR_CW) { + cairo_arc(p_ctx->cr, c.x, c.y, r, a0, a1); + } else { + cairo_arc_negative(p_ctx->cr, c.x, c.y, r, a0, a1); + } +} + +void script_ops_push_state(void* v_ctx) +{ + if (g_opts.debug_mode) { + log_script_ops_push_state(log_prefix, __func__, log_level_info); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + + pattern_stack_push(p_ctx); + cairo_save(p_ctx->cr); +} + +void script_ops_pop_state(void* v_ctx) +{ + if (g_opts.debug_mode) { + log_script_ops_pop_state(log_prefix, __func__, log_level_info); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + + cairo_restore(p_ctx->cr); + pattern_stack_pop(p_ctx); +} + +void script_ops_scissor(void* v_ctx, + float w, float h) +{ + if (g_opts.debug_mode) { + log_script_ops_scissor(log_prefix, __func__, log_level_info, + w, h); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + cairo_rectangle(p_ctx->cr, 0, 0, w, h); + cairo_clip(p_ctx->cr); +} + + +void script_ops_transform(void* v_ctx, + float a, float b, + float c, float d, + float e, float f) +{ + if (g_opts.debug_mode) { + log_script_ops_transform(log_prefix, __func__, log_level_info, + a, b, c, d, e, f); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + cairo_matrix_t matrix = { + a, b, c, d, e, f + }; + + cairo_transform(p_ctx->cr, &matrix); +} + +void script_ops_scale(void* v_ctx, + float x, float y) +{ + if (g_opts.debug_mode) { + log_script_ops_scale(log_prefix, __func__, log_level_info, + x, y); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + cairo_scale(p_ctx->cr, x, y); +} + +void script_ops_rotate(void* v_ctx, + float radians) +{ + if (g_opts.debug_mode) { + log_script_ops_rotate(log_prefix, __func__, log_level_info, + radians); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + cairo_rotate(p_ctx->cr, radians); +} + +void script_ops_translate(void* v_ctx, + float x, float y) +{ + if (g_opts.debug_mode) { + log_script_ops_translate(log_prefix, __func__, log_level_info, + x, y); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + + cairo_translate(p_ctx->cr, x, y); +} + +void script_ops_fill_color(void* v_ctx, + color_rgba_t color) +{ + if (g_opts.debug_mode) { + log_script_ops_fill_color(log_prefix, __func__, log_level_info, + color); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + + cairo_pattern_t* color_rgba = cairo_pattern_create_rgba(color.red / 255.0f, + color.green / 255.0f, + color.blue / 255.0f, + color.alpha / 255.0f); + set_fill_pattern(p_ctx, color_rgba); +} + +void script_ops_fill_linear(void* v_ctx, + coordinates_t start, coordinates_t end, + color_rgba_t color_start, color_rgba_t color_end) +{ + if (g_opts.debug_mode) { + log_script_ops_fill_linear(log_prefix, __func__, log_level_info, + start, end, color_start, color_end); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + + cairo_pattern_t* linear_gradient = cairo_pattern_create_linear(start.x, start.y, + end.x, end.y); + cairo_pattern_add_color_stop_rgba(linear_gradient, 0.0, + color_start.red / 255.0f, + color_start.green / 255.0f, + color_start.blue / 255.0f, + color_start.alpha / 255.0f); + cairo_pattern_add_color_stop_rgba(linear_gradient, 1.0, + color_end.red / 255.0f, + color_end.green / 255.0f, + color_end.blue / 255.0f, + color_end.alpha / 255.0f); + set_fill_pattern(p_ctx, linear_gradient); +} + +void script_ops_fill_radial(void* v_ctx, + coordinates_t center, + float inner_radius, + float outer_radius, + color_rgba_t color_start, + color_rgba_t color_end) +{ + if (g_opts.debug_mode) { + log_script_ops_fill_radial(log_prefix, __func__, log_level_info, + center, inner_radius, outer_radius, color_start, color_end); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + + cairo_pattern_t* radial_gradient = cairo_pattern_create_radial(center.x, center.y, + inner_radius, + center.x, center.y, + outer_radius); + cairo_pattern_add_color_stop_rgba(radial_gradient, 0.0, + color_start.red / 255.0f, + color_start.green / 255.0f, + color_start.blue / 255.0f, + color_start.alpha / 255.0f); + cairo_pattern_add_color_stop_rgba(radial_gradient, 1.0, + color_end.red / 255.0f, + color_end.green / 255.0f, + color_end.blue / 255.0f, + color_end.alpha / 255.0f); + set_fill_pattern(p_ctx, radial_gradient); +} + +void script_ops_fill_image(void* v_ctx, sid_t id) +{ + if (g_opts.debug_mode) { + log_script_ops_fill_image(log_prefix, __func__, log_level_info, + id); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + + // get the mapped image_id for this image_id + image_t* p_image = get_image(id); + if (!p_image) return; + + image_pattern_data_t* image_data = find_image_pattern(p_ctx, p_image->image_id); + + cairo_set_antialias(p_ctx->cr, CAIRO_ANTIALIAS_NONE); + set_fill_pattern(p_ctx, image_data->pattern); +} + +void script_ops_fill_stream(void* v_ctx, + sid_t id) +{ + if (g_opts.debug_mode) { + log_script_ops_fill_stream(log_prefix, __func__, log_level_info, + id); + } + + script_ops_fill_image(v_ctx, id); +} + +void script_ops_stroke_width(void* v_ctx, + float w) +{ + if (g_opts.debug_mode) { + log_script_ops_stroke_width(log_prefix, __func__, log_level_info, + w); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + + cairo_set_line_width(p_ctx->cr, w); +} + +void script_ops_stroke_color(void* v_ctx, + color_rgba_t color) +{ + if (g_opts.debug_mode) { + log_script_ops_stroke_color(log_prefix, __func__, log_level_info, + color); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + + cairo_pattern_t* color_rgba = cairo_pattern_create_rgba(color.red / 255.0f, + color.green / 255.0f, + color.blue / 255.0f, + color.alpha / 255.0f); + set_stroke_pattern(p_ctx, color_rgba); +} + +void script_ops_stroke_linear(void* v_ctx, + coordinates_t start, coordinates_t end, + color_rgba_t color_start, color_rgba_t color_end) +{ + if (g_opts.debug_mode) { + log_script_ops_stroke_linear(log_prefix, __func__, log_level_info, + start, end, color_start, color_end); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + + cairo_pattern_t* linear_gradient = cairo_pattern_create_linear(start.x, start.y, + end.x, end.y); + cairo_pattern_add_color_stop_rgba(linear_gradient, 0.0, + color_start.red / 255.0f, + color_start.green / 255.0f, + color_start.blue / 255.0f, + color_start.alpha / 255.0f); + cairo_pattern_add_color_stop_rgba(linear_gradient, 1.0, + color_end.red / 255.0f, + color_end.green / 255.0f, + color_end.blue / 255.0f, + color_end.alpha / 255.0f); + set_stroke_pattern(p_ctx, linear_gradient); +} + +void script_ops_stroke_radial(void* v_ctx, + coordinates_t center, + float inner_radius, + float outer_radius, + color_rgba_t color_start, + color_rgba_t color_end) +{ + if (g_opts.debug_mode) { + log_script_ops_stroke_radial(log_prefix, __func__, log_level_info, + center, inner_radius, outer_radius, color_start, color_end); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + + cairo_pattern_t* radial_gradient = cairo_pattern_create_radial(center.x, center.y, + inner_radius, + center.x, center.y, + outer_radius); + cairo_pattern_add_color_stop_rgba(radial_gradient, 0.0, + color_start.red / 255.0f, + color_start.green / 255.0f, + color_start.blue / 255.0f, + color_start.alpha / 255.0f); + cairo_pattern_add_color_stop_rgba(radial_gradient, 1.0, + color_end.red / 255.0f, + color_end.green / 255.0f, + color_end.blue / 255.0f, + color_end.alpha / 255.0f); + set_stroke_pattern(p_ctx, radial_gradient); +} + +void script_ops_stroke_image(void* v_ctx, sid_t id) +{ + if (g_opts.debug_mode) { + log_script_ops_stroke_image(log_prefix, __func__, log_level_info, + id); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + + // get the mapped image_id for this image_id + image_t* p_image = get_image(id); + if (!p_image) return; + + image_pattern_data_t* image_data = find_image_pattern(p_ctx, p_image->image_id); + + cairo_set_antialias(p_ctx->cr, CAIRO_ANTIALIAS_NONE); + set_stroke_pattern(p_ctx, image_data->pattern); +} + +void script_ops_stroke_stream(void* v_ctx, + sid_t id) +{ + if (g_opts.debug_mode) { + log_script_ops_stroke_stream(log_prefix, __func__, log_level_info, + id); + } + + script_ops_stroke_image(v_ctx, id); +} + +void script_ops_line_cap(void* v_ctx, + line_cap_t type) +{ + if (g_opts.debug_mode) { + log_script_ops_line_cap(log_prefix, __func__, log_level_info, + type); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + switch(type) { + case LINE_CAP_BUTT: + cairo_set_line_cap(p_ctx->cr, CAIRO_LINE_CAP_BUTT); + break; + case LINE_CAP_ROUND: + cairo_set_line_cap(p_ctx->cr, CAIRO_LINE_CAP_ROUND); + break; + case LINE_CAP_SQUARE: + cairo_set_line_cap(p_ctx->cr, CAIRO_LINE_CAP_SQUARE); + break; + } +} + +void script_ops_line_join(void* v_ctx, + line_join_t type) +{ + if (g_opts.debug_mode) { + log_script_ops_line_join(log_prefix, __func__, log_level_info, + type); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + switch(type) { + case LINE_JOIN_BEVEL: + cairo_set_line_join(p_ctx->cr, CAIRO_LINE_JOIN_BEVEL); + break; + case LINE_JOIN_ROUND: + cairo_set_line_join(p_ctx->cr, CAIRO_LINE_JOIN_ROUND); + break; + case LINE_JOIN_MITER: + cairo_set_line_join(p_ctx->cr, CAIRO_LINE_JOIN_MITER); + break; + } +} + +void script_ops_miter_limit(void* v_ctx, + uint32_t limit) +{ + if (g_opts.debug_mode) { + log_script_ops_miter_limit(log_prefix, __func__, log_level_info, + limit); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + cairo_set_miter_limit(p_ctx->cr, limit); +} + +void script_ops_font(void* v_ctx, + sid_t id) +{ + if (g_opts.debug_mode) { + log_script_ops_font(log_prefix, __func__, log_level_info, + id); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + font_t* p_font = get_font(id); + if (!p_font) return; + + font_data_t* font_data = find_font(p_ctx, p_font->font_id); + if (!font_data) return; + + cairo_set_font_face(p_ctx->cr, font_data->font_face); +} + +void script_ops_font_size(void* v_ctx, + float size) +{ + if (g_opts.debug_mode) { + log_script_ops_font_size(log_prefix, __func__, log_level_info, + size); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + p_ctx->font_size = size; + cairo_set_font_size(p_ctx->cr, size); +} + +void script_ops_text_align(void* v_ctx, + text_align_t type) +{ + if (g_opts.debug_mode) { + log_script_ops_text_align(log_prefix, __func__, log_level_info, + type); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + p_ctx->text_align = type; +} + +void script_ops_text_base(void* v_ctx, + text_base_t type) +{ + if (g_opts.debug_mode) { + log_script_ops_text_base(log_prefix, __func__, log_level_info, + type); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)v_ctx; + p_ctx->text_base = type; +} diff --git a/c_src/scenic/ops/nvg_script_ops.c b/c_src/scenic/ops/nvg_script_ops.c index ccc6b4f..8a739a0 100644 --- a/c_src/scenic/ops/nvg_script_ops.c +++ b/c_src/scenic/ops/nvg_script_ops.c @@ -221,11 +221,10 @@ void script_ops_draw_text(void* v_ctx, //--------------------------------------------------------- // see: https://github.com/memononen/nanovg/issues/348 -static void draw_image(void* v_ctx, +static void draw_image(NVGcontext* p_ctx, sid_t id, const sprite_t s) { - NVGcontext* p_ctx = (NVGcontext*)v_ctx; float ax, ay; NVGpaint img_pattern; diff --git a/c_src/scenic/ops/script_ops.c b/c_src/scenic/ops/script_ops.c index d31918d..17ed51c 100644 --- a/c_src/scenic/ops/script_ops.c +++ b/c_src/scenic/ops/script_ops.c @@ -2,6 +2,8 @@ #include "script.h" #include "script_ops.h" +extern device_opts_t g_opts; + static const char* log_prefix = "Unimplemented"; __attribute__((weak)) @@ -197,11 +199,11 @@ void log_script_ops_draw_text(const char* prefix, const char* func, log_level_t { log_message(level, "%s %s: %{" "size: %d, " - "text: '%s'" + "text: '%.*s'" "}", prefix, func, size, - text); + size, text); } __attribute__((weak)) @@ -211,12 +213,30 @@ void script_ops_draw_sprites(void* v_ctx, sid_t id, uint32_t count, const sprite } void log_script_ops_draw_sprites(const char* prefix, const char* func, log_level_t level, sid_t id, uint32_t count, const sprite_t* sprites) { - log_message(level, "%s %s", prefix, func); + log_message(level, "%s %s: %{" + "id: '%.*s', " + "count: %d" + "}", prefix, func, + id.size, id.p_data, + count); + for (int i = 0; i < count; i++) { + log_message(level, "%s %s: index: %d %{" + "s: {{%.1f,%.1f},{%.1f,%.1f}}, " + "d: {{%.1f,%.1f},{%.1f,%.1f}}" + "}", prefix, func, i, + sprites[i].sx, sprites[i].sy, sprites[i].sw, sprites[i].sh, + sprites[i].dx, sprites[i].dy, sprites[i].dw, sprites[i].dh); + } } __attribute__((weak)) void script_ops_draw_script(void* v_ctx, sid_t id) { + if (g_opts.debug_mode) { + log_debug("%s id: '%.*s'", __func__, + id.size, id.p_data); + } + render_script(v_ctx, id); } diff --git a/c_src/scenic/script.c b/c_src/scenic/script.c index f027b7a..b5e2189 100644 --- a/c_src/scenic/script.c +++ b/c_src/scenic/script.c @@ -14,6 +14,7 @@ #include "script.h" #include "utils.h" +extern device_opts_t g_opts; //--------------------------------------------------------- typedef struct _script_t { @@ -63,6 +64,11 @@ void do_delete_script(sid_t id) { script_t* p_script = get_script(id); if (p_script) { + if (g_opts.debug_mode) { + log_debug("%s id:'%.*s'", __func__, + id.size, id.p_data); + } + tommy_hashlin_remove_existing(&scripts, &p_script->node); free(p_script); @@ -99,6 +105,11 @@ void put_script(int* p_msg_length) // if there is already is a script with the same id, delete it do_delete_script(p_script->id); + if (g_opts.debug_mode) { + log_debug("%s id:'%.*s'", __func__, + p_script->id.size, p_script->id.p_data); + } + // insert the script into the tommy hash tommy_hashlin_insert(&scripts, &p_script->node, diff --git a/lib/driver.ex b/lib/driver.ex index 8e92af2..b2890e9 100644 --- a/lib/driver.ex +++ b/lib/driver.ex @@ -25,6 +25,7 @@ defmodule Scenic.Driver.Local do layer: [type: :integer, default: @default_layer], opacity: [type: :integer, default: @default_opacity], debug: [type: :boolean, default: false], + debugger: [type: :string, default: ""], antialias: [type: :boolean, default: true], calibration: [ type: {:custom, __MODULE__, :validate_calibration, []}, @@ -212,6 +213,7 @@ defmodule Scenic.Driver.Local do false -> 0 end + {:ok, debugger} = Keyword.fetch(opts, :debugger) {:ok, layer} = Keyword.fetch(opts, :layer) {:ok, opacity} = Keyword.fetch(opts, :opacity) @@ -231,7 +233,9 @@ defmodule Scenic.Driver.Local do # open and initialize the window Process.flag(:trap_exit, true) - executable = :code.priv_dir(:scenic_driver_local) ++ @port ++ to_charlist(args) + executable = + to_charlist(debugger) ++ + ' ' ++ :code.priv_dir(:scenic_driver_local) ++ @port ++ to_charlist(args) port = Port.open({:spawn, executable}, [:binary, {:packet, 4}]) diff --git a/mix.exs b/mix.exs index 8f5efc7..93c9c0f 100644 --- a/mix.exs +++ b/mix.exs @@ -93,7 +93,7 @@ defmodule Scenic.Driver.Local.MixProject do defp package do [ name: @app_name, - contributors: ["Boyd Multerer"], + contributors: ["Boyd Multerer", "Jon Ringle"], maintainers: ["Boyd Multerer"], licenses: ["Apache-2.0"], links: %{Github: @github}, diff --git a/test/driver_test.exs b/test/driver_test.exs index 768c8ea..316f7d1 100644 --- a/test/driver_test.exs +++ b/test/driver_test.exs @@ -8,6 +8,7 @@ defmodule Scenic.Driver.LocalTest do layer: 0, opacity: 1, debug: false, + debugger: "", antialias: true, calibration: [{"calibration_name", {{0, 0, 0}, {0, 0, 0}}}], position: [