ImageSharp и высота шрифта - PullRequest
       35

ImageSharp и высота шрифта

0 голосов
/ 18 октября 2018

У меня есть задача создать изображение, которое будет напечатано.На картинке мне нужно поставить одну заглавную букву (верхний регистр, [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);

IВы можете получить размер моего текста здесь:

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

Однако, как вы можете видеть, я жестко закодировал размер для своего шрифта здесь.Высота должна соответствовать высоте изображения.

Есть ли способ указать это без растяжения и потери качества?Есть ли способ указать высоту в пикселях?Может быть, есть цвет к размеру шрифта, который я могу использовать безопасно?

Когда я устанавливаю размер шрифта на высоту пикселя моего изображения, я вижу это: enter image description here

Я не уверен, почему у обведенных частей есть промежутки.Я устанавливаю свою верхнюю левую позицию левого текста на 0,0 .... и верхнюю правую точку группы 'QWW' на ширину изображения, а 0 на Y. Но я бы ожидалони должны быть на одном уровне с размером и дном.

Ответы [ 2 ]

0 голосов
/ 27 октября 2018

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);
    }
}
0 голосов
/ 20 октября 2018

Я разделил ваш вопрос на 3 части:

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

Таким образом, изначально установленный размер шрифта в значительной степени игнорируется.Теперь мы можем нарисовать текст динамически масштабируемым шрифтом, в зависимости от высоты изображения:

initial.png

Раздуть текст, чтобы использовать всю высотуimage

В зависимости от каждого символа у нас теперь может быть промежуток между верхней / нижней стороной изображения и верхней / нижней стороной текста.Способ визуализации или отрисовки глифа сильно зависит от используемого шрифта.Я не являюсь экспертом в области типографики, но AFAIK у каждого шрифта есть свои поля и отступы, а также пользовательские высоты вокруг 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

Это окончательное изображение теперь имеет шрифт, динамически масштабируемый в зависимости от размераизображение было дополнительно масштабировано и перемещено, чтобы заполнить всю высоту изображения, и было дополнительно перемещено, чтобы не было зазора слева.

Примечание

При рисовании текста DPI из 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, поскольку данный шрифт может быть недоступен на целевом компьютере.Чтобы получить стабильное автономное решение, разверните шрифт TTF вместе с приложением и установите шрифт вручную с помощью FontCollection.

...