以编程方式向RTF文档添加图像

31

我正在尝试向创建的RTF文档中添加一张图片。我不想使用“复制/粘贴”方法(包括将图片粘贴到RichTextBox中并访问.RTF属性),因为这将清除剪贴板,这对我的终端用户来说是一个麻烦和困惑。

到目前为止,我所拥有的代码返回需要插入到RTF文档中以打印图像的字符串。输入的图像(位于$path)通常以bmp或jpeg格式存储,但在此阶段,我不关心图像在RTF中的存储方式,只要能使它正常工作即可。

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
}

然而问题在于,据我观察,字符串str没有正确的字符串转换以使其在RTF中运行。

编辑:我的问题是@"\hex "后面缺少一个空格,并且未从BitConverter返回的值中去除"-"字符。

5个回答

35

可以尝试以下链接:

你必须将"picwgoa"改为"picwgoal",并将"pichgoa"改为"pichgoal"。

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

以下是支持的图像格式列表:

\emfblip      图片来源为EMF(增强型元文件)。
\pngblip      图片来源为PNG。
\jpegblip     图片来源为JPEG。
\shppict      指定Word 97-2000图片。这是一个目标控制字。
\nonshppict   指定Word 97-2000写入了一个{\pict}目标,但在输入时不会读取它。此关键字用于与其他阅读器的兼容性。
\macpict      图片来源为QuickDraw。
\pmmetafileN  图片来源为OS/2元文件。N参数标识元文件类型。N值在下面的\pmmetafile表中进行描述。
\wmetafileN   图片来源为Windows元文件。N参数标识元文件类型(默认为1)。
\dibitmapN    图片来源为Windows设备无关位图。N参数标识位图类型(必须等于0)。从Windows设备无关位图中包括在RTF中的信息是BITMAPINFO结构的串联,后跟实际像素数据。
\wbitmapN     图片来源为Windows设备依赖位图。N参数标识位图类型(必须等于0)。从Windows设备依赖位图中包括在RTF中的信息是GetBitmapBits函数的结果。

以上 RTF 字符串(图像)在 Winform RTF 中无法识别。有人知道如何处理 Winform RTF 吗? - JharPaat

21

花了一天时间在谷歌上找答案。从stackoverflow和其他来源汇集了碎片信息。将图像传递给它,它将返回你需要添加到richtextbox.rtf扩展的字符串。图像宽度会发生变化,需要计算,公式已给出。

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

VS 2019,在WinForms RichTextbox和Wordpad中运行得非常好...完美! - DAG

6

这个页面的后续访问者(比如我几天前)可能会发现以下链接有用:使用.NET将图像转换为WMF

你会发现WordPad忽略任何未存储在正确Windows MetaFile格式中的图像。因此,即使在OpenOffice和Word本身中可以正常工作,但本页上的先前示例根本不会显示出来。WordPad支持的格式是:

{/pict/wmetafile8/picw[width]/pich[height]/picwgoal[scaledwidth]/pichgoal[scaledheight] [image-as-string-of-byte-hex-values]}(用方括号替换相应数据)。

获取“适当的数据”可以通过遵循上述链接中的过程完成。对于那些寻找解决方案的Python用户,这里是一个开始(我认为仍然存在一些dpi/缩放问题)。这需要PIL(或Pillow),ctypesclr(Python .NET)。首先使用PIL/Pillow打开图像。这里我将其命名为“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)

3

我合并了RRUZ和JiffyWhip的答案,并清理了代码。

现在,您可以插入PNG或WMF格式的图像。

我还将透明度替换为白色,因为WMF不支持透明度。

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

2
我发现大多数RTF框使用以下格式:
{\object\objemb{\*\objclass Paint.Picture}\objw2699\objh4799{\*\objdata [hex/bin]}}

[hex/bin]是大量表示图像格式的十六进制字符串时,这种方式适用于Word rtf和RTF框,因此更有效。
在我的电脑上,180x320像素大小的图像被转换为2699x4799 twips,这意味着1像素=15 twips。据我所见,在我测试过的3台计算机中(其中包括WinXP prof、WinXP home和Win 7),情况都是如此。

1
澄清一下,“twip是点的1/20”(来源:http://msdn.microsoft.com/en-us/library/aa140283%28office.10%29.aspx)。 - 0b10011
4
“Twips = Pixels / 96 * 1440” 这个公式中的因子15来自于图像通常是96点每英寸,而一个twip是1/1440英寸。 - Jens

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接