Text erased from screenshot after using Clipboard.GetImage() on Windows 10?
Asked Answered
C

1

3

This is a weird one: I recently upgraded my workstation from Windows 7 to Windows 10. I have a Chat client, that accepts Images from the clipboard using the Code below:

if (Clipboard.ContainsImage())
{
        BitmapSource source = Clipboard.GetImage();
        BitmapFrame frame = BitmapFrame.Create(source);
        var encoder = new System.Windows.Media.Imaging.PngBitmapEncoder();
        encoder.Frames.Add(frame);
        var stream = new MemoryStream();
        encoder.Save(stream);
        byte[] daten = stream.ToArray();
        if (daten != null && daten.Length > 0)
        {
            sendFile(DateTime.Now.ToString("yyyyMMddHHmmss_") + "clipboard.png", stream.ToArray());
        }
}

Here is what the area I screenshot should look like (for instance if i paste it into MS-Paint or save directly from Snipping Tool): Actual Screenshot

Now here is what it looks like, after I import the screenshot using Clipboard.GetImage();. Screenshot after using Glipboard.GetImage()

As you can see, all the text is erased and if you look very closely, you can see that the normally white background is now transparent.

If I use JpegBitmapEncoder instead of PngBitmapEncoder, it works fine, so it's properbly an encoding problem, but what baffels me is this:

  1. This has never happend on Windows 7 - what has changed in Windows 10 that could make screenshots any different?
  2. If I save the screenshot to a file from Snipping Tool, a PNG is created (with PNG-Header in the data itself). So why is PNG not the right Encoding?
Cuspid answered 31/5, 2017 at 14:25 Comment(4)
Bizarre... as far as I know, the Windows clipboard doesn't even support transparency.Saleme
It sure doesn't work when pasting it in Gimp... not to mention, the window you screenshotted doesn't HAVE transparency there.Saleme
The image still contains all information if you remove all alpha. I think I figured it out, but I'm not 100% sure. What version of the .net framework are you using? Because on 3.5 it doesn't seem to do this, but versions 4+ might react differently.Saleme
I haven't actually tested the difference, but I can observe it just from comparing your 4.5 experiences with my 3.5 ones. I hope my answer is useful to you.Saleme
S
18

I've been researching the windows clipboard, and I think I know what is going on. Bear with me, this is a pretty long explanation, and it took a lot of research to get enough information on the DIB format that's at the core of this mess.

By default, the clipboard doesn't support transparency at all, for legacy reasons; back when the clipboard was first designed there was no such thing as alpha channels. As you may know, though, multiple formats can be put on the clipboard together, to give programs reading the clipboard a wider range of possibilities of getting data out, and it seems that in recent Windows versions, the image apparently also gets put on the clipboard in DIB (Device Independent Bitmap) format. Now, the .Net framework v3.5 normally takes the standard non-transparent "Image" format version from the clipboard, so it never gives transparency, but it seems the 4.5 one might actually get this DIB one instead.

The DIB, as it exists on the clipboard, is technically a 32 bits per pixel RGB image. Note, RGB, not ARGB. This means that there are four bytes per pixel, but while the red, green and blue bytes are filled in normally, the fourth byte is actually undefined, and should not be counted on to actually mean "alpha". It's just there to get the data nicely aligned to a multiple of four bytes.

The internal format of this DIB is set to 32-bit "BITFIELDS", which differs from the standard 32-bit "RGB" type in that it specifically defines in the header which bits of each 32-bit pixel to use for each colour. But it defines only three such bit fields; for red, green and blue. There is no fourth field; it's not supposed to have alpha. Consider, if that image was created by a system that really does just treat it as RGB without alpha, and which doesn't clear its memory beforehand (as a lot of C/C++ systems don't), and which only actually writes those R,G and B bytes, then the fourth byte might just be random junk left behind in memory from previous operations. You can't even be sure it's cleared to value 0 or set to standard opaque value 255.

And there is the problem... because a lot of applications, including, apparently, Windows 10 and the .Net framework 4.5, do treat it like that. When I pressed Print Screen on a Windows 10 desktop made of 2 screens with different heights, the resulting image was actually put in the clipboard by Windows itself as that bastard-BITFIELDS-DIB format with transparency in it; when I dumped the image with my function to save that DIB as ARGB image, the area on the image that fell outside the actual monitors I have was indeed transparent, and not just black; it was the only piece of the image with its 'alpha' bytes set to 0. So that seems to indicate that Windows 10 itself also considers the format alpha-capable.

So what we have here seems to be a problem with Microsoft itself not using its own specifications correctly. (In case you're wondering, yes, they made the DIB specs.) They could perfectly have switched to the more recent DIB format with a larger header, which does support real alpha, but instead they use a bastardised old RGB format that apparently everyone (I discovered it when copying an image from Google Chrome) just assumes contains alpha.

This, in turn, seems to result in bizarre behaviour like what you had. I suspect something in the drawing mechanic of the Explorer window fills these alpha bytes with something, maybe even the alpha of one of its own drawing layers, and it's never cleared when putting the final image on the clipboard. And then, as you noticed, the .Net framework 4.5 apparently assumes that these extra bytes are indeed an alpha channel for the image you get.

The solution is to either clear these bytes to 255, or make your system interpret the result as RGB instead of ARGB. The second of those I can actually help you with: I detailed how to get bytes from image data in the GetImageData function in this answer, and this one has my BuildImage method for writing bytes back to a new image. So if you just use that to get the 32-bit ARGB data and then just write it back to a new 32-bit RGB image, you can make the framework treat the image data as nontransparent without having to modify a single byte.

Saleme answered 25/9, 2017 at 7:47 Comment(4)
Note, not everyone assumes it contains alpha. I found a specific ticket on the Gimp issue tracker that rejects the format as alpha-capable for unreliability reasons. Gimp will always treat this DIB as opaque.Saleme
In case you're interested in my full clipboard copy code, I posted it here: https://mcmap.net/q/506332/-copying-from-and-to-clipboard-loses-image-transparencySaleme
I have a program that takes screenshots of control elements and puts them in the clipboard. In order not to repeat the screenshots to the point of filling up the clipboard, I tried to determine if the clipboard image already existed, and I had to use the following code to process the image byte sequence: if ((i - 1) % 4 == 0) ->bytes[i] = 0;, I happened to see your research today, so I know the principle :-)Windy
@Windy Unless you're actually processing all bytes, it's a lot more efficient to just make the for loop start i on the first alpha byte offset and increase it in steps of 4, though. Loop gets 4x smaller, and no if checks needed.Saleme

© 2022 - 2024 — McMap. All rights reserved.