Качество DrawString отличается между PictureBox и Bitmap - PullRequest
1 голос
/ 27 мая 2019

Мне нужно было написать текст слева направо, но каждый символ поворачивался на 90 градусов против часовой стрелки, чтобы на распечатке это выглядело так, как будто текст написан сверху вниз, как здесь:

vertical text

Чтобы добиться этого, я создал временное растровое изображение с тем же разрешением, свойствами PageUnit и PageScale, что и основной холст, на котором он должен быть нарисован. Я рисую строку на растровом изображении и размещаю символы по мере необходимости, а затем рисую изображение на растровом холсте.

Это работает правильно, за исключением того, что настройка PageScale основного холста приводит к пикселизации текста растрового изображения, даже если его PageScale корректируется до того, как произойдет DrawString. Рисование одной и той же строки непосредственно на основном холсте (без поворота, конечно) дает идеальный результат. Текст плавно масштабируется с помощью PageScale.

Может ли кто-нибудь подумать о потенциальной причине для этого?

private void DrawStringVerticalStackingV4(string text, Graphics g, SizeF sizeMaxArea, PointF startPoint, Font font, Brush brush, StringFormat sf)
        {
            if (text.Length == 0) return;


            // Duplicate the StringFormat but change the alignment and directionality.
            StringFormat sfVertical = (StringFormat)sf.Clone();

            sfVertical.Alignment = StringAlignment.Near;
            sfVertical.LineAlignment = StringAlignment.Near;
            sfVertical.FormatFlags = StringFormatFlags.DirectionVertical;

            SizeF sizeFullText = g.MeasureString(text, font);

            // Measure all the characters
            SizeF[] sizeChars = new SizeF[text.Length];

            for (int i = 0; i < text.Length; i++)
            {
                sizeChars[i] = g.MeasureString(text[i].ToString(), font, sizeMaxArea, StringFormat.GenericTypographic);
            }

            // Create bitmap to draw the string on
            float sumCharsHeight = (from item in sizeChars select item.Height).Sum();
            float maxCharsWidth = (from item in sizeChars select item.Width).Max() * 1.25f;

            Bitmap bmp = new Bitmap(MillimeterToPixel(g, sumCharsHeight), MillimeterToPixel(g, sizeMaxArea.Height), g);
            Graphics gBmp = Graphics.FromImage(bmp);

            bmp.MakeTransparent();

            gBmp.PageUnit = g.PageUnit;
            gBmp.TextRenderingHint = System.Drawing.Text.TextRenderingHint.SingleBitPerPixelGridFit;

            // Draw each character at a time in reverse order
            float currentX = -1.5f;

            for (int i = text.Length - 1; i >= 0; i--)
            {
                string currentChar = text[i].ToString();

                gBmp.DrawString(currentChar, font, brush, currentX, (sizeMaxArea.Height / 2) - (sizeChars[i].Width / 2), sfVertical);

                currentX += sizeChars[i].Height;
            }

            // Flip the bitmap
            bmp.RotateFlip(RotateFlipType.Rotate180FlipNone);

            // Crop the temporary bitmap to the maximum size
            RectangleF srcRect = new RectangleF(0, 0, Math.Min(gBmp.VisibleClipBounds.Width, sizeMaxArea.Width), Math.Min(gBmp.VisibleClipBounds.Height, sizeMaxArea.Height));

            Bitmap bmpCropped = bmp.Clone(new Rectangle(0, 0, MillimeterToPixel(gBmp, srcRect.Width), MillimeterToPixel(gBmp, srcRect.Height)) , bmp.PixelFormat);
            Graphics gCropped = Graphics.FromImage(bmpCropped);

            gCropped.PageUnit = g.PageUnit;

            // Draw the bitmap on the original canvas
            srcRect.Width = gCropped.VisibleClipBounds.Width;
            srcRect.Height = gCropped.VisibleClipBounds.Height;

            RectangleF destRect = new RectangleF(startPoint.X, startPoint.Y, gCropped.VisibleClipBounds.Width, gCropped.VisibleClipBounds.Height);

            // This is important!!! Without it, the copied image is distorted.
            g.InterpolationMode = InterpolationMode.NearestNeighbor;

            g.DrawImage(bmpCropped, destRect, srcRect, g.PageUnit);

            // Housekeeping
            gCropped.Dispose();
            bmpCropped.Dispose();
            gBmp.Dispose();
            bmp.Dispose();
        }

Дополнительная информация

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

Рассмотрим пустую форму, в которой только PictureBox и TrackBar имеют значение от 1 до 19 и значение 10 по умолчанию. TrackBar управляет переменной масштаба и обновляет PictureBox при его перемещении.

        private void trackBar1_Scroll(object sender, EventArgs e)
        {
            scale = trackBar1.Value / 10f;

            pictureBox1.Refresh();
        }

Сценарий 1 PageScale не изменяется, но масштаб контролирует размер шрифта. В этом сценарии MeasureString возвращает ожидаемый результат, в результате чего он возвращает другой размер, когда размер шрифта изменяется, а размер нарисованного теста изменяется с размером шрифта.

