Search code examples
c#.netvb.netimagedpi

How to save image along with DPI information into clipboard?


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


Solution

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