简体   繁体   English

ImageSharp和字体高度

[英]ImageSharp and Font Height

I have a task to create an image that will be printed. 我有一项任务是创建一个将要打印的图像。 On the picture, I need to put a single uppercase letter (Upper case, [AZ]). 在图片上,我需要放一个大写字母(大写,[AZ])。

The printed image size can vary between 15cm height, and 30cm height (including any size in between). 打印的图像尺寸可以在15cm高度和30cm高度之间变化(包括其间的任何尺寸)。

The letter needs to span the full height of the printed image. 这封信需要跨越打印图像的整个高度。

When setting the font size, I see you can get the size of the text. 设置字体大小时,我看到你可以得到文本的大小。

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

I can get the size of my text here: 我可以在这里得到我的文字大小:

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

However, as you can see, I hard coded the size for my font here. 但是,正如您所看到的,我在这里硬编码了我的字体的大小。 The height needs to be matched to the height of the image. 高度需要与图像的高度相匹配。

Is there any way to specify this, without stretching and losing quality? 有没有办法指明这一点,没有拉伸和失去质量? Is there a way I can specify the height, in pixels? 有没有办法指定高度,以像素为单位? Maybe there's coloration to the font size that I can use safely? 也许我可以安全地使用字体大小的颜色?

When I set the Font size to the pixel height of my Image, I am seeing this: 当我将字体大小设置为我的图像的像素高度时,我看到这个: 在此输入图像描述

I'm not sure why the circled parts have gaps. 我不确定为什么圆圈部分有间隙。 I am setting my top left position of the left hand text, to 0,0.... and the top right hand point of the 'QWW' group to the width of the image, and 0 as Y. But I'd expect them to be flush against the size, and the bottom. 我将左手文字的左上角位置设置为0,0 ....并将'QWW'组的右上角设置为图像的宽度,将0设置为Y.但我希望它们与大小和底部齐平。

I split up your question into 3 parts: 我将你的问题分成三部分:

  1. dynamic font size, rather than hard coded font size 动态字体大小,而不是硬编码字体大小
  2. the glyph should use the full height of the image 字形应使用图像的完整高度
  3. the glyph should be aligned to the left 字形应该左对齐

Dynamically scale the text to fill the height of the image 动态缩放文本以填充图像的高度

After measuring the text size, calculate the factor by which the font needs to be scaled up or down to match the height of the image: 测量文本大小后,计算字体需要按比例放大或缩小以匹配图像高度的因子:

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

This way the initially set font size is largely ignored. 这样,最初设置的字体大小在很大程度上被忽略。 Now we can draw the text with the dynamically scaled font, depending on the height of the image: 现在我们可以使用动态缩放字体绘制文本,具体取决于图像的高度:

initial.png

Inflate text to use the entire height of the image 膨胀文本以使用图像的整个高度

Depending on each glyph, we might now have a gap in between the top/bottom side of the image and the top/bottom side of the text. 根据每个字形,我们现在可能在图像的顶部/底部与文本的顶部/底部之间存在间隙。 How a glyph is rendered or drawn depends heavily on the font in use. 如何渲染或绘制字形在很大程度上取决于使用的字体。 I am not an expert in typography, but AFAIK every font has it's own margin/padding, and has custom heights around the baseline . 我不是排版专家,但AFAIK每种字体都有自己的边距/填充,并且在基线周围有自定义高度。

In order for our glyph to align with the top and bottom of the image, we have to further scale up the font. 为了使我们的字形与图像的顶部和底部对齐,我们必须进一步扩展字体。 To calculate this factor, we can determine the top and bottom edge of the currently drawn text by searching for the height ( y ) of the top-most and bottom-most pixels, and scale up the font with this difference. 要计算此因子,我们可以通过搜索最顶部和最底部像素的高度( y )来确定当前绘制文本的顶部和底部边缘,并使用此差异放大字体。 Additionally, we need to offset the glyph by the distance from the top of the image to the top edge of the glyph: 另外,我们需要将字形偏移从图像顶部到字形顶边的距离:

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

Now we can draw the text with the top and the bottom snapping to the top and bottom edges of the image: 现在我们可以绘制顶部和底部对齐图像的顶部和底部边缘的文本:

intermediate.png

Move glyph to the very left 将字形移动到最左侧

Lastly, depending on the glyph, the left side of the glyph might not snap with the left side of the image. 最后,根据字形,字形的左侧可能不会与图像的左侧对齐。 Similar to the previous step, we can determine the left-most pixel of the text within the current image containing the inflated glyph, and move the text accordingly to the left to remove the gap in between: 与上一步类似,我们可以确定包含膨胀字形的当前图像中文本的最左侧像素,并相应地向左移动文本以消除之间的间隙:

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

location.Offset(-left, 0.0f);

Now we can draw the text aligning with the left side of the image: 现在我们可以绘制与图像左侧对齐的文本:

final.png

This final image now has the font dynamically scaled depending on the size of the image, has been further scaled up and moved to fill up the entire height of the image, and has been further moved to have no gap to the left. 此最终图像现在具有根据图像大小动态缩放的字体,已进一步放大并移动以填充图像的整个高度,并且已进一步移动到左侧没有间隙。

Note 注意

When drawing the text, the DPI of the TextGraphicsOptions should match the DPI of the image: 当绘制文本时,的DPI TextGraphicsOptions应该匹配图像的DPI:

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

Code

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.");
}

We don't need to save all 3 images, however we do need to create all 3 images and inflate and move the text step by step in order to fill up the entire height of the image and start at the very left of the image. 我们不需要保存所有3个图像,但是我们需要创建所有3个图像并逐步膨胀和移动文本以填充图像的整个高度并从图像的最左侧开始。

This solution works independently of the used font. 此解决方案独立于使用的字体。 Also, for a production application avoid finding a font via SystemFonts , because the font in question might not be available at the target machine. 此外,对于生产应用程序,请避免通过SystemFonts查找字体,因为相关字体可能在目标计算机上不可用。 To have an stable stand-alone solution, deploy a TTF font with the application and install the font via FontCollection manually. 要拥有稳定的独立解决方案,请在应用程序中部署TTF字体,然后通过FontCollection手动安装字体。

TextMeasurer is designed for measurer text in the context of line and words not on individual characters because it doesn't look at individual glyph forms instead looks at the font as a whole to measure against line spacing etc. TextMeasurer设计用于线条和单词字符上下文中的测量文本,因为它不查看单个字形,而是查看字体作为整体来测量线间距等。

Instead you will want to render the glyph directly to a vector using the nuget package SixLabors.Shapes.Text . 相反,您需要使用nuget包SixLabors.Shapes.Text将字形直接渲染到矢量。 This will allow you to accurately measure the final glyph + apply scaling and transforms to guarantee the glyph lines up with the edges of your image. 这将允许您准确测量最终字形+应用缩放和变换,以保证字形与图像边缘对齐。 It also saves you having to perform any expensive pixel level operations except the final drawing of the glyph to the image. 除了最终将字形绘制到图像之外,它还可以节省您执行任何昂贵的像素级操作。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM