讀取圖片文件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