Эффективное отображение текста на растровом изображении C# с помощью System.Drawing - PullRequest
0 голосов
/ 04 мая 2020

Я пишу программу, которая принимает растровые изображения и записывает на них текст. У меня есть функциональный метод, который будет делать это, я предоставляю ему строку, которая должна быть нарисована, прямоугольник, в который он должен быть помещен, имя используемого семейства шрифтов, получает наибольший размер это возможно для шрифта, а затем др aws его на предоставленном растровом изображении (по центру прямоугольника, если это то, что нужно)

Код, который я сейчас использую, выглядит следующим образом

public static Bitmap Write_Text(Bitmap i, string s, Rectangle r, string font, bool centered, bool bold)
        {
            //TODO check that there isnt a better way to do this
            //first off we need to make sure this rectangle we are given remains in the bounds
            //of the bitmap it will be drawn on
            //since pixels start at (0,0) we need the combined origin and dimension of the rectangle
            //to be of a lesser value than the dimenion of the rectangle (since = could give out of bounds)
            if((r.Width + r.X)<i.Width && (r.Height + r.Y) < i.Height && r.X >= 0 && r.Y >= 0)
            {
                //now we need to ensure that the graphics object that
                //draws the text is properly disposed of
                using(Graphics g = Graphics.FromImage(i))
                {
                    //The graphics object will have some settings tweaked
                    //to ensure high quality rendering 
                    g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
                    //Normally Compositing Mode Would Be Set But Writing Text requires its own non enum setting
                    //and so is excluded here
                    g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
                    g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
                    g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
                    //and one more dedicated to ensuring the text renders with nice contrast
                    //and non jagged letters
                    g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;

                    //now since we need to actually loop over to try and fit the text to the box 
                    //we need a control variable for a do-while loop
                    bool fits = false;
                    //and storage for the parameter for the fonts size
                    //the font can't actually be any larger than the smaller
                    //dimension of the box it goes in
                    int size = Math.Min(r.Width, r.Height);
                    do
                    {
                        //now a font family may not exist on the computer microsofts 
                        //sans seriff will be used so no need for try catches
                        Font f;
                        //If the font is to be bold set it as such
                        if (bold)
                        {
                            f = new Font(font, size, System.Drawing.FontStyle.Bold, GraphicsUnit.Pixel);
                        }
                        else
                        {
                            f = new Font(font, size, GraphicsUnit.Pixel);
                        }
                        //now we measure the string and if it fits inside the rectangle we can proceed
                        if(g.MeasureString(s,f).Width <= r.Width && g.MeasureString(s,f).Height <= r.Height)
                        {
                            fits = true;
                        }
                        else
                        {
                            //if the string doesnt fit the box decrease the size and try again
                            size--;
                        }
                        //regardless dispose of f to avoid memory leaks
                        f.Dispose();                                                                        
                    }
                    while (!fits);
                    //now we just need to make a string attributes object since the string may want to be centered
                    StringFormat Format = new StringFormat();
                    if (centered)
                    {
                        Format.Alignment = StringAlignment.Center;
                        Format.LineAlignment = StringAlignment.Center;
                    }
                    //now construct the font object that will be used for the drawing
                    //as above
                    Font ff;
                    if (bold)
                    {
                        ff = new Font(font, size, System.Drawing.FontStyle.Bold, GraphicsUnit.Pixel);
                    }
                    else
                    {
                        ff = new Font(font, size, GraphicsUnit.Pixel);
                    }
                    //now draw the text in place on the bitmap
                    g.DrawString(s, ff, Brushes.Black, r, Format);
                    //dispose of the font so its not leaking memory
                    ff.Dispose();
                    Format.Dispose();
                }
            }
            return i;
        }

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

Заранее благодарю за любую помощь, оказанную по этому вопросу.

Ответы [ 2 ]

0 голосов
/ 07 мая 2020

С точки зрения производительности, есть две причины проблем с производительностью.

1) Загрузка шрифтов - это длительный процесс в целом, даже вне. NET (Подумайте о том, насколько медленно Word или любой другой Программа для рисования шрифтов при открытии списка шрифтов). Поэтому постарайтесь найти способы кэширования объектов шрифтов в вашем классе, если вы можете вместо того, чтобы воссоздавать их каждый раз.

