如何将包含DPI信息的图像保存到剪贴板?

3

看起来 Bitmap.SetResolution()对剪贴板没有影响,以下是简单的代码:

Dim bitmap1 As Image = New System.Drawing.Bitmap(100, 100)
Using gr As Graphics = Graphics.FromImage(bitmap1)
    gr.FillRectangle(System.Drawing.Brushes.Black, 0, 0, 99, 99)
    gr.FillRectangle(System.Drawing.Brushes.White, 10, 10, 89, 89)
End Using

bitmap1.SetResolution(150, 150)  

Clipboard.SetImage(bitmap1)                              ' DPI not set
bitmap1.Save("D:\bitmap1.png", imaging.ImageFormat.Png)  ' DPI set

该文件包含正确设置的图像DPI。在剪贴板中,它不存在。
证明:在剪贴板转储中(由IrfanView插入到剪贴板的图像),请参见60×120 DPI的DIB位图头(黄色=水平DPI,绿色=垂直DPI):

enter image description here

使用Clipboard.SetImage()插入图像后,这两个数都是0
我的目标是能够将图像以正确的大小(根据DPI和尺寸)粘贴到Microsoft Word中。如果在剪贴板中没有设置DPI,则粘贴后的图像将会过大。但它已经包含了条形码,其中1个条形码= 1个 px 的分辨率,因此我无法对其进行采样缩小。
如何验证DPI:使用剪贴板查看器或打开显示图像属性的图像编辑器。如果只有Word,则将图像拖放到文档上。上面示例的图像尺寸应该为1.69×1.69厘米-如果从文件中获取,实际上就是这样。如果来自.NET制作的剪贴板,则不是这样。
在设置图像DPI的过程中我缺少了什么?
(C#或VB,您喜欢哪种就用哪种。)

不确定您的意思是否是这样,但根据文档:"更改图像的分辨率不会改变其物理大小。" - theB
你在绘制之前尝试设置分辨率了吗? - user3697824
@miroxlav - 你是怎么做到的?SetImage只能写入BitmapSource而不能写入Image。也许你使用了Clipboard.SetData(DataFormats.Bitmap, bitmap1); - GreenEyedAndy
如果您能找到如何将位图转换为增强格式的元文件,并且要粘贴到的应用程序也理解该格式,那么就有一线希望。 - Andrew Morton
3个回答

1
我找到的解决方法是创建一个临时文件,将其推送到剪贴板中,而不是使用SetImage:
  bitmap1.SetResolution(300, 300)
  Dim strTempFilename As String = TempDirectory & "\mytempfilename.TIF"
  bitmap1.Save(strTempFilename, Imaging.ImageFormat.Tiff)
  Dim DataObject As New DataObject()
  Dim file(0) As String
  file(0) = strTempFilename
  DataObject.SetData(DataFormats.FileDrop, True, file)
  Clipboard.SetDataObject(DataObject)

我为了简洁省略了所有的错误检查等。这种方法的缺点是之后需要删除一个临时文件。

1
设备无关位图格式包含某种DPI信息,但据我所见,剪贴板图像通常不会填充此信息。但如果您想使用DIB与实际读取该信息的东西交换数据,那么可以将其填写。

我已经详细说明了如何通过操作DIB标头和数据作为裸字节数组来设置和提取Windows剪贴板中的DIB图像:

A:从剪贴板复制和粘贴时丢失图像透明度

DPI值在代码中未填充,但在剪贴板DIB写入函数ConvertToDib(Image image)的注释中提到了它们。查看DIB标头规格, DPI值应为Int32值放置在偏移量0x18和0x1C上。这些值可能可以从输入给ConvertToDib函数的Image对象中提取,但具体情况由您自己确定。

因此,如果您在该代码中找到已注释的 biXPelsPerMeter biYPelsPerMeter 提及,并将实际代码放置在那里以填充该数据,则应该可以正常工作:

ArrayUtils.WriteIntToByteArray(fullImage, 0x18, 4, true, (UInt32)dpiX);
ArrayUtils.WriteIntToByteArray(fullImage, 0x1C, 4, true, (UInt32)dpiY);

DPI是每英寸的点数,但这似乎希望每米有纯整数像素,因此如果Image对象实际上将其作为DPI,则可能需要进行某种转换。

执行剪贴板粘贴时可以读取相同的索引(再次提供代码),但我没有研究如何将该信息实际放入新的Bitmap对象中。如果要这样做,您可能需要扩展链接的BuildImage函数。


0

编辑(2017-10-04):这是我最初接受的答案。我将接受的答案更改为更近期的解决方案,通过绕过开箱即用的流程来解决问题。


Hans Passant的解释:(不确定他为什么删除了他的答案,我正要接受它。)

复制到剪贴板的位图是由DataObject.CreateCompatibleBitmap()方法创建的。该方法的注释与您的问题相关:

// GDI+ returns a DIBSECTION based HBITMAP. The clipboard deals well
// only with bitmaps created using CreateCompatibleBitmap(). So, we
// convert the DIBSECTION into a compatible bitmap.

一个 DIBSECTION 通过 dsBmih.biXPelsPerMeterbiYPelsPerMeter 字段具有分辨率感知能力。 而 "兼容位图", 即使用 CreateCompatibleBitmap 创建的位图,则没有这种能力,它是与视频适配器设置匹配的 DDB(设备相关位图)。
所以当然不行。评论有点误导人,“剪贴板只处理得好” 是无意义的,剪贴板并不在乎存储在其中的数据类型。粘贴来自剪贴板的应用可能无法很好地处理除 DDB 之外的其他格式数据。虽然今天是否仍然如此值得怀疑,但很少有机会对其进行测试。您可以尝试使用 DataFormats.Dib,但既不 Winforms 也不 WPF 支持它,因此很难找到可用的工作代码。 引用结束。

这是我的丑陋而疯狂的临时解决方案 - 但至少有些东西在运作

Shared Sub CopyImageToClipboardAlongWithDpi(image As Image)
    Clipboard.SetImage(image) 'standard case, without DPI information

    '*** now with DPI information, if possible
    Const toolPath As String = "C:\Program Files (x86)\IrfanView\i_view32.exe"
    If image IsNot Nothing AndAlso IO.File.Exists(toolPath) Then
        Dim tempFilename As String = IO.Path.Combine(IO.Path.GetTempPath, "tempimage.png")
        If clsFileUtils.TryDeleteFileIfExists(tempFilename) Then
            image.Save(tempFilename, Imaging.ImageFormat.Png)
            With New Process
                .StartInfo.FileName = toolPath
                .StartInfo.Arguments = $"""{tempFilename}"" /clipcopy /killmesoftly"
                .Start()
            End With
            Threading.Thread.Sleep(500)
            clsFileUtils.TryDeleteFileIfExists(tempFilename)
        End If
    End If
End Sub

感谢Irfan Skiljan提供出色的IrfanView。


我仍然乐意接受有用的答案或建议。


那么,为什么你不尝试自己从剪贴板解析DIB信息呢? - Nyerguds
@Nyerguds - 这不是关于写入剪贴板而不是读取吗?(但当然,存储原始数据可能会有所帮助。) - miroxlav
嗯,同样的事情适用。如果您查找标题格式,则可以将图像作为DIB放入剪贴板中。 - Nyerguds
1
@Nyerguds - 当然。我使用了内置方法。如果您发布了使用DIB格式的工作解决方案,我和其他读者肯定会受益,并且我会将您的答案标记为已接受。 - miroxlav

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