/**************************************************************************** ** ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the QtGui module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser General Public ** License version 2.1 as published by the Free Software Foundation and ** appearing in the file LICENSE.LGPL included in the packaging of this ** file. Please review the following information to ensure the GNU Lesser ** General Public License version 2.1 requirements will be met: ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU General ** Public License version 3.0 as published by the Free Software Foundation ** and appearing in the file LICENSE.GPL included in the packaging of this ** file. Please review the following information to ensure the GNU General ** Public License version 3.0 requirements will be met: ** http://www.gnu.org/copyleft/gpl.html. ** ** Other Usage ** Alternatively, this file may be used in accordance with the terms and ** conditions contained in a signed written agreement between you and Nokia. ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qvolatileimagedata_p.h" #include #include #include #include QT_BEGIN_NAMESPACE static CFbsBitmap *rasterizeBitmap(CFbsBitmap *bitmap, TDisplayMode newMode) { if (!bitmap) { return 0; } QScopedPointer newBitmap(new CFbsBitmap); if (newBitmap->Create(bitmap->SizeInPixels(), newMode) != KErrNone) { qWarning("QVolatileImage: Failed to create new bitmap"); return 0; } CFbsBitmapDevice *bitmapDevice = 0; CFbsBitGc *bitmapGc = 0; QT_TRAP_THROWING(bitmapDevice = CFbsBitmapDevice::NewL(newBitmap.data())); QScopedPointer bitmapDevicePtr(bitmapDevice); QT_TRAP_THROWING(bitmapGc = CFbsBitGc::NewL()); bitmapGc->Activate(bitmapDevice); bitmapGc->BitBlt(TPoint(), bitmap); delete bitmapGc; return newBitmap.take(); } static inline TDisplayMode format2TDisplayMode(QImage::Format format) { TDisplayMode mode; switch (format) { case QImage::Format_MonoLSB: mode = EGray2; break; case QImage::Format_Indexed8: mode = EColor256; break; case QImage::Format_RGB444: mode = EColor4K; break; case QImage::Format_RGB16: mode = EColor64K; break; case QImage::Format_RGB888: mode = EColor16M; break; case QImage::Format_RGB32: mode = EColor16MU; break; case QImage::Format_ARGB32: mode = EColor16MA; break; case QImage::Format_ARGB32_Premultiplied: mode = Q_SYMBIAN_ECOLOR16MAP; break; default: mode = ENone; break; } return mode; } static CFbsBitmap *imageToBitmap(const QImage &image) { if (image.isNull()) { return 0; } CFbsBitmap *bitmap = new CFbsBitmap; if (bitmap->Create(TSize(image.width(), image.height()), format2TDisplayMode(image.format())) == KErrNone) { bitmap->BeginDataAccess(); uchar *dptr = reinterpret_cast(bitmap->DataAddress()); int bmpLineLen = bitmap->DataStride(); int imgLineLen = image.bytesPerLine(); if (bmpLineLen == imgLineLen) { qMemCopy(dptr, image.constBits(), image.byteCount()); } else { int len = qMin(bmpLineLen, imgLineLen); const uchar *sptr = image.constBits(); for (int y = 0; y < image.height(); ++y) { qMemCopy(dptr, sptr, len); dptr += bmpLineLen; sptr += imgLineLen; } } bitmap->EndDataAccess(); } else { qWarning("QVolatileImage: Failed to create source bitmap"); delete bitmap; bitmap = 0; } return bitmap; } static CFbsBitmap *copyData(const QVolatileImageData &source) { source.beginDataAccess(); CFbsBitmap *bmp = imageToBitmap(source.image); source.endDataAccess(); return bmp; } static CFbsBitmap *convertData(const QVolatileImageData &source, QImage::Format newFormat) { source.beginDataAccess(); QImage img = source.image.convertToFormat(newFormat); CFbsBitmap *bmp = imageToBitmap(img); source.endDataAccess(); return bmp; } static CFbsBitmap *duplicateBitmap(const CFbsBitmap &sourceBitmap) { CFbsBitmap *bitmap = new CFbsBitmap; if (bitmap->Duplicate(sourceBitmap.Handle()) != KErrNone) { qWarning("QVolatileImage: Failed to duplicate source bitmap"); delete bitmap; bitmap = 0; } return bitmap; } static CFbsBitmap *createBitmap(int w, int h, QImage::Format format) { CFbsBitmap *bitmap = new CFbsBitmap; if (bitmap->Create(TSize(w, h), format2TDisplayMode(format)) != KErrNone) { qWarning("QVolatileImage: Failed to create source bitmap %d,%d (%d)", w, h, format); delete bitmap; bitmap = 0; } return bitmap; } static inline bool bitmapNeedsCopy(CFbsBitmap *bitmap) { bool needsCopy = bitmap->IsCompressedInRAM(); #ifdef Q_SYMBIAN_HAS_EXTENDED_BITMAP_TYPE needsCopy |= (bitmap->ExtendedBitmapType() != KNullUid); #endif return needsCopy; } static bool cleanup_function_registered = false; static QVolatileImageData *firstImageData = 0; static void cleanup() { if (RFbsSession::GetSession()) { QVolatileImageData *imageData = firstImageData; while (imageData) { imageData->release(); imageData = imageData->next; } } } static void ensureCleanup() { // Destroy all underlying bitmaps in a post routine to prevent panics. // This is a must because CFbsBitmap destructor needs the fbs session, // that was used to create the bitmap, to be open still. if (!cleanup_function_registered) { qAddPostRoutine(cleanup); cleanup_function_registered = true; } } static void registerImageData(QVolatileImageData *imageData) { ensureCleanup(); imageData->next = firstImageData; if (firstImageData) { firstImageData->prev = imageData; } firstImageData = imageData; } static void unregisterImageData(QVolatileImageData *imageData) { if (imageData->prev) { imageData->prev->next = imageData->next; } else { firstImageData = imageData->next; } if (imageData->next) { imageData->next->prev = imageData->prev; } } QVolatileImageData::QVolatileImageData() : next(0), prev(0), bitmap(0), pengine(0) { registerImageData(this); } QVolatileImageData::QVolatileImageData(int w, int h, QImage::Format format) : next(0), prev(0), bitmap(0), pengine(0) { registerImageData(this); bitmap = createBitmap(w, h, format); updateImage(); } QVolatileImageData::QVolatileImageData(const QImage &sourceImage) : next(0), prev(0), bitmap(0), pengine(0) { registerImageData(this); image = sourceImage; // The following is not mandatory, but we do it here to have a bitmap // created always in order to reduce local heap usage. ensureBitmap(); } QVolatileImageData::QVolatileImageData(void *nativeImage, void *nativeMask) : next(0), prev(0), bitmap(0), pengine(0) { registerImageData(this); if (nativeImage) { CFbsBitmap *source = static_cast(nativeImage); CFbsBitmap *mask = static_cast(nativeMask); initWithBitmap(source); if (mask) { applyMask(mask); } } } QVolatileImageData::QVolatileImageData(const QVolatileImageData &other) { bitmap = 0; pengine = 0; next = prev = 0; registerImageData(this); if (!other.image.isNull()) { bitmap = copyData(other); updateImage(); } } QVolatileImageData::~QVolatileImageData() { release(); unregisterImageData(this); } void QVolatileImageData::release() { delete bitmap; bitmap = 0; delete pengine; pengine = 0; } void QVolatileImageData::beginDataAccess() const { if (bitmap) { bitmap->BeginDataAccess(); } } void QVolatileImageData::endDataAccess(bool readOnly) const { if (bitmap) { bitmap->EndDataAccess(readOnly); } } bool QVolatileImageData::ensureFormat(QImage::Format format) { if (image.isNull()) { return false; } if (image.format() != format) { CFbsBitmap *newBitmap = convertData(*this, format); if (newBitmap && newBitmap != bitmap) { delete bitmap; bitmap = newBitmap; updateImage(); } else { return false; } } return true; } void *QVolatileImageData::duplicateNativeImage() const { const_cast(this)->ensureBitmap(); if (bitmap) { if (bitmap->DisplayMode() == EColor16M) { // slow path: needs rgb swapping beginDataAccess(); QImage tmp = image.rgbSwapped(); endDataAccess(true); return imageToBitmap(tmp); } else if (bitmap->DisplayMode() == EGray2) { // slow path: needs inverting pixels beginDataAccess(); QImage tmp = image.copy(); endDataAccess(true); tmp.invertPixels(); return imageToBitmap(tmp); } else { // fast path: just duplicate the bitmap return duplicateBitmap(*bitmap); } } return 0; } void QVolatileImageData::updateImage() { if (bitmap) { TSize size = bitmap->SizeInPixels(); beginDataAccess(); // Use existing buffer, no copy. The data address never changes so it // is enough to do this whenever we have a new CFbsBitmap. N.B. never // use const uchar* here, that would create a read-only image data which // would make a copy in detach() even when refcount is 1. image = QImage(reinterpret_cast(bitmap->DataAddress()), size.iWidth, size.iHeight, bitmap->DataStride(), qt_TDisplayMode2Format(bitmap->DisplayMode())); endDataAccess(true); } else { image = QImage(); } } void QVolatileImageData::initWithBitmap(CFbsBitmap *source) { bool needsCopy = bitmapNeedsCopy(source); if (source->DisplayMode() == EColor16M) { // EColor16M is BGR CFbsBitmap *unswappedBmp = source; if (needsCopy) { unswappedBmp = rasterizeBitmap(source, source->DisplayMode()); } unswappedBmp->BeginDataAccess(); TSize sourceSize = unswappedBmp->SizeInPixels(); QImage img((uchar *) unswappedBmp->DataAddress(), sourceSize.iWidth, sourceSize.iHeight, unswappedBmp->DataStride(), qt_TDisplayMode2Format(unswappedBmp->DisplayMode())); img = img.rgbSwapped(); unswappedBmp->EndDataAccess(true); bitmap = imageToBitmap(img); if (needsCopy) { delete unswappedBmp; } } else if (needsCopy) { // Rasterize extended and compressed bitmaps. bitmap = rasterizeBitmap(source, EColor16MAP); } else if (source->DisplayMode() == EGray2) { // The pixels will be inverted, must make a copy. bitmap = rasterizeBitmap(source, source->DisplayMode()); } else { // Efficient path: no pixel data copying. Just duplicate. This of course // means the original bitmap's data may get modified, but that's fine // and is in accordance with the QPixmap::fromSymbianCFbsBitmap() docs. bitmap = duplicateBitmap(*source); } updateImage(); if (bitmap && bitmap->DisplayMode() == EGray2) { // Symbian thinks set pixels are white/transparent, Qt thinks they are // foreground/solid. Invert mono bitmaps so that masks work correctly. beginDataAccess(); image.invertPixels(); endDataAccess(); } } void QVolatileImageData::applyMask(CFbsBitmap *mask) { ensureFormat(QImage::Format_ARGB32_Premultiplied); bool destroyMask = false; if (bitmapNeedsCopy(mask)) { mask = rasterizeBitmap(mask, EColor16MU); if (!mask) { return; } destroyMask = true; } mask->BeginDataAccess(); TSize maskSize = mask->SizeInPixels(); QImage maskImg((const uchar *) mask->DataAddress(), maskSize.iWidth, maskSize.iHeight, mask->DataStride(), qt_TDisplayMode2Format(mask->DisplayMode())); if (mask->DisplayMode() == EGray2) { maskImg = maskImg.copy(); maskImg.invertPixels(); } beginDataAccess(); image.setAlphaChannel(maskImg); endDataAccess(); mask->EndDataAccess(true); ensureImage(); if (destroyMask) { delete mask; } } void QVolatileImageData::ensureImage() { if (bitmap && !image.isNull()) { QImageData *imaged = image.data_ptr(); if (imaged->ref != 1 || imaged->ro_data) { // This is bad, the imagedata got shared somehow. Detach, in order to // have the next check fail and thus have 'image' recreated. beginDataAccess(); image.detach(); endDataAccess(true); } } if (bitmap && image.constBits() != reinterpret_cast(bitmap->DataAddress())) { // Should not ever get here. If we do it means that either 'image' has // been replaced with a copy (e.g. because some QImage API assigned a // new, regular QImage to *this) or the bitmap's data address changed // unexpectedly. qWarning("QVolatileImageData: Ptr mismatch"); // Recover by recreating the image so that it uses the bitmap as its buffer. updateImage(); } } void QVolatileImageData::ensureBitmap() { if (!bitmap && !image.isNull()) { bitmap = imageToBitmap(image); updateImage(); } } QT_END_NAMESPACE