From 2268b6bcdd1b4ddcd78753b81c477f0aa2855261 Mon Sep 17 00:00:00 2001 From: Friedemann Kleint Date: Wed, 28 Mar 2018 11:33:23 +0200 Subject: QtGui: Add qt_imageToWinHBITMAP(), qt_imageFromWinHBITMAP() Add functions for converting QImage to HBITMAP and back supporting additional formats of QImage (RGB888, RGB555, Indexed8 and Mono). Add test with roundtrip to tst_qimage similar to tst_QPixmap::toWinHBITMAP(). Task-number: QTBUG-51124 Change-Id: Ib568898e7162686bfa527d828785628eb0b78e21 Reviewed-by: Eirik Aavitsland --- src/gui/image/qpixmap_win.cpp | 388 +++++++++++++++++++++++++++++++++--------- 1 file changed, 310 insertions(+), 78 deletions(-) (limited to 'src/gui/image/qpixmap_win.cpp') diff --git a/src/gui/image/qpixmap_win.cpp b/src/gui/image/qpixmap_win.cpp index 92f6964783..ea979db781 100644 --- a/src/gui/image/qpixmap_win.cpp +++ b/src/gui/image/qpixmap_win.cpp @@ -42,36 +42,73 @@ #include #include "qpixmap_raster_p.h" -#include +#include #include #include +#include +#include + QT_BEGIN_NAMESPACE -static inline void initBitMapInfoHeader(int width, int height, bool topToBottom, BITMAPINFOHEADER *bih) +template +static inline Int pad4(Int v) +{ + return (v + Int(3)) & ~Int(3); +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug d, const BITMAPINFOHEADER &bih) +{ + QDebugStateSaver saver(d); + d.nospace(); + d << "BITMAPINFOHEADER(" << bih.biWidth << 'x' << qAbs(bih.biHeight) + << (bih.biHeight < 0 ? ", top-down" : ", bottom-up") + << ", planes=" << bih.biPlanes << ", bitCount=" << bih.biBitCount + << ", compression=" << bih.biCompression << ", size=" + << bih.biSizeImage << ')'; + return d; +} +#endif // !QT_NO_DEBUG_STREAM + +static inline void initBitMapInfoHeader(int width, int height, bool topToBottom, + DWORD compression, DWORD bitCount, + BITMAPINFOHEADER *bih) { memset(bih, 0, sizeof(BITMAPINFOHEADER)); bih->biSize = sizeof(BITMAPINFOHEADER); bih->biWidth = width; bih->biHeight = topToBottom ? -height : height; bih->biPlanes = 1; - bih->biBitCount = 32; - bih->biCompression = BI_RGB; - bih->biSizeImage = width * height * 4; + bih->biBitCount = WORD(bitCount); + bih->biCompression = compression; + // scan lines are word-aligned (unless RLE) + const DWORD bytesPerLine = pad4(DWORD(width) * bitCount / 8); + bih->biSizeImage = bytesPerLine * DWORD(height); } -static inline void initBitMapInfo(int width, int height, bool topToBottom, BITMAPINFO *bmi) +enum { Indexed8ColorTableSize = 256 }; + +struct BITMAPINFO_COLORTABLE256 { // BITMAPINFO with 256 entry color table for Indexed 8 format + BITMAPINFOHEADER bmiHeader; + RGBQUAD bmiColors[Indexed8ColorTableSize]; +}; + +template // BITMAPINFO, BITMAPINFO_COLORTABLE256 +static inline void initBitMapInfo(int width, int height, bool topToBottom, + DWORD compression, DWORD bitCount, + BITMAPINFO_T *bmi) { - initBitMapInfoHeader(width, height, topToBottom, &bmi->bmiHeader); - memset(bmi->bmiColors, 0, sizeof(RGBQUAD)); + initBitMapInfoHeader(width, height, topToBottom, compression, bitCount, &bmi->bmiHeader); + memset(bmi->bmiColors, 0, sizeof(bmi->bmiColors)); } static inline uchar *getDiBits(HDC hdc, HBITMAP bitmap, int width, int height, bool topToBottom = true) { BITMAPINFO bmi; - initBitMapInfo(width, height, topToBottom, &bmi); + initBitMapInfo(width, height, topToBottom, BI_RGB, 32u, &bmi); uchar *result = new uchar[bmi.bmiHeader.biSizeImage]; - if (!GetDIBits(hdc, bitmap, 0, height, result, &bmi, DIB_RGB_COLORS)) { + if (!GetDIBits(hdc, bitmap, 0, UINT(height), result, &bmi, DIB_RGB_COLORS)) { delete [] result; qErrnoWarning("%s: GetDIBits() failed to get bitmap bits.", __FUNCTION__); return 0; @@ -98,18 +135,92 @@ static inline void copyImageDataCreateAlpha(const uchar *data, QImage *target) } } -static inline void copyImageData(const uchar *data, QImage *target) +// Flip RGB triplets from DIB to QImage formats. Scan lines are padded to 32bit +// both in QImage and DIB. +static inline void flipRgb3(uchar *p, int width, int height) { - const int height = target->height(); - const int bytesPerLine = target->bytesPerLine(); + const int lineSize = 3 * width; + const int linePad = pad4(lineSize) - lineSize; for (int y = 0; y < height; ++y) { - void *dest = static_cast(target->scanLine(y)); - const void *src = data + y * bytesPerLine; - memcpy(dest, src, bytesPerLine); + uchar *end = p + lineSize; + for ( ; p < end; p += 3) + std::swap(*p, *(p + 2)); + p += linePad; + } +} + +static inline RGBQUAD qRgbToRgbQuad(QRgb qrgb) +{ + RGBQUAD result = {BYTE(qBlue(qrgb)), BYTE(qGreen(qrgb)), BYTE(qRed(qrgb)), 0}; + return result; +} + +static inline QRgb rgbQuadToQRgb(RGBQUAD quad) +{ + return QRgb(quad.rgbBlue) + (QRgb(quad.rgbGreen) << 8) + (QRgb(quad.rgbRed) << 16) + + 0xff000000; +} + +// Helper for imageFromWinHBITMAP_*(), create image in desired format +static QImage copyImageData(const BITMAPINFOHEADER &header, const RGBQUAD *colorTableIn, + const void *data, QImage::Format format) +{ + const QSize size = QSize(header.biWidth, qAbs(header.biHeight)); + QImage image(size, format); + + int colorTableSize = 0; + switch (format) { + case QImage::Format_Mono: + colorTableSize = 2; + break; + case QImage::Format_Indexed8: + colorTableSize = Indexed8ColorTableSize; + break; + default: + break; + } + if (colorTableSize) { + Q_ASSERT(colorTableIn); + QVector colorTable; + colorTable.reserve(colorTableSize); + std::transform(colorTableIn, colorTableIn + colorTableSize, + std::back_inserter(colorTable), rgbQuadToQRgb); + image.setColorTable(colorTable); } + switch (header.biBitCount) { + case 32: + copyImageDataCreateAlpha(static_cast(data), &image); + break; + case 1: + case 8: + case 16: + case 24: + Q_ASSERT(DWORD(image.byteCount()) == header.biSizeImage); + memcpy(image.bits(), data, header.biSizeImage); + if (format == QImage::Format_RGB888) + image = image.rgbSwapped(); + break; + default: + Q_UNREACHABLE(); + break; + } + return image; } +class DisplayHdc +{ + Q_DISABLE_COPY(DisplayHdc) +public: + DisplayHdc() : m_displayDc(GetDC(0)) {} + ~DisplayHdc() { ReleaseDC(0, m_displayDc); } + + operator HDC() const { return m_displayDc; } + +private: + const HDC m_displayDc; +}; + enum HBitmapFormat { HBitmapNoAlpha, @@ -131,34 +242,93 @@ Q_GUI_EXPORT HBITMAP qt_createIconMask(const QBitmap &bitmap) return hbm; } -Q_GUI_EXPORT HBITMAP qt_pixmapToWinHBITMAP(const QPixmap &p, int hbitmapFormat = 0) +static inline QImage::Format format32(int hbitmapFormat) { - if (p.isNull()) + switch (hbitmapFormat) { + case HBitmapNoAlpha: + return QImage::Format_RGB32; + case HBitmapAlpha: + return QImage::Format_ARGB32; + default: + break; + } + return QImage::Format_ARGB32_Premultiplied; +} + +Q_GUI_EXPORT HBITMAP qt_imageToWinHBITMAP(const QImage &imageIn, int hbitmapFormat = 0) +{ + if (imageIn.isNull()) return 0; - HBITMAP bitmap = 0; - if (p.handle()->classId() != QPlatformPixmap::RasterClass) { - QRasterPlatformPixmap *data = new QRasterPlatformPixmap(p.depth() == 1 ? - QRasterPlatformPixmap::BitmapType : QRasterPlatformPixmap::PixmapType); - data->fromImage(p.toImage(), Qt::AutoColor); - return qt_pixmapToWinHBITMAP(QPixmap(data), hbitmapFormat); - } + // Define the header + DWORD compression = 0; + DWORD bitCount = 0; - QRasterPlatformPixmap *d = static_cast(p.handle()); - const QImage *rasterImage = d->buffer(); - const int w = rasterImage->width(); - const int h = rasterImage->height(); + // Copy over the data + QImage image = imageIn; + switch (image.format()) { + case QImage::Format_Mono: + bitCount = 1u; + break; + case QImage::Format_RGB32: + case QImage::Format_ARGB32: + case QImage::Format_ARGB32_Premultiplied: { + compression = BI_RGB; + bitCount = 32u; + const QImage::Format targetFormat = format32(hbitmapFormat); + if (targetFormat != image.format()) + image = image.convertToFormat(targetFormat); + } + break; + case QImage::Format_RGB888: + compression = BI_RGB; + bitCount = 24u; + break; + case QImage::Format_Indexed8: + bitCount = 8u; + break; + case QImage::Format_RGB555: + bitCount = 16u; + break; + default: { + QImage::Format fallbackFormat = QImage::Format_ARGB32_Premultiplied; + switch (image.format()) { // Convert to a suitable format. + case QImage::Format_MonoLSB: + fallbackFormat = QImage::Format_Mono; + break; + case QImage::Format_RGB16: + fallbackFormat = QImage::Format_RGB555; + break; + case QImage::Format_Grayscale8: + fallbackFormat = QImage::Format_Indexed8; + break; + default: + break; + } // switch conversion format + return qt_imageToWinHBITMAP(imageIn.convertToFormat(fallbackFormat), hbitmapFormat); + } + } - HDC display_dc = GetDC(0); + const int w = image.width(); + const int h = image.height(); - // Define the header - BITMAPINFO bmi; - initBitMapInfo(w, h, true, &bmi); + BITMAPINFO_COLORTABLE256 bmiColorTable256; + initBitMapInfo(w, h, true, compression, bitCount, &bmiColorTable256); + BITMAPINFO &bmi = reinterpret_cast(bmiColorTable256); + switch (image.format()) { + case QImage::Format_Mono: // Color table with 2 entries + case QImage::Format_Indexed8: + std::transform(image.colorTable().constBegin(), image.colorTable().constEnd(), + bmiColorTable256.bmiColors, qRgbToRgbQuad); + break; + default: + break; + } // Create the pixmap - uchar *pixels = 0; - bitmap = CreateDIBSection(display_dc, &bmi, DIB_RGB_COLORS, (void **) &pixels, 0, 0); - ReleaseDC(0, display_dc); + uchar *pixels = nullptr; + const HBITMAP bitmap = CreateDIBSection(0, &bmi, DIB_RGB_COLORS, + reinterpret_cast(&pixels), 0, 0); if (!bitmap) { qErrnoWarning("%s, failed to create dibsection", __FUNCTION__); return 0; @@ -167,59 +337,121 @@ Q_GUI_EXPORT HBITMAP qt_pixmapToWinHBITMAP(const QPixmap &p, int hbitmapFormat = qErrnoWarning("%s, did not allocate pixel data", __FUNCTION__); return 0; } - - // Copy over the data - QImage::Format imageFormat = QImage::Format_RGB32; - if (hbitmapFormat == HBitmapAlpha) - imageFormat = QImage::Format_ARGB32; - else if (hbitmapFormat == HBitmapPremultipliedAlpha) - imageFormat = QImage::Format_ARGB32_Premultiplied; - const QImage image = rasterImage->convertToFormat(imageFormat); - const int bytes_per_line = w * 4; - for (int y=0; y < h; ++y) - memcpy(pixels + y * bytes_per_line, image.scanLine(y), bytes_per_line); - + memcpy(pixels, image.constBits(), bmi.bmiHeader.biSizeImage); + if (image.format() == QImage::Format_RGB888) + flipRgb3(pixels, w, h); return bitmap; } +Q_GUI_EXPORT HBITMAP qt_pixmapToWinHBITMAP(const QPixmap &p, int hbitmapFormat = 0) +{ + if (p.isNull()) + return 0; -Q_GUI_EXPORT QPixmap qt_pixmapFromWinHBITMAP(HBITMAP bitmap, int hbitmapFormat = 0) + QPlatformPixmap *platformPixmap = p.handle(); + if (platformPixmap->classId() != QPlatformPixmap::RasterClass) { + QRasterPlatformPixmap *data = new QRasterPlatformPixmap(p.depth() == 1 ? + QRasterPlatformPixmap::BitmapType : QRasterPlatformPixmap::PixmapType); + data->fromImage(p.toImage(), Qt::AutoColor); + return qt_pixmapToWinHBITMAP(QPixmap(data), hbitmapFormat); + } + + return qt_imageToWinHBITMAP(*static_cast(platformPixmap)->buffer(), hbitmapFormat); +} + +static QImage::Format imageFromWinHBITMAP_Format(const BITMAPINFOHEADER &header, int hbitmapFormat) { - // Verify size - BITMAP bitmap_info; - memset(&bitmap_info, 0, sizeof(BITMAP)); + QImage::Format result = QImage::Format_Invalid; + switch (header.biBitCount) { + case 32: + result = hbitmapFormat == HBitmapNoAlpha + ? QImage::Format_RGB32 : QImage::Format_ARGB32_Premultiplied; + break; + case 24: + result = QImage::Format_RGB888; + break; + case 16: + result = QImage::Format_RGB555; + break; + case 8: + result = QImage::Format_Indexed8; + break; + case 1: + result = QImage::Format_Mono; + break; + } + return result; +} - const int res = GetObject(bitmap, sizeof(BITMAP), &bitmap_info); - if (!res) { - qErrnoWarning("QPixmap::fromWinHBITMAP(), failed to get bitmap info"); - return QPixmap(); +// Fast path for creating a QImage directly from a HBITMAP created by CreateDIBSection(), +// not requiring memory allocation. +static QImage imageFromWinHBITMAP_DibSection(HBITMAP bitmap, int hbitmapFormat) +{ + DIBSECTION dibSection; + memset(&dibSection, 0, sizeof(dibSection)); + dibSection.dsBmih.biSize = sizeof(dibSection.dsBmih); + + if (!GetObject(bitmap, sizeof(dibSection), &dibSection) + || !dibSection.dsBm.bmBits + || dibSection.dsBmih.biBitCount <= 8 // Cannot access the color table for Indexed8, Mono + || dibSection.dsBmih.biCompression != BI_RGB) { + return QImage(); } - const int w = bitmap_info.bmWidth; - const int h = bitmap_info.bmHeight; - - // Get bitmap bits - HDC display_dc = GetDC(0); - QScopedArrayPointer data(getDiBits(display_dc, bitmap, w, h, true)); - if (data.isNull()) { - ReleaseDC(0, display_dc); - return QPixmap(); + + const QImage::Format imageFormat = imageFromWinHBITMAP_Format(dibSection.dsBmih, hbitmapFormat); + if (imageFormat == QImage::Format_Invalid) + return QImage(); + + return copyImageData(dibSection.dsBmih, nullptr, dibSection.dsBm.bmBits, imageFormat); +} + +// Create QImage from a HBITMAP using GetDIBits(), potentially with conversion. +static QImage imageFromWinHBITMAP_GetDiBits(HBITMAP bitmap, bool forceQuads, int hbitmapFormat) +{ + BITMAPINFO_COLORTABLE256 bmiColorTable256; + BITMAPINFO &info = reinterpret_cast(bmiColorTable256); + memset(&info, 0, sizeof(info)); + info.bmiHeader.biSize = sizeof(info.bmiHeader); + + DisplayHdc displayDc; + if (!GetDIBits(displayDc, bitmap, 0, 1, 0, &info, DIB_RGB_COLORS)) { + qErrnoWarning("%s: GetDIBits() failed to query data.", __FUNCTION__); + return QImage(); } - const QImage::Format imageFormat = hbitmapFormat == HBitmapNoAlpha ? - QImage::Format_RGB32 : QImage::Format_ARGB32_Premultiplied; + if (info.bmiHeader.biHeight > 0) // Force top-down + info.bmiHeader.biHeight = -info.bmiHeader.biHeight; + info.bmiHeader.biCompression = BI_RGB; // Extract using no compression (can be BI_BITFIELD) + if (forceQuads) + info.bmiHeader.biBitCount = 32; - // Create image and copy data into image. - QImage image(w, h, imageFormat); - if (image.isNull()) { // failed to alloc? - ReleaseDC(0, display_dc); - qWarning("%s, failed create image of %dx%d", __FUNCTION__, w, h); - return QPixmap(); + const QImage::Format imageFormat = imageFromWinHBITMAP_Format(info.bmiHeader, hbitmapFormat); + if (imageFormat == QImage::Format_Invalid) { + qWarning().nospace() << __FUNCTION__ << ": unsupported image format:" << info.bmiHeader; + return QImage(); } - copyImageDataCreateAlpha(data.data(), &image); - ReleaseDC(0, display_dc); - return QPixmap::fromImage(image); + + QScopedPointer data(new uchar[info.bmiHeader.biSizeImage]); + if (!GetDIBits(displayDc, bitmap, 0, qAbs(info.bmiHeader.biHeight), data.data(), &info, DIB_RGB_COLORS)) { + qErrnoWarning("%s: GetDIBits() failed to get data.", __FUNCTION__); + return QImage(); + } + return copyImageData(info.bmiHeader, bmiColorTable256.bmiColors, data.data(), imageFormat); +} + +Q_GUI_EXPORT QImage qt_imageFromWinHBITMAP(HBITMAP bitmap, int hbitmapFormat = 0) +{ + QImage result = imageFromWinHBITMAP_DibSection(bitmap, hbitmapFormat); + if (result.isNull()) + result = imageFromWinHBITMAP_GetDiBits(bitmap, /* forceQuads */ false, hbitmapFormat); + return result; } +Q_GUI_EXPORT QPixmap qt_pixmapFromWinHBITMAP(HBITMAP bitmap, int hbitmapFormat = 0) +{ + const QImage image = imageFromWinHBITMAP_GetDiBits(bitmap, /* forceQuads */ true, hbitmapFormat); + return QPixmap::fromImage(image); +} Q_GUI_EXPORT HICON qt_pixmapToWinHICON(const QPixmap &p) { @@ -267,7 +499,7 @@ static QImage qt_imageFromWinIconHBITMAP(HDC hdc, HBITMAP bitmap, int w, int h) QScopedArrayPointer data(getDiBits(hdc, bitmap, w, h, true)); if (data.isNull()) return QImage(); - copyImageData(data.data(), &image); + memcpy(image.bits(), data.data(), image.byteCount()); return image; } @@ -303,7 +535,7 @@ Q_GUI_EXPORT QPixmap qt_pixmapFromWinHICON(HICON icon) const int h = iconinfo.yHotspot * 2; BITMAPINFOHEADER bitmapInfo; - initBitMapInfoHeader(w, h, false, &bitmapInfo); + initBitMapInfoHeader(w, h, false, BI_RGB, 32u, &bitmapInfo); DWORD* bits; HBITMAP winBitmap = CreateDIBSection(hdc, (BITMAPINFO*)&bitmapInfo, DIB_RGB_COLORS, (VOID**)&bits, NULL, 0); -- cgit v1.2.3