From 84f84be77b7a1f52cb1151eeef8e5df1bbec5fad Mon Sep 17 00:00:00 2001 From: Tanner Date: Sat, 19 Dec 2020 10:10:21 -0700 Subject: [PATCH] Clipboard workaround for Google Chrome bugs See #343 for details. Thank you to @EsterLoken for catching and reporting, and @wqweto for additional follow-up. tl;dr: this fixes Copy+Paste into Google Chrome. By manually marking CF_DIB as available at Cut/Copy-time, PD can reliably receive notifications for copy+paste when old-style CF_DIB format is requested. This is an ancient Windows bug going back to at least Windows 7 (possibly earlier? idk), but the bug was useful because you could "force" apps to choose between bitmaps/DDBs (which every app supports all the way back to Win 3.1) or DIBv5 (which any Win 98+ app should support, as it fixes DIB issues by e.g. making alpha-handling and color-management explicit). I have never before encountered an app that didn't support either DDBs or DIBv5, but at some point, Chrome developers apparently decided this was the way to go. I'm shocked that Google developers are this incompetent (/s obviously, Google's app behavior on Windows is perpetually incompetent) Longer explanation: from a code standpoint this change is trivial, but it has many potential implications for interop with other software, including breaking 32-bpp copy+paste ops in old software. Given Chrome's ubiquity, I think changing this is probably the least of several evils, but I'm extremely annoyed that Chrome is incapable of using modern clipboard formats, and that its clipboard interop breaks with regularity. (I've lost track of how many times I've tweaked PD's clipboard code to make Chrome happy. At least this time it involves broken pasting into Chrome instead of broken copying out of Chrome... so points for at least breaking in a novel way? uuuuuugh) Even longer explanation: Windows DIB format, like nearly everything in GDI, is inconsistent in its handling of alpha bytes. As an easy example, PrintScreen keypresses often place a 32-bpp DIB on the clipboard with random alpha bytes. Because you don't ever know if a clipboard DIB has useful or broken alpha, you must assume broken alpha and treat the data as 24-bpp only. (Heuristics can be used to try and "guess" alpha state, but like any heuristics, edge-cases are complicated and impossible to handle perfectly.) Old-school DDBs (CF_BITMAP) don't have this issue because alpha is explicitly *not* supported, and "modern" DIBv5 fixes the issue with not just explicit alpha support, but extra support for nice things like color-management. There is no reason to forcibly use CF_DIB except for software which predates Windows 98. (Or if you're a Google dev, apparently.) By reenabling old-style DIB support to make Chrome work, I must manually composite 32-bpp images against a backcolor when using DIB format. This is required to making pasting into e.g. MS Paint still viable. (MS Paint preferentially grabs DIB data over DDB data, so when placing 32-bpp DIB data on the clipboard you *must* composite first or you'll get a broken image when pasting into MS Paint. Paint will ignore a DDB copy completely if a DIB is present, alas.) This change also breaks some software that would preferentially choose DIBv5 over DIB when available (e.g. Chasys Draw, which can no longer paste 32-bpp data from PD. This is technically a problem on their end but users will only know "it worked before and now it doesn't", alas). I'm especially frustrated with Chrome because this change doesn't even allow you to paste 32-bpp data into Chrome - that still isn't possible, because they won't take DIBv5 or PNG-format data if available, no matter what you do. All it does is break interop with a bunch of legacy software, just so you can paste into Chrome *at all*. Anyway, I don't maintain an exhaustive list of clipboard format support in other apps, but I'm hoping that PNG as an interop format is more ubiquitious in 2020, which may negate some of the worst issues of this change. (For example, the last time I changed PD's clipboard mechanism, this fix didn't work because pasting into Paint.NET was downgraded to 24-bpp data only. Two years later, however, I can revert this because Paint.NET added PNG clipboard support in 2019, and it will preferentially use PNG data over the DIB data PD is now forced to place on the clipboard.) Anyway, I've been interrupted by my toddler roughly 1000x while trying to explain this issue, so I've probably repeated myself many times over. Sorry! I'm just frustrated with Google for what feels like the billionth time, and being forced to enable a Win-95-era clipboard format just to appease Chrome makes me very unhappy. --- Classes/pdClipboardMain.cls | 55 +++++++++++++++++++++++++++++-------- Classes/pdDIB.cls | 3 ++ PhotoDemon.vbp | 2 +- 3 files changed, 48 insertions(+), 12 deletions(-) diff --git a/Classes/pdClipboardMain.cls b/Classes/pdClipboardMain.cls index cf138fb262..70889ceddf 100644 --- a/Classes/pdClipboardMain.cls +++ b/Classes/pdClipboardMain.cls @@ -344,7 +344,16 @@ Friend Sub ClipboardCopy(ByVal copyMerged As Boolean, Optional ByVal updateUI As ' 'As always, PNG is the preferred interchange format for images. If you use a program that ' doesn't copy/paste PNG-format data, get them to fix their software! - 'm_Clipboard.SetClipboardData_DelayedRendering CF_DIB + + 'UPDATE December 2020: due to clipboard issues in Google Chrome, I've reenabled explicit DIB + ' availability on the clipboard. This allows pasting into Chrome, and my hope is that as of + ' this year, any programs that support alpha channels will be smart enough to grab PNG or + ' DIBv5 data instead of bare DIB data (which is pre-composited against a white background to + ' ensure correct behavior in alpha-unaware software like MS Paint). This does break 32-bpp + ' pasting into some legacy software. As a workaround, PD's Edit > Special Copy/Cut menu allows + ' you to explicitly paste DIBv5 data which is an expensive workaround, but hey - at least it's + ' there if you need it. + m_Clipboard.SetClipboardData_DelayedRendering CF_DIB ElseIf (cFormat = pdcf_Bitmap) Then m_Clipboard.SetClipboardData_DelayedRendering CF_BITMAP @@ -1770,14 +1779,21 @@ Friend Sub RenderAllClipboardFormatsManually() RenderClipboard_PNG RenderClipboard_BITMAP - 'DIBs have some special considerations. For details, check out the comments in the ClipboardCopy function, but in a nutshell, - ' PD doesn't render CF_DIB images because various programs handle alpha in varying ways, making it impossible to please everyone. - ' Instead, as far as standard formats go, PD renders CF_BITMAP and CF_DIBv5 ONLY, with the assumption that "advanced" image - ' editors can pick up the DIBv5 without trouble, while "basic" editors (like MS Paint) will take the CF_BITMAP copy. - ' (This approach also spares the user's system a good chunk of resources, as we're not copying a crapload of mega-sized images - ' in varying DIB formats.) + 'DIBs have some special considerations. For details, check out the comments in the + ' ClipboardCopy() function, but in a nutshell, PD doesn't render CF_DIB images because + ' various programs handle alpha in varying ways, making it impossible to please everyone. + ' Instead, as far as standard formats go, PD renders CF_BITMAP and CF_DIBv5 ONLY, with the + ' assumption that "advanced" image editors can pick up the DIBv5 without trouble, while "basic" + ' editors (like MS Paint) will take the CF_BITMAP copy. (This approach also spares the user's + ' system a good chunk of resources, as we're not copying a crapload of mega-sized images in + ' varying DIB formats.) RenderClipboard_DIB True + 'NOTE: regular DIB rendering has been reinstated to workaround issues with Chrome, + ' which apparently won't grab BITMAP, DIBv5, or PNG formats if available (instead always + ' defaulting to DIB regardless of its success or failure when retrieving) + RenderClipboard_DIB False + 'Close the clipboard m_Clipboard.ClipboardClose @@ -2002,9 +2018,22 @@ Private Sub RenderClipboard_DIB(Optional ByVal useV5Header As Boolean = False) PDDebug.LogAction "Clipboard copy update: allocating global memory for DIB object..." End If - 'DIBs should be unpremultiplied prior to copying; note that some esoteric software (*cough* XNView *cough*) wants - ' premultiplied alpha, but the general consensus seems to be "use unpremultiplied", so that's what we do too. - If m_ClipboardDIB.GetAlphaPremultiplication Then m_ClipboardDIB.SetAlphaPremultiplication False + 'DIBs should be unpremultiplied prior to copying; note that some esoteric software + ' (*cough* XNView *cough*) wants premultiplied alpha, but the general consensus seems to be + ' "use unpremultiplied", so that's what we do too. + ' + 'Note that alpha state is only relevant for DIBv5 DIBs; for compatibility reasons, PD always + ' composites plain DIBs against a white backdrop, since alpha channel compatibility is so + ' variable between apps. + Dim tmpDIB As pdDIB + Set tmpDIB = New pdDIB + + If useV5Header Then + If m_ClipboardDIB.GetAlphaPremultiplication Then m_ClipboardDIB.SetAlphaPremultiplication False + Else + tmpDIB.CreateFromExistingDIB m_ClipboardDIB + tmpDIB.CompositeBackgroundColor 255, 255, 255 + End If 'Figure out how much size is required for the global allocation. This is just (size_of_header + size_of_pixels). Dim headerSize As Long @@ -2016,7 +2045,11 @@ Private Sub RenderClipboard_DIB(Optional ByVal useV5Header As Boolean = False) End If Dim dibPointer As Long, dibSize As Long - m_ClipboardDIB.RetrieveDIBPointerAndSize dibPointer, dibSize + If useV5Header Then + m_ClipboardDIB.RetrieveDIBPointerAndSize dibPointer, dibSize + Else + tmpDIB.RetrieveDIBPointerAndSize dibPointer, dibSize + End If Dim memSize As Long memSize = headerSize + dibSize diff --git a/Classes/pdDIB.cls b/Classes/pdDIB.cls index 048ab2d05d..e9d072052e 100644 --- a/Classes/pdDIB.cls +++ b/Classes/pdDIB.cls @@ -1829,6 +1829,9 @@ Friend Sub CompositeBackgroundColor(Optional ByVal newR As Byte = 255, Optional 'With our alpha channel complete, point dibPixels() away from the DIB Me.UnwrapArrayFromDIB dibPixels + 'A composited image is always premultiplied + Me.SetInitialAlphaPremultiplicationState True + End Sub 'Blend byte1 w/ byte2 based on mixRatio. mixRatio is expected to be a value between 0 and 1. diff --git a/PhotoDemon.vbp b/PhotoDemon.vbp index 4fc9b575af..c5b9cb7eb1 100644 --- a/PhotoDemon.vbp +++ b/PhotoDemon.vbp @@ -462,7 +462,7 @@ Description="PhotoDemon Photo Editor" CompatibleMode="0" MajorVer=8 MinorVer=9 -RevisionVer=297 +RevisionVer=301 AutoIncrementVer=1 ServerSupportFiles=0 VersionComments="Copyright 2000-2020 Tanner Helland - photodemon.org"