【问题标题】:How to save image along with DPI information into clipboard?如何将图像与 DPI 信息一起保存到剪贴板?
【发布时间】:2015-09-29 16:13:42
【问题描述】:

看起来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 ):

但是使用的Clipboard.SetImage()插入图片后,这两个数字都是0。

我的目标:能够以适当的大小(取自 DPI 和尺寸)将图像粘贴到 Microsoft Word 中。剪贴板中未设置 DPI,粘贴后图像太大。但它已经包含 1 bar = 1 px 分辨率的条形码,所以我无法对其进行采样。

如何验证 DPI:通过剪贴板查看器或在显示图像属性的图像编辑器中打开图像。如果您只有 Word,请将图像拖放到文档上。上述示例的图像尺寸应该是 1.69×1.69 厘米 - 如果从文件中获取,它实际上是。如果来自 .NET 制作的剪贴板,则不是。

我在设置图像 DPI 的过程中缺少什么?

(C# 或 VB,随你喜欢。)

【问题讨论】:

  • 不确定这是否是您的意思,但来自文档:"Changing the resolution of the image does not change its physical size."
  • 您是否在绘制之前尝试设置分辨率?
  • @miroxlav - 你是怎么做到的? SetImage 只能写 BitmapSource 而不能写 Image。也许你用过Clipboard.SetData(DataFormats.Bitmap, bitmap1);
  • 如果您能了解如何将位图转换为Enhanced-Format Metafile并且您要粘贴到的应用程序能够理解该格式,那么就有一线希望。

标签: .net c# .net vb.net image dpi


【解决方案1】:

设备无关位图格式包含某种 DPI 信息,但据我所知,它通常不会填充到剪贴板图像中。但如果你想使用 DIB 与实际读取数据的东西交换数据,那么,当然,你可以直接填写。

我详细介绍了通过 Windows 剪贴板设置和提取 DIB 图像的方法,方法是在此答案中将 DIB 标头和数据操作为裸字节数组:

A: Copying From and To Clipboard loses image transparency

代码中没有填写DPI值,但是在剪贴板DIB写入函数ConvertToDib(Image image)的注释中提到了。查看the DIB header specs,DPI 值应该是位于偏移量 0x18 和 0x1C 上的 Int32 值。这些值可能可以从提供给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 函数。

【讨论】:

    【解决方案2】:

    我发现的解决方法是创建一个临时文件并将其推送到剪贴板而不是使用 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)
    

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

    【讨论】:

      【解决方案3】:

      编辑 (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.biXPelsPerMeter 和 biYPelsPerMeter 字段。 “兼容位图”,由CreateCompatibleBitmap 创建的那种 不是,它是一个与视频适配器设置匹配的 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
      

      Thanks to Irfan Skiljan for great IrfanView.


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

      【讨论】:

      • 那么,你为什么不自己尝试解析剪贴板中的DIB信息呢?
      • @Nyerguds - 这不是写剪贴板而不是阅读吗? (但可以肯定的是,存储原始数据会有所帮助。)
      • 嗯。同样适用。如果您查找标题格式,可以将图像作为 DIB 放入剪贴板。
      • @Nyerguds - 当然。我使用了内置方法。如果您以 DIB 格式发布工作解决方案,我和其他读者肯定会从中受益,我会将您的答案标记为已接受。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-02-21
      • 1970-01-01
      • 2020-05-05
      • 1970-01-01
      • 1970-01-01
      • 2021-01-20
      相关资源
      最近更新 更多