Click here to Skip to main content
15,885,811 members
Please Sign up or sign in to vote.
4.00/5 (2 votes)
Hi im working on a little "lol" project that should work like a semi-simple image editor, and i am currently looking for a way to add text to a image. How ever the text is RTF encoded (to get the bold/italic etc. options) and i really wanna have this added to the program.

I've seen a few "hacks" to get around but they're absolutely not easy to use, and i would prefer to follow the "KISS Model" (Keep It Simple Stupid) if possible.

What basically happens is you can add a text to the image where i've made a little popup with the most necessary things needed (Bold/italic, font types ets.) but i have seached for hours now to find a way to convert the text ONLY (not whole RTF box) onto the picture but no luck... i've looked at stuff like this:

http://www.sitepoint.com/forums/showthread.php?676093-Font-Metrics-C[^]
http://msdn.microsoft.com/en-US/library/xwf9s90b(v=vs.80).aspx[^]
http://help.syncfusion.com/UG/Reporting/pdf/WPF/default.htm#!documents/41211drawingtext.htm[^]

and many options such as System.Windows.Forms.Control.DrawToBitmap(System.Drawing.Bitmap, System.Drawing.Rectangle) (sadly i get find this one/make it work)

none of above have worked as planned so i hope some in here may be able to help me.

Thanks A LOT in advance.

EDIT: HTML Encoding may be a option too if that is easier.
Posted
Updated 1-Oct-12 23:45pm
v3

I found this class recent extending the graphics class, i changed it a little, and it does everything i could ask for!

