C 语言 TinyVG 入门





5.00/5 (1投票)
TinyVG 是一种紧凑的矢量图形文件格式,适用于嵌入式系统。我们在此进行探讨。
引言
我建议您从这个链接开始。您会找到一个网站,其中包含 TinyVG 的概述,这是一种用于表示矢量图形的紧凑二进制格式。
TinyVG 旨在消除处理 SVG 等矢量图形格式的冗余和痛苦。它比 SVG 更容易解析,与我用于嵌入式系统的基本 SVG 解析器相比,所需的代码量大约只有三分之一。
我必须明确指出,TinyVG 并非我创建。它由 Felix Queißner(GitHub 上的 @ikskuh - TinyVG Discord 服务器上的 xq)创建。不过,我是在他的一些指导下创建了此实现。
我创建此实现是因为我认为它会帮助 TinyVG 惠及更多的人,而不仅仅是 Zig 参考实现所覆盖的范围,我在此将其展示出来供您在自己的软件中使用。
我选择简单的 C 语言进行实现,因为它本质上是一个参考实现,而且只要原始代码不过于复杂,C 语言是一个很好的可移植语言。即使您不是 C 专家,它也应该相当易读。
您应该能够使用 MSVC、GCC 或(可能)Clang 在任何 32 位 PC 或更高版本上构建和执行此代码。您可以通过下载它并使用 VS Code 和 CMake 工具扩展打开它来运行它。
为了避免您需要修改 CMake 设置来更改命令行参数,我简单地在 main()
中硬编码了输入、输出和缩放因子。
背景
TinyVG 的 TVG 格式是一种二进制格式。它进行了大量的位操作,以尽可能少的空间 packed 尽可能多的信息,这使得初次阅读时可能会有些棘手。幸运的是,该格式总体上很简单,因此这并不是一个大问题。
我们将使用PlutoVG来渲染输出。PlutoVG 似乎是为渲染 SVG 而设计的,因为其 API 参数与 SVG 类似,因此从 SVG 到 PlutoVG 的映射非常直接。它还具有相对简单的 API,所以我认为它是渲染输出的良好选择。正如我所做的,您可以通过查看此代码来大致了解 SVG 和 TVG 格式之间的关系。
实现这个混乱
在这里,我们将探讨 main.c,其中包含所有相关位。从顶部开始
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <math.h>
#include <stdio.h>
#include <plutovg.h> // renderer
除了我们的渲染器后端 plutovg.h 之外,大多数都是相当标准的。
接下来,我们有几个匿名枚举。
第一个是命令集,您可以在规范的第 6 页开始找到。
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
};
现在我们有单位大小。单位是定点实数。这是 TVG 格式不惜一切代价节省空间的领域之一。基本上,它使用尽可能小的数值来表示坐标空间中的值。下面的枚举表示总宽度的
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 {
// A classic 4-tuple with 8 bit unsigned channels.
// Encodes red, green, blue and alpha. If not specified
// otherwise (via external means) the color channels encode
// sRGB color data and the alpha stores linear transparency.
TVG_COLOR_U8888 = 0,
// A 16 bit color format with 5 bit for red and blue, and
// 6 bit color depth for green channel.
// This format is typically used in embedded devices or cheaper
// displays. If not specified otherwise (via external means) the
// color channels encode sRGB color data.
TVG_COLOR_U565,
// A format with 16 byte per color and 4 channels. Each channel
// is encoded as a `binary32` IEEE 754 value.
// The first three channels encode color data, the fourth
// channel encodes linear alpha.
// If not specified otherwise (via external means) the color
// channels encode sRGB color data and the alpha stores linear
// transparency.
TVG_COLOR_F32,
// This format is specified by external means and is meant to
// signal that these files are *valid*, but it's not possible
// to decode them without external knowledge about the color
// encoding. This is meant for special cases where huge savings
// might be possible by not encoding any color information in
// the files itself or special device dependent color formats
// are required.
//
// Possible uses cases are:
//
// - External fixed or shared color palettes
// - CMYK format for printing
// - High precision 16 bit color formats
// - Using non-sRGB color spaces
// - Using RAL numbers for painting
// - ...
//
// **NOTE:** A conforming parser is allowed to reject any file with a
// custom color encoding, as these are meant to be parsed with a
// specific use case.
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,
};
路径命令。您应该注意到,这些命令中的每一个在 SVG <path>
元素上的 d
属性中都有一个对应的命令。
// 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
};
现在我们有了宏!我们主要用它们来隐藏丑陋的位移操作并保存一些急需的常量值
#define TVG_PI (3.1415926536f)
// 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;
现在我们有了渐变结构。TVG 中的渐变可以是径向或线性的,但除此之外,它们非常简单。
// gradient data
typedef struct {
tvg_point_t point0;
tvg_point_t point1;
uint32_t color0;
uint32_t color1;
} tvg_gradient_t;
它由两个点和两种颜色组成。在径向数据的情况下,第二个点没有完全利用——它只是用来计算与 point0
的距离以获得半径。我们使用它时会进一步讨论。
接下来是样式数据。它由一个表示纯色、线性渐变或径向渐变的类型字段,以及一个包含每个类型的联合组成。
// style data
typedef struct {
uint8_t kind;
union {
uint32_t flat; // color index
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;
第一个是对象没有“SVG 描边”,只有“SVG 填充”的情况。第二个是只有描边的情况,最后一个是描边和填充都有的情况。我在这里使用 SVG 类比,以便您有一个参考框架。
现在我们有了输入函数。它提供了一个对任意数据源(例如文件或字节数组)的游标。其中的实现为 FILE
对象提供了一个实现。
// 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 canvas
plutovg_canvas_t* canvas;
// 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)
tvg_rgba_t* colors;
} tvg_context_t;
下一个 typedef
仅定义了我们面向用户的 API 函数的返回类型。它要么是 0 (TVG_SUCCESS
),要么是 TVG_E_
错误代码之一。
// the result type. TVG_SUCCESS = OK
// anything else, is a TVG_E_xxxx error
// code
typedef int tvg_result_t;
下一个函数解决了 TVG 处理零值的方式的特殊性。由于在某些情况下(例如文档宽度和高度)零值没有意义,因此任何零值都会被映射到最大可能值。
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;
}
}
下一个函数从文件中读取颜色,将其从文件中存储的格式转换为 tvg_rgba_t
运行时表示形式。
static tvg_result_t tvg_read_color(tvg_context_t* ctx, tvg_rgba_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;
}
out_color->r = data.r;
out_color->g = data.g;
out_color->b = data.b;
out_color->a = data.a;
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;
}
out_color->r = ((float)TVG_RGB16_R(data)) / 15.0f;
out_color->g = ((float)TVG_RGB16_G(data)) / 31.0f;
out_color->b = ((float)TVG_RGB16_B(data)) / 15.0f;
out_color->a = 1.0f;
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->r = ((float)data.r) / 255.0f;
out_color->g = ((float)data.g) / 255.0f;
out_color->b = ((float)data.b) / 255.0f;
out_color->a = ((float)data.a) / 255.0f;
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;
}
下一个函数处理 TVG 打包整数的方式,使得一个 uint32_t
可以**大部分**使用不同数量(最多 4 个)的字节来表示。规范中包含详细信息,包括一个实现示例。
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 = (tvg_rgba_t*)malloc(color_count * sizeof(tvg_rgba_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 float tvg_distance(const tvg_point_t* lhs, const tvg_point_t* rhs) {
float xd = rhs->x - lhs->x;
float yd = rhs->y - lhs->y;
return sqrtf((xd * xd) + (yd * yd));
}
以下代码简单地将 TVG 颜色表示转换为适合 PlutoVG 的颜色表示。
static plutovg_color_t tvg_color_to_plutovg(const tvg_rgba_t* col) {
plutovg_color_t result;
plutovg_color_init_rgba(&result, col->r, col->g, col->b, col->a);
return result;
}
下一个函数接受样式数据并将其插入 PlutoVG。PlutoVG 分两次渲染描边和填充,因此应用哪种样式取决于我们是在进行描边还是填充。
static tvg_result_t tvg_apply_style(tvg_context_t* ctx, const tvg_style_t* style) {
plutovg_color_t col;
float r;
plutovg_gradient_stop_t stops[2];
switch (style->kind) {
case TVG_STYLE_FLAT:
col = tvg_color_to_plutovg(&ctx->colors[style->flat]);
plutovg_canvas_set_color(ctx->canvas, &col);
break;
case TVG_STYLE_LINEAR:
col = tvg_color_to_plutovg(&ctx->colors[style->linear.color0]);
stops[0].color = col;
stops[0].offset = 0;
col = tvg_color_to_plutovg(&ctx->colors[style->linear.color1]);
stops[1].color = col;
stops[1].offset = 1;
plutovg_canvas_set_linear_gradient(
ctx->canvas, style->linear.point0.x, style->linear.point0.y,
style->linear.point1.x, style->linear.point1.y,
PLUTOVG_SPREAD_METHOD_PAD, stops, 2, NULL);
break;
case TVG_STYLE_RADIAL:
col = tvg_color_to_plutovg(&ctx->colors[style->radial.color0]);
stops[0].color = col;
stops[0].offset = 0;
col = tvg_color_to_plutovg(&ctx->colors[style->radial.color1]);
stops[1].color = col;
stops[1].offset = 1;
r = tvg_distance(&style->radial.point0, &style->radial.point1);
plutovg_canvas_set_radial_gradient(
ctx->canvas, style->radial.point0.x, style->radial.point0.y, r,
style->radial.point1.x, style->radial.point1.y, r,
PLUTOVG_SPREAD_METHOD_REFLECT, stops, 2, NULL);
break;
default:
return TVG_E_INVALID_FORMAT;
}
return TVG_SUCCESS;
}
请注意,渐变始终只有 0% 和 100% 处的两个停止点,这使得它比 SVG 更具限制性。
接下来的三个函数解析与命令关联的三种不同类型头部的头部信息。第一个是仅填充,第二个是仅描边,最后一个是填充和描边——同样,我在这里使用 SVG 术语。
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) {
uint32_t u32;
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;
}
下一个函数从文件中解析路径命令。请记住,这与 SVG 中 <path>
属性上的 d
属性非常相似。TVG 中的所有路径命令几乎或完全直接映射到 SVG 等效项。两个细微差别是:初始的“移动到”命令不被视为命令,而只是由一个指示路径起始位置的初始点表示;另一个区别是“圆弧到”上的扫描标志与 SVG 相反。
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;
uint32_t u32;
float f32;
uint8_t d;
res = tvg_read_point(ctx, &pt);
if (res != TVG_SUCCESS) {
goto error;
}
plutovg_canvas_move_to(ctx->canvas, pt.x, 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;
}
plutovg_canvas_line_to(ctx->canvas, pt.x, 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;
plutovg_canvas_line_to(ctx->canvas, pt.x, 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;
plutovg_canvas_line_to(ctx->canvas, pt.x, 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;
}
plutovg_canvas_cubic_to(ctx->canvas, ctrl1.x, ctrl1.y, ctrl2.x,
ctrl2.y, endp.x, 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;
}
plutovg_canvas_arc_to(ctx->canvas, radius, radius, 0,
TVG_ARC_LARGE(d), 1 - TVG_ARC_SWEEP(d),
pt.x, 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;
}
plutovg_canvas_arc_to(
ctx->canvas, radius_x, radius_y, rotation * (TVG_PI / 180.0f),
TVG_ARC_LARGE(d), 1 - TVG_ARC_SWEEP(d), pt.x, pt.y);
cur = pt;
} break;
case TVG_PATH_CLOSE:
plutovg_canvas_close_path(ctx->canvas);
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;
}
plutovg_canvas_quad_to(ctx->canvas, ctrl.x, ctrl.y, endp.x,
endp.y);
cur = endp;
} break;
default:
res = TVG_E_INVALID_FORMAT;
goto error;
}
}
error:
return res;
}
下一个函数从文件中解析一个 tvg_rect_t
矩形。
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 tvg_result_t tvg_parse_fill_rectangles(tvg_context_t* ctx, size_t size,
const tvg_style_t* fill_style) {
size_t count = size;
size_t szb = count * sizeof(tvg_rect_t);
tvg_result_t res;
tvg_rect_t r;
plutovg_canvas_set_fill_rule(ctx->canvas, PLUTOVG_FILL_RULE_EVEN_ODD);
plutovg_canvas_set_opacity(ctx->canvas, 1.0);
res = tvg_apply_style(ctx, fill_style);
if (res != TVG_SUCCESS) return res;
while (count--) {
res = tvg_parse_rect(ctx,&r);
if (res != TVG_SUCCESS) return res;
plutovg_canvas_rect(ctx->canvas, r.x, r.y, r.width, r.height);
plutovg_canvas_fill(ctx->canvas);
}
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;
size_t szb = count * sizeof(tvg_rect_t);
tvg_result_t res;
tvg_rect_t r;
if (line_width == 0) { // 0 width is invalid
line_width = .001;
}
plutovg_canvas_set_fill_rule(ctx->canvas, PLUTOVG_FILL_RULE_EVEN_ODD);
plutovg_canvas_set_opacity(ctx->canvas, 1.0);
while (count--) {
res = tvg_parse_rect(ctx,&r);
if (res != TVG_SUCCESS) return res;
res = tvg_apply_style(ctx, fill_style);
if (res != TVG_SUCCESS) return res;
plutovg_canvas_rect(ctx->canvas, r.x, r.y, r.width, r.height);
plutovg_canvas_fill_preserve(ctx->canvas);
plutovg_canvas_set_line_width(ctx->canvas, line_width);
res = tvg_apply_style(ctx, line_style);
if (res != TVG_SUCCESS) return res;
plutovg_canvas_stroke(ctx->canvas);
}
return TVG_SUCCESS;
}
接下来的三个函数处理基于路径的命令。
对于路径,需要预先从文件中读取一系列大小。由于 0 是无效的,每个大小都会递增 1。对于每个路径,我们从文件中解析路径并相应地绘制。
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;
size_t total = 0;
uint32_t* sizes = (uint32_t*)malloc(size * sizeof(uint32_t));
if (sizes == NULL) {
return TVG_E_OUT_OF_MEMORY;
}
plutovg_canvas_set_fill_rule(ctx->canvas, PLUTOVG_FILL_RULE_EVEN_ODD);
plutovg_canvas_set_opacity(ctx->canvas, 1.0);
plutovg_canvas_set_rgb(ctx->canvas, 0, 0, 0);
for (size_t i = 0; i < size; ++i) {
res = tvg_read_varuint(ctx, &sizes[i]);
++sizes[i];
if (res != TVG_SUCCESS) {
goto error;
}
total += sizes[i];
}
res = tvg_apply_style(ctx, style);
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;
}
}
plutovg_canvas_fill(ctx->canvas);
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;
size_t total = 0;
uint32_t* sizes = (uint32_t*)malloc(size * sizeof(uint32_t));
if (sizes == NULL) {
return TVG_E_OUT_OF_MEMORY;
}
plutovg_canvas_set_opacity(ctx->canvas, 1.0);
plutovg_canvas_set_rgb(ctx->canvas, 0, 0, 0);
for (size_t i = 0; i < size; ++i) {
res = tvg_read_varuint(ctx, &sizes[i]);
++sizes[i];
if (res != TVG_SUCCESS) {
goto error;
}
total += sizes[i];
}
res = tvg_apply_style(ctx, line_style);
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;
}
}
plutovg_canvas_stroke(ctx->canvas);
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;
size_t total = 0;
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;
}
total += sizes[i];
}
plutovg_canvas_set_fill_rule(ctx->canvas, PLUTOVG_FILL_RULE_EVEN_ODD);
plutovg_canvas_set_opacity(ctx->canvas, 1.0);
res = tvg_apply_style(ctx, fill_style);
// parse path
for (size_t i = 0; i < size; ++i) {
res = tvg_parse_path(ctx, sizes[i]);
if (res != TVG_SUCCESS) {
goto error;
}
}
plutovg_canvas_fill_preserve(ctx->canvas);
res = tvg_apply_style(ctx, line_style);
if (res != TVG_SUCCESS) {
goto error;
}
if (line_width == 0) { // 0 width is invalid
line_width = .001;
}
plutovg_canvas_set_line_width(ctx->canvas, line_width);
// render
plutovg_canvas_stroke(ctx->canvas);
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;
plutovg_canvas_set_fill_rule(ctx->canvas, PLUTOVG_FILL_RULE_EVEN_ODD);
plutovg_canvas_set_opacity(ctx->canvas, 1.0);
res = tvg_apply_style(ctx, fill_style);
if (res != TVG_SUCCESS) return res;
plutovg_canvas_move_to(ctx->canvas, pt.x, pt.y);
while (--count) {
res = tvg_read_point(ctx, &pt);
if (res != TVG_SUCCESS) return res;
plutovg_canvas_line_to(ctx->canvas, pt.x, pt.y);
}
tvg_apply_style(ctx, fill_style);
plutovg_canvas_fill(ctx->canvas);
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;
}
plutovg_canvas_move_to(ctx->canvas, pt.x, pt.y);
for (int i = 1; i < size; ++i) {
res = tvg_read_point(ctx, &pt);
if (res != TVG_SUCCESS) {
return res;
}
plutovg_canvas_line_to(ctx->canvas, pt.x, pt.y);
}
if (close) {
plutovg_canvas_close_path(ctx->canvas);
}
res = tvg_apply_style(ctx, line_style);
if (res != TVG_SUCCESS) {
return res;
}
if (line_width == 0) { // 0 width is invalid
line_width = .001;
}
plutovg_canvas_set_line_width(ctx->canvas, line_width);
// render
plutovg_canvas_stroke(ctx->canvas);
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) {
plutovg_canvas_set_fill_rule(ctx->canvas, PLUTOVG_FILL_RULE_EVEN_ODD);
plutovg_canvas_set_opacity(ctx->canvas, 1.0);
tvg_result_t res = tvg_apply_style(ctx, fill_style);
if (res != TVG_SUCCESS) {
return res;
}
tvg_point_t pt;
res = tvg_read_point(ctx, &pt);
plutovg_canvas_move_to(ctx->canvas, pt.x, pt.y);
for (int i = 1; i < size; ++i) {
res = tvg_read_point(ctx, &pt);
if (res != TVG_SUCCESS) {
return res;
}
plutovg_canvas_line_to(ctx->canvas, pt.x, pt.y);
}
if (close) {
plutovg_canvas_close_path(ctx->canvas);
}
plutovg_canvas_fill_preserve(ctx->canvas);
res = tvg_apply_style(ctx, line_style);
if (res != TVG_SUCCESS) {
return res;
}
if (line_width == 0) { // 0 width is invalid
line_width = .001;
}
plutovg_canvas_set_line_width(ctx->canvas, line_width);
// render
plutovg_canvas_stroke(ctx->canvas);
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;
for (int i = 0; i < size; ++i) {
res = tvg_read_point(ctx, &pt);
if (res != TVG_SUCCESS) {
return res;
}
plutovg_canvas_move_to(ctx->canvas, pt.x, pt.y);
res = tvg_read_point(ctx, &pt);
if (res != TVG_SUCCESS) {
return res;
}
plutovg_canvas_line_to(ctx->canvas, pt.x, pt.y);
}
res = tvg_apply_style(ctx, line_style);
if (res != TVG_SUCCESS) {
return res;
}
if (line_width == 0) { // 0 width is invalid
line_width = .001;
}
plutovg_canvas_set_line_width(ctx->canvas, line_width);
// render
plutovg_canvas_stroke(ctx->canvas);
return TVG_SUCCESS;
}
最后,解析的核心是解析命令。这些命令在头部之后立即从文件中读取,我们不读取到文件末尾,而是读取直到命令索引为零,这表示 TVG 的结束。
这是一个很长的函数,但它所做的只是读取命令数据,并根据找到的内容分派相应的函数。
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;
}
下一个函数是文件中的第一个外部 API 函数。它检索文件中头部记录的文档尺寸(以像素为单位)。
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;
}
将 1 作为第二个参数传递给 tvg_parse_header()
会使其在计算出宽度和高度后立即退出。
最后,主要 API 函数:tvg_render_document()
extern tvg_result_t tvg_render_document(tvg_input_func_t inp, void* inp_state,
plutovg_canvas_t* canvas,
const plutovg_rect_t* bounds) {
// initialize the context
tvg_context_t ctx;
if (inp == NULL) {
return TVG_E_INVALID_ARG;
}
ctx.inp = inp;
ctx.inp_state = inp_state;
ctx.canvas = canvas;
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;
}
// compute the final scaling
float scale_x = bounds->w / ctx.width;
float scale_y = bounds->h / ctx.height;
plutovg_matrix_t m;
// scale and translate to fit the document to the bounds
plutovg_matrix_init_scale(&m, scale_x, scale_y);
plutovg_matrix_translate(&m, bounds->x / scale_x, bounds->y / scale_y);
plutovg_canvas_set_matrix(ctx.canvas, &m);
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;
}
现在是最后的工作——输入函数(在此例中为 FILE
对象)和入口点 main()
。
// read from a FILE object
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);
}
int main(int argc, char* argv[]) {
// scaling factor:
float scale = 1.f;
// files
const char* input = "..\\..\\everything-32.tvg";
const char* output = "..\\..\\output.png";
FILE* inp_file = fopen(input, "rb");
// get the dimensions of the document:
uint32_t w, h;
tvg_result_t res = tvg_document_dimensions(inp_func, inp_file, &w, &h);
if (res != TVG_SUCCESS) {
fprintf(stderr, "Unable to parse '%s'\n", input);
return 1;
}
// start at the beginning again
fseek(inp_file, 0, SEEK_SET);
// create a plutovg surface of the final document size after scaling
plutovg_surface_t* surface =
plutovg_surface_create((int)(w * scale), (int)(h * scale));
// create the canvas
plutovg_canvas_t* canvas = plutovg_canvas_create(surface);
// create the final destination rectangle
plutovg_rect_t r = {0.f, 0.f, w * scale, h * scale};
res = tvg_render_document(inp_func, inp_file, canvas, &r);
if (res != TVG_SUCCESS) {
fprintf(stderr, "Unable to parse '%s' - error (%d)\n", input, (int)res);
}
if (!plutovg_surface_write_to_png(surface, output)) {
fprintf(stderr, "Unable to write '%s'\n", output);
goto cleanup;
} else {
fprintf(stderr, "Wrote '%s'\n", output);
}
cleanup:
fclose(inp_file);
plutovg_canvas_destroy(canvas);
plutovg_surface_destroy(surface);
return 0;
}
这设置为在构建目录树中运行,向上找到几个文件夹的文档。它还假设是 Windows,因为路径反斜杠,但这很容易更改。它可以很容易地修改为接受命令行参数。
历史
- 2024 年 10 月 10 日 - 初次提交