summaryrefslogtreecommitdiffstats
path: root/src/gui/image
diff options
context:
space:
mode:
authorBenjamin Poulain <benjamin.poulain@nokia.com>2010-06-19 19:56:29 (GMT)
committerBenjamin Poulain <benjamin.poulain@nokia.com>2010-06-20 02:30:33 (GMT)
commit0a96503e84b418708712af61497df4a493ed9072 (patch)
treea1b29c6416f0896a3c9fee394c7858a0ec744713 /src/gui/image
parentcf5971503ee1f7a5ce96758e33796dfdf48375bf (diff)
downloadQt-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.cpp274
-rw-r--r--src/gui/image/qimage_p.h3
-rw-r--r--src/gui/image/qpixmap_raster.cpp214
-rw-r--r--src/gui/image/qpixmap_raster_p.h3
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 &image;
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: