目录
引言
我的课程的裸机实验部分有使用NanoSVG进行SVG图片的渲染的作业。这个作业几年来一直没有问题,最近有个学生反映她的程序运行第二次的时候就不能正常显示图片,我的本能反应是她的程序有bug,不过结果分析才发现NanoSVG库存在解析文件后修改输入字符串的bug。
问题分析
裸机测试
为了方便查找原因,我在学生的代码中添加了一些打印输出,以检查程序的问题。
// 假设串口通道号为 2
const int SERIAL_CHANNEL = 2;
serial_printf(SERIAL_CHANNEL, "Entering main_svg_display function with scale %f\r\n", svg_scale);
struct surface_t *screen = s5pv210_screen_surface();
serial_printf(SERIAL_CHANNEL, "Got screen surface\r\n");
NSVGrasterizer *rast = nsvgCreateRasterizer();
serial_printf(SERIAL_CHANNEL, "Created SVG rasterizer\r\n");
NSVGimage *svg = nsvgParse((char *)logo_svg, "px", SVG_DPI);
serial_printf(SERIAL_CHANNEL, "Parsed SVG image\r\n");
if (!svg || !rast)
{
serial_printf(SERIAL_CHANNEL, "Error: Failed to parse SVG or create rasterizer. Exiting function.\r\n");
return;
}
int width = svg->width * svg_scale;
int height = svg->height * svg_scale;
serial_printf(SERIAL_CHANNEL, "Calculated scaled image size: svg->width = %f, svg->height = %f width = %d, height = %d\r\n",
svg->width, svg->height, width, height);
if (width < 0 || height < 0)
{
serial_printf(SERIAL_CHANNEL, "Error: Invalid scaled image size. Cleaning up and exiting.\r\n");
nsvgDelete(svg);
nsvgDeleteRasterizer(rast);
return;
}
运行后得到如下的结果:
由此可以看出,程序不能正确执行的原因是第二次解析之后SVG图片的宽度和高度都是0。这个结果令我感到意外,因为这个程序在内存分配和使用方面还是比较规范的,没有看到有内存覆盖的问题。
接下来,我就在解析语句前后加了打印,看看解析前后输入字符串是否发生了变化。
// 打印 logo_svg 前 20 字节内容
serial_printf(SERIAL_CHANNEL, "First 20 bytes of logo_svg: ");
int n;
for (n = 0; n < 20; n++) {
serial_printf(SERIAL_CHANNEL, "%c", ((unsigned char *)logo_svg)[n]);
}
serial_printf(SERIAL_CHANNEL, "\r\n");
NSVGimage *svg = nsvgParse((char *)logo_svg, "px", SVG_DPI);
serial_printf(SERIAL_CHANNEL, "Parsed SVG image\r\n");
if (!svg || !rast)
{
serial_printf(SERIAL_CHANNEL, "Error: Failed to parse SVG or create rasterizer. Exiting function.\r\n");
return;
}
// 打印 logo_svg 前 20 字节内容
serial_printf(SERIAL_CHANNEL, "First 20 bytes of logo_svg: ");
for (n = 0; n < 20; n++) {
serial_printf(SERIAL_CHANNEL, "%c", ((unsigned char *)logo_svg)[n]);
}
serial_printf(SERIAL_CHANNEL, "\r\n");
此次运行结果如下:
从这个运行结果看确实nsvgParse函数在解析后对输入字符串进行了修改,导致程序第二次运行时没有了正确的SVG文件,从而导致无法解析。
Linux测试
为了进一步验证这个问题,我在Linux下对官方提供的样例Example2进行了修改,发现也会存在这个问题,下面是修改的程序:
//
// Copyright (c) 2013 Mikko Mononen memon@inside.org
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would be
// appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.
//
#include <stdio.h>
#include <string.h>
#include <float.h>
#include <stdlib.h> // 为了使用 malloc 和 free
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"
#define NANOSVG_IMPLEMENTATION
#include "nanosvg.h"
#define NANOSVGRAST_IMPLEMENTATION
#include "nanosvgrast.h"
// 读取文件内容到字符串
char* readFileToString(const char* filename) {
FILE* file = fopen(filename, "r");
if (file == NULL) {
return NULL;
}
// 获取文件大小
fseek(file, 0, SEEK_END);
long size = ftell(file);
fseek(file, 0, SEEK_SET);
// 分配内存
char* buffer = (char*)malloc(size + 1);
if (buffer == NULL) {
fclose(file);
return NULL;
}
// 读取文件内容
fread(buffer, 1, size, file);
buffer[size] = '\0';
fclose(file);
return buffer;
}
int main()
{
NSVGimage *image = NULL;
NSVGrasterizer *rast = NULL;
unsigned char* img = NULL;
int w, h;
const char* filename = "../example/23.svg";
// 读取文件内容到字符串
char* svgContent = readFileToString(filename);
if (svgContent == NULL) {
printf("Could not read SVG file.\n");
return 1;
}
// 打印调用 nsvgParse 前的字符串前 50 个字符
size_t printLen = strlen(svgContent) > 50 ? 50 : strlen(svgContent);
printf("Before nsvgParse, first 50 characters of SVG content:\n%.50s\n", svgContent);
printf("parsing %s\n", filename);
image = nsvgParse(svgContent, "px", 96.0f);
if (image == NULL) {
printf("Could not parse SVG image.\n");
free(svgContent);
goto error;
}
// 打印调用 nsvgParse 后的字符串前 50 个字符
printLen = strlen(svgContent) > 50 ? 50 : strlen(svgContent);
printf("After nsvgParse, first 50 characters of SVG content:\n%.50s\n", svgContent);
w = (int)image->width;
h = (int)image->height;
rast = nsvgCreateRasterizer();
if (rast == NULL) {
printf("Could not init rasterizer.\n");
free(svgContent);
goto error;
}
img = malloc(w*h*4);
if (img == NULL) {
printf("Could not alloc image buffer.\n");
free(svgContent);
goto error;
}
printf("rasterizing image %d x %d\n", w, h);
nsvgRasterize(rast, image, 0,0,1, img, w, h, w*4);
printf("writing svg.png\n");
stbi_write_png("svg.png", w, h, 4, img, w*4);
error:
nsvgDeleteRasterizer(rast);
nsvgDelete(image);
free(img);
free(svgContent);
return 0;
}
测试结果如下:
可以发现,确实在解析之后,输入的字符串发生了变化。
解决方案
没有时间去分析为什么NanoSVG存在bug,不过有个简单的办法可以解决这个问题,就是在解析前先备份一下输入字符串。例如:
char* logo_svg_copy = strdup(logo_svg);
if (logo_svg_copy == NULL) {
// 处理内存分配失败的情况
return;
}
// NSVGimage *svg = nsvgParse((char *)logo_svg, "px", SVG_DPI);
NSVGimage *svg = nsvgParse((char *)logo_svg_copy, "px", SVG_DPI);
serial_printf(SERIAL_CHANNEL, "Parsed SVG image\r\n");
free(logo_svg_copy);
结语
这个作业做了几年了,为什么以前没有学生遇到这个bug呢?因为一般都是解析文件之后就不使用原始的字符串了,而这次学生的作业中多次使用这个字符串进行解析问题就出现了。NanoSVG的作者平时是对文件进行操作的,这个问题也就不容易发现。