读取图片文件MetaFile放入Windows剪切板

前言

前段时间群里有个小伙在工作中遇到一个问题,透明的图片存入剪切板在粘贴到adobe PDF中出现不透明问题但是粘贴到Excel可以,还有就是从excel复制再粘贴到PDF也是可以。小伙在群里发了两天都没有解决,当时看到这个问题感觉很有趣我就去尝试了一下,当时我用的WPS,我试了一下可以粘贴到WPS 打开的PDF中,当时我感觉是PDF编辑器的问题,建议小伙换个,小伙说不能换客户要求必须是这个,好的嘛那就开始搞

号脉

我打开两个神级IDE VS 分别获取一下从excel复制之后和通过小伙程序复制之后存到剪切板中的数据,我对比了一下剪切板中的确有很多的不同,那就开始尝试是因啥不同导致的,我用程序把从Excel中复制获取的数据按照参数对象一个一个从新清空剪切板在重新放入剪切板,尝试是哪个参数对象导致的PDF无法呈现预想的结果。最后确定是因为剪切板中没有存储MetafilePict数据导致的

开药

下面获取图片文件的MetaFile数据

public Metafile GetGeometryMetafile(Bitmap bitmap)
  	{

  		Metafile metafile;
  		using (MemoryStream stream = new MemoryStream())
  		using (Graphics rtfBoxGraphics = Graphics.FromImage(bitmap))
  		{
  			IntPtr pDeviceContext = rtfBoxGraphics.GetHdc();

  			metafile = new Metafile(stream, pDeviceContext);
  			using (Graphics imageGraphics = Graphics.FromImage(metafile))
  			{
  				//imageGraphics.DrawImage(bitmap, new Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height)); 
  				imageGraphics.DrawImageUnscaled(bitmap, new Rectangle(0, 0, bitmap.Width, bitmap.Height));
  			}
  			rtfBoxGraphics.ReleaseHdc(pDeviceContext);
  		}
  		return metafile;


  	}

吃药

下面把获取的文件MetaFile放入剪切板
因为特殊原因,需要调用系统dll进行剪切板操作

internal static class ClipboardMetafileHelper
		{

			[DllImport("user32.dll")]
			static extern bool OpenClipboard(IntPtr hWndNewOwner);

			[DllImport("user32.dll")]
			static extern bool EmptyClipboard();

			[DllImport("user32.dll")]
			static extern IntPtr SetClipboardData(uint uFormat, IntPtr hMem);

			[DllImport("user32.dll")]
			static extern bool CloseClipboard();

			[DllImport("gdi32.dll")]
			static extern IntPtr CopyEnhMetaFile(IntPtr hemfSrc, string fileName);

			[DllImport("gdi32.dll")]
			static extern bool DeleteEnhMetaFile(IntPtr hemf);


			/// <summary>
			/// Copies the given <see cref="T:System.Drawing.Imaging.MetaFile" /> to the clipboard.
			/// The given <see cref="T:System.Drawing.Imaging.MetaFile" /> is set to an invalid state inside this function.
			/// </summary>
			static public bool PutEnhMetafileOnClipboard(IntPtr hWnd, Metafile metafile)
			{
				return PutEnhMetafileOnClipboard(hWnd, metafile, true);
			}


			/// <summary>
			/// Copies the given <see cref="T:System.Drawing.Imaging.MetaFile" /> to the clipboard.
			/// The given <see cref="T:System.Drawing.Imaging.MetaFile" /> is set to an invalid state inside this function.
			/// </summary>
			static public bool PutEnhMetafileOnClipboard(IntPtr hWnd, Metafile metafile, bool clearClipboard)
			{
				if (metafile == null) throw new ArgumentNullException("metafile");
				bool bResult = false;
				IntPtr hEMF, hEMF2;
				hEMF = metafile.GetHenhmetafile(); // invalidates mf
				if (!hEMF.Equals(IntPtr.Zero))
				{
					try
					{
						hEMF2 = CopyEnhMetaFile(hEMF, null);
						if (!hEMF2.Equals(IntPtr.Zero))
						{
							if (OpenClipboard(hWnd))
							{
								try
								{
									if (clearClipboard)
									{
										if (!EmptyClipboard())
											return false;
									}
									IntPtr hRes = SetClipboardData(14 /*CF_ENHMETAFILE*/, hEMF2);
									bResult = hRes.Equals(hEMF2);
								}
								finally
								{
									CloseClipboard();
								}
							}
						}
					}
					finally
					{
						DeleteEnhMetaFile(hEMF);
					}
				}
				return bResult;
			}


			/// <summary>
			/// Copies the given <see cref="T:System.Drawing.Imaging.MetaFile" /> to the specified file. If the file does not exist, it will be created.
			/// The given <see cref="T:System.Drawing.Imaging.MetaFile" /> is set to an invalid state inside this function.
			/// </summary>
			static public bool SaveEnhMetaFile(string fileName, Metafile metafile)
			{
				if (metafile == null) throw new ArgumentNullException("metafile");
				bool result = false;
				IntPtr hEmf = metafile.GetHenhmetafile();
				if (hEmf != IntPtr.Zero)
				{
					IntPtr resHEnh = CopyEnhMetaFile(hEmf, fileName);
					if (resHEnh != IntPtr.Zero)
					{
						DeleteEnhMetaFile(resHEnh);
						result = true;
					}
					DeleteEnhMetaFile(hEmf);
					metafile.Dispose();
				}
				return result;
			}
		}
var path = AppDomain.CurrentDomain.BaseDirectory + "1.png";
		
			Bitmap bm = new Bitmap(path, false);
			
			var mf = GetGeometryMetafile(bm);
			ClipboardMetafileHelper.PutEnhMetafileOnClipboard(IntPtr.Zero, mf);

总结

功能看着挺简单,其实内部有很多的坑(文件MetaFIle数据的获取,MetaFile怎么才能正确放到剪切板中),从网上找的资料也不是很全,没有现成的代码,这里做一下总结汇总,供大家使用。上述代码也有一定的问题(剪切板中的数据放到别的编辑器有的失效是因为剪切板中数据缺少对应编辑器所需的参数,可根据Clipboard.GetDataObject()获取缓存数据对象判断编辑器需要的对象然后利用下面方法进行后续代码优化。

public static void SetClipboardImage(Bitmap image, Bitmap imageNoTr, DataObject data)
		{
			Clipboard.Clear();
			if (data == null)
				data = new DataObject();
			if (imageNoTr == null)
				imageNoTr = image;
			using (MemoryStream pngMemStream = new MemoryStream())
			using (MemoryStream dibMemStream = new MemoryStream())
			{
				// As standard bitmap, without transparency support
				data.SetData(DataFormats.Bitmap, imageNoTr);
				// As PNG. Gimp will prefer this over the other two.
				image.Save(pngMemStream, ImageFormat.Png);
				data.SetData("PNG", pngMemStream);
				// As DIB. This is (wrongly) accepted as ARGB by many applications.
				Byte[] dibData = ConvertToDib(image);
				dibMemStream.Write(dibData, 0, dibData.Length);
				data.SetData(DataFormats.Dib, dibMemStream);
				// The 'copy=true' argument means the MemoryStreams can be safely disposed after the operation.
				Clipboard.SetDataObject(data, true);
			}
		}

引用资料

//www.cnblogs.com/watsonyin/archive/2007/11/22/968651.html
//docs.microsoft.com/zh-cn/dotnet/api/system.windows.forms.clipboard?view=windowsdesktop-6.0
//github.com/LudovicT/NShape