How to save drew Graphics of “Paint()” into image

2020-02-07 08:15发布

I actually wanted to Convert RTF into Image so after googling a lot I've got a code that does it by Paint() Event of Picturebox1 and it works perfectly :

private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
    e.Graphics.Clear(richTextBox1.BackColor);
    e.Graphics.DrawRtfText(this.richTextBox1.Rtf,  this.pictureBox1.ClientRectangle);            

    base.OnPaint(e);

    // below code just create an empty image file
    Bitmap newBitmap = new Bitmap(pictureBox1.Width, pictureBox1.Height);
    e.Graphics.DrawImage(newBitmap, new Rectangle(0, 0, pictureBox1.Width, pictureBox1.Height), new Rectangle(0, 0, pictureBox1.Width, pictureBox1.Height), GraphicsUnit.Pixel);
    newBitmap.Save(@"c:\adv.jpg");
}

My App

in the picture above the left is my richTextBox and the right is a Picturebox.

the ISSUE is I don't know how to save Paint() drew graphic into a file because the 3 last lines of my code just save an empty image.

UPDATE #1:

g.SmoothingMode = SmoothingMode.AntiAlias;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;

g.Clear(richTextBox1.BackColor);
g.DrawRtfText(this.richTextBox1.Rtf, this.pictureBox1.ClientRectangle);

by changing the graphics from e.graphics to g the issue is resolved but with one other issue that the quality of bitmap is too low. I've Added this bunch of code but I've got same result, the quality is too low! Any suggestions?

UPDATE #2

here is the Graphics_DrawRtfText class that does the conversion :

public static class Graphics_DrawRtfText
{
    private static RichTextBoxDrawer rtfDrawer;
    public static void DrawRtfText(this Graphics graphics, string rtf, Rectangle layoutArea)
    {
        if (Graphics_DrawRtfText.rtfDrawer == null)
        {
            Graphics_DrawRtfText.rtfDrawer = new RichTextBoxDrawer();
        }
        Graphics_DrawRtfText.rtfDrawer.Rtf = rtf;
        Graphics_DrawRtfText.rtfDrawer.Draw(graphics, layoutArea);
    }

    private class RichTextBoxDrawer : RichTextBox
    {
        //Code converted from code found here: http://support.microsoft.com/kb/812425/en-us

        //Convert the unit used by the .NET framework (1/100 inch) 
        //and the unit used by Win32 API calls (twips 1/1440 inch)
        private const double anInch = 14.4;

        protected override CreateParams CreateParams
        {
            get
            {
                CreateParams createParams = base.CreateParams;
                if (SafeNativeMethods.LoadLibrary("msftedit.dll") != IntPtr.Zero)
                {
                    createParams.ExStyle |= SafeNativeMethods.WS_EX_TRANSPARENT; // transparent
                    createParams.ClassName = "RICHEDIT50W";
                }
                return createParams;
            }
        }
        public void Draw(Graphics graphics, Rectangle layoutArea)
        {
            //Calculate the area to render.
            SafeNativeMethods.RECT rectLayoutArea;
            rectLayoutArea.Top = (int)(layoutArea.Top * anInch);
            rectLayoutArea.Bottom = (int)(layoutArea.Bottom * anInch);
            rectLayoutArea.Left = (int)(layoutArea.Left * anInch);
            rectLayoutArea.Right = (int)(layoutArea.Right * anInch);

            IntPtr hdc = graphics.GetHdc();

            SafeNativeMethods.FORMATRANGE fmtRange;
            fmtRange.chrg.cpMax = -1;                    //Indicate character from to character to 
            fmtRange.chrg.cpMin = 0;
            fmtRange.hdc = hdc;                                //Use the same DC for measuring and rendering
            fmtRange.hdcTarget = hdc;                    //Point at printer hDC
            fmtRange.rc = rectLayoutArea;            //Indicate the area on page to print
            fmtRange.rcPage = rectLayoutArea;    //Indicate size of page

            IntPtr wParam = IntPtr.Zero;
            wParam = new IntPtr(1);

            //Get the pointer to the FORMATRANGE structure in memory
            IntPtr lParam = IntPtr.Zero;
            lParam = Marshal.AllocCoTaskMem(Marshal.SizeOf(fmtRange));
            Marshal.StructureToPtr(fmtRange, lParam, false);

            SafeNativeMethods.SendMessage(this.Handle, SafeNativeMethods.EM_FORMATRANGE, wParam, lParam);

            //Free the block of memory allocated
            Marshal.FreeCoTaskMem(lParam);

            //Release the device context handle obtained by a previous call
            graphics.ReleaseHdc(hdc);
        }

        #region SafeNativeMethods
        private static class SafeNativeMethods
        {
            [DllImport("USER32.dll")]
            public static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);

