ImageSharp和字体高度

Cra*_*aig 4 c# .net-core imagesharp

我的任务是创建要打印的图像。在图片上,我需要输入一个大写字母(大写[AZ])。

打印的图像尺寸可以在15厘米高和30厘米高之间变化(包括两者之间的任何尺寸)。

字母需要横跨打印图像的整个高度。

设置字体大小时,我看到您可以获取文本的大小。

using (Image<Rgba32> img = new Image<Rgba32>(imageWidth, imageHeight))
{
    img.Mutate(x => x.Fill(Rgba32.White));
    img.MetaData.HorizontalResolution = 96;
    img.MetaData.VerticalResolution = 96;
    var fo = SystemFonts.Find("Arial");
    var font = new Font(fo, 1350, FontStyle.Regular);
Run Code Online (Sandbox Code Playgroud)

我可以在这里获取文本的大小:

SizeF size = TextMeasurer.Measure(group.Text, new RendererOptions(font));
Run Code Online (Sandbox Code Playgroud)

但是,如您所见,我在这里将字体的大小硬编码。高度需要与图像的高度匹配。

有什么方法可以指定这一点,而又不会增加质量?有没有一种方法可以指定高度(以像素为单位)?也许我可以安全使用的字体大小有颜色?

当我将“字体大小”设置为“图像”的像素高度时,会看到以下信息: 在此处输入图片说明

我不确定为什么圆圈部分有间隙。我将左手文本的左上角位置设置为0,0 ....,将“ QWW”组的右上角点设置为图像的宽度,将0设置为Y。但是我希望它们要与尺寸和底部齐平。

toc*_*oft 8

TextMeasurer 专为行和单词上下文中的度量文本而设计,而不是在单个字符上,因为它不查看单个字形形式,而是查看整个字体以针对行间距等进行测量。

相反,您需要使用 nuget 包将字形直接渲染为向量SixLabors.Shapes.Text。这将允许您准确测量最终字形 + 应用缩放和变换以确保字形与图像边缘对齐。除了最终将字形绘制到图像之外,它还可以使您不必执行任何昂贵的像素级操作。

/// <param name="text">one or more characters to scale to fill as much of the target image size as required.</param>
/// <param name="targetSize">the size in pixels to generate the image</param>
/// <param name="outputFileName">path/filename where to save the image to</param>
private static void GenerateImage(string text, Primitives.Size targetSize, string outputFileName)
{
    FontFamily fam = SystemFonts.Find("Arial");
    Font font = new Font(fam, 100); // size doesn't matter too much as we will be scaling shortly anyway
    RendererOptions style = new RendererOptions(font, 72); // again dpi doesn't overlay matter as this code genreates a vector

    // this is the important line, where we render the glyphs to a vector instead of directly to the image
    // this allows further vector manipulation (scaling, translating) etc without the expensive pixel operations.
    IPathCollection glyphs = SixLabors.Shapes.TextBuilder.GenerateGlyphs(text, style);

    var widthScale = (targetSize.Width / glyphs.Bounds.Width);
    var heightScale = (targetSize.Height / glyphs.Bounds.Height);
    var minScale = Math.Min(widthScale, heightScale);

    // scale so that it will fit exactly in image shape once rendered
    glyphs = glyphs.Scale(minScale);

    // move the vectorised glyph so that it touchs top and left edges 
    // could be tweeked to center horizontaly & vertically here
    glyphs = glyphs.Translate(-glyphs.Bounds.Location);

    using (Image<Rgba32> img = new Image<Rgba32>(targetSize.Width, targetSize.Height))
    {
        img.Mutate(i => i.Fill(new GraphicsOptions(true), Rgba32.Black, glyphs));

        img.Save(outputFileName);
    }
}
Run Code Online (Sandbox Code Playgroud)


Fla*_*ver 5

我将您的问题分为三个部分:

  1. 动态字体大小,而不是硬编码字体大小
  2. 字形应使用图像的整个高度
  3. 该字形应左对齐

动态缩放文本以填充图像的高度

在测量了文本大小之后,计算需要放大或缩小字体以匹配图像高度的因子:

SizeF size = TextMeasurer.Measure(text, new RendererOptions(font));
float scalingFactor = finalImage.Height / size.Height;
var scaledFont = new Font(font, scalingFactor * font.Size);
Run Code Online (Sandbox Code Playgroud)

这样,初始设置的字体大小在很大程度上被忽略。现在,我们可以根据图像的高度使用动态缩放的字体绘制文本:

initial.png

膨胀文字以使用图像的整个高度

根据每个字形,我们现在可能在图像的顶部/底部与文本的顶部/底部之间有一个缝隙。字形的渲染或绘制方式在很大程度上取决于所使用的字体。我不是排版专家,但是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);
Run Code Online (Sandbox Code Playgroud)

现在,我们可以绘制文本的顶部和底部,使其紧贴图像的顶部和底部边缘:

中间体.png

将字形移到最左边

最后,取决于字形,字形的左侧可能不会与图像的左侧对齐。与上一步相似,我们可以确定当前图像中包含膨胀字形的文本的最左侧像素,然后将文本相应地向左移动以消除两者之间的间隙:

int left = GetLeftPixel(intermediateImage, Rgba32.White);

location.Offset(-left, 0.0f);
Run Code Online (Sandbox Code Playgroud)

现在我们可以绘制与图像左侧对齐的文本:

final.png

现在,此最终图像具有根据图像大小动态缩放的字体,已进一步缩放并移动以填满图像的整个高度,并且已进一步移动以使左侧没有间隙。

注意

当绘制文本时,DPITextGraphicsOptions应该匹配DPI的图像:

var textGraphicOptions = new TextGraphicsOptions(true)
{
    HorizontalAlignment = HorizontalAlignment.Left,
    VerticalAlignment = VerticalAlignment.Top,
    DpiX = (float)finalImage.MetaData.HorizontalResolution,
    DpiY = (float)finalImage.MetaData.VerticalResolution
};
Run Code Online (Sandbox Code Playgroud)

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.");
}
Run Code Online (Sandbox Code Playgroud)

我们不需要保存所有3张图像,但是我们确实需要创建所有3张图像并逐步填充和移动文本,以填充图像的整个高度并从图像的最左侧开始。

该解决方案独立于所使用的字体工作。另外,对于生产应用程序,请避免通过查找字体SystemFonts,因为所讨论的字体可能在目标计算机上不可用。要获得稳定的独立解决方案,请在应用程序中部署TTF字体,然后FontCollection手动安装该字体。

  • 这可以通过使用“SixLabors.Shapes.Text”首先将字形渲染为向量而不执行昂贵的像素操作来更简单地完成。 (2认同)