Programmatically adding Images to RTF Document
Asked Answered
I

5

33

I am trying to add a image to a RTF document which I am creating. I would prefer to not use 'copy/paste' methods (that involve pasting the image within a RichTextBox and then accessing the .RTF property) which purge the clipboard (as this will be a bother and confusion for my end users).

The code I have so far returns the string that needs to be inserted into the RTF document to print the image. The inputted image (located at $path) is usually in bmp or jpeg format, but at this stage I am not concerned with how the image is stored within the RTF only that i can get it to work.

public string GetImage(string path, int width, int height)
{
    MemoryStream stream = new MemoryStream();
    string newPath = Path.Combine(Environment.CurrentDirectory, path);
    Image img = Image.FromFile(newPath);
    img.Save(stream, System.Drawing.Imaging.ImageFormat.Png);

    byte [] bytes = stream.ToArray();

    string str = BitConverter.ToString(bytes, 0).Replace("-", string.Empty);
    //string str = System.Text.Encoding.UTF8.GetString(bytes);

    string mpic = @"{\pict\pngblip\picw" + 
        img.Width.ToString() + @"\pich" + img.Height.ToString() +
        @"\picwgoa" + width.ToString() + @"\pichgoa" + height.ToString() + 
        @"\hex " + str + "}";
    return mpic
}

However the problem is that this code does not work because as far as I can tell, the string str does not have the correct string conversion to work within the RTF.

Edit: My problem was missing a space after the \hex in @"\hex " and also not stripping out the "-" characters from the returned value of the BitConverter

Impasse answered 29/9, 2009 at 5:38 Comment(0)
P
37

try these links

you must change "picwgoa" to "picwgoal" and "pichgoa" to "pichgoal"

string mpic = @"{\pict\pngblip\picw" + 
    img.Width.ToString() + @"\pich" + img.Height.ToString() +
    @"\picwgoal" + width.ToString() + @"\pichgoal" + height.ToString() + 
    @"\bin " + str + "}";

Here you have a list of the supported image formats

