Windows: avoid losing transparency when pasting images into Qt apps

Before Qt6, we would only request a DIBV5 format image from the
clipboard if that format was available without synthesizing. Then, in
commit 8fa91c75ad that check was believed to be needless, and
removed. However, it turns out that it is needed to avoid loosing
transparency information, so we revert that part and bring back the
check against synthesized DIBV5.

The logic is that some widely used apps will provide images in both
PNG and DIB on the clipboard, and only the former can have an alpha
channel. When we request a synthesized DIBV5 rather than a PNG, it
seems we only get the (opaque) DIB image repackaged as a DIBV5.

On Windows 11, there even seems to be an issue (possibly a Windows
bug) where, after asking for a synthesized DIBV5, the clipboard
contents is somehow changed in a way so that pasting again into other
(non-Qt) apps later will yield a corrupted (pixel-shifted) image. (It
looks like they get a DIBV5 when they expect a DIB). So avoiding
requests for synthesized DIBV5 also avoids triggering that issue.

Also add some logging outout to help future debugging.

Fixes: QTBUG-104872
Fixes: QTBUG-105338
Pick-to: 6.4 6.2
Change-Id: I33ffa2b50870b887a083e9bbb1b2234604e4d8ab
Reviewed-by: André de la Rocha <andre.rocha@qt.io>
This commit is contained in:
Eirik Aavitsland 2022-08-31 10:29:25 +02:00
parent cf3461bab3
commit 8cb78647e3

View File

@ -906,6 +906,7 @@ public:
QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QMetaType preferredType) const override; QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QMetaType preferredType) const override;
QString mimeForFormat(const FORMATETC &formatetc) const override; QString mimeForFormat(const FORMATETC &formatetc) const override;
private: private:
bool hasOriginalDIBV5(IDataObject *pDataObj) const;
UINT CF_PNG; UINT CF_PNG;
}; };
@ -987,15 +988,41 @@ bool QWindowsMimeImage::convertFromMime(const FORMATETC &formatetc, const QMimeD
return false; return false;
} }
bool QWindowsMimeImage::hasOriginalDIBV5(IDataObject *pDataObj) const
{
bool isSynthesized = true;
IEnumFORMATETC *pEnum = nullptr;
HRESULT res = pDataObj->EnumFormatEtc(1, &pEnum);
if (res == S_OK && pEnum) {
FORMATETC fc;
while ((res = pEnum->Next(1, &fc, nullptr)) == S_OK) {
if (fc.ptd)
CoTaskMemFree(fc.ptd);
if (fc.cfFormat == CF_DIB)
break;
if (fc.cfFormat == CF_DIBV5) {
isSynthesized = false;
break;
}
}
pEnum->Release();
}
return !isSynthesized;
}
QVariant QWindowsMimeImage::convertToMime(const QString &mimeType, IDataObject *pDataObj, QMetaType preferredType) const QVariant QWindowsMimeImage::convertToMime(const QString &mimeType, IDataObject *pDataObj, QMetaType preferredType) const
{ {
Q_UNUSED(preferredType); Q_UNUSED(preferredType);
QVariant result; QVariant result;
if (mimeType != u"application/x-qt-image") if (mimeType != u"application/x-qt-image")
return result; return result;
//Try to convert from DIBV5 as it is the most // Try to convert from DIBV5 as it is the most widespread format that supports transparency,
//widespread format that support transparency // but avoid synthesizing it, as that typically loses transparency, e.g. from Office
if (canGetData(CF_DIBV5, pDataObj)) { const bool canGetDibV5 = canGetData(CF_DIBV5, pDataObj);
const bool hasOrigDibV5 = canGetDibV5 ? hasOriginalDIBV5(pDataObj) : false;
qCDebug(lcQpaMime) << "canGetDibV5:" << canGetDibV5 << "hasOrigDibV5:" << hasOrigDibV5;
if (hasOrigDibV5) {
qCDebug(lcQpaMime) << "Decoding DIBV5";
QImage img; QImage img;
QByteArray data = getData(CF_DIBV5, pDataObj); QByteArray data = getData(CF_DIBV5, pDataObj);
QBuffer buffer(&data); QBuffer buffer(&data);
@ -1004,6 +1031,7 @@ QVariant QWindowsMimeImage::convertToMime(const QString &mimeType, IDataObject *
} }
//PNG, MS Office place this (undocumented) //PNG, MS Office place this (undocumented)
if (canGetData(CF_PNG, pDataObj)) { if (canGetData(CF_PNG, pDataObj)) {
qCDebug(lcQpaMime) << "Decoding PNG";
QImage img; QImage img;
QByteArray data = getData(CF_PNG, pDataObj); QByteArray data = getData(CF_PNG, pDataObj);
if (img.loadFromData(data, "PNG")) { if (img.loadFromData(data, "PNG")) {
@ -1012,6 +1040,7 @@ QVariant QWindowsMimeImage::convertToMime(const QString &mimeType, IDataObject *
} }
//Fallback to DIB //Fallback to DIB
if (canGetData(CF_DIB, pDataObj)) { if (canGetData(CF_DIB, pDataObj)) {
qCDebug(lcQpaMime) << "Decoding DIB";
QImage img; QImage img;
QByteArray data = getData(CF_DIBV5, pDataObj); QByteArray data = getData(CF_DIBV5, pDataObj);
QBuffer buffer(&data); QBuffer buffer(&data);