GraphicsMagick TTF字体性能

GraphicsMagick TTF Font Performance

本文关键字:性能 字体 TTF GraphicsMagick      更新时间:2023-10-16

我在C++库中使用GraphicsMagick来创建光栅化输出,该输出主要由文本组成。

我正在做这样的事情:

void gfx_writer::add_text(Magick::Image& img) const
{
using namespace Magick;
const unsigned x = // just a position;
const unsigned y_title =  // just a position;
const unsigned y_heading =  // just a position;
const unsigned y_value =  // just a position;
img.strokeColor("transparent");
img.fillColor("black");
img.font(font_title_);
img.fontPointsize(font_size_title_);
img.draw(DrawableText{static_cast<double>(x), static_cast<double>(y_title), "a text"});
img.font(font_heading_);
img.fontPointsize(font_size_heading_);
img.draw(DrawableText{static_cast<double>(x), static_cast<double>(y_heading), "another text"});
img.font(font_value_);
img.fontPointsize(font_size_value_);
img.draw(DrawableText{static_cast<double>(x), static_cast<double>(y_value), "third text"});
}

font_title_font_heading_font_value_是到TTF文件的路径。

这已经不止一次了,我的表现相当糟糕。当我看到使用Sysinternals Process Monitor会发生什么时,我会看到TTF文件被反复读取。所以我的问题是:

  • 每次调用img.font(...)时都会读取TTF文件,我的观察结果正确吗
  • 有没有一种方法可以使用GraphicsMagick缓存字体,或者提供TTF文件的路径之外的其他内容
  • 我还缺什么吗

注意:此答案使用ImageMagick的Magick++库,并且GraphicsMagick可能存在较小的可移植性问题,但底层解决方案是相同的

我的观察结果正确吗,每次调用img.font(…)时都会读取TTF文件?

是的,每次都会重新加载TTF字体。一种选择是在系统中安装字体,并调用字体族构造函数。

DrawableFont ( const std::string &family_,
StyleType style_,
const unsigned long weight_,
StretchType stretch_ );

大多数系统都有某种字体缓存系统,可以更快地访问,但在现代硬件上并不明显。

我还缺少什么?

尝试构建图形上下文,并且只调用Magick::Image.draw一次。请记住,Drawable...调用只是包装MVG语句,创建std::list<Drawable>可以构建复杂的向量。只有当draw方法使用绘制命令时,TTF才会被加载,因此提前准备所有绘制命令是其关键。

让我们从重写您提供的代码开始(我在这里有一定程度的自由)。

#include <Magick++.h>
const char * font_title_ = "fonts/OpenSans-Regular.ttf";
const char * font_heading_ = "fonts/LiberationMono-Regular.ttf";
const char * font_value_ = "fonts/sansation.ttf";
double font_size_title_ = 32;
double font_size_heading_ = 24;
double font_size_value_ = 16;
void gfx_writer_add_text(Magick::Image& img)
{
using namespace Magick;
double x = 10.0;
double y_title = 10;
double y_heading = 20.0;
double y_value =  30.0;
img.strokeColor("transparent");
img.fillColor("black");
img.font(font_title_);
img.fontPointsize(font_size_title_);
img.draw(DrawableText{x, y_title, "a text"});
img.font(font_heading_);
img.fontPointsize(font_size_heading_);
img.draw(DrawableText{x, y_heading, "another text"});
img.font(font_value_);
img.fontPointsize(font_size_value_);
img.draw(DrawableText{x, y_value, "third text"});
}
int main()
{
Magick::Image img("wizard:");
gfx_writer_add_text(img);
gfx_writer_add_text(img);
gfx_writer_add_text(img);
img.write("output.png");
}

我可以对运行时进行编译和基准测试。我得到以下时间:

$ time ./original.o 
real    0m5.061s
user    0m0.094s
sys     0m0.029s

重构代码以使用绘图上下文,并且只调用Magick::Image.draw一次。

#include <Magick++.h>
#include <list>
const char * font_title_ = "fonts/OpenSans-Regular.ttf";
const char * font_heading_ = "fonts/LiberationMono-Regular.ttf";
const char * font_value_ = "fonts/sansation.ttf";
double font_size_title_ = 32;
double font_size_heading_ = 24;
double font_size_value_ = 16;
void gfx_writer_add_text(Magick::Image& img)
{
using namespace Magick;
double x = 10.0;
double y_title = 10;
double y_heading = 20.0;
double y_value =  30.0;
std::list<Drawable> ctx;
ctx.push_back(DrawableStrokeColor("transparent"));
ctx.push_back(DrawableFillColor("black"));
/* TITLE */
ctx.push_back(DrawablePushGraphicContext());
ctx.push_back(DrawableFont(font_title_);
ctx.push_back(DrawablePointSize(font_size_title_));
ctx.push_back(DrawableText{x, y_title, "a text"});
ctx.push_back(DrawablePopGraphicContext());
/* HEADING */
ctx.push_back(DrawablePushGraphicContext());
ctx.push_back(DrawableFont(font_heading_));
ctx.push_back(DrawablePointSize(font_size_heading_));
ctx.push_back(DrawableText{x, y_heading, "another text"});
ctx.push_back(DrawablePopGraphicContext());
/* Value */
ctx.push_back(DrawablePushGraphicContext());
ctx.push_back(DrawableFont(font_value_));
ctx.push_back(DrawablePointSize(font_size_value_));
ctx.push_back(DrawableText{x, y_value, "third text"});
ctx.push_back(DrawablePopGraphicContext());
img.draw(ctx);
}
int main()
{
Magick::Image img("wizard:");
gfx_writer_add_text(img);
gfx_writer_add_text(img);
gfx_writer_add_text(img);
img.write("output2.png");
}

基准时间稍微好一点。

$ time ./with_context.o
real    0m0.106s
user    0m0.090s
sys     0m0.012s

这样做不止一次,我的表现相当糟糕。

值得退一步,问问:"如何重构我的解决方案,只在最后一刻绘制?"。