private void pictureBox1_Paint(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            g.PageUnit = GraphicsUnit.Millimeter;

            Console.WriteLine("Scale: " + scale);

            Font fontMM = new Font("Calibri", 8 * scale);

            Console.WriteLine("Font: " + fontMM.Size + " Millimeters: " + g.MeasureString("Test 123", fontMM));

            g.DrawString("Test 123", fontMM, new SolidBrush(Color.Black), 10, 10);
        }

Возвращает:

Scale: 1
Font: 8 Millimeters: {Width=10.99215, Height=3.797873}
Scale: 1.1
Font: 8.8 Millimeters: {Width=12.09137, Height=4.17766}
Scale: 1.2
Font: 9.6 Millimeters: {Width=13.19058, Height=4.557447}
Scale: 1.3
Font: 10.4 Millimeters: {Width=14.28979, Height=4.937234}
Scale: 1.4
Font: 11.2 Millimeters: {Width=15.38901, Height=5.317022}
Scale: 1.5
Font: 12 Millimeters: {Width=16.48823, Height=5.696809}
Scale: 1.6
Font: 12.8 Millimeters: {Width=17.58744, Height=6.076596}
Scale: 1.7
Font: 13.6 Millimeters: {Width=18.68666, Height=6.456384}

Сценарий 2 Изменяется только PageScale, но размер шрифта остается прежним. Как и ожидалось, MeasureString возвращает уменьшенный размер при увеличении масштаба, потому что текст станет меньше, поскольку шрифт остался прежним. НО - DrawString каждый раз рисует одну и ту же строку размера независимо от PageScale, если размер шрифта один и тот же !!!

private void pictureBox1_Paint(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            g.PageScale = scale;

            Console.WriteLine("Scale: " + scale);

            g.PageUnit = GraphicsUnit.Millimeter;

            Font fontMM = new Font("Calibri", 8);

            Console.WriteLine("Font: " + fontMM.Size + " Millimeters: " + g.MeasureString("Test 123", fontMM));

            g.DrawString("Test 123", fontMM, new SolidBrush(Color.Black), 10, 10);
        }

Возвращает:

Scale: 1
Font: 8 Millimeters: {Width=10.99215, Height=3.797873}
Scale: 1.1
Font: 8 Millimeters: {Width=9.992863, Height=3.452611}
Scale: 1.2
Font: 8 Millimeters: {Width=9.160125, Height=3.164894}
Scale: 1.3
Font: 8 Millimeters: {Width=8.455501, Height=2.921441}
Scale: 1.4
Font: 8 Millimeters: {Width=7.851536, Height=2.712766}
Scale: 1.5
Font: 8 Millimeters: {Width=7.3281, Height=2.531915}
Scale: 1.6
Font: 8 Millimeters: {Width=6.870094, Height=2.37367}
Scale: 1.7
Font: 8 Millimeters: {Width=6.465971, Height=2.234043}
Scale: 1.8
Font: 8 Millimeters: {Width=6.10675, Height=2.109929}
Scale: 1.9
Font: 8 Millimeters: {Width=5.785342, Height=1.99888}

Сценарий 3 Поскольку размер шрифта на экране фактически не менялся при увеличении PageScale, остается только увеличить размер шрифта, и в этом заключается проблема. Увеличение PageScale и размера шрифта на один и тот же фактор эффективно сводит на нет друг друга, поскольку, похоже, это касается MeasureString! Это означает, что MesaureString возвращает одинаковый размер независимо от размера шрифта. Однако положительным моментом является то, что нарисованная строка действительно увеличивается в размере, как и следовало ожидать при увеличении PageScale.

private void pictureBox1_Paint(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            g.PageScale = scale;

            Console.WriteLine("Scale: " + scale);

            g.PageUnit = GraphicsUnit.Millimeter;

            Font fontMM = new Font("Calibri", 8 * scale);

            Console.WriteLine("Font: " + fontMM.Size + " Millimeters: " + g.MeasureString("Test 123", fontMM));

            g.DrawString("Test 123", fontMM, new SolidBrush(Color.Black), 10, 10);
        }

Возвращает:

Scale: 1
Font: 8 Millimeters: {Width=10.99215, Height=3.797873}
Scale: 1.1
Font: 8.8 Millimeters: {Width=10.99215, Height=3.797873}
Scale: 1.2
Font: 9.6 Millimeters: {Width=10.99215, Height=3.797873}
Scale: 1.3
Font: 10.4 Millimeters: {Width=10.99215, Height=3.797873}
Scale: 1.4
Font: 11.2 Millimeters: {Width=10.99215, Height=3.797873}
Scale: 1.5
Font: 12 Millimeters: {Width=10.99215, Height=3.797873}
Scale: 1.6
Font: 12.8 Millimeters: {Width=10.99215, Height=3.797873}
Scale: 1.7
Font: 13.6 Millimeters: {Width=10.99215, Height=3.797873}
Scale: 1.8
Font: 14.4 Millimeters: {Width=10.99215, Height=3.797873}
Scale: 1.9
Font: 15.2 Millimeters: {Width=10.99215, Height=3.797873}

Заключение Насколько я могу судить, ошибка здесь кроется в DrawString, на которую, похоже, не влияет PageScale. Я ошибаюсь, думая, что это не ожидаемое поведение?!

...