C#
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Drawing.Imaging;
using System.Text;

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

    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;

        public void LineSpace()
        {
            SafeNativeMethods.PARAFORMAT2 fmt = new SafeNativeMethods.PARAFORMAT2();
            fmt.cbSize = Marshal.SizeOf(fmt);
            fmt.dwMask |= SafeNativeMethods.PFM_LINESPACING | SafeNativeMethods.PFM_SPACEAFTER;

            fmt.dyLineSpacing = (int)(22 * this.SelectionFont.SizeInPoints); // in twips
            // specify exact line spacing
            fmt.bLineSpacingRule = Convert.ToByte(4);
            SafeNativeMethods.SendMessage(this.Handle, SafeNativeMethods.EM_SETPARAFORMAT, 0, ref fmt); // 0 - to all text. 1 - only to seleted
        }

        public void LineSpace(int _linespace)
        {
            SafeNativeMethods.PARAFORMAT2 fmt = new SafeNativeMethods.PARAFORMAT2();
            fmt.cbSize = Marshal.SizeOf(fmt);
            fmt.dwMask |= SafeNativeMethods.PFM_LINESPACING | SafeNativeMethods.PFM_SPACEAFTER;

            fmt.dyLineSpacing = _linespace; // in twips
            // specify exact line spacing
            fmt.bLineSpacingRule = Convert.ToByte(4);
            SafeNativeMethods.SendMessage(this.Handle, SafeNativeMethods.EM_SETPARAFORMAT, 0, ref fmt); // 0 - to all text. 1 - only to seleted
        }

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

        private void DPToHIMETRIC(Graphics graphics,ref SizeF size)
        {
            size.Width = (size.Width * 2540.0f) / graphics.DpiX;
            size.Height = (size.Height * 2540.0f) / graphics.DpiY;
        }

        public void Draw(Graphics graphics, RectangleF layoutArea, float xFactor) 
        {
            System.Diagnostics.Debug.WriteLine("LayoutArea " + layoutArea);
            
            SizeF metaSize = layoutArea.Size;
            DPToHIMETRIC(graphics, ref metaSize);

            System.Diagnostics.Debug.WriteLine("MetaSize " + metaSize);

            IntPtr hdc = graphics.GetHdc();

            //create a metafile, convert the size to himetrics
            Metafile metafile = new Metafile(hdc, new RectangleF(0,0,metaSize.Width,metaSize.Height));

            graphics.ReleaseHdc(hdc);

            Graphics g = Graphics.FromImage(metafile);
            IntPtr hDCEMF = g.GetHdc();

            //Calculate the area to render.
            SafeNativeMethods.RECT rectLayoutArea;
            rectLayoutArea.Left = 0;
            rectLayoutArea.Top = 0;
            rectLayoutArea.Right = (int)((1440 * metaSize.Width + 2540 / 2) / 2540);
            rectLayoutArea.Bottom = (int)((1440 * metaSize.Height + 2540 / 2) / 2540);

            System.Diagnostics.Debug.WriteLine(String.Format("RectLayoutArea ({0},{1})",rectLayoutArea.Right,rectLayoutArea.Bottom));

            SafeNativeMethods.FORMATRANGE fmtRange;
            fmtRange.chrg.cpMax = -1;            //Indicate character from to character to 
            fmtRange.chrg.cpMin = 0;
            fmtRange.hdc = hDCEMF;                  //Use the same DC for measuring and rendering
            fmtRange.hdcTarget = hDCEMF;         //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);
            SafeNativeMethods.SendMessage(this.Handle, SafeNativeMethods.EM_FORMATRANGE, wParam, IntPtr.Zero);

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

            //Release the device context handle obtained by a previous call
            g.ReleaseHdc(hDCEMF);
            g.Dispose();

            hdc = graphics.GetHdc();
            int nHorzSize = SafeNativeMethods.GetDeviceCaps(hdc, SafeNativeMethods.DeviceCap.HORZSIZE);
            int nVertSize = SafeNativeMethods.GetDeviceCaps(hdc, SafeNativeMethods.DeviceCap.VERTSIZE);
            int nHorzRes = SafeNativeMethods.GetDeviceCaps(hdc, SafeNativeMethods.DeviceCap.HORZRES);
            int nVertRes = SafeNativeMethods.GetDeviceCaps(hdc, SafeNativeMethods.DeviceCap.VERTRES);
            int nLogPixelsX = SafeNativeMethods.GetDeviceCaps(hdc, SafeNativeMethods.DeviceCap.LOGPIXELSX);
            int nLogPixelsY = SafeNativeMethods.GetDeviceCaps(hdc, SafeNativeMethods.DeviceCap.LOGPIXELSY);
            graphics.ReleaseHdc(hdc);

            float fHorzSizeInches = nHorzSize / 25.4f;
            float fVertSizeInches = nVertSize / 25.4f;
            float fHorzFudgeFactor = (nHorzRes / fHorzSizeInches) / nLogPixelsX;
            float fVertFudgeFactor = (nVertRes / fVertSizeInches) / nLogPixelsY;

            System.Diagnostics.Debug.WriteLine("Fudge Factor " + fHorzFudgeFactor.ToString() + " " + fVertFudgeFactor.ToString() + " XFactor " + xFactor.ToString());

            Pen RedPen = new Pen(Color.Red);
            graphics.DrawRectangle(RedPen, layoutArea.X * xFactor, layoutArea.Y * xFactor, layoutArea.Width * xFactor, layoutArea.Height * xFactor);

            float Left = layoutArea.Left;
            float Top = layoutArea.Top;
            //layoutArea.X = layoutArea.Y = 0;
            layoutArea.Offset(-Left, -Top);
            layoutArea.Offset(Left / fHorzFudgeFactor, Top / fVertFudgeFactor);

            System.Drawing.Drawing2D.GraphicsState state = graphics.Save();
            graphics.ScaleTransform(fHorzFudgeFactor * xFactor, fVertFudgeFactor * xFactor);
            graphics.DrawImage(metafile, layoutArea);
            graphics.Restore(state);


            System.Diagnostics.Debug.WriteLine("Layout Aread : "+layoutArea);
       }

        #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);
            
            [DllImport("gdi32.dll")]
            public static extern int GetDeviceCaps(IntPtr hdc, DeviceCap nIndex);

            [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
            public static extern int SendMessage(IntPtr hWnd, int msg, int wParam, ref PARAFORMAT2 lParam);

            [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 enum DeviceCap : int
            {
                /// <summary>
                /// Device driver version
                /// </summary>
                DRIVERVERSION = 0,
                /// <summary>
                /// Device classification
                /// </summary>
                TECHNOLOGY = 2,
                /// <summary>
                /// Horizontal size in millimeters
                /// </summary>
                HORZSIZE = 4,
                /// <summary>
                /// Vertical size in millimeters
                /// </summary>
                VERTSIZE = 6,
                /// <summary>
                /// Horizontal width in pixels
                /// </summary>
                HORZRES = 8,
                /// <summary>
                /// Vertical height in pixels
                /// </summary>
                VERTRES = 10,
                /// <summary>
                /// Number of bits per pixel
                /// </summary>
                BITSPIXEL = 12,
                /// <summary>
                /// Number of planes
                /// </summary>
                PLANES = 14,
                /// <summary>
                /// Number of brushes the device has
                /// </summary>
                NUMBRUSHES = 16,
                /// <summary>
                /// Number of pens the device has
                /// </summary>
                NUMPENS = 18,
                /// <summary>
                /// Number of markers the device has
                /// </summary>
                NUMMARKERS = 20,
                /// <summary>
                /// Number of fonts the device has
                /// </summary>
                NUMFONTS = 22,
                /// <summary>
                /// Number of colors the device supports
                /// </summary>
                NUMCOLORS = 24,
                /// <summary>
                /// Size required for device descriptor
                /// </summary>
                PDEVICESIZE = 26,
                /// <summary>
                /// Curve capabilities
                /// </summary>
                CURVECAPS = 28,
                /// <summary>
                /// Line capabilities
                /// </summary>
                LINECAPS = 30,
                /// <summary>
                /// Polygonal capabilities
                /// </summary>
                POLYGONALCAPS = 32,
                /// <summary>
                /// Text capabilities
                /// </summary>
                TEXTCAPS = 34,
                /// <summary>
                /// Clipping capabilities
                /// </summary>
                CLIPCAPS = 36,
                /// <summary>
                /// Bitblt capabilities
                /// </summary>
                RASTERCAPS = 38,
                /// <summary>
                /// Length of the X leg
                /// </summary>
                ASPECTX = 40,
                /// <summary>
                /// Length of the Y leg
                /// </summary>
                ASPECTY = 42,
                /// <summary>
                /// Length of the hypotenuse
                /// </summary>
                ASPECTXY = 44,
                /// <summary>
                /// Shading and Blending caps
                /// </summary>
                SHADEBLENDCAPS = 45,

                /// <summary>
                /// Logical pixels inch in X
                /// </summary>
                LOGPIXELSX = 88,
                /// <summary>
                /// Logical pixels inch in Y
                /// </summary>
                LOGPIXELSY = 90,

                /// <summary>
                /// Number of entries in physical palette
                /// </summary>
                SIZEPALETTE = 104,
                /// <summary>
                /// Number of reserved entries in palette
                /// </summary>
                NUMRESERVED = 106,
                /// <summary>
                /// Actual color resolution
                /// </summary>
                COLORRES = 108,

                // Printing related DeviceCaps. These replace the appropriate Escapes
                /// <summary>
                /// Physical Width in device units
                /// </summary>
                PHYSICALWIDTH = 110,
                /// <summary>
                /// Physical Height in device units
                /// </summary>
                PHYSICALHEIGHT = 111,
                /// <summary>
                /// Physical Printable Area x margin
                /// </summary>
                PHYSICALOFFSETX = 112,
                /// <summary>
                /// Physical Printable Area y margin
                /// </summary>
                PHYSICALOFFSETY = 113,
                /// <summary>
                /// Scaling factor x
                /// </summary>
                SCALINGFACTORX = 114,
                /// <summary>
                /// Scaling factor y
                /// </summary>
                SCALINGFACTORY = 115,

                /// <summary>
                /// Current vertical refresh rate of the display device (for displays only) in Hz
                /// </summary>
                VREFRESH = 116,
                /// <summary>
                /// Horizontal width of entire desktop in pixels
                /// </summary>
                DESKTOPVERTRES = 117,
                /// <summary>
                /// Vertical height of entire desktop in pixels
                /// </summary>
                DESKTOPHORZRES = 118,
                /// <summary>
                /// Preferred blt alignment
                /// </summary>
                BLTALIGNMENT = 119
            }

            public struct PARAFORMAT2
            {

                public int cbSize;

                public uint dwMask;

                public short wNumbering;

                public short wReserved;

                public int dxStartIndent;

                public int dxRightIndent;

                public int dxOffset;

                public short wAlignment;

                public short cTabCount;

                [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]

                public int[] rgxTabs;

                // PARAFORMAT2 from here onwards.

                public int dySpaceBefore;

                public int dySpaceAfter;

                public int dyLineSpacing;

                public short sStyle;

                public byte bLineSpacingRule;

                public byte bOutlineLevel;

                public short wShadingWeight;

                public short wShadingStyle;

                public short wNumberingStart;

                public short wNumberingStyle;

                public short wNumberingTab;

                public short wBorderSpace;

                public short wBorderWidth;

                public short wBorders;

            }

            public const int EM_FORMATRANGE = WM_USER + 57;

            public const int PFM_SPACEAFTER = 128;

            public const int PFM_LINESPACING = 256;

            public const int EM_SETPARAFORMAT = 1095;

            public const int WM_USER = 0x0400;

            public const int WS_EX_TRANSPARENT = 0x20;

        }
        #endregion
    }
}
 
