Correctly read and write CF_DIB bmp data

When decoding CF_DIB data through the bmp handler we have to
do some assumptions on where the pixel data starts since there's
no file header to get the offset value from. We have to do this
because theres some optional data after the info header that
needs to be skipped over in some cases.

These optional color mask values are now also written when
putting a CF_DIB into the clipboard for maximum compatibility
with other apps on Windows.

This fixes the issue where pasted dibs would be offset by 3 pixels
on Windows.

Fixes: QTBUG-100351
Pick-to: 6.2 6.3
Change-Id: Icafaf82e0aa3476794b671c638455402a0d5206f
Reviewed-by: Eirik Aavitsland <eirik.aavitsland@qt.io>
This commit is contained in:
Viktor Arvidsson 2022-04-10 02:57:06 +02:00
parent 0613146d21
commit 13c0fb137d
2 changed files with 50 additions and 22 deletions

View File

@ -104,6 +104,7 @@ const int BMP_RGB = 0; // no compression
const int BMP_RLE8 = 1; // run-length encoded, 8 bits
const int BMP_RLE4 = 2; // run-length encoded, 4 bits
const int BMP_BITFIELDS = 3; // RGB values encoded in data as bit-fields
const int BMP_ALPHABITFIELDS = 4; // RGBA values encoded in data as bit-fields
static QDataStream &operator>>(QDataStream &s, BMP_INFOHDR &bi)
@ -216,13 +217,13 @@ static bool read_dib_infoheader(QDataStream &s, BMP_INFOHDR &bi)
return true;
}
static bool read_dib_body(QDataStream &s, const BMP_INFOHDR &bi, qint64 offset, qint64 startpos, QImage &image)
static bool read_dib_body(QDataStream &s, const BMP_INFOHDR &bi, qint64 datapos, qint64 startpos, QImage &image)
{
QIODevice* d = s.device();
if (d->atEnd()) // end of stream/file
return false;
#if 0
qDebug("offset...........%lld", offset);
qDebug("offset...........%lld", datapos);
qDebug("startpos.........%lld", startpos);
qDebug("biSize...........%d", bi.biSize);
qDebug("biWidth..........%d", bi.biWidth);
@ -250,25 +251,28 @@ static bool read_dib_body(QDataStream &s, const BMP_INFOHDR &bi, qint64 offset,
int green_scale = 0;
int blue_scale = 0;
int alpha_scale = 0;
bool bitfields = comp == BMP_BITFIELDS || comp == BMP_ALPHABITFIELDS;
if (!d->isSequential())
d->seek(startpos + BMP_FILEHDR_SIZE + bi.biSize); // goto start of colormap or masks
d->seek(startpos + bi.biSize); // goto start of colormap or masks
if (bi.biSize >= BMP_WIN4) {
red_mask = bi.biRedMask;
green_mask = bi.biGreenMask;
blue_mask = bi.biBlueMask;
alpha_mask = bi.biAlphaMask;
} else if (comp == BMP_BITFIELDS && (nbits == 16 || nbits == 32)) {
} else if (bitfields && (nbits == 16 || nbits == 32)) {
if (d->read((char *)&red_mask, sizeof(red_mask)) != sizeof(red_mask))
return false;
if (d->read((char *)&green_mask, sizeof(green_mask)) != sizeof(green_mask))
return false;
if (d->read((char *)&blue_mask, sizeof(blue_mask)) != sizeof(blue_mask))
return false;
if (comp == BMP_ALPHABITFIELDS && d->read((char *)&alpha_mask, sizeof(alpha_mask)) != sizeof(alpha_mask))
return false;
}
bool transp = comp == BMP_BITFIELDS || (comp == BMP_RGB && nbits == 32 && alpha_mask == 0xff000000);
bool transp = bitfields || (comp == BMP_RGB && nbits == 32 && alpha_mask == 0xff000000);
transp = transp && alpha_mask;
int ncols = 0;
@ -317,7 +321,7 @@ static bool read_dib_body(QDataStream &s, const BMP_INFOHDR &bi, qint64 offset,
if (d->atEnd()) // truncated file
return false;
}
} else if (comp == BMP_BITFIELDS && (nbits == 16 || nbits == 32)) {
} else if (bitfields && (nbits == 16 || nbits == 32)) {
red_shift = calc_shift(red_mask);
if (((red_mask >> red_shift) + 1) == 0)
return false;
@ -370,10 +374,9 @@ static bool read_dib_body(QDataStream &s, const BMP_INFOHDR &bi, qint64 offset,
qDebug("Amask: %08x Ashift: %08x Ascale:%08x", alpha_mask, alpha_shift, alpha_scale);
#endif
// offset can be bogus, be careful
if (offset>=0 && startpos + offset > d->pos()) {
if (datapos >= 0 && datapos > d->pos()) {
if (!d->isSequential())
d->seek(startpos + offset); // start of image data
d->seek(datapos); // start of image data
}
int bpl = image.bytesPerLine();
@ -591,7 +594,6 @@ static bool read_dib_body(QDataStream &s, const BMP_INFOHDR &bi, qint64 offset,
return true;
}
// this is also used in qmime_win.cpp
bool qt_write_dib(QDataStream &s, const QImage &image, int bpl, int bpl_bmp, int nbits)
{
QIODevice* d = s.device();
@ -678,15 +680,6 @@ bool qt_write_dib(QDataStream &s, const QImage &image, int bpl, int bpl_bmp, int
return true;
}
// this is also used in qmime_win.cpp
bool qt_read_dib(QDataStream &s, QImage &image)
{
BMP_INFOHDR bi;
if (!read_dib_infoheader(s, bi))
return false;
return read_dib_body(s, bi, -1, -BMP_FILEHDR_SIZE, image);
}
QBmpHandler::QBmpHandler(InternalFormat fmt) :
m_format(fmt), state(Ready)
{
@ -769,9 +762,32 @@ bool QBmpHandler::read(QImage *image)
s.setByteOrder(QDataStream::LittleEndian);
// read image
qint64 datapos = startpos;
if (m_format == BmpFormat) {
datapos += fileHeader.bfOffBits;
} else {
// QTBUG-100351: We have no file header when reading dib format so we have to depend on the size of the
// buffer and the biSizeImage value to find where the pixel data starts since there's sometimes optional
// color mask values after biSize, like for example when pasting from the windows snipping tool.
if (infoHeader.biSizeImage > 0 && infoHeader.biSizeImage < d->size()) {
datapos = d->size() - infoHeader.biSizeImage;
} else {
// And sometimes biSizeImage is not filled in like when pasting from Microsoft Edge, so then we just
// have to assume the optional color mask values are there.
datapos += infoHeader.biSize;
if (infoHeader.biBitCount == 16 || infoHeader.biBitCount == 32) {
if (infoHeader.biCompression == BMP_BITFIELDS) {
datapos += 12;
} else if (infoHeader.biCompression == BMP_ALPHABITFIELDS) {
datapos += 16;
}
}
}
}
const bool readSuccess = m_format == BmpFormat ?
read_dib_body(s, infoHeader, fileHeader.bfOffBits, startpos, *image) :
read_dib_body(s, infoHeader, -1, startpos - BMP_FILEHDR_SIZE, *image);
read_dib_body(s, infoHeader, datapos, startpos + BMP_FILEHDR_SIZE, *image) :
read_dib_body(s, infoHeader, datapos, startpos, *image);
if (!readSuccess)
return false;
@ -875,7 +891,7 @@ QVariant QBmpHandler::option(ImageOption option) const
case 32:
case 24:
case 16:
if (infoHeader.biCompression == BMP_BITFIELDS && infoHeader.biSize >= BMP_WIN4 && infoHeader.biAlphaMask)
if ((infoHeader.biCompression == BMP_BITFIELDS || infoHeader.biCompression == BMP_ALPHABITFIELDS) && infoHeader.biSize >= BMP_WIN4 && infoHeader.biAlphaMask)
format = QImage::Format_ARGB32;
else
format = QImage::Format_RGB32;

View File

@ -177,6 +177,18 @@ static bool qt_write_dibv5(QDataStream &s, QImage image)
if (s.status() != QDataStream::Ok)
return false;
d->write(reinterpret_cast<const char *>(&bi.bV5RedMask), sizeof(bi.bV5RedMask));
if (s.status() != QDataStream::Ok)
return false;
d->write(reinterpret_cast<const char *>(&bi.bV5GreenMask), sizeof(bi.bV5GreenMask));
if (s.status() != QDataStream::Ok)
return false;
d->write(reinterpret_cast<const char *>(&bi.bV5BlueMask), sizeof(bi.bV5BlueMask));
if (s.status() != QDataStream::Ok)
return false;
if (image.format() != QImage::Format_ARGB32)
image = image.convertToFormat(QImage::Format_ARGB32);