3

It looks like Bitmap.SetResolution() has no effect on clipboard, see the following trivial code:

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

The file contains correctly set image DPI. In the clipbaord, it is not present.

Proof: In clipboard dump (image inserted into clipboard by IrfanView), see DIB bitmap header of 60 × 120 DPI (yellow=horizontal DPI, green=vertical DPI):

enter image description here

But after inserting image using 's Clipboard.SetImage(), both these numbers are 0.

My goal: be able to paste image into Microsoft Word with proper size (taken from DPI and dimensions). Without DPI set in the clipboard, the image is too big after pasting. But it contains barcode already with 1 bar = 1 px resolution, so I cannot sample it down.

How to verify DPI: Either by clipboard viewer OR by opening the image in image editor which shows image properties. If you have only Word, drag&drop the image over the document. Image size of the above example should have been 1.69×1.69 cm – and if taken from file, it actually is. If from .NET-made clipboard, it isn't.

What I am missing in process of setting the image DPI?

(C# or VB, whatever you prefer.)

miroxlav
  • 11,796
  • 5
  • 58
  • 99
  • Not sure if this is what you mean, but from the documentation: ["Changing the resolution of the image does not change its physical size."](https://msdn.microsoft.com/en-us/library/system.drawing.bitmap.setresolution%28v=vs.110%29.aspx) – theB Sep 29 '15 at 16:24
  • Did you try setting the resolution before you draw to it? – user3697824 Sep 29 '15 at 16:29
  • @miroxlav - How do you made this? SetImage can only write a BitmapSource and no Image. Maybe you used `Clipboard.SetData(DataFormats.Bitmap, bitmap1);` – GreenEyedAndy Sep 29 '15 at 17:20
  • There is a faint glimmer of hope if you can find out how to get a bitmap into an [Enhanced-Format Metafile](https://msdn.microsoft.com/en-us/library/windows/desktop/dd162600%28v=vs.85%29.aspx) *and* the application you want to paste into understands the format. – Andrew Morton Sep 29 '15 at 17:22

3 Answers3

1

The Device Independent Bitmap format contains some sort of DPI information, though from what I've seen it is generally not filled in on clipboard images. But if you want to use DIB to exchange data with something that actually reads that, then, sure, you can just fill it in.

I have detailed the ways to both set and extract DIB images through the Windows clipboard by manipulating the DIB header and data as bare bytes array in this answer:

A: Copying From and To Clipboard loses image transparency

The DPI values are not filled in in the code, but they are mentioned in comment in the clipboard DIB writing function ConvertToDib(Image image). Looking at the DIB header specs, the DPI values should be Int32 values put on offsets 0x18 and 0x1C. These values can probably be extracted from the input Image object given to the ConvertToDib function, but that'll be up to you to figure out exactly.

So if you just find the commented biXPelsPerMeter and biYPelsPerMeter mentions in that code and put the actual code there to fill in that data, that should work:

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

DPI is dots per inch, though, while this seems to expect pure-integer pixels per meter, so if the Image object actually has it as DPI, some kind of conversion may be required there.

The same indices can be read when performing a clipboard paste (again, the code is in the answer I linked), though I haven't looked into how to actually put that information into the new Bitmap object. You'd probably have to expand the linked BuildImage function if you want to do that.

Nyerguds
  • 5,360
  • 1
  • 31
  • 63
1

The work-around I found was to create a temporary file and push that into the Clipboard instead of using 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)

I've omitted all the error checking etc. for brevity. The downside of this approach is that there is a temporary file to delete afterwards.

SSS
  • 4,807
  • 1
  • 23
  • 44
0

Edit (2017-10-04): this was my own answer I originally accepted. I changed accepted answer to more recent one which solves the problem by bypassing out-of-the-box process.


Hans Passant's explanation: (Not sure why he deleted his answer, I was about to accept it.)

The bitmap copied to the Clipboard is created by the DataObject.CreateCompatibleBitmap() method. The comment on this method is relevant to your problem:

// 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.

A DIBSECTION has resolution awareness through the dsBmih.biXPelsPerMeter and biYPelsPerMeter fields. A "compatible bitmap", the kind created by CreateCompatibleBitmap does not, it is a DDB (device dependent bitmap) that matches the video adapter setting.

So sure, it doesn't work. The comment is a bit misleading, "the clipboard deals well only" is nonsensical, the clipboard doesn't give a hoot about the kind of data you store on it. It is the applications that paste from the clipboard that might not deal well with something else than a DDB. Whether that's still true today is questionable but doesn't exactly get put to the test very often. You'd have to try DataFormats.Dib but neither Winforms nor WPF support it so working code is going to be hard to come by.

End of quote.


And this is my ugly and crazy temporary workaround – but at least something is working:

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.


I am still open to helpful answers or suggestions.

miroxlav
  • 11,796
  • 5
  • 58
  • 99
  • So, why didn't you just try to parse the DIB information from the clipboard yourself? – Nyerguds Oct 02 '17 at 14:21
  • @Nyerguds – wasn't this about writing the clipboard instead of reading? (But sure, storing raw data could help.) – miroxlav Oct 02 '17 at 14:38
  • Eh. Same thing applies. It's possible to put an image into the clipboard as DIB, if you look up the header format. – Nyerguds Oct 03 '17 at 09:07
  • 1
    @Nyerguds – sure. I used built-in methods. If you post working solution with DIB format, I and other readers will definitely benefit from that and I'll mark your answer as accepted. – miroxlav Oct 03 '17 at 10:11