From 0c1834509cb3b084a1121cd012e5a8b661634237 Mon Sep 17 00:00:00 2001 From: BO KAI HUANG Date: Thu, 5 Dec 2024 10:38:51 +0800 Subject: [PATCH] Support TinyVG format decoding and rendering Implemented support for decoding TinyVG format by parsing commands step-by-step. Each decoded command is executed using twin's path functions to render graphics. This enhances twin's capability to handle compact vector graphics . Ref: https://tinyvg.tech/download/specification.pdf Close #71 --- Makefile | 5 + apps/apps_image.h | 18 + apps/image.c | 81 +++ apps/main.c | 4 + assets/chart.tvg | Bin 0 -> 6540 bytes configs/Kconfig | 9 + src/image-tvg.c | 1381 +++++++++++++++++++++++++++++++++++++++++++++ src/image.c | 15 + src/path.c | 2 + 9 files changed, 1515 insertions(+) create mode 100644 apps/apps_image.h create mode 100644 apps/image.c create mode 100644 assets/chart.tvg create mode 100644 src/image-tvg.c diff --git a/Makefile b/Makefile index 8a612ec..c1d0927 100644 --- a/Makefile +++ b/Makefile @@ -86,6 +86,10 @@ ifeq ($(CONFIG_LOADER_GIF), y) libtwin.a_files-y += src/image-gif.c endif +ifeq ($(CONFIG_LOADER_GIF), y) +libtwin.a_files-y += src/image-tvg.c +endif + # Applications libapps.a_files-y := apps/dummy.c @@ -96,6 +100,7 @@ libapps.a_files-$(CONFIG_DEMO_CALCULATOR) += apps/calc.c libapps.a_files-$(CONFIG_DEMO_LINE) += apps/line.c libapps.a_files-$(CONFIG_DEMO_SPLINE) += apps/spline.c libapps.a_files-$(CONFIG_DEMO_ANIMATION) += apps/animation.c +libapps.a_files-$(CONFIG_DEMO_IMAGE) += apps/image.c libapps.a_includes-y := include diff --git a/apps/apps_image.h b/apps/apps_image.h new file mode 100644 index 0000000..5ad9a24 --- /dev/null +++ b/apps/apps_image.h @@ -0,0 +1,18 @@ +/* + * Twin - A Tiny Window System + * Copyright (c) 2024 National Cheng Kung University + * All rights reserved. + */ + +#ifndef _APPS_IMAGE_H_ +#define _APPS_IMAGE_H_ + +#include + +void apps_image_start(twin_screen_t *screen, + const char *name, + const char *path, + int x, + int y); + +#endif /* _APPS_ANIMATION_H_ */ diff --git a/apps/image.c b/apps/image.c new file mode 100644 index 0000000..b09ce9e --- /dev/null +++ b/apps/image.c @@ -0,0 +1,81 @@ +/* + * Twin - A Tiny Window System + * Copyright (c) 2024 National Cheng Kung University + * All rights reserved. + */ + +#include + +#include "twin_private.h" + +#include "apps_image.h" + +#define _apps_image_pixmap(image) ((image)->widget.window->pixmap) + +typedef struct { + twin_widget_t widget; + twin_pixmap_t *pix; + twin_timeout_t *timeout; +} apps_image_t; + +static void _apps_image_paint(apps_image_t *img) +{ + twin_operand_t srcop = { + .source_kind = TWIN_PIXMAP, + .u.pixmap = img->pix, + }; + twin_composite(_apps_image_pixmap(img), 0, 0, &srcop, 0, 0, NULL, 0, 0, + TWIN_SOURCE, img->pix->width, img->pix->height); +} + +static twin_dispatch_result_t _apps_image_dispatch(twin_widget_t *widget, + twin_event_t *event) +{ + apps_image_t *img = (apps_image_t *) widget; + if (_twin_widget_dispatch(widget, event) == TwinDispatchDone) + return TwinDispatchDone; + switch (event->kind) { + case TwinEventPaint: + _apps_image_paint(img); + break; + default: + break; + } + return TwinDispatchContinue; +} + +static void _apps_image_init(apps_image_t *anim, + twin_box_t *parent, + twin_dispatch_proc_t dispatch) +{ + static const twin_widget_layout_t preferred = {0, 0, 1, 1}; + _twin_widget_init(&anim->widget, parent, 0, preferred, dispatch); + + anim->timeout = NULL; +} + +static apps_image_t *apps_image_create(twin_box_t *parent, twin_pixmap_t *pix) +{ + apps_image_t *img = malloc(sizeof(apps_image_t)); + img->pix = pix; + _apps_image_init(img, parent, _apps_image_dispatch); + return img; +} + +void apps_image_start(twin_screen_t *screen, + const char *name, + const char *path, + int x, + int y) +{ + twin_pixmap_t *pix = twin_pixmap_from_file(path, TWIN_ARGB32); + if (!pix) + return; + twin_toplevel_t *toplevel = + twin_toplevel_create(screen, TWIN_ARGB32, TwinWindowApplication, x, y, + pix->width, pix->height, name); + apps_image_t *img = apps_image_create(&toplevel->box, pix); + (void) img; + twin_toplevel_show(toplevel); +} + diff --git a/apps/main.c b/apps/main.c index afd3dac..0638d3f 100644 --- a/apps/main.c +++ b/apps/main.c @@ -17,6 +17,7 @@ #include "apps_calc.h" #include "apps_clock.h" #include "apps_hello.h" +#include "apps_image.h" #include "apps_line.h" #include "apps_multi.h" #include "apps_spline.h" @@ -127,6 +128,9 @@ int main(void) apps_animation_start(tx->screen, "Viewer", ASSET_PATH "nyancat.gif", 20, 20); #endif +#if defined(CONFIG_DEMO_IMAGE) + apps_image_start(tx->screen, "Viewer", ASSET_PATH "chart.tvg", 20, 20); +#endif twin_dispatch(tx); diff --git a/assets/chart.tvg b/assets/chart.tvg new file mode 100644 index 0000000000000000000000000000000000000000..745021d0ae4c6e58fd579f79e1b0f47682b07a98 GIT binary patch literal 6540 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 literal 0 HcmV?d00001 diff --git a/configs/Kconfig b/configs/Kconfig index d85b63d..9f82cf3 100644 --- a/configs/Kconfig +++ b/configs/Kconfig @@ -71,6 +71,10 @@ config LOADER_GIF bool "Enable GIF loader" default y +config LOADER_TVG + bool "Enable TVG loader" + default y + endmenu menu "Demo Applications" @@ -113,4 +117,9 @@ config DEMO_ANIMATION bool "Build animation demo" default y depends on DEMO_APPLICATIONS + +config DEMO_IMAGE + bool "Build image demo" + default y + depends on DEMO_APPLICATIONS endmenu diff --git a/src/image-tvg.c b/src/image-tvg.c new file mode 100644 index 0000000..f5642b2 --- /dev/null +++ b/src/image-tvg.c @@ -0,0 +1,1381 @@ +#include +#include +#include +#include +#include +#include + +#include "twin.h" +#include "twin_private.h" + +#define D(x) twin_double_to_fixed(x) +#define GET_COLOR(ctx, idx) ctx->colors[idx] +#define PIXEL_ARGB(a, r, g, b) (((a) << 24) | ((r) << 16) | ((g) << 8) | (b)) + +enum { + /* end of document This command determines the end of file. */ + TVG_CMD_END_DOCUMENT = 0, + /* fill polygon This command fills an N-gon.*/ + TVG_CMD_FILL_POLYGON, + /* fill rectangles This command fills a set of rectangles.*/ + TVG_CMD_FILL_RECTANGLES, + /* fill path This command fills a free-form path.*/ + TVG_CMD_FILL_PATH, + /* draw lines This command draws a set of lines.*/ + TVG_CMD_DRAW_LINES, + /* draw line loop This command draws the outline of a polygon.*/ + TVG_CMD_DRAW_LINE_LOOP, + /* draw line strip This command draws a list of end-to-end lines.*/ + TVG_CMD_DRAW_LINE_STRIP, + /* draw line path This command draws a free-form path. */ + TVG_CMD_DRAW_LINE_PATH, + /* outline fill polygon This command draws a filled polygon with an outline. + */ + TVG_CMD_OUTLINE_FILL_POLYGON, + /* outline fill rectangles This command draws several filled + * rectangles with an outline. + */ + TVG_CMD_OUTLINE_FILL_RECTANGLES, + /* outline fill path This command combines the fill and draw + * line path command into one + */ + TVG_CMD_OUTLINE_FILL_PATH +}; + +enum { + /* solid color */ + TVG_STYLE_FLAT = 0, + /* linear gradient */ + TVG_STYLE_LINEAR, + /* radial gradient */ + TVG_STYLE_RADIAL +}; + +enum { + // unit uses 16 bit, + TVG_RANGE_DEFAULT = 0, + // unit takes only 8 bit + TVG_RANGE_REDUCED, + // unit uses 32 bit, + TVG_RANGE_ENHANCED, +}; + +/* The color encoding used in a TinyVG file. + * This enum describes how the data in the color table + * section of the format looks like. + */ +enum { + TVG_COLOR_U8888 = 0, + TVG_COLOR_U565, + TVG_COLOR_F32, + TVG_COLOR_CUSTOM, +}; + +/* A TinyVG scale value. Defines the scale for all units inside a graphic. + * The scale is defined by the number of decimal bits in a `i32`, thus scaling + * can be trivially implemented by shifting the integers right by the scale + * bits. + */ +enum { + TVG_SCALE_1_1 = 0, + TVG_SCALE_1_2, + TVG_SCALE_1_4, + TVG_SCALE_1_8, + TVG_SCALE_1_16, + TVG_SCALE_1_32, + TVG_SCALE_1_64, + TVG_SCALE_1_128, + TVG_SCALE_1_256, + TVG_SCALE_1_512, + TVG_SCALE_1_1024, + TVG_SCALE_1_2048, + TVG_SCALE_1_4096, + TVG_SCALE_1_8192, + TVG_SCALE_1_16384, + TVG_SCALE_1_32768, +}; + +/* path commands */ +enum { + TVG_PATH_LINE = 0, + TVG_PATH_HLINE, + TVG_PATH_VLINE, + TVG_PATH_CUBIC, + TVG_PATH_ARC_CIRCLE, + TVG_PATH_ARC_ELLIPSE, + TVG_PATH_CLOSE, + TVG_PATH_QUAD +}; + +enum { + TVG_SUCCESS = 0, + TVG_E_INVALID_ARG, + TVG_E_INVALID_STATE, + TVG_E_INVALID_FORMAT, + TVG_E_IO_ERROR, + TVG_E_OUT_OF_MEMORY, + TVG_E_NOT_SUPPORTED +}; + +/* clamp a value to a range */ +#define TVG_CLAMP(x, mn, mx) (x > mx ? mx : (x < mn ? mn : x)) +/* get the red channel of an RGB565 color */ +#define TVG_RGB16_R(x) (x & 0x1F) +/* get the green channel of an RGB565 color */ +#define TVG_RGB16_G(x) ((x >> 5) & 0x3F) +/* get the blue channel of an RGB565 color */ +#define TVG_RGB16_B(x) ((x >> 11) & 0x1F) +/* get the index of the command + * essentially the command id + */ +#define TVG_CMD_INDEX(x) (x & 0x3F) +/* get the style kind flags in the command */ +#define TVG_CMD_STYLE_KIND(x) ((x >> 6) & 0x3) +/* get the packed size out of the size + * and style kind packed value + */ +#define TVG_SIZE_AND_STYLE_SIZE(x) ((x & 0x3F) + 1) +/* get the style kind out of the size + * and style kind packed value + */ +#define TVG_SIZE_AND_STYLE_STYLE_KIND(x) ((x >> 6) & 0x3) +/* get the scale from the header */ +#define TVG_HEADER_DATA_SCALE(x) (x & 0x0F) +/* get the color encoding from the header */ +#define TVG_HEADER_DATA_COLOR_ENC(x) ((x >> 4) & 0x03) +/* get the color range from the header */ +#define TVG_HEADER_DATA_RANGE(x) ((x >> 6) & 0x03) +/* get the path command index/id */ +#define TVG_PATH_CMD_INDEX(x) (x & 0x7) +/* flag indicating the path has line/stroke to it */ +#define TVG_PATH_CMD_HAS_LINE(x) ((x >> 4) & 0x1) +/* flag indicating the arc is a large arc */ +#define TVG_ARC_LARGE(x) (x & 0x1) +/* flag indicating the sweep direction 0=left, 1=right */ +#define TVG_ARC_SWEEP(x) ((x >> 1) & 1) + +/* F32 pixel format */ +typedef struct { + float r, g, b, a; +} tvg_f32_pixel_t; + +/* rgba32 color struct */ +typedef struct { + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t a; +} tvg_rgba32_t; + +/* TVG internal color struct */ +typedef struct { + float r; + float g; + float b; +} tvg_rgb_t; + +/* TVG internal color struct with alpha channel */ +typedef struct { + float r; + float g; + float b; + float a; +} tvg_rgba_t; + +/* coordinate */ +typedef struct { + float x; + float y; +} tvg_point_t; + +/* rectangle */ +typedef struct { + float x; + float y; + float width; + float height; +} tvg_rect_t; + +/* gradient data */ +typedef struct { + tvg_point_t point0; + tvg_point_t point1; + uint32_t color0; + uint32_t color1; +} tvg_gradient_t; + +/* style data */ +typedef struct { + uint8_t kind; + union { + uint32_t flat; + tvg_gradient_t linear; + tvg_gradient_t radial; + }; +} tvg_style_t; + +/* fill header */ +typedef struct { + tvg_style_t style; + size_t size; +} tvg_fill_header_t; + +/* line header */ +typedef struct { + tvg_style_t style; + float line_width; + size_t size; +} tvg_line_header_t; + +/* line and fill header */ +typedef struct { + tvg_style_t fill_style; + tvg_style_t line_style; + float line_width; + size_t size; +} tvg_line_fill_header_t; + +/* used to provide an input cursor over an arbitrary source */ +typedef size_t (*tvg_input_func_t)(uint8_t *data, size_t size, void *state); + +typedef struct { + /* the input source */ + tvg_input_func_t inp; + /* the user defined input state */ + void *inp_state; + /* the target pixmap */ + twin_pixmap_t *pixmap; + twin_path_t *path; + /* the scaling used */ + uint8_t scale; + /* the color encoding */ + uint8_t color_encoding; + /* the coordinate range */ + uint8_t coord_range; + /* the width and height of the drawing */ + uint32_t width, height; + /* the size of the color table */ + size_t colors_size; + /* the color table (must be freed) */ + twin_argb32_t *colors; + uint32_t current_color; +} tvg_context_t; + +/* the result type. TVG_SUCCESS = OK + * anything else, is a TVG_E_xxxx error + * code + */ +typedef int tvg_result_t; + +static uint32_t tvg_map_zero_to_max(tvg_context_t *ctx, uint32_t value) +{ + if (0 == value) { + switch (ctx->coord_range) { + case TVG_RANGE_DEFAULT: + return 0xFFFF; + case TVG_RANGE_REDUCED: + return 0xFF; + default: + return 0xFFFFFFFF; + } + } + return value; +} + +static tvg_result_t tvg_read_coord(tvg_context_t *ctx, uint32_t *out_raw_value) +{ + size_t read; + switch (ctx->coord_range) { + case TVG_RANGE_DEFAULT: { + uint16_t u16; + read = ctx->inp((uint8_t *) &u16, sizeof(uint16_t), ctx->inp_state); + if (sizeof(uint16_t) > read) { + return TVG_E_IO_ERROR; + } + *out_raw_value = u16; + return TVG_SUCCESS; + } + case TVG_RANGE_REDUCED: { + uint8_t u8; + read = ctx->inp((uint8_t *) &u8, sizeof(uint8_t), ctx->inp_state); + if (sizeof(uint8_t) > read) { + return TVG_E_IO_ERROR; + } + *out_raw_value = u8; + return TVG_SUCCESS; + } + default: + read = ctx->inp((uint8_t *) out_raw_value, sizeof(uint32_t), + ctx->inp_state); + if (sizeof(uint32_t) > read) { + return TVG_E_IO_ERROR; + } + return TVG_SUCCESS; + } +} + +static tvg_result_t tvg_read_color(tvg_context_t *ctx, twin_argb32_t *out_color) +{ + size_t read; + switch (ctx->color_encoding) { + case TVG_COLOR_F32: { + tvg_f32_pixel_t data; + read = ctx->inp((uint8_t *) &data, sizeof(data), ctx->inp_state); + if (sizeof(data) > read) { + return TVG_E_IO_ERROR; + } + uint8_t a = (uint8_t) (255.0f * data.a); + uint8_t r = (uint8_t) (255.0f * data.r); + uint8_t g = (uint8_t) (255.0f * data.g); + uint8_t b = (uint8_t) (255.0f * data.b); + *out_color = PIXEL_ARGB(a, r, g, b); + return TVG_SUCCESS; + } + case TVG_COLOR_U565: { + uint16_t data; + read = ctx->inp((uint8_t *) &data, sizeof(data), ctx->inp_state); + if (sizeof(data) > read) { + return TVG_E_IO_ERROR; + } + uint8_t a = 0xff; + uint8_t r = (uint8_t) ((255.0f * (float) TVG_RGB16_R(data)) / 15.0f); + uint8_t g = (uint8_t) ((255.0f * (float) TVG_RGB16_G(data)) / 31.0f); + uint8_t b = (uint8_t) ((255.0f * (float) TVG_RGB16_B(data)) / 15.0f); + *out_color = PIXEL_ARGB(a, r, g, b); + return TVG_SUCCESS; + } + case TVG_COLOR_U8888: { + tvg_rgba32_t data; + read = ctx->inp((uint8_t *) &data.r, 1, ctx->inp_state); + if (1 > read) { + return TVG_E_IO_ERROR; + } + read = ctx->inp((uint8_t *) &data.g, 1, ctx->inp_state); + if (1 > read) { + return TVG_E_IO_ERROR; + } + read = ctx->inp((uint8_t *) &data.b, 1, ctx->inp_state); + if (1 > read) { + return TVG_E_IO_ERROR; + } + read = ctx->inp((uint8_t *) &data.a, 1, ctx->inp_state); + if (1 > read) { + return TVG_E_IO_ERROR; + } + *out_color = PIXEL_ARGB(data.a, data.r, data.g, data.b); + return TVG_SUCCESS; + } + case TVG_COLOR_CUSTOM: + return TVG_E_NOT_SUPPORTED; + default: + return TVG_E_INVALID_FORMAT; + } +} + +static float tvg_downscale_coord(tvg_context_t *ctx, uint32_t coord) +{ + uint16_t factor = (((uint16_t) 1) << ctx->scale); + return (float) coord / (float) factor; +} + +static tvg_result_t tvg_read_varuint(tvg_context_t *ctx, uint32_t *out_value) +{ + int count = 0; + uint32_t result = 0; + uint8_t byte; + while (true) { + if (1 > ctx->inp(&byte, 1, ctx->inp_state)) { + return TVG_E_IO_ERROR; + } + const uint32_t val = ((uint32_t) (byte & 0x7F)) << (7 * count); + result |= val; + if ((byte & 0x80) == 0) + break; + ++count; + } + *out_value = result; + return TVG_SUCCESS; +} + +static tvg_result_t tvg_read_unit(tvg_context_t *ctx, float *out_value) +{ + uint32_t val; + tvg_result_t res = tvg_read_coord(ctx, &val); + if (res != TVG_SUCCESS) { + return res; + } + *out_value = tvg_downscale_coord(ctx, val); + return TVG_SUCCESS; +} + +static tvg_result_t tvg_read_point(tvg_context_t *ctx, tvg_point_t *out_point) +{ + float f32; + tvg_result_t res = tvg_read_unit(ctx, &f32); + if (res != TVG_SUCCESS) { + return res; + } + out_point->x = f32; + res = tvg_read_unit(ctx, &f32); + if (res != TVG_SUCCESS) { + return res; + } + out_point->y = f32; + return TVG_SUCCESS; +} + +static tvg_result_t tvg_parse_header(tvg_context_t *ctx, int dim_only) +{ + uint8_t data[2]; + /* read the magic number */ + if (2 > ctx->inp((uint8_t *) data, 2, ctx->inp_state)) { + return TVG_E_IO_ERROR; + } + if (data[0] != 0x72 || data[1] != 0x56) { + return TVG_E_INVALID_FORMAT; + } + /* read the version */ + if (1 > ctx->inp(data, 1, ctx->inp_state)) { + return TVG_E_IO_ERROR; + } + /* we only support version 1 */ + if (data[0] != 1) { + return TVG_E_NOT_SUPPORTED; + } + /* read the scale, encoding, and coordinate range: */ + if (1 > ctx->inp(data, 1, ctx->inp_state)) { + return TVG_E_IO_ERROR; + } + /* it's all packed in a byte, so crack it up */ + ctx->scale = TVG_HEADER_DATA_SCALE(data[0]); + ctx->color_encoding = TVG_HEADER_DATA_COLOR_ENC(data[0]); + ctx->coord_range = TVG_HEADER_DATA_RANGE(data[0]); + /* now read the width and height: */ + uint32_t tmp; + tvg_result_t res = tvg_read_coord(ctx, &tmp); + if (res != TVG_SUCCESS) { + return res; + } + ctx->width = tvg_map_zero_to_max(ctx, tmp); + res = tvg_read_coord(ctx, &tmp); + if (res != TVG_SUCCESS) { + return res; + } + ctx->height = tvg_map_zero_to_max(ctx, tmp); + if (dim_only) { + return TVG_SUCCESS; + } + /* next read the color table */ + uint32_t color_count; + res = tvg_read_varuint(ctx, &color_count); + if (res != TVG_SUCCESS) { + return res; + } + if (color_count == 0) { + return TVG_E_INVALID_FORMAT; + } + ctx->colors = (twin_argb32_t *) malloc(color_count * sizeof(twin_argb32_t)); + if (ctx->colors == NULL) { + return TVG_E_OUT_OF_MEMORY; + } + ctx->colors_size = (size_t) color_count; + for (size_t i = 0; i < ctx->colors_size; ++i) { + res = tvg_read_color(ctx, &ctx->colors[i]); + if (res != TVG_SUCCESS) { + free(ctx->colors); + ctx->colors = NULL; + return res; + } + } + return TVG_SUCCESS; +} + +static tvg_result_t tvg_parse_gradient(tvg_context_t *ctx, + tvg_gradient_t *out_gradient) +{ + uint32_t u32; + tvg_point_t pt; + tvg_result_t res = tvg_read_point(ctx, &pt); + if (res != TVG_SUCCESS) { + return res; + } + out_gradient->point0 = pt; + res = tvg_read_point(ctx, &pt); + if (res != TVG_SUCCESS) { + return res; + } + out_gradient->point1 = pt; + res = tvg_read_varuint(ctx, &u32); + if (res != TVG_SUCCESS) { + return res; + } + out_gradient->color0 = u32; + if (u32 > ctx->colors_size) { + return TVG_E_INVALID_FORMAT; + } + res = tvg_read_varuint(ctx, &u32); + if (res != TVG_SUCCESS) { + return res; + } + if (u32 > ctx->colors_size) { + return TVG_E_INVALID_FORMAT; + } + out_gradient->color1 = u32; + return TVG_SUCCESS; +} + +static tvg_result_t tvg_parse_style(tvg_context_t *ctx, + int kind, + tvg_style_t *out_style) +{ + tvg_result_t res; + uint32_t flat; + tvg_gradient_t grad; + out_style->kind = kind; + switch (kind) { + case TVG_STYLE_FLAT: + res = tvg_read_varuint(ctx, &flat); + if (res != TVG_SUCCESS) { + return res; + } + out_style->flat = flat; + break; + case TVG_STYLE_LINEAR: + res = tvg_parse_gradient(ctx, &grad); + out_style->linear = grad; + break; + case TVG_STYLE_RADIAL: + res = tvg_parse_gradient(ctx, &grad); + out_style->radial = grad; + break; + default: + res = TVG_E_INVALID_FORMAT; + break; + } + if (res != TVG_SUCCESS) { + return res; + } + return res; +} + +static tvg_result_t tvg_parse_fill_header(tvg_context_t *ctx, + int kind, + tvg_fill_header_t *out_header) +{ + uint32_t u32; + tvg_result_t res = tvg_read_varuint(ctx, &u32); + if (res != TVG_SUCCESS) { + return res; + } + size_t count = (size_t) u32 + 1; + out_header->size = count; + res = tvg_parse_style(ctx, kind, &out_header->style); + if (res != TVG_SUCCESS) { + return res; + } + return TVG_SUCCESS; +} + +static tvg_result_t tvg_parse_line_header(tvg_context_t *ctx, + int kind, + tvg_line_header_t *out_header) +{ + uint32_t u32; + tvg_result_t res = tvg_read_varuint(ctx, &u32); + if (res != TVG_SUCCESS) { + return res; + } + size_t count = (size_t) u32 + 1; + out_header->size = count; + res = tvg_parse_style(ctx, kind, &out_header->style); + if (res != TVG_SUCCESS) { + return res; + } + res = tvg_read_unit(ctx, &out_header->line_width); + if (res != TVG_SUCCESS) { + return res; + } + + return TVG_SUCCESS; +} + +static tvg_result_t tvg_parse_line_fill_header( + tvg_context_t *ctx, + int kind, + tvg_line_fill_header_t *out_header) +{ + uint8_t d; + if (1 > ctx->inp(&d, 1, ctx->inp_state)) { + return TVG_E_IO_ERROR; + } + tvg_result_t res = TVG_SUCCESS; + + size_t count = TVG_SIZE_AND_STYLE_SIZE(d); + out_header->size = count; + res = tvg_parse_style(ctx, kind, &out_header->fill_style); + if (res != TVG_SUCCESS) { + return res; + } + res = tvg_parse_style(ctx, TVG_SIZE_AND_STYLE_STYLE_KIND(d), + &out_header->line_style); + if (res != TVG_SUCCESS) { + return res; + } + res = tvg_read_unit(ctx, &out_header->line_width); + if (res != TVG_SUCCESS) { + return res; + } + + return TVG_SUCCESS; +} + +static tvg_result_t tvg_parse_path(tvg_context_t *ctx, size_t size) +{ + tvg_result_t res = TVG_SUCCESS; + tvg_point_t st, cur; + tvg_point_t pt; + float f32; + uint8_t d; + twin_path_t *path = ctx->path; + res = tvg_read_point(ctx, &pt); + if (res != TVG_SUCCESS) { + goto error; + } + twin_path_move(path, D(pt.x), D(pt.y)); + st = pt; + cur = pt; + for (size_t j = 0; j < size; ++j) { + if (1 > ctx->inp(&d, 1, ctx->inp_state)) { + goto error; + } + float line_width = 0.0f; + if (TVG_PATH_CMD_HAS_LINE(d)) { + res = tvg_read_unit(ctx, &line_width); + if (res != TVG_SUCCESS) { + goto error; + } + } + switch (TVG_PATH_CMD_INDEX(d)) { + case TVG_PATH_LINE: + res = tvg_read_point(ctx, &pt); + if (res != TVG_SUCCESS) { + goto error; + } + twin_path_draw(path, D(pt.x), D(pt.y)); + cur = pt; + break; + case TVG_PATH_HLINE: + res = tvg_read_unit(ctx, &f32); + if (res != TVG_SUCCESS) { + goto error; + } + pt.x = f32; + pt.y = cur.y; + twin_path_draw(path, D(pt.x), D(pt.y)); + cur = pt; + break; + case TVG_PATH_VLINE: + res = tvg_read_unit(ctx, &f32); + if (res != TVG_SUCCESS) { + goto error; + } + pt.x = cur.x; + pt.y = (float) f32; + twin_path_draw(path, D(pt.x), D(pt.y)); + cur = pt; + break; + case TVG_PATH_CUBIC: { + tvg_point_t ctrl1, ctrl2, endp; + res = tvg_read_point(ctx, &ctrl1); + if (res != TVG_SUCCESS) { + goto error; + } + res = tvg_read_point(ctx, &ctrl2); + if (res != TVG_SUCCESS) { + goto error; + } + res = tvg_read_point(ctx, &endp); + if (res != TVG_SUCCESS) { + goto error; + } + twin_path_curve(path, D(ctrl1.x), D(ctrl1.y), D(ctrl2.x), + D(ctrl2.y), D(endp.x), D(endp.y)); + cur = endp; + } break; + case TVG_PATH_ARC_CIRCLE: { + uint8_t d; + if (1 > ctx->inp(&d, 1, ctx->inp_state)) { + res = TVG_E_IO_ERROR; + goto error; + } + float radius; + res = tvg_read_unit(ctx, &radius); + if (res != TVG_SUCCESS) { + goto error; + } + res = tvg_read_point(ctx, &pt); + if (res != TVG_SUCCESS) { + goto error; + } + twin_path_arc_circle(path, TVG_ARC_LARGE(d), TVG_ARC_SWEEP(d), + D(radius), D(cur.x), D(cur.y), D(pt.x), + D(pt.y)); + cur = pt; + } break; + case TVG_PATH_ARC_ELLIPSE: { + uint8_t d; + if (1 > ctx->inp(&d, 1, ctx->inp_state)) { + res = TVG_E_IO_ERROR; + goto error; + } + float radius_x, radius_y; + float rotation; + res = tvg_read_unit(ctx, &radius_x); + if (res != TVG_SUCCESS) { + goto error; + } + res = tvg_read_unit(ctx, &radius_y); + if (res != TVG_SUCCESS) { + goto error; + } + res = tvg_read_unit(ctx, &rotation); + if (res != TVG_SUCCESS) { + goto error; + } + res = tvg_read_point(ctx, &pt); + if (res != TVG_SUCCESS) { + goto error; + } + twin_path_arc_ellipse(path, TVG_ARC_LARGE(d), TVG_ARC_SWEEP(d), + D(radius_x), D(radius_y), D(cur.x), D(cur.y), + D(pt.x), D(pt.y), + rotation * TWIN_ANGLE_360 / 360); + cur = pt; + } break; + case TVG_PATH_CLOSE: + twin_path_draw(path, D(st.x), D(st.y)); + cur = st; + break; + case TVG_PATH_QUAD: { + tvg_point_t ctrl, endp; + res = tvg_read_point(ctx, &ctrl); + if (res != TVG_SUCCESS) { + goto error; + } + res = tvg_read_point(ctx, &endp); + if (res != TVG_SUCCESS) { + goto error; + } + twin_path_quadratic_curve(path, D(ctrl.x), D(ctrl.y), D(endp.x), + D(endp.y)); + cur = endp; + } break; + default: + res = TVG_E_INVALID_FORMAT; + goto error; + } + } +error: + return res; +} + +static tvg_result_t tvg_parse_rect(tvg_context_t *ctx, tvg_rect_t *out_rect) +{ + tvg_point_t pt; + tvg_result_t res = tvg_read_point(ctx, &pt); + if (res != TVG_SUCCESS) + return res; + float w, h; + res = tvg_read_unit(ctx, &w); + if (res != TVG_SUCCESS) + return res; + res = tvg_read_unit(ctx, &h); + if (res != TVG_SUCCESS) + return res; + out_rect->x = pt.x; + out_rect->y = pt.y; + out_rect->width = w; + out_rect->height = h; + return TVG_SUCCESS; +} + +static void _stroke_path_with_style(tvg_context_t *ctx, + const tvg_style_t *fill_style, + twin_fixed_t pen_width) +{ + switch (fill_style->kind) { + case TVG_STYLE_FLAT: + twin_paint_stroke(ctx->pixmap, GET_COLOR(ctx, fill_style->flat), + ctx->path, pen_width); + break; + case TVG_STYLE_LINEAR: + /* TODO: Implement linear gradient color */ + twin_paint_stroke(ctx->pixmap, + GET_COLOR(ctx, fill_style->linear.color0), ctx->path, + pen_width); + break; + case TVG_STYLE_RADIAL: + /* TODO: Implement radial gradient color */ + twin_paint_stroke(ctx->pixmap, + GET_COLOR(ctx, fill_style->radial.color0), ctx->path, + pen_width); + break; + } +} + +static void _fill_path_with_style(tvg_context_t *ctx, + const tvg_style_t *fill_style) +{ + switch (fill_style->kind) { + case TVG_STYLE_FLAT: + twin_paint_path(ctx->pixmap, GET_COLOR(ctx, fill_style->flat), + ctx->path); + break; + case TVG_STYLE_LINEAR: + /* TODO: Implement linear gradient color */ + twin_paint_path(ctx->pixmap, GET_COLOR(ctx, fill_style->linear.color0), + ctx->path); + log_info("LIEAR"); + break; + case TVG_STYLE_RADIAL: + /* TODO: Implement radial gradient color */ + twin_paint_path(ctx->pixmap, GET_COLOR(ctx, fill_style->radial.color0), + ctx->path); + log_info("RADIAL"); + break; + } +} + +static tvg_result_t tvg_parse_fill_rectangles(tvg_context_t *ctx, + size_t size, + const tvg_style_t *fill_style) +{ + size_t count = size; + tvg_result_t res; + tvg_rect_t r; + twin_path_t *path = ctx->path; + while (count--) { + res = tvg_parse_rect(ctx, &r); + if (res != TVG_SUCCESS) + return res; + twin_path_rectangle(path, D(r.x), D(r.y), D(r.width), D(r.height)); + _fill_path_with_style(ctx, fill_style); + twin_path_empty(path); + } + return TVG_SUCCESS; +} + +static tvg_result_t tvg_parse_line_fill_rectangles( + tvg_context_t *ctx, + size_t size, + const tvg_style_t *fill_style, + const tvg_style_t *line_style, + float line_width) +{ + size_t count = size; + tvg_result_t res; + tvg_rect_t r; + if (line_width == 0) { + line_width = .01; + } + twin_path_t *path = ctx->path; + while (count--) { + res = tvg_parse_rect(ctx, &r); + if (res != TVG_SUCCESS) + return res; + + twin_path_rectangle(path, D(r.x), D(r.y), D(r.width), D(r.height)); + _fill_path_with_style(ctx, fill_style); + _stroke_path_with_style(ctx, line_style, D(line_width)); + twin_path_empty(path); + } + return TVG_SUCCESS; +} + +static tvg_result_t tvg_parse_fill_paths(tvg_context_t *ctx, + size_t size, + const tvg_style_t *style) +{ + tvg_result_t res = TVG_SUCCESS; + uint32_t *sizes = (uint32_t *) malloc(size * sizeof(uint32_t)); + if (sizes == NULL) { + return TVG_E_OUT_OF_MEMORY; + } + for (size_t i = 0; i < size; ++i) { + res = tvg_read_varuint(ctx, &sizes[i]); + ++sizes[i]; + if (res != TVG_SUCCESS) { + goto error; + } + } + twin_path_t *path = ctx->path; + if (!path) { + res = TVG_E_OUT_OF_MEMORY; + goto error; + } + /* parse path */ + for (size_t i = 0; i < size; ++i) { + res = tvg_parse_path(ctx, sizes[i]); + if (res != TVG_SUCCESS) { + goto error; + } + } + _fill_path_with_style(ctx, style); + twin_path_empty(path); +error: + free(sizes); + return res; +} + +static tvg_result_t tvg_parse_line_paths(tvg_context_t *ctx, + size_t size, + const tvg_style_t *line_style, + float line_width) +{ + tvg_result_t res = TVG_SUCCESS; + uint32_t *sizes = (uint32_t *) malloc(size * sizeof(uint32_t)); + if (sizes == NULL) { + return TVG_E_OUT_OF_MEMORY; + } + twin_path_t *path = ctx->path; + for (size_t i = 0; i < size; ++i) { + res = tvg_read_varuint(ctx, &sizes[i]); + ++sizes[i]; + if (res != TVG_SUCCESS) { + goto error; + } + } + /* parse path */ + for (size_t i = 0; i < size; ++i) { + res = tvg_parse_path(ctx, sizes[i]); + if (res != TVG_SUCCESS) { + goto error; + } + } + _stroke_path_with_style(ctx, line_style, D(line_width)); + twin_path_empty(path); +error: + free(sizes); + return res; +} + +static tvg_result_t tvg_parse_line_fill_paths(tvg_context_t *ctx, + size_t size, + const tvg_style_t *fill_style, + const tvg_style_t *line_style, + float line_width) +{ + tvg_result_t res = TVG_SUCCESS; + uint32_t *sizes = (uint32_t *) malloc(size * sizeof(uint32_t)); + if (sizes == NULL) { + return TVG_E_OUT_OF_MEMORY; + } + for (size_t i = 0; i < size; ++i) { + res = tvg_read_varuint(ctx, &sizes[i]); + ++sizes[i]; + if (res != TVG_SUCCESS) { + free(sizes); + return res; + } + } + twin_path_t *path = ctx->path; + + /* parse path */ + for (size_t i = 0; i < size; ++i) { + res = tvg_parse_path(ctx, sizes[i]); + if (res != TVG_SUCCESS) { + goto error; + } + } + if (line_width == 0) { + line_width = .1; + } + _fill_path_with_style(ctx, fill_style); + _stroke_path_with_style(ctx, line_style, D(line_width)); + twin_path_empty(path); +error: + free(sizes); + return res; +} + +static tvg_result_t tvg_parse_fill_polygon(tvg_context_t *ctx, + size_t size, + const tvg_style_t *fill_style) +{ + size_t count = size; + tvg_point_t pt; + tvg_result_t res = tvg_read_point(ctx, &pt); + if (res != TVG_SUCCESS) + return res; + twin_path_t *path = twin_path_create(); + twin_path_move(path, D(pt.x), D(pt.y)); + while (--count) { + res = tvg_read_point(ctx, &pt); + if (res != TVG_SUCCESS) + return res; + twin_path_draw(path, D(pt.x), D(pt.y)); + } + twin_path_close(path); + _fill_path_with_style(ctx, fill_style); + twin_path_empty(path); + return TVG_SUCCESS; +} + +static tvg_result_t tvg_parse_polyline(tvg_context_t *ctx, + size_t size, + const tvg_style_t *line_style, + float line_width, + bool close) +{ + tvg_point_t pt; + tvg_result_t res = tvg_read_point(ctx, &pt); + if (res != TVG_SUCCESS) { + return res; + } + twin_path_t *path = ctx->path; + twin_path_move(path, D(pt.x), D(pt.y)); + for (size_t i = 1; i < size; ++i) { + res = tvg_read_point(ctx, &pt); + if (res != TVG_SUCCESS) { + return res; + } + twin_path_draw(path, D(pt.x), D(pt.y)); + } + if (close) { + twin_path_close(path); + } + if (line_width == 0) { + line_width = .01; + } + _stroke_path_with_style(ctx, line_style, D(line_width)); + twin_path_empty(path); + return TVG_SUCCESS; +} + +static tvg_result_t tvg_parse_line_fill_polyline(tvg_context_t *ctx, + size_t size, + const tvg_style_t *fill_style, + const tvg_style_t *line_style, + float line_width, + bool close) +{ + tvg_point_t pt; + tvg_result_t res = tvg_read_point(ctx, &pt); + twin_path_t *path = ctx->path; + twin_path_move(path, D(pt.x), D(pt.y)); + for (size_t i = 1; i < size; ++i) { + res = tvg_read_point(ctx, &pt); + if (res != TVG_SUCCESS) { + return res; + } + } + if (close) { + twin_path_close(path); + } + _fill_path_with_style(ctx, fill_style); + if (line_width == 0) { + line_width = .01; + } + _stroke_path_with_style(ctx, line_style, D(line_width)); + twin_path_empty(path); + return res; +} + +static tvg_result_t tvg_parse_lines(tvg_context_t *ctx, + size_t size, + const tvg_style_t *line_style, + float line_width) +{ + tvg_point_t pt; + tvg_result_t res; + twin_path_t *path = ctx->path; + for (size_t i = 0; i < size; ++i) { + res = tvg_read_point(ctx, &pt); + if (res != TVG_SUCCESS) { + return res; + } + twin_path_move(path, D(pt.x), D(pt.y)); + res = tvg_read_point(ctx, &pt); + if (res != TVG_SUCCESS) { + return res; + } + twin_path_draw(path, D(pt.x), D(pt.y)); + } + if (line_width == 0) { + line_width = .01; + } + _stroke_path_with_style(ctx, line_style, D(line_width)); + twin_path_empty(path); + return TVG_SUCCESS; +} + +static tvg_result_t tvg_parse_commands(tvg_context_t *ctx) +{ + tvg_result_t res = TVG_SUCCESS; + uint8_t cmd = 255; + while (cmd != 0) { + if (1 > ctx->inp(&cmd, 1, ctx->inp_state)) { + return TVG_E_IO_ERROR; + } + switch (TVG_CMD_INDEX(cmd)) { + case TVG_CMD_END_DOCUMENT: + break; + case TVG_CMD_FILL_POLYGON: { + tvg_fill_header_t data; + res = tvg_parse_fill_header(ctx, TVG_CMD_STYLE_KIND(cmd), &data); + if (res != TVG_SUCCESS) { + return res; + } + res = tvg_parse_fill_polygon(ctx, data.size, &data.style); + if (res != TVG_SUCCESS) { + return res; + } + + } break; + case TVG_CMD_FILL_RECTANGLES: { + tvg_fill_header_t data; + res = tvg_parse_fill_header(ctx, TVG_CMD_STYLE_KIND(cmd), &data); + if (res != TVG_SUCCESS) { + return res; + } + res = tvg_parse_fill_rectangles(ctx, data.size, &data.style); + if (res != TVG_SUCCESS) { + return res; + } + + } break; + case TVG_CMD_FILL_PATH: { + tvg_fill_header_t data; + res = tvg_parse_fill_header(ctx, TVG_CMD_STYLE_KIND(cmd), &data); + if (res != TVG_SUCCESS) { + return res; + } + res = tvg_parse_fill_paths(ctx, data.size, &data.style); + if (res != TVG_SUCCESS) { + return res; + } + + } break; + case TVG_CMD_DRAW_LINES: { + tvg_line_header_t data; + res = tvg_parse_line_header(ctx, TVG_CMD_STYLE_KIND(cmd), &data); + if (res != TVG_SUCCESS) { + return res; + } + res = tvg_parse_lines(ctx, data.size, &data.style, data.line_width); + if (res != TVG_SUCCESS) { + return res; + } + + } break; + case TVG_CMD_DRAW_LINE_LOOP: { + tvg_line_header_t data; + res = tvg_parse_line_header(ctx, TVG_CMD_STYLE_KIND(cmd), &data); + if (res != TVG_SUCCESS) { + return res; + } + res = tvg_parse_polyline(ctx, data.size, &data.style, + data.line_width, true); + if (res != TVG_SUCCESS) { + return res; + } + + } break; + case TVG_CMD_DRAW_LINE_STRIP: { + tvg_line_header_t data; + res = tvg_parse_line_header(ctx, TVG_CMD_STYLE_KIND(cmd), &data); + if (res != TVG_SUCCESS) { + return res; + } + res = tvg_parse_polyline(ctx, data.size, &data.style, + data.line_width, false); + if (res != TVG_SUCCESS) { + return res; + } + + } break; + case TVG_CMD_DRAW_LINE_PATH: { + tvg_line_header_t data; + res = tvg_parse_line_header(ctx, TVG_CMD_STYLE_KIND(cmd), &data); + if (res != TVG_SUCCESS) { + return res; + } + res = tvg_parse_line_paths(ctx, data.size, &data.style, + data.line_width); + if (res != TVG_SUCCESS) { + return res; + } + + } break; + case TVG_CMD_OUTLINE_FILL_POLYGON: { + tvg_line_fill_header_t data; + res = + tvg_parse_line_fill_header(ctx, TVG_CMD_STYLE_KIND(cmd), &data); + if (res != TVG_SUCCESS) { + return res; + } + res = tvg_parse_line_fill_polyline(ctx, data.size, &data.fill_style, + &data.line_style, + data.line_width, true); + if (res != TVG_SUCCESS) { + return res; + } + + } break; + case TVG_CMD_OUTLINE_FILL_RECTANGLES: { + tvg_line_fill_header_t data; + res = + tvg_parse_line_fill_header(ctx, TVG_CMD_STYLE_KIND(cmd), &data); + if (res != TVG_SUCCESS) { + return res; + } + res = tvg_parse_line_fill_rectangles( + ctx, data.size, &data.fill_style, &data.line_style, + data.line_width); + if (res != TVG_SUCCESS) { + return res; + } + } break; + case TVG_CMD_OUTLINE_FILL_PATH: { + tvg_line_fill_header_t data; + res = + tvg_parse_line_fill_header(ctx, TVG_CMD_STYLE_KIND(cmd), &data); + if (res != TVG_SUCCESS) { + return res; + } + res = tvg_parse_line_fill_paths(ctx, data.size, &data.fill_style, + &data.line_style, data.line_width); + if (res != TVG_SUCCESS) { + return res; + } + + } break; + default: + return TVG_E_INVALID_FORMAT; + } + } + return TVG_SUCCESS; +} + +extern tvg_result_t tvg_document_dimensions(tvg_input_func_t inp, + void *inp_state, + uint32_t *out_width, + uint32_t *out_height) +{ + /* initialize the context */ + tvg_context_t ctx; + if (inp == NULL) { + return TVG_E_INVALID_ARG; + } + ctx.inp = inp; + ctx.inp_state = inp_state; + ctx.colors = NULL; + ctx.colors_size = 0; + /* parse the header, early outing before the color table */ + tvg_result_t res = tvg_parse_header(&ctx, 1); + if (res != TVG_SUCCESS) { + return res; + } + *out_width = ctx.width; + *out_height = ctx.height; + return res; +} + +extern tvg_result_t tvg_render_document(tvg_input_func_t inp, + void *inp_state, + twin_pixmap_t *pix) +{ + /* initialize the context */ + tvg_context_t ctx; + if (inp == NULL) { + return TVG_E_INVALID_ARG; + } + ctx.inp = inp; + ctx.inp_state = inp_state; + ctx.pixmap = pix; + ctx.colors = NULL; + ctx.colors_size = 0; + /* parse the header */ + tvg_result_t res = tvg_parse_header(&ctx, 0); + if (res != TVG_SUCCESS) { + goto error; + } + ctx.path = twin_path_create(); + if (!ctx.path) { + goto error; + } + res = tvg_parse_commands(&ctx); + if (res != TVG_SUCCESS) { + goto error; + } +error: + if (ctx.colors != NULL) { + free(ctx.colors); + ctx.colors = NULL; + ctx.colors_size = 0; + } + return res; +} + +static size_t inp_func(uint8_t *data, size_t to_read, void *state) +{ + FILE *f = (FILE *) state; + return fread(data, 1, to_read, f); +} + +twin_pixmap_t *_twin_tvg_to_pixmap(const char *filepath, twin_format_t fmt) +{ + FILE *infile = NULL; + twin_pixmap_t *pix = NULL; + uint32_t width, height; + tvg_result_t res; + + /* Current implementation only produces TWIN_ARGB32 */ + if (fmt != TWIN_ARGB32) { + goto bail; + } + + if (!filepath) { + log_error("Invalid filepath"); + goto bail; + } + + infile = fopen(filepath, "rb"); + if (!infile) { + log_error("Failed to open %s", filepath); + goto bail; + } + + res = tvg_document_dimensions(inp_func, infile, &width, &height); + if (res != TVG_SUCCESS) { + log_error("Failed to get document dimensions"); + goto bail_infile; + } + + if (fseek(infile, 0, SEEK_SET) != 0) { + log_error("Failed to seek file"); + goto bail_infile; + } + + pix = twin_pixmap_create(fmt, width, height); + if (!pix) { + log_error("Failed to create pixmap"); + goto bail_infile; + } + + res = tvg_render_document(inp_func, infile, pix); + if (res != TVG_SUCCESS) { + log_error("Failed to render document"); + goto bail_pixmap; + } + + fclose(infile); + return pix; + +bail_pixmap: + twin_pixmap_destroy(pix); +bail_infile: + fclose(infile); +bail: + return NULL; +} diff --git a/src/image.c b/src/image.c index f91c194..c2c5b5a 100644 --- a/src/image.c +++ b/src/image.c @@ -21,6 +21,10 @@ #define CONFIG_LOADER_GIF 0 #endif +#if !defined(CONFIG_LOADER_TVG) +#define CONFIG_LOADER_TVG 0 +#endif + /* Feature test macro */ #define LOADER_HAS(x) CONFIG_LOADER_##x @@ -34,6 +38,9 @@ ) \ IIF(LOADER_HAS(GIF))( \ _(gif) \ + ) \ + IIF(LOADER_HAS(TVG))( \ + _(tvg) \ ) /* clang-format on */ @@ -54,12 +61,15 @@ typedef enum { * https://www.file-recovery.com/jpg-signature-format.htm * - GIF: * https://www.file-recovery.com/gif-signature-format.htm + * - TVG: + * https://tinyvg.tech/download/specification.pdf */ static const uint8_t header_png[8] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, }; static const uint8_t header_jpeg[3] = {0xFF, 0xD8, 0xFF}; static const uint8_t header_gif[4] = {0x47, 0x49, 0x46, 0x38}; +static const uint8_t header_tvg[2] = {0x72, 0x56}; static twin_image_format_t image_type_detect(const char *path) { @@ -91,6 +101,11 @@ static twin_image_format_t image_type_detect(const char *path) type = IMAGE_TYPE_gif; } #endif +#if LOADER_HAS(TVG) + else if (!memcmp(header, header_tvg, sizeof(header_tvg))) { + type = IMAGE_TYPE_tvg; + } +#endif /* otherwise, unsupported format */ return type; diff --git a/src/path.c b/src/path.c index 6bc7a7e..c721f3a 100644 --- a/src/path.c +++ b/src/path.c @@ -358,6 +358,7 @@ void twin_path_arc_ellipse(twin_path_t *path, twin_angle_t rotation) { twin_ellipse_param_t param; + twin_path_draw(path, cur_x, cur_y); param = get_center_parameters(cur_x, cur_y, target_x, target_y, large_arc, sweep, radius_x, radius_y, rotation); twin_matrix_t save = twin_path_current_matrix(path); @@ -369,6 +370,7 @@ void twin_path_arc_ellipse(twin_path_t *path, param.extent); twin_path_set_matrix(path, save); + twin_path_draw(path, target_x, target_y); } void twin_path_arc_circle(twin_path_t *path,