\emfblip      Source of the picture is an EMF (enhanced metafile).
\pngblip      Source of the picture is a PNG.
\jpegblip     Source of the picture is a JPEG.
\shppict      Specifies a Word 97-2000 picture. This is a destination control word.
\nonshppict   Specifies that Word 97-2000 has written a {\pict destination that it will not read on input. This keyword is for compatibility with other readers.
\macpict      Source of the picture is QuickDraw.
\pmmetafileN  Source of the picture is an OS/2 metafile. The N argument identifies the metafile type. The N values are described in the \pmmetafile table below.
\wmetafileN   Source of the picture is a Windows metafile. The N argument identifies the metafile type (the default is 1).
\dibitmapN    Source of the picture is a Windows device-independent bitmap. The N argument identifies the bitmap type (must equal 0).The information to be included in RTF from a Windows device-independent bitmap is the concatenation of the BITMAPINFO structure followed by the actual pixel data.    
\wbitmapN     Source of the picture is a Windows device-dependent bitmap. The N argument identifies the bitmap type (must equal 0).The information to be included in RTF from a Windows device-dependent bitmap is the result of the GetBitmapBits function.
Patsy answered 29/9, 2009 at 5:57 Comment(1)
above rtf string (of image) is not recognized in Winform Rtf. Does anyone has idea how to proceed for Winform RTF ?Geomancy
S
22

Spent a day or so googling answers for this. Pieced together tidbits from all over stackoverflow and other sources. Feed this an image, it'll return the string you need to add to your richtextbox.rtf extension. The image width changes and needs to be calculated, the formula is given.

    // RTF Image Format
    // {\pict\wmetafile8\picw[A]\pich[B]\picwgoal[C]\pichgoal[D]
    //  
    // A    = (Image Width in Pixels / Graphics.DpiX) * 2540 
    //  
    // B    = (Image Height in Pixels / Graphics.DpiX) * 2540 
    //  
    // C    = (Image Width in Pixels / Graphics.DpiX) * 1440 
    //  
    // D    = (Image Height in Pixels / Graphics.DpiX) * 1440 

    [Flags]
    enum EmfToWmfBitsFlags
    {
        EmfToWmfBitsFlagsDefault = 0x00000000,
        EmfToWmfBitsFlagsEmbedEmf = 0x00000001,
        EmfToWmfBitsFlagsIncludePlaceable = 0x00000002,
        EmfToWmfBitsFlagsNoXORClip = 0x00000004
    }

    const int MM_ISOTROPIC = 7;
    const int MM_ANISOTROPIC = 8;

    [DllImport("gdiplus.dll")]
    private static extern uint GdipEmfToWmfBits(IntPtr _hEmf, uint _bufferSize,
        byte[] _buffer, int _mappingMode, EmfToWmfBitsFlags _flags);
    [DllImport("gdi32.dll")]
    private static extern IntPtr SetMetaFileBitsEx(uint _bufferSize,
        byte[] _buffer);
    [DllImport("gdi32.dll")]
    private static extern IntPtr CopyMetaFile(IntPtr hWmf,
        string filename);
    [DllImport("gdi32.dll")]
    private static extern bool DeleteMetaFile(IntPtr hWmf);
    [DllImport("gdi32.dll")]
    private static extern bool DeleteEnhMetaFile(IntPtr hEmf);

        public static string GetEmbedImageString(Bitmap image)
        {
                Metafile metafile = null;
                float dpiX; float dpiY;

                using (Graphics g = Graphics.FromImage (image)) 
                {
                    IntPtr hDC = g.GetHdc ();
                    metafile = new Metafile (hDC, EmfType.EmfOnly);
                    g.ReleaseHdc (hDC);
                }

                using (Graphics g = Graphics.FromImage (metafile)) 
                {
                    g.DrawImage (image, 0, 0);
            dpiX = g.DpiX;
            dpiY = g.DpiY;
                }

                IntPtr _hEmf = metafile.GetHenhmetafile ();
                uint _bufferSize = GdipEmfToWmfBits (_hEmf, 0, null, MM_ANISOTROPIC,
                EmfToWmfBitsFlags.EmfToWmfBitsFlagsDefault);
                byte[] _buffer = new byte[_bufferSize];
                GdipEmfToWmfBits (_hEmf, _bufferSize, _buffer, MM_ANISOTROPIC,
                                            EmfToWmfBitsFlags.EmfToWmfBitsFlagsDefault);
                IntPtr hmf = SetMetaFileBitsEx (_bufferSize, _buffer);
                string tempfile = Path.GetTempFileName ();
                CopyMetaFile (hmf, tempfile);
                DeleteMetaFile (hmf);
                DeleteEnhMetaFile (_hEmf);

                var stream = new MemoryStream ();
                byte[] data = File.ReadAllBytes (tempfile);
                //File.Delete (tempfile);
                int count = data.Length;
                stream.Write (data, 0, count);

                string proto = @"{\rtf1{\pict\wmetafile8\picw" + (int)( ( (float)image.Width / dpiX ) * 2540 )
                                  + @"\pich" + (int)( ( (float)image.Height / dpiY ) * 2540 )
                                  + @"\picwgoal" + (int)( ( (float)image.Width / dpiX ) * 1440 )
                                  + @"\pichgoal" + (int)( ( (float)image.Height / dpiY ) * 1440 )
                                  + " " 
                      + BitConverter.ToString(stream.ToArray()).Replace("-", "")
                                  + "}}";                   
                return proto;
        }
Sparkie answered 3/12, 2014 at 21:13 Comment(1)
VS 2019, works great in WinForms RichTextbox and in Wordpad... perfect!Exophthalmos
S
6

Later visitors to this page (such as I was a few days ago) may find the following link useful: Convert an image into WMF with .NET

One will find that WordPad ignores any image not stored in the proper Windows MetaFile format. Thus, the previous example on this page will not show up at all (Even though it works just fine in OpenOffice and Word itself). The format that WordPad WILL support is:

{/pict/wmetafile8/picw[width]/pich[height]/picwgoal[scaledwidth]/pichgoal[scaledheight] [image-as-string-of-byte-hex-values]} (with terms in square brackets replaced with the appropriate data).

Getting the 'appropriate data' can be done by following the procedures in the above link. For those with python looking for a solution, here is the start of one (I believe some dpi/scaling issues remain). This requires PIL(or Pillow), ctypes, and clr (Python .NET). Use PIL/Pillow and open up the image first. Here I have it open as "canv":

from ctypes import *
import clr
clr.AddReference("System.IO")
clr.AddReference("System.Drawing")
from System import IntPtr
from System.Drawing import SolidBrush
from System.Drawing import Color
from System.Drawing import Imaging
from System.Drawing import Graphics
from System.IO import FileStream
from System.IO import FileMode
from System.IO import MemoryStream
from System.IO import File

def byte_to_hex(bytefile):
  acc = ''
  b = bytefile.read(1)
  while b:
    acc+=("%02X" % ord(b))
    b = bytefile.read(1)
  return acc.strip()

#... in here is some code where 'canv' is created as the PIL image object, and
#...   'template' is defined as a string with placeholders for picw, pich, 
#...   picwgoal, pichgoal, and the image data


mfstream     = MemoryStream()
offscrDC     = Graphics.FromHwndInternal(IntPtr.Zero)
imgptr       = offscrDC.GetHdc()
mfile        = Imaging.Metafile(mfstream, imgptr, Imaging.EmfType.EmfOnly)
gfx          = Graphics.FromImage(mfile)
width,height = canv.size
pixels       = canv.load()
for x in range(width):
  for y in range(height):
    _r,_g,_b = pixels[x, y]
    c     = Color.FromArgb(_r, _g, _b)
    brush = SolidBrush(c)
    gfx.FillRectangle(brush, x, y, 1, 1)
gfx.Dispose()
offscrDC.ReleaseHdc()
_hEmf            = mfile.GetHenhmetafile()
GdipEmfToWmfBits = windll.gdiplus.GdipEmfToWmfBits
_bufferSize      = GdipEmfToWmfBits(
                      int(str(_hEmf)),
                      c_uint(0),
                      None,
                      c_int(8),           # MM_ANISOTROPIC
                      c_uint(0x00000000)) # Default flags
_buffer = c_int * _bufferSize
_buffer = _buffer(*[0 for x in range(_bufferSize)])
GdipEmfToWmfBits( int(str(_hEmf)),
                  c_uint(_bufferSize),
                  _buffer,
                  c_int(8),            # MM_ANISOTROPIC
                  c_uint(0x00000000) ) # Default flags
hmf = windll.gdi32.SetMetaFileBitsEx(c_uint(_bufferSize), _buffer)
windll.gdi32.CopyMetaFileA(int(str(hmf)), "temp.wmf")
windll.gdi32.DeleteMetaFile(int(str(hmf)))
windll.gdi32.DeleteEnhMetaFile(int(str(_hEmf)))
mfstream.Close()

imgstr = open("temp.wmf", 'rb')
imgstr = byte_to_hex(imgstr)
with open('script-out.rtf','wb') as outf:
  template = template % (str(_cx),str(_cy),str(15*_cx),str(15*_cy),imgstr)
  outf.write(template)
Salientian answered 7/9, 2014 at 8:40 Comment(0)
S
3

I merged answers from RRUZ and JiffyWhip and cleaned up the code.

Now you can insert an image as PNG or as WMF.

I also replaced transparency with white color, because WMF doesn't support opacity.

private static string GetWidthAndHeight(Image image, float dpiX, float dpiY)
{
    float width = (float)image.Width / dpiX;
    float height = (float)image.Height / dpiY;

    int picw = (int)(width * 2540);
    int pich = (int)(height * 2540);

    int picwgoal = (int)(width * 1440);
    int pichgoal = (int)(height * 1440);

    return "\\picw" + picw + "\\pich" + pich + "\\picwgoal" + picwgoal + "\\pichgoal" + pichgoal;
}

public static string InsertPngImage(Image image)
{
    byte[] buffer;

    using (var stream = new MemoryStream())
    {
        image.Save(stream, ImageFormat.Png);
        buffer = stream.ToArray();
    }

    string hex = BitConverter.ToString(buffer, 0).Replace("-", string.Empty);

    string widthAndHeight = GetWidthAndHeight(image, image.HorizontalResolution, image.VerticalResolution);

    return "{\\pict\\pngblip" + widthAndHeight + " " + hex + "}";
}

[Flags]
enum EmfToWmfBitsFlags
{
    EmfToWmfBitsFlagsDefault = 0x00000000,
    EmfToWmfBitsFlagsEmbedEmf = 0x00000001,
    EmfToWmfBitsFlagsIncludePlaceable = 0x00000002,
    EmfToWmfBitsFlagsNoXORClip = 0x00000004
}

private static int MM_ANISOTROPIC = 8;

[DllImportAttribute("gdiplus.dll")]
private static extern uint GdipEmfToWmfBits(IntPtr hEmf, uint bufferSize, byte[] buffer, int mappingMode, EmfToWmfBitsFlags flags);

public static string InsertWmfImage(Image image)
{
    image = ReplaceTransparency(image, Color.White);

    Metafile metaFile;
    float dpiX;
    float dpiY;

    using (Graphics graphics = Graphics.FromImage(image))
    {
        IntPtr hdc = graphics.GetHdc();
        metaFile = new Metafile(hdc, EmfType.EmfOnly);
        graphics.ReleaseHdc(hdc);
    }

    using (Graphics graphics = Graphics.FromImage(metaFile))
    {
        graphics.DrawImage(image, 0, 0, image.Width, image.Height);
        dpiX = graphics.DpiX;
        dpiY = graphics.DpiY;
    }

    IntPtr hEmf = metaFile.GetHenhmetafile();

    uint bufferSize = GdipEmfToWmfBits(hEmf, 0, null, MM_ANISOTROPIC, EmfToWmfBitsFlags.EmfToWmfBitsFlagsDefault);

    byte[] buffer = new byte[bufferSize];

    uint convertedSize = GdipEmfToWmfBits(hEmf, bufferSize, buffer, MM_ANISOTROPIC, EmfToWmfBitsFlags.EmfToWmfBitsFlagsDefault);

    string hex = BitConverter.ToString(buffer, 0).Replace("-", string.Empty);

    string widthAndHeight = GetWidthAndHeight(image, dpiX, dpiY);

    return "{\\pict\\wmetafile8" + widthAndHeight + " " + hex + "}";
}

private static Image ReplaceTransparency(Image image, Color background)
{
    Bitmap bitmap = new Bitmap(image.Width, image.Height, PixelFormat.Format24bppRgb);

    using (Graphics graphics = Graphics.FromImage(bitmap))
    {
        graphics.Clear(background);
        graphics.CompositingMode = CompositingMode.SourceOver;
        graphics.DrawImage(image, 0, 0, image.Width, image.Height);
    }

    return bitmap;
}
Surbeck answered 31/1, 2020 at 17:7 Comment(0)
R
2

I found that most of RTF boxes using the following format:

{\object\objemb{\*\objclass Paint.Picture}\objw2699\objh4799{\*\objdata [hex/bin]}}

When [hex/bin] is large amount of hex strings representing the image format. This way working both for Word rtf, and both for RTF box - so it is more efficient.

In my computer image in a size of 180x320 pixels been converted to 2699x4799 twips, which means 1pix=15 twips, as far as I can see, it is like this in 3 computers that I've been testing, between them WinXP prof, WinXP home and Win 7.

Regret answered 2/8, 2013 at 2:28 Comment(2)
To clarify, "a twip is one-twentieth of a point" (source).Clincher
Twips = Pixels / 96 * 1440 is were the factor 15 comes from, since an image is usually 96 dots-per-inch and a twip is 1/1440th inch.Glassman

© 2022 - 2024 — McMap. All rights reserved.