/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtGui module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qbitmap.h" #include "qpixmap.h" #include #include "qpixmap_raster_p.h" #include #include #include #include #include QT_BEGIN_NAMESPACE 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 = 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); } 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, 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, BI_RGB, 32u, &bmi); uchar *result = new uchar[bmi.bmiHeader.biSizeImage]; if (!GetDIBits(hdc, bitmap, 0, UINT(height), result, &bmi, DIB_RGB_COLORS)) { delete [] result; qErrnoWarning("%s: GetDIBits() failed to get bitmap bits.", __FUNCTION__); return nullptr; } return result; } static inline void copyImageDataCreateAlpha(const uchar *data, QImage *target) { const uint mask = target->format() == QImage::Format_RGB32 ? 0xff000000 : 0; const int height = target->height(); const int width = target->width(); const int bytesPerLine = width * int(sizeof(QRgb)); for (int y = 0; y < height; ++y) { QRgb *dest = reinterpret_cast(target->scanLine(y)); const QRgb *src = reinterpret_cast(data + y * bytesPerLine); for (int x = 0; x < width; ++x) { const uint pixel = src[x]; if ((pixel & 0xff000000) == 0 && (pixel & 0x00ffffff) != 0) dest[x] = pixel | 0xff000000; else dest[x] = pixel | mask; } } } // 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 lineSize = 3 * width; const int linePad = pad4(lineSize) - lineSize; for (int y = 0; y < height; ++y) { 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.sizeInBytes()) == 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_MOVE(DisplayHdc) public: DisplayHdc() : m_displayDc(GetDC(nullptr)) {} ~DisplayHdc() { ReleaseDC(nullptr, m_displayDc); } operator HDC() const { return m_displayDc; } private: const HDC m_displayDc; }; enum HBitmapFormat { HBitmapNoAlpha, HBitmapPremultipliedAlpha, HBitmapAlpha }; Q_GUI_EXPORT HBITMAP qt_createIconMask(const QBitmap &bitmap) { QImage bm = bitmap.toImage().convertToFormat(QImage::Format_Mono); const int w = bm.width(); const int h = bm.height(); const int bpl = ((w+15)/16)*2; // bpl, 16 bit alignment QScopedArrayPointer bits(new uchar[size_t(bpl * h)]); bm.invertPixels(); for (int y = 0; y < h; ++y) memcpy(bits.data() + y * bpl, bm.constScanLine(y), size_t(bpl)); HBITMAP hbm = CreateBitmap(w, h, 1, 1, bits.data()); return hbm; } static inline QImage::Format format32(int hbitmapFormat) { 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 nullptr; // Define the header DWORD compression = 0; DWORD bitCount = 0; // 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: case QImage::Format_BGR888: 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); } } const int w = image.width(); const int h = image.height(); 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 = nullptr; const HBITMAP bitmap = CreateDIBSection(nullptr, &bmi, DIB_RGB_COLORS, reinterpret_cast(&pixels), nullptr, 0); if (!bitmap) { qErrnoWarning("%s, failed to create dibsection", __FUNCTION__); return nullptr; } if (!pixels) { qErrnoWarning("%s, did not allocate pixel data", __FUNCTION__); return nullptr; } 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 nullptr; 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) { 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_BGR888; break; case 16: result = QImage::Format_RGB555; break; case 8: result = QImage::Format_Indexed8; break; case 1: result = QImage::Format_Mono; break; } return result; } // 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 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(); } 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) size_t allocSize = info.bmiHeader.biSizeImage; if (forceQuads) { info.bmiHeader.biBitCount = 32; allocSize = info.bmiHeader.biWidth * qAbs(info.bmiHeader.biHeight) * 4; } const QImage::Format imageFormat = imageFromWinHBITMAP_Format(info.bmiHeader, hbitmapFormat); if (imageFormat == QImage::Format_Invalid) { qWarning().nospace() << __FUNCTION__ << ": unsupported image format:" << info.bmiHeader; return QImage(); } QScopedArrayPointer data(new uchar[allocSize]); 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) { return QPixmap::fromImage(imageFromWinHBITMAP_GetDiBits(bitmap, /* forceQuads */ true, hbitmapFormat)); } Q_GUI_EXPORT HICON qt_pixmapToWinHICON(const QPixmap &p) { if (p.isNull()) return nullptr; QBitmap maskBitmap = p.mask(); if (maskBitmap.isNull()) { maskBitmap = QBitmap(p.size()); maskBitmap.fill(Qt::color1); } ICONINFO ii; ii.fIcon = true; ii.hbmMask = qt_createIconMask(maskBitmap); ii.hbmColor = qt_pixmapToWinHBITMAP(p, HBitmapAlpha); ii.xHotspot = 0; ii.yHotspot = 0; HICON hIcon = CreateIconIndirect(&ii); DeleteObject(ii.hbmColor); DeleteObject(ii.hbmMask); return hIcon; } Q_GUI_EXPORT QImage qt_imageFromWinHBITMAP(HDC hdc, HBITMAP bitmap, int w, int h) { QImage image(w, h, QImage::Format_ARGB32_Premultiplied); if (image.isNull()) return image; QScopedArrayPointer data(getDiBits(hdc, bitmap, w, h, true)); if (data.isNull()) return QImage(); copyImageDataCreateAlpha(data.data(), &image); return image; } static QImage qt_imageFromWinIconHBITMAP(HDC hdc, HBITMAP bitmap, int w, int h) { QImage image(w, h, QImage::Format_ARGB32_Premultiplied); if (image.isNull()) return image; QScopedArrayPointer data(getDiBits(hdc, bitmap, w, h, true)); if (data.isNull()) return QImage(); memcpy(image.bits(), data.data(), size_t(image.sizeInBytes())); return image; } static inline bool hasAlpha(const QImage &image) { const int w = image.width(); const int h = image.height(); for (int y = 0; y < h; ++y) { const QRgb *scanLine = reinterpret_cast(image.scanLine(y)); for (int x = 0; x < w; ++x) { if (qAlpha(scanLine[x]) != 0) return true; } } return false; } Q_GUI_EXPORT QPixmap qt_pixmapFromWinHICON(HICON icon) { HDC screenDevice = GetDC(nullptr); HDC hdc = CreateCompatibleDC(screenDevice); ReleaseDC(nullptr, screenDevice); ICONINFO iconinfo; const bool result = GetIconInfo(icon, &iconinfo); //x and y Hotspot describes the icon center if (!result) { qErrnoWarning("QPixmap::fromWinHICON(), failed to GetIconInfo()"); DeleteDC(hdc); return QPixmap(); } const int w = int(iconinfo.xHotspot) * 2; const int h = int(iconinfo.yHotspot) * 2; BITMAPINFOHEADER bitmapInfo; initBitMapInfoHeader(w, h, false, BI_RGB, 32u, &bitmapInfo); DWORD* bits; HBITMAP winBitmap = CreateDIBSection(hdc, reinterpret_cast(&bitmapInfo), DIB_RGB_COLORS, reinterpret_cast(&bits), nullptr, 0); HGDIOBJ oldhdc = static_cast(SelectObject(hdc, winBitmap)); DrawIconEx(hdc, 0, 0, icon, w, h, 0, nullptr, DI_NORMAL); QImage image = qt_imageFromWinIconHBITMAP(hdc, winBitmap, w, h); if (!image.isNull() && !hasAlpha(image)) { //If no alpha was found, we use the mask to set alpha values DrawIconEx( hdc, 0, 0, icon, w, h, 0, nullptr, DI_MASK); const QImage mask = qt_imageFromWinIconHBITMAP(hdc, winBitmap, w, h); for (int y = 0 ; y < h ; y++){ QRgb *scanlineImage = reinterpret_cast(image.scanLine(y)); const QRgb *scanlineMask = mask.isNull() ? nullptr : reinterpret_cast(mask.scanLine(y)); for (int x = 0; x < w ; x++){ if (scanlineMask && qRed(scanlineMask[x]) != 0) scanlineImage[x] = 0; //mask out this pixel else scanlineImage[x] |= 0xff000000; // set the alpha channel to 255 } } } //dispose resources created by iconinfo call DeleteObject(iconinfo.hbmMask); DeleteObject(iconinfo.hbmColor); SelectObject(hdc, oldhdc); //restore state DeleteObject(winBitmap); DeleteDC(hdc); return QPixmap::fromImage(std::move(image)); } QT_END_NAMESPACE