代码之家  ›  专栏  ›  技术社区  ›  Craig

图像清晰度和字体高度

  •  2
  • Craig  · 技术社区  · 7 年前

    我有一个任务,以创建一个图像,将打印。在图片上,我需要放一个大写字母(大写,[a-Z])。

    打印图像的大小可以在15cm高和30cm高之间变化(包括两者之间的任何大小)。

    当设置字体大小时,我看到你可以得到文本的大小。

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

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

    有没有办法在不拉伸和失去质量的情况下具体说明这一点?有没有办法用像素来指定高度?也许我可以安全使用字体的颜色?

    当我将字体大小设置为图像的像素高度时,我看到: enter image description here

    2 回复  |  直到 7 年前
        1
  •  9
  •   FlashOver    7 年前

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

    1. 动态字体大小,而不是硬编码字体大小
    2. 图示符应与左侧对齐

    测量文本大小后,计算字体需要放大或缩小以匹配图像高度的系数:

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

    initial.png

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

    根据每个glyph,我们现在可能在图像的顶/底面和文本的顶/底面之间有一个间隙。如何呈现或绘制glyph在很大程度上取决于所使用的字体。我不是排版方面的专家,但是每种字体都有自己的边距/填充,并且在字体周围有自己的高度 baseline .

    为了使字形与图像的顶部和底部对齐,我们必须进一步放大字体。为了计算这个系数,我们可以通过搜索高度来确定当前绘制文本的上边缘和下边缘( 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);
    

    intermediate.png

    将字形移到最左边

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

    int left = GetLeftPixel(intermediateImage, Rgba32.White);
    
    location.Offset(-left, 0.0f);
    

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

    final.png

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

    注意

    TextGraphicsOptions 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 ,因为有问题的字体可能在目标计算机上不可用。要获得稳定的独立解决方案,请部署 FontCollection 手动。

        2
  •  8
  •   FlashOver    7 年前

    TextMeasurer 是为行和单词上下文中的度量文本而设计的,而不是单个字符,因为它不查看单个字形形式,而是查看字体作为一个整体来度量行距等。

    相反,您需要使用nuget包将glyph直接渲染到向量 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);
        }
    }