2) GDI быстрее, чем GDI +, согласно документам : «Вы можете выберите либо GDI, либо GDI + для рендеринга текста, однако GDI обычно предлагает более высокую производительность и более точное измерение текста. " метод в классе TextRenderer, вы можете получить доступ к функциональности GDI для рисования текста в форме или элементе управления. Рендеринг текста GDI обычно предлагает лучшую производительность и более точное измерение текста, чем GDI +. "

0 голосов
/ 04 мая 2020

Поэтому я воспользовался советом @Raju Джозефа в комментариях к вопросу и разбил код. Вероятно, сейчас он работает не быстрее, чем раньше, но, по крайней мере, выглядит аккуратно, поэтому функция, которая теперь делает dr aws текст, выглядит так:

public static Bitmap Write_Text(Bitmap i, string s, Rectangle r, bool centered, string font, bool bold, bool italic)
        {
            //Since we want to avoid out of bounds errors make sure the rectangle remains within the bounds of the bitmap
            //and only execute if it does
            if(r.X>= 0 && r.Y>=0&&(r.X+r.Width < i.Width) && (r.Y + r.Height < i.Height))
            {
                //Step one is to make a graphics object that will draw the text in place
                using (Graphics g = Graphics.FromImage(i))
                {
                    //Set some of the graphics properties so that the text renders nicely
                    g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
                    //Compositing Mode can't be set since string needs source over to be valid
                    g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
                    g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
                    g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
                    //And an additional step to make sure text is proper anti-aliased and takes advantage
                    //of clear type as necessary
                    g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;

                    //this also requires a font object we need to make sure we dispose of properly
                    using (Font f = Functions.Generate_Font(s, font, r, bold, italic))
                    {
                        //the using function actually makes sure the font is as large as it can be for the 
                        //purpose of fitting the rectangle we just need to check if its centered
                        using (StringFormat format = new StringFormat())
                        {
                            //the format won't always be doing anything but
                            //just in case we need it
                            //and if the text is centered we need to tell the formatting
                            if (centered)
                            {
                                format.Alignment = StringAlignment.Center;
                                format.Alignment = StringAlignment.Center;
                            }
                            //and draw the text into place
                            g.DrawString(s, f, Brushes.Black, r, format);
                        }
                    }
                }
            }
            return i;
        }

С выяснением того, насколько большим должен быть шрифт обрабатываются другим методом класса, как показано ниже

 public static Font Generate_Font(string s,string font_family, Rectangle r, bool bold, bool italic)
        {
            //First things first, the font can't be of a size larger than the rectangle in pixels so 
            //we need to find the smaller dimension as that will constrain the max size
            int Max_Size = Math.Min(r.Width, r.Height);
            //Now we loop backwards from this max size until we find a size of font that fits inside the 
            //rectangle given
            for(int size = Max_Size; size > 0; size--)
            {
                //Since a default font is used if the font family specified doesnt exist 
                //checking the family exists isnt necessary
                //However we need to cover if the font is bold or italic
                Font f;
                if (bold)
                {
                    f = new Font(font_family, size, System.Drawing.FontStyle.Bold, GraphicsUnit.Pixel);
                }
                else if (italic)
                {
                    f = new Font(font_family, size, System.Drawing.FontStyle.Italic, GraphicsUnit.Pixel);
                }
                else if (bold && italic)
                {
                    //the pipe is a bitwise or and plays with the enum flags to get both bold and italic 
                    f = new Font(font_family, size, System.Drawing.FontStyle.Bold | System.Drawing.FontStyle.Italic, GraphicsUnit.Pixel);
                }
                else
                {
                    //otherwise make a simple font
                    f = new Font(font_family, size, GraphicsUnit.Pixel);
                }
                //because graphics are weird we need a bitmap and graphics object to measure the string
                //we also need a sizef to store the measured results
                SizeF result;
                using(Bitmap b = new Bitmap(100,100))
                {
                    using(Graphics g = Graphics.FromImage(b))
                    {
                        result = g.MeasureString(s, f);
                    }
                }
                //if the new string fits the constraints of the rectangle we return it
                if(result.Width<= r.Width && result.Height <= r.Height)
                {
                    return f;
                }
                //if it didnt we dispose of f and try again
                f.Dispose();
            }
            //If something goes horribly wrong and no font size fits just return comic sans in 12 pt font
            //that won't upset anyone and the rectangle it will be drawn to will clip the excess anyway
            return new Font("Comic Sans", 12, GraphicsUnit.Point);
        }

Возможно, это какой-то другой способ сделать это, но это кажется достаточно быстрым и выглядит достаточно аккуратно в источнике, поэтому большие пальцы за это.

...