65.9K
CodeProject 正在变化。 阅读更多。
Home

C 语言 TinyVG 入门

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2024 年 10 月 11 日

公共领域

8分钟阅读

viewsIcon

2470

downloadIcon

166

TinyVG 是一种紧凑的矢量图形文件格式,适用于嵌入式系统。我们在此进行探讨。

Output

引言

我建议您从这个链接开始。您会找到一个网站,其中包含 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 日 - 初次提交
© . All rights reserved.