首先,我很确定没有不使用Graphics.MeasureString() 的方法。至少,您必须分析 GDI+ 字体渲染代码的重要部分,才能估计最终字体大小,since it uses its own renderer。
幸运的是,有一些方法可以将每帧的MeasureString() 调用次数减少到一个小的常数(甚至为零),具体取决于您使用的字体。
等宽字体
如果你使用等宽字体,事情就简单了:
- 针对
n 不同的字体大小测量任意字符的边界。
- 将图像宽度除以文本长度,得到字符的最大宽度。
- 使用预先计算的边界列表来确定字体大小。
对MeasureString() 的调用次数:启动期间n 次,每帧0 次。
比例(可变间距)字体
如果您使用具有可变字符大小的比例字体,事情会变得更加复杂。正如前言所说,难免要打MeasureString();但是,您可以通过计算和改进估算值,将所需的调用次数显着减少到一个小常数。
一般算法是:
- 选择最大 (
max) 字体大小。
- 使用
max 字体大小测量您的字符串。
- 缩放测量的字符串边界以适应图像边界。由于字体大小大致是线性的,比例因子
s 可用于计算估计的字体大小:est = s * max。
- 如果估计
est 尚未达到最佳状态,请检查极端情况并稍微调整字体大小。
实现可能如下所示:
public Font GetFont(string str, Graphics g, int imgWidth, int imgHeight)
{
// Measure with maximum sized font
var baseSize = g.MeasureString(str, _fontCache[_maxFontSize]);
// Downsample to actual image size
float widthRatio = imgWidth / baseSize.Width;
float heightRatio = imgHeight / baseSize.Height;
float minRatio = Math.Min(widthRatio, heightRatio);
int estimatedFontSize = (int)(_maxFontSize * minRatio);
// Make sure the precomputed font list is always hit
if(estimatedFontSize > _maxFontSize)
estimatedFontSize = _maxFontSize;
else if(estimatedFontSize < _minFontSize)
estimatedFontSize = _minFontSize;
// Make sure the estimated size is not too large
var estimatedSize = g.MeasureString(str, _fontCache[estimatedFontSize]);
bool estimatedSizeWasReduced = false;
while(estimatedSize.Width > imgWidth || estimatedSize.Height > imgHeight)
{
if(estimatedFontSize == _minFontSize)
break;
--estimatedFontSize;
estimatedSizeWasReduced = true;
estimatedSize = g.MeasureString(str, _fontCache[estimatedFontSize]);
++counter;
}
// Can we increase the size a bit?
if(!estimatedSizeWasReduced)
{
while(estimatedSize.Width < imgWidth && estimatedSize.Height < imgHeight)
{
if(estimatedFontSize == _maxFontSize)
break;
++estimatedFontSize;
estimatedSize = g.MeasureString(str, _fontCache[estimatedFontSize]);
}
// We increase the size until it is larger than the image, so we need to go back one step afterwards
if(estimatedFontSize > _minFontSize)
--estimatedFontSize;
}
return _fontCache[estimatedFontSize];
}
可惜网上各种C#编译器都不支持GDI+,不过我上传了self-contained sample program as a Gist。该程序测试了各种不同的图像大小,同时记录了对MeasureString()的调用次数。
对MeasureString() 的调用次数:在启动期间0 次,2 或3 次每帧。
因此,(在实践中)该算法具有恒定复杂度,并且比线性方法效率更高,同时仍能找到最佳整数字体大小。
如果特定字体和字符集允许,可以通过添加额外检查来进一步优化此方法,以便保存另外一两个 MeasureString() 调用。
注意事项:
- 示例实现适用于宽度和高度;由于您的问题表明您只对宽度感兴趣,因此您可以安全地删除高度部分。
- 我只为整数字体大小实现了这个。如果渲染的文本确实应该尽可能大,那么在估计的大小和一个小的(可能是常数)偏移之间进行额外的二分搜索应该会产生一个很好的近似值,同时保持
MeasureString() 调用的数量很小。
- 在示例实现中,为方便起见,我预先分配了所有
n Font 对象并将它们放入_fontCache 列表中。从性能的角度来看,这可能没有必要;您可能想要测量分配 Font 对象的开销。
- 您不必将字体大小限制在
max。升级也是可能的,但您可能会失去精度。
- 您可以利用有关呈现文本内容的附加信息。例如,如果只有
m 可能的字符串,缓存其计算的字体大小可能会有所帮助。