            [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
            public static extern IntPtr LoadLibrary(string lpFileName);

            [StructLayout(LayoutKind.Sequential)]
            public struct RECT
            {
                public int Left;
                public int Top;
                public int Right;
                public int Bottom;
            }

            [StructLayout(LayoutKind.Sequential)]
            public struct CHARRANGE
            {
                public int cpMin;        //First character of range (0 for start of doc)
                public int cpMax;        //Last character of range (-1 for end of doc)
            }

            [StructLayout(LayoutKind.Sequential)]
            public struct FORMATRANGE
            {
                public IntPtr hdc;                //Actual DC to draw on
                public IntPtr hdcTarget;    //Target DC for determining text formatting
                public RECT rc;                        //Region of the DC to draw to (in twips)
                public RECT rcPage;                //Region of the whole DC (page size) (in twips)
                public CHARRANGE chrg;        //Range of text to draw (see earlier declaration)
            }

            public const int WM_USER = 0x0400;
            public const int EM_FORMATRANGE = WM_USER + 57;
            public const int WS_EX_TRANSPARENT = 0x20;

        }
        #endregion
    }
}

2条回答
干净又极端
2楼-- · 2020-02-07 08:29

Disclaimer: I don't have the time to dig into the posted extension method but it is interesting and works well, at least when drawing onto a control surface.

But I could reproduce how bad the results are when drawing into a bitmap..

But: When done right the saved results are excellent!

So here here are a few things to keep in mind:

  • Saving in the Paint event is a bad idea, as this event will be triggered by the system whenever it needs to redraw the control; test by doing a minimize/maximize cycle.

  • In addition the DrawRtfText semms to create a double-vision effect when drawing into a bitmap.

  • So make sure you use DrawToBitmap to grab the results. For this you need to place the call to DrawRtfText in the Paint event of a control!

  • Also make sure to have large enough resolutions both in the control (pixel size) and the Bitmap (dpi) to get nice, crispy and (if needed) printable results.

  • Do not save to jpg as this is bound to result in blurry text! Png is the format of choice!

Here is a Paint event:

private void panel1_Paint(object sender, PaintEventArgs e)
{
    e.Graphics.Clear(richTextBox1.BackColor);
    e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
    e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
    Padding pad = new Padding(120, 230, 10, 30);  // pick your own numbers!
    Size sz = panel1.ClientSize;
    Rectangle rect = new Rectangle(pad.Left, pad.Top, 
                                   sz.Width - pad.Horizontal, sz.Height - pad.Vertical);
    e.Graphics.DrawRtfText(this.richTextBox1.Rtf, rect);            

}

Note that it pays to improve on the default quality settings; if you don't the text in the resulting file will break apart when zooming in..

Here is a Save button click:

private void button1_Click(object sender, EventArgs e)
{
    Size sz = panel1.ClientSize;

    // first we (optionally) create a bitmap in the original panel size:
    Rectangle rect1 = panel1.ClientRectangle;
    Bitmap bmp = new Bitmap(rect1.Width, rect1.Height);
    panel1.DrawToBitmap(bmp, rect);
    bmp.Save("D:\\rtfImage1.png", ImageFormat.Png);

    // now we create a 4x larger one:
    Rectangle rect2 = new Rectangle(0, 0, sz.Width * 4, sz.Height * 4);
    Bitmap bmp2 = new Bitmap(rect2.Width, rect2.Height);

    // we need to temporarily enlarge the panel:
    panel1.ClientSize = rect2.Size;

    // now we can let the routine draw
    panel1.DrawToBitmap(bmp2, rect2);
    // and before saving we optionally can set the dpi resolution
    bmp2.SetResolution(300, 300);

    // optionally make background transparent:
    bmp2.MakeTransparent(richTextBox1.BackColor);
    UnSemi(bmp2);  // see the link in the comment!

    // save text always as png; jpg is only for fotos!
    bmp2.Save("D:\\rtfImage2.png", ImageFormat.Png);

    // restore the panels size
    panel1.ClientSize = sz;
}

I found the result to be really good.

Note that DrawToBitmap will internally trigger the Paint event to grab the drawn graphics.

Of course you don't need both parts - use only the one you want (.e. skip the 1st part, between first and now ) and do use your own numbers. It helps to know what the output shall be and calculate the necessary sizes and resolutions backward from there.

I added the enlarged version because usually the monitor resolution, which is what the controls all have, is rather limited, around 75-100dpi, while print quality starts only at 150dpi..

enter image description hereenter image description here

Here is a link to the UnSemi function

查看更多
Ridiculous、
3楼-- · 2020-02-07 08:39

Your code produces an empty image file because you are not drawing anything onto 'newBitmap'.

If you want to draw anything onto 'newBitmap' you need to create a Graphics object from it. As I do not know where 'DrawRtfText' comes from and how it works my guess would be:

private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
        //... 
        Bitmap newBitmap = new Bitmap(pictureBox1.Width, pictureBox1.Height);
        Graphics g = Graphics.FromImage(newBitmap);
        g.DrawRtfText(this.richTextBox1.Rtf, this.pictureBox1.ClientRectangle);
        newBitmap.Save(@"d:\adv.jpg");
}
查看更多
登录 后发表回答