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:
parent
0613146d21
commit
13c0fb137d
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user