我把你的问题分成三部分:
- 动态字体大小,而不是硬编码的字体大小
- 字形应使用图像的全高
- 字形应左对齐
动态缩放文本以填充图像的高度
测量文本大小后,计算字体需要放大或缩小以匹配图像高度的因子:
SizeF size = TextMeasurer.Measure(text, new RendererOptions(font));
float scalingFactor = finalImage.Height / size.Height;
var scaledFont = new Font(font, scalingFactor * font.Size);
这样,最初设置的字体大小在很大程度上被忽略了。现在我们可以根据图像的高度使用动态缩放的字体来绘制文本:

膨胀文本以使用图像的整个高度
根据每个字形,我们现在可能在图像的顶部/底部和文本的顶部/底部之间有一个间隙。如何渲染或绘制字形在很大程度上取决于使用的字体。我不是排版专家,但 AFAIK 每种字体都有自己的边距/填充,并且在基线周围有自定义高度。
为了使我们的字形与图像的顶部和底部对齐,我们必须进一步放大字体。要计算这个因子,我们可以通过搜索最顶部和最底部像素的高度 ( y ) 来确定当前绘制文本的顶部和底部边缘,并使用此差异放大字体。此外,我们需要将字形偏移从图像顶部到字形顶部边缘的距离:
int top = GetTopPixel(initialImage, Rgba32.White);
int bottom = GetBottomPixel(initialImage, Rgba32.White);
int offset = top + (initialImage.Height - bottom);
SizeF inflatedSize = TextMeasurer.Measure(text, new RendererOptions(scaledFont));
float inflatingFactor = (inflatedSize.Height + offset) / inflatedSize.Height;
var inflatedFont = new Font(font, inflatingFactor * scaledFont.Size);
location.Offset(0.0f, -top);
现在我们可以绘制顶部和底部对齐图像顶部和底部边缘的文本:

将字形移到最左边
最后,根据字形,字形的左侧可能不会与图像的左侧对齐。与上一步类似,我们可以确定当前图像中包含膨胀字形的文本的最左侧像素,并将文本相应地向左移动以消除两者之间的间隙:
int left = GetLeftPixel(intermediateImage, Rgba32.White);
location.Offset(-left, 0.0f);
现在我们可以绘制与图像左侧对齐的文本:

这个最终图像现在具有根据图像大小动态缩放的字体,进一步放大并移动以填满图像的整个高度,并且进一步移动到左侧没有间隙。
笔记
绘制文字时,其DPITextGraphicsOptions
应与图像的DPI相匹配:
var textGraphicOptions = new TextGraphicsOptions(true)
{
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
DpiX = (float)finalImage.MetaData.HorizontalResolution,
DpiY = (float)finalImage.MetaData.VerticalResolution
};
代码
private static void CreateImageFiles()
{
Directory.CreateDirectory("output");
string text = "J";
Rgba32 backgroundColor = Rgba32.White;
Rgba32 foregroundColor = Rgba32.Black;
int imageWidth = 256;
int imageHeight = 256;
using (var finalImage = new Image<Rgba32>(imageWidth, imageHeight))
{
finalImage.Mutate(context => context.Fill(backgroundColor));
finalImage.MetaData.HorizontalResolution = 96;
finalImage.MetaData.VerticalResolution = 96;
FontFamily fontFamily = SystemFonts.Find("Arial");
var font = new Font(fontFamily, 10, FontStyle.Regular);
var textGraphicOptions = new TextGraphicsOptions(true)
{
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
DpiX = (float)finalImage.MetaData.HorizontalResolution,
DpiY = (float)finalImage.MetaData.VerticalResolution
};
SizeF size = TextMeasurer.Measure(text, new RendererOptions(font));
float scalingFactor = finalImage.Height / size.Height;
var scaledFont = new Font(font, scalingFactor * font.Size);
PointF location = new PointF();
using (Image<Rgba32> initialImage = finalImage.Clone(context => context.DrawText(textGraphicOptions, text, scaledFont, foregroundColor, location)))
{
initialImage.Save("output/initial.png");
int top = GetTopPixel(initialImage, backgroundColor);
int bottom = GetBottomPixel(initialImage, backgroundColor);
int offset = top + (initialImage.Height - bottom);
SizeF inflatedSize = TextMeasurer.Measure(text, new RendererOptions(scaledFont));
float inflatingFactor = (inflatedSize.Height + offset) / inflatedSize.Height;
var inflatedFont = new Font(font, inflatingFactor * scaledFont.Size);
location.Offset(0.0f, -top);
using (Image<Rgba32> intermediateImage = finalImage.Clone(context => context.DrawText(textGraphicOptions, text, inflatedFont, foregroundColor, location)))
{
intermediateImage.Save("output/intermediate.png");
int left = GetLeftPixel(intermediateImage, backgroundColor);
location.Offset(-left, 0.0f);
finalImage.Mutate(context => context.DrawText(textGraphicOptions, text, inflatedFont, foregroundColor, location));
finalImage.Save("output/final.png");
}
}
}
}
private static int GetTopPixel(Image<Rgba32> image, Rgba32 backgroundColor)
{
for (int y = 0; y < image.Height; y++)
{
for (int x = 0; x < image.Width; x++)
{
Rgba32 pixel = image[x, y];
if (pixel != backgroundColor)
{
return y;
}
}
}
throw new InvalidOperationException("Top pixel not found.");
}
private static int GetBottomPixel(Image<Rgba32> image, Rgba32 backgroundColor)
{
for (int y = image.Height - 1; y >= 0; y--)
{
for (int x = image.Width - 1; x >= 0; x--)
{
Rgba32 pixel = image[x, y];
if (pixel != backgroundColor)
{
return y;
}
}
}
throw new InvalidOperationException("Bottom pixel not found.");
}
private static int GetLeftPixel(Image<Rgba32> image, Rgba32 backgroundColor)
{
for (int x = 0; x < image.Width; x++)
{
for (int y = 0; y < image.Height; y++)
{
Rgba32 pixel = image[x, y];
if (pixel != backgroundColor)
{
return x;
}
}
}
throw new InvalidOperationException("Left pixel not found.");
}
我们不需要保存所有 3 个图像,但是我们需要创建所有 3 个图像并逐步膨胀和移动文本,以填充图像的整个高度并从图像的最左侧开始。
此解决方案独立于使用的字体工作。此外,对于生产应用程序,请避免通过 查找字体SystemFonts
,因为有问题的字体可能在目标计算机上不可用。要获得稳定的独立解决方案,请在应用程序中部署TTF字体并FontCollection
手动安装字体。