Share this answer
 
v2
Comments
Member 2147446 20-Nov-15 4:02am    
will you plz show hoew to use it
You need the Graphics.DrawString method. You can get the Graphics object by Graphics.FromImage. And then draw all the text your self...
 
Share this answer
 
Comments
Jackie00100 2-Oct-12 5:18am    
I know but the DrawString only accept one font type/style at the time, i want to write the whole thing without the white background from the control.
Bernhard Hiller 2-Oct-12 5:20am    
Exactly: step by step!
Jackie00100 2-Oct-12 5:32am    
Ow god, so I'm really in need to calculate the font size and line length for every word? That is exactly what i would like to avoid if there was a easier way around...
koul.bhawna 7-May-13 14:08pm    
I need to write rtf data to image and used this solution but it trims the text on right hand side of the layout area as well as on the bottom. Can you help!
There are two options here:


  • You can write the text to a RichTextBox and then screen capture it with Control.DrawToBitmap[^]. You probably need to post-process the Bitmap you get to sort out the transparency.
  • You can write a miniature RTF (or HTML or other tagging system; RTF is pretty disgusting to type) interpreter to break the text up into sections, and then use Graphics.DrawString and Graphics.MeasureCharacterRanges to write the text onto your target image.


The second will be cleaner and will also give you high quality anti-aliasing, the ability to draw rotated text etc, so I recommend that approach.
 
Share this answer
 
Comments
Jackie00100 2-Oct-12 7:22am    
Thanks for your help, can you be so kind to provide some sample code or links to both of them? i've seen the DrawToBitmap but i can't find the refference to it for some reason, im using 4.0

The 2nd one have i looked on too but it seems to have a lot of work to it (i might be wrong)
BobJanova 2-Oct-12 8:11am    
I don't have a code example for the first way, but it's fairly simple:
Bitmap b = new Bitmap(control.Width, control.Height);
control.DrawToBitmap(b, control.Bounds)

I linked to that method in my answer already :-).

The second way is a moderate amount of work.
Jackie00100 2-Oct-12 8:51am    
I'll try and see how it goes thanks, more ideas are welcome.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900