当我们有raw数据,想存成QImage图像数据时,往往使用下列函数进行生成:
QImage(uchar *data, int width, int height, int bytesPerLine, Format format, QImageCleanupFunction cleanupFunction = nullptr, void *cleanupInfo = nullptr);
或者这样:
uchar* imgBuf = ...//假设imgBuf的位深度已经与下面设置QImage::Format_Grayscale8一致
int width = 300, height = 300;
QImage img = QImage(width, height, QImage::Format_Grayscale8);
uchar* pLine = img.scanLine(0); //获取图像数据指针基地址
memcpy(pLine, imgBuf, width * height);
但能看到有的新手是这样写的:
uchar* buf = ... //已有的raw数据缓存
QImage img = QImage(300, 300, QImage::Format_Grayscale8);
for(int y = 0; y < 300; y++)
for(int x = 0; x < 300; x++)
img.setPixel(x, y, buf[y*300 + x]);
这样写确实也能实现功能,但这样效率会很低,而且可能低的吓人。
//(Qt5.12.1 源码摘录)
void QImage::setPixel(int x, int y, uint index_or_rgb)
{
if (!d || x < 0 || x >= width() || y < 0 || y >= height()) {
qWarning("QImage::setPixel: coordinate (%d,%d) out of range", x, y);
return;
}
// detach is called from within scanLine
uchar * s = scanLine(y);
switch(d->format) {
case Format_Mono:
case Format_MonoLSB:
if (index_or_rgb > 1) {
qWarning("QImage::setPixel: Index %d out of range", index_or_rgb);
} else if (format() == Format_MonoLSB) {
if (index_or_rgb==0)
*(s + (x >> 3)) &= ~(1 << (x & 7));
else
*(s + (x >> 3)) |= (1 << (x & 7));
} else {
if (index_or_rgb==0)
*(s + (x >> 3)) &= ~(1 << (7-(x & 7)));
else
*(s + (x >> 3)) |= (1 << (7-(x & 7)));
}
return;
case Format_Indexed8:
if (index_or_rgb >= (uint)d->colortable.size()) {
qWarning("QImage::setPixel: Index %d out of range", index_or_rgb);
return;
}
s[x] = index_or_rgb;
return;
case Format_RGB32:
//make sure alpha is 255, we depend on it in qdrawhelper for cases
// when image is set as a texture pattern on a qbrush
((uint *)s)[x] = 0xff000000 | index_or_rgb;
return;
case Format_ARGB32:
case Format_ARGB32_Premultiplied:
((uint *)s)[x] = index_or_rgb;
return;
case Format_RGB16:
((quint16 *)s)[x] = qConvertRgb32To16(qUnpremultiply(index_or_rgb));
return;
case Format_RGBX8888:
((uint *)s)[x] = ARGB2RGBA(0xff000000 | index_or_rgb);
return;
case Format_RGBA8888:
case Format_RGBA8888_Premultiplied:
((uint *)s)[x] = ARGB2RGBA(index_or_rgb);
return;
case Format_BGR30:
((uint *)s)[x] = qConvertRgb32ToRgb30<PixelOrderBGR>(index_or_rgb);
return;
case Format_A2BGR30_Premultiplied:
((uint *)s)[x] = qConvertArgb32ToA2rgb30<PixelOrderBGR>(index_or_rgb);
return;
case Format_RGB30:
((uint *)s)[x] = qConvertRgb32ToRgb30<PixelOrderRGB>(index_or_rgb);
return;
case Format_A2RGB30_Premultiplied:
((uint *)s)[x] = qConvertArgb32ToA2rgb30<PixelOrderRGB>(index_or_rgb);
return;
case Format_Invalid:
case NImageFormats:
Q_ASSERT(false);
return;
default:
break;
}
const QPixelLayout *layout = &qPixelLayouts[d->format];
layout->storeFromARGB32PM(s, &index_or_rgb, x, 1, nullptr, nullptr);
这里最耗时的可能还不是中间那一大串的switch语句。首先是scanLine(),其中会调用detach(),该函数 在最坏的情况下,会进行整个类的深拷贝:
//(Qt5.12.1 源码摘录)
void QImage::detach()
{
if (d) {
if (d->is_cached && d->ref.load() == 1)
QImagePixmapCleanupHooks::executeImageHooks(cacheKey());
if (d->ref.load() != 1 || d->ro_data)
*this = copy();
if (d)
++d->detach_no;
}
}
当然,上面的例子不符合深拷贝的条件,但是storeFromARGB32PM会进行不同像素数据格式转换,一次两次没啥问题,如果放在循环里面,真就是大可不必。