9

我的任务是创建要打印的图像。在图片上,我需要输入一个大写字母(大写,[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);

我可以在这里得到我的文本的大小:

SizeF size = TextMeasurer.Measure(group.Text, new RendererOptions(font));

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

有没有办法指定这一点,而不会拉伸和失去质量?有没有办法可以指定高度,以像素为单位?也许我可以安全使用的字体大小有颜色?

当我将字体大小设置为图像的像素高度时,我看到了这个: 在此处输入图像描述

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

4

2 回答 2

11

我把你的问题分成三部分:

  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);

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

初始.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);

现在我们可以绘制顶部和底部对齐图像顶部和底部边缘的文本:

中间件.png

将字形移到最左边

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

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

location.Offset(-left, 0.0f);

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

最终的.png

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

笔记

绘制文字时,其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手动安装字体。

于 2018-10-20T00:39:52.323 回答
10

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);
    }
}
于 2018-10-27T15:28:24.653 回答