diff options
author | Benjamin Poulain <benjamin.poulain@nokia.com> | 2010-06-19 19:56:29 (GMT) |
---|---|---|
committer | Benjamin Poulain <benjamin.poulain@nokia.com> | 2010-06-20 02:30:33 (GMT) |
commit | 0a96503e84b418708712af61497df4a493ed9072 (patch) | |
tree | a1b29c6416f0896a3c9fee394c7858a0ec744713 /src/gui/image | |
parent | cf5971503ee1f7a5ce96758e33796dfdf48375bf (diff) | |
download | Qt-0a96503e84b418708712af61497df4a493ed9072.zip Qt-0a96503e84b418708712af61497df4a493ed9072.tar.gz Qt-0a96503e84b418708712af61497df4a493ed9072.tar.bz2 |
Start the implementation of in-place recoding for images
Currently, with the graphics system raster, converting from images
to QPixmap often needs to allocate a new image to convert the right
format.
For example, for an image in ARGB32 of 10 mbytes, we need to allocate
a second image of 10 mbytes in ARGB32_PM to convert the source image
in the right format for pixmap.
This can create a hight peak of memory, and is a bit slower
than it should.
This patch introduce in-place conversion of images when they are
loaded with QPixmap::loadFromData().
The images are loaded in their default format by QImageReader,
and are then converted in-place, trying to reduce memory
allocations.
Reviewed-by: Samuel Rødal
Diffstat (limited to 'src/gui/image')
-rw-r--r-- | src/gui/image/qimage.cpp | 274 | ||||
-rw-r--r-- | src/gui/image/qimage_p.h | 3 | ||||
-rw-r--r-- | src/gui/image/qpixmap_raster.cpp | 214 | ||||
-rw-r--r-- | src/gui/image/qpixmap_raster_p.h | 3 |
4 files changed, 399 insertions, 95 deletions
diff --git a/src/gui/image/qimage.cpp b/src/gui/image/qimage.cpp index adc2632..bb8a994 100644 --- a/src/gui/image/qimage.cpp +++ b/src/gui/image/qimage.cpp @@ -2274,6 +2274,8 @@ bool QImage::create(const QSize& size, int depth, int numColors, QImage::Endian typedef void (*Image_Converter)(QImageData *dest, const QImageData *src, Qt::ImageConversionFlags); +typedef bool (*InPlace_Image_Converter)(QImageData *data, Qt::ImageConversionFlags); + static void convert_ARGB_to_ARGB_PM(QImageData *dest, const QImageData *src, Qt::ImageConversionFlags) { Q_ASSERT(src->format == QImage::Format_ARGB32); @@ -2298,6 +2300,169 @@ static void convert_ARGB_to_ARGB_PM(QImageData *dest, const QImageData *src, Qt: } } +static bool convert_ARGB_to_ARGB_PM_inplace(QImageData *data, Qt::ImageConversionFlags) +{ + Q_ASSERT(data->format == QImage::Format_ARGB32); + + const int pad = (data->bytes_per_line >> 2) - data->width; + QRgb *rgb_data = (QRgb *) data->data; + + for (int i = 0; i < data->height; ++i) { + const QRgb *end = rgb_data + data->width; + while (rgb_data < end) { + *rgb_data = PREMUL(*rgb_data); + ++rgb_data; + } + rgb_data += pad; + } + data->format = QImage::Format_ARGB32_Premultiplied; + return true; +} + +static bool convert_indexed8_to_ARGB_PM_inplace(QImageData *data, Qt::ImageConversionFlags) +{ + Q_ASSERT(data->format == QImage::Format_Indexed8); + const int depth = 32; + + const int dst_bytes_per_line = ((data->width * depth + 31) >> 5) << 2; + const int nbytes = dst_bytes_per_line * data->height; + uchar *const newData = (uchar *)realloc(data->data, nbytes); + if (!newData) + return false; + + data->data = newData; + + // start converting from the end because the end image is bigger than the source + uchar *src_data = newData + data->nbytes; // end of src + quint32 *dest_data = (quint32 *) (newData + nbytes); // end of dest > end of src + const int width = data->width; + const int src_pad = data->bytes_per_line - width; + const int dest_pad = (dst_bytes_per_line >> 2) - width; + + for (int i = 0; i < data->height; ++i) { + src_data -= src_pad; + dest_data -= dest_pad; + for (int pixI = 0; pixI < width; ++pixI) { + --src_data; + --dest_data; + const uint pixel = data->colortable[*src_data]; + *dest_data = (quint32) PREMUL(pixel); + } + } + + data->format = QImage::Format_ARGB32_Premultiplied; + data->bytes_per_line = dst_bytes_per_line; + data->depth = depth; + data->nbytes = nbytes; + + return true; +} + +static bool convert_indexed8_to_RGB_inplace(QImageData *data, Qt::ImageConversionFlags) +{ + Q_ASSERT(data->format == QImage::Format_Indexed8); + const int depth = 32; + + const int dst_bytes_per_line = ((data->width * depth + 31) >> 5) << 2; + const int nbytes = dst_bytes_per_line * data->height; + uchar *const newData = (uchar *)realloc(data->data, nbytes); + if (!newData) + return false; + + data->data = newData; + + // start converting from the end because the end image is bigger than the source + uchar *src_data = newData + data->nbytes; + quint32 *dest_data = (quint32 *) (newData + nbytes); + const int width = data->width; + const int src_pad = data->bytes_per_line - width; + const int dest_pad = (dst_bytes_per_line >> 2) - width; + + for (int i = 0; i < data->height; ++i) { + src_data -= src_pad; + dest_data -= dest_pad; + for (int pixI = 0; pixI < width; ++pixI) { + --src_data; + --dest_data; + *dest_data = (quint32) data->colortable[*src_data]; + } + } + + data->format = QImage::Format_RGB32; + data->bytes_per_line = dst_bytes_per_line; + data->depth = depth; + data->nbytes = nbytes; + + return true; +} + +static bool convert_indexed8_to_RGB16_inplace(QImageData *data, Qt::ImageConversionFlags) +{ + Q_ASSERT(data->format == QImage::Format_Indexed8); + const int depth = 16; + + const int dst_bytes_per_line = ((data->width * depth + 31) >> 5) << 2; + const int nbytes = dst_bytes_per_line * data->height; + uchar *const newData = (uchar *)realloc(data->data, nbytes); + if (!newData) + return false; + + data->data = newData; + + // start converting from the end because the end image is bigger than the source + uchar *src_data = newData + data->nbytes; + quint16 *dest_data = (quint16 *) (newData + nbytes); + const int width = data->width; + const int src_pad = data->bytes_per_line - width; + const int dest_pad = (dst_bytes_per_line >> 1) - width; + + for (int i = 0; i < data->height; ++i) { + src_data -= src_pad; + dest_data -= dest_pad; + for (int pixI = 0; pixI < width; ++pixI) { + --src_data; + --dest_data; + const uint pixel = data->colortable[*src_data]; + *dest_data = qt_colorConvert<quint16, quint32>(pixel, 0); + } + } + + data->format = QImage::Format_RGB16; + data->bytes_per_line = dst_bytes_per_line; + data->depth = depth; + data->nbytes = nbytes; + + return true; +} + +static bool convert_RGB_to_RGB16_inplace(QImageData *data, Qt::ImageConversionFlags) +{ + Q_ASSERT(data->format == QImage::Format_RGB32); + const int depth = 16; + + const int dst_bytes_per_line = ((data->width * depth + 31) >> 5) << 2; + const int src_bytes_per_line = data->bytes_per_line; + quint32 *src_data = (quint32 *) data->data; + quint16 *dst_data = (quint16 *) data->data; + + for (int i = 0; i < data->height; ++i) { + qt_memconvert(dst_data, src_data, data->width); + src_data = (quint32 *) (((char*)src_data) + src_bytes_per_line); + dst_data = (quint16 *) (((char*)dst_data) + dst_bytes_per_line); + } + data->format = QImage::Format_RGB16; + data->bytes_per_line = dst_bytes_per_line; + data->depth = depth; + data->nbytes = dst_bytes_per_line * data->height; + uchar *const newData = (uchar *)realloc(data->data, data->nbytes); + if (newData) { + data->data = newData; + return true; + } else { + return false; + } +} + static void convert_ARGB_PM_to_ARGB(QImageData *dest, const QImageData *src, Qt::ImageConversionFlags) { Q_ASSERT(src->format == QImage::Format_ARGB32_Premultiplied); @@ -3447,6 +3612,103 @@ static const Image_Converter converter_map[QImage::NImageFormats][QImage::NImage } // Format_ARGB4444_Premultiplied }; +static const InPlace_Image_Converter inplace_converter_map[QImage::NImageFormats][QImage::NImageFormats] = +{ + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, // Format_Mono + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, // Format_MonoLSB + { + 0, + 0, + 0, + 0, + 0, + convert_indexed8_to_RGB_inplace, + convert_indexed8_to_ARGB_PM_inplace, + convert_indexed8_to_RGB16_inplace, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + }, // Format_Indexed8 + { + 0, + 0, + 0, + 0, + 0, + 0, + 0, + convert_RGB_to_RGB16_inplace, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + }, // Format_ARGB32 + { + 0, + 0, + 0, + 0, + 0, + 0, + convert_ARGB_to_ARGB_PM_inplace, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + }, // Format_ARGB32 + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, // Format_ARGB32_Premultiplied + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, // Format_RGB16 + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, // Format_ARGB8565_Premultiplied + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, // Format_RGB666 + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, // Format_ARGB6666_Premultiplied + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, // Format_RGB555 + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, // Format_ARGB8555_Premultiplied + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, // Format_RGB888 + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, // Format_RGB444 + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + } // Format_ARGB4444_Premultiplied +}; + /*! Returns a copy of the image in the given \a format. @@ -6276,6 +6538,18 @@ QTransform QImage::trueMatrix(const QTransform &matrix, int w, int h) return matrix * QTransform().translate(-delta.x(), -delta.y()); } +bool QImageData::convertInPlace(QImage::Format newFormat, Qt::ImageConversionFlags flags) +{ + if (format == newFormat) + return true; + + const InPlace_Image_Converter *const converterPtr = &inplace_converter_map[format][newFormat]; + InPlace_Image_Converter converter = *converterPtr; + if (converter) + return converter(this, flags); + else + return false; +} /*! \typedef QImage::DataPtr diff --git a/src/gui/image/qimage_p.h b/src/gui/image/qimage_p.h index 0c19647..f1a0c47 100644 --- a/src/gui/image/qimage_p.h +++ b/src/gui/image/qimage_p.h @@ -96,6 +96,9 @@ struct Q_GUI_EXPORT QImageData { // internal image data bool checkForAlphaPixels() const; + // Convert the image in-place, minimizing memory reallocation + // Return false if the conversion cannot be done in-place. + bool convertInPlace(QImage::Format newFormat, Qt::ImageConversionFlags); #ifndef QT_NO_IMAGE_TEXT QMap<QString, QString> text; diff --git a/src/gui/image/qpixmap_raster.cpp b/src/gui/image/qpixmap_raster.cpp index 4f32d2f..13c03a1 100644 --- a/src/gui/image/qpixmap_raster.cpp +++ b/src/gui/image/qpixmap_raster.cpp @@ -47,6 +47,9 @@ #include "qbitmap.h" #include "qimage.h" +#include <QBuffer> +#include <QImageReader> +#include <private/qsimd_p.h> #include <private/qwidget_p.h> #include <private/qdrawhelper_p.h> @@ -127,104 +130,26 @@ void QRasterPixmapData::resize(int width, int height) setSerialNumber(image.serialNumber()); } +bool QRasterPixmapData::fromData(const uchar *buffer, uint len, const char *format, + Qt::ImageConversionFlags flags) +{ + QByteArray a = QByteArray::fromRawData(reinterpret_cast<const char *>(buffer), len); + QBuffer b(&a); + b.open(QIODevice::ReadOnly); + QImage image = QImageReader(&b, format).read(); + if (image.isNull()) + return false; + + createPixmapForImage(image, flags, /* inplace = */true); + return !isNull(); +} + void QRasterPixmapData::fromImage(const QImage &sourceImage, Qt::ImageConversionFlags flags) { Q_UNUSED(flags); - -#ifdef Q_WS_QWS - QImage::Format format; - if (pixelType() == BitmapType) { - format = QImage::Format_Mono; - } else { - format = QScreen::instance()->pixelFormat(); - if (format == QImage::Format_Invalid) - format = QImage::Format_ARGB32_Premultiplied; - else if (format == QImage::Format_Indexed8) // currently not supported - format = QImage::Format_RGB444; - } - - if (sourceImage.hasAlphaChannel() - && ((flags & Qt::NoOpaqueDetection) - || const_cast<QImage &>(sourceImage).data_ptr()->checkForAlphaPixels())) { - switch (format) { - case QImage::Format_RGB16: - format = QImage::Format_ARGB8565_Premultiplied; - break; - case QImage::Format_RGB666: - format = QImage::Format_ARGB6666_Premultiplied; - break; - case QImage::Format_RGB555: - format = QImage::Format_ARGB8555_Premultiplied; - break; - case QImage::Format_RGB444: - format = QImage::Format_ARGB4444_Premultiplied; - break; - default: - format = QImage::Format_ARGB32_Premultiplied; - break; - } - } else if (format == QImage::Format_Invalid) { - format = QImage::Format_ARGB32_Premultiplied; - } - - image = sourceImage.convertToFormat(format); -#else - if (pixelType() == BitmapType) { - image = sourceImage.convertToFormat(QImage::Format_MonoLSB); - } else { - if (sourceImage.depth() == 1) { - image = sourceImage.hasAlphaChannel() - ? sourceImage.convertToFormat(QImage::Format_ARGB32_Premultiplied) - : sourceImage.convertToFormat(QImage::Format_RGB32); - } else { - - QImage::Format opaqueFormat = QNativeImage::systemFormat(); - QImage::Format alphaFormat = QImage::Format_ARGB32_Premultiplied; - -#ifndef QT_HAVE_NEON - switch (opaqueFormat) { - case QImage::Format_RGB16: - alphaFormat = QImage::Format_ARGB8565_Premultiplied; - break; - default: // We don't care about the others... - break; - } -#endif - - if (!sourceImage.hasAlphaChannel()) { - image = sourceImage.convertToFormat(opaqueFormat); - } else if ((flags & Qt::NoOpaqueDetection) == 0 - && !const_cast<QImage &>(sourceImage).data_ptr()->checkForAlphaPixels()) - { - // image has alpha format but is really opaque, so try to do a - // more efficient conversion - if (sourceImage.format() == QImage::Format_ARGB32 - || sourceImage.format() == QImage::Format_ARGB32_Premultiplied) - { - QImage rgbImage(sourceImage.bits(), sourceImage.width(), sourceImage.height(), - QImage::Format_RGB32); - image = rgbImage.convertToFormat(opaqueFormat); - image.detach(); - } else { - image = sourceImage.convertToFormat(opaqueFormat); - } - } else { - image = sourceImage.convertToFormat(alphaFormat); - } - } - } -#endif - if (image.d) { - w = image.d->width; - h = image.d->height; - d = image.d->depth; - } else { - w = h = d = 0; - } - is_null = (w <= 0 || h <= 0); - - setSerialNumber(image.serialNumber()); + QImage image = sourceImage; + createPixmapForImage(image, flags, /* inplace = */false); } // from qwindowsurface.cpp @@ -253,7 +178,7 @@ void QRasterPixmapData::fill(const QColor &color) if (alpha != 255) { if (!image.hasAlphaChannel()) { QImage::Format toFormat; -#ifndef QT_HAVE_NEON +#if !(defined(QT_HAVE_NEON) || defined(QT_ALWAYS_HAVE_SSE2)) if (image.format() == QImage::Format_RGB16) toFormat = QImage::Format_ARGB8565_Premultiplied; else if (image.format() == QImage::Format_RGB666) @@ -411,6 +336,105 @@ int QRasterPixmapData::metric(QPaintDevice::PaintDeviceMetric metric) const return 0; } +void QRasterPixmapData::createPixmapForImage(QImage &sourceImage, Qt::ImageConversionFlags flags, bool inPlace) +{ + QImage::Format format; +#ifdef Q_WS_QWS + if (pixelType() == BitmapType) { + format = QImage::Format_Mono; + } else { + format = QScreen::instance()->pixelFormat(); + if (format == QImage::Format_Invalid) + format = QImage::Format_ARGB32_Premultiplied; + else if (format == QImage::Format_Indexed8) // currently not supported + format = QImage::Format_RGB444; + } + + if (sourceImage.hasAlphaChannel() + && ((flags & Qt::NoOpaqueDetection) + || const_cast<QImage &>(sourceImage).data_ptr()->checkForAlphaPixels())) { + switch (format) { + case QImage::Format_RGB16: + format = QImage::Format_ARGB8565_Premultiplied; + break; + case QImage::Format_RGB666: + format = QImage::Format_ARGB6666_Premultiplied; + break; + case QImage::Format_RGB555: + format = QImage::Format_ARGB8555_Premultiplied; + break; + case QImage::Format_RGB444: + format = QImage::Format_ARGB4444_Premultiplied; + break; + default: + format = QImage::Format_ARGB32_Premultiplied; + break; + } + } else if (format == QImage::Format_Invalid) { + format = QImage::Format_ARGB32_Premultiplied; + } +#else + if (pixelType() == BitmapType) { + format = QImage::Format_MonoLSB; + } else { + if (sourceImage.depth() == 1) { + format = sourceImage.hasAlphaChannel() + ? QImage::Format_ARGB32_Premultiplied + : QImage::Format_RGB32; + } else { + QImage::Format opaqueFormat = QNativeImage::systemFormat(); + QImage::Format alphaFormat = QImage::Format_ARGB32_Premultiplied; + +#if !defined(QT_HAVE_NEON) && !defined(QT_ALWAYS_HAVE_SSE2) + switch (opaqueFormat) { + case QImage::Format_RGB16: + alphaFormat = QImage::Format_ARGB8565_Premultiplied; + break; + default: // We don't care about the others... + break; + } +#endif + + if (!sourceImage.hasAlphaChannel()) { + format = opaqueFormat; + } else if ((flags & Qt::NoOpaqueDetection) == 0 + && !const_cast<QImage &>(sourceImage).data_ptr()->checkForAlphaPixels()) + { + // image has alpha format but is really opaque, so try to do a + // more efficient conversion + if (sourceImage.format() == QImage::Format_ARGB32 + || sourceImage.format() == QImage::Format_ARGB32_Premultiplied) + { + if (!inPlace) + sourceImage.detach(); + sourceImage.d->format = QImage::Format_RGB32; + } + format = opaqueFormat; + } else { + format = alphaFormat; + } + } + } +#endif + + if (inPlace && sourceImage.d->convertInPlace(format, flags)) { + image = sourceImage; + } else { + image = sourceImage.convertToFormat(format); + } + + if (image.d) { + w = image.d->width; + h = image.d->height; + d = image.d->depth; + } else { + w = h = d = 0; + } + is_null = (w <= 0 || h <= 0); + + setSerialNumber(image.serialNumber()); +} + QImage* QRasterPixmapData::buffer() { return ℑ diff --git a/src/gui/image/qpixmap_raster_p.h b/src/gui/image/qpixmap_raster_p.h index 6cdd3d7..d7e3f85 100644 --- a/src/gui/image/qpixmap_raster_p.h +++ b/src/gui/image/qpixmap_raster_p.h @@ -72,6 +72,7 @@ public: void resize(int width, int height); void fromFile(const QString &filename, Qt::ImageConversionFlags flags); + bool fromData(const uchar *buffer, uint len, const char *format, Qt::ImageConversionFlags flags); void fromImage(const QImage &image, Qt::ImageConversionFlags flags); bool scroll(int dx, int dy, const QRect &rect); @@ -85,6 +86,8 @@ public: protected: int metric(QPaintDevice::PaintDeviceMetric metric) const; + void createPixmapForImage(QImage &sourceImage, Qt::ImageConversionFlags flags, bool inPlace); + void setImage(const QImage &image); QImage image; private: |