Qt中QImage用于16位图像的显示,QImage数据对齐

Qt中QImage用于16位图像的显示,QImage数据对齐

之前总结过QImage类,https://blog.csdn.net/weixin_43294620/article/details/122419099?spm=1001.2014.3001.5501

但是对于16位图像的显示没有详细说明,Qt也支持16位的图像,例如QImage::Format_Grayscale16QImage::Format_RGBX64,以上两种格式分别是Qt5.13和5.12版本引入的,现在分别对以上两种图像进行分析,为了图像转换方便,在分析的时候同时引入QImage::Format_Indexed8格式,图像分析的时候统一用一个height=10, width=9的图像进行分析,width设为9是为了说明QImage数据对齐的问题。

一、Format_Indexed8格式

QImage qimg8(9,10,QImage::Format_Indexed8); //新建一个10行9列的QImage数组,会自动数据对齐,每行12字节
qimg8.setColorCount(256);				// grayscale 256
for (int i = 0; i < 256; i++)
{
    qimg8.setColor(i, qRgb(i, i, i));
}
for(int y =0;y<10;y++)
{
    for(int x=0;x<9;x++)
    {
        qimg8.setPixel(x,y,(y+1)*10+x+1);   //Note: (x,y) means coordinate,不是行列下标!!!!
    }
}
//为了显示方便,最后一行设为255
for(int final=0; final<9; final++)
{
    qimg8.setPixel(final,9,255);
}
qDebug() << u8"Total bytes of Image: " << qimg8.sizeInBytes() << Qt::endl;
//'byteCount()' is deprecated: Use the sizeInBytes.
//Deprecated means this method is obsolete and temporarily available, but it will not be updated in the future.
//It may be deleted later. It is recommended that later people not call this method.

qDebug() << u8"Bytes per Line of Image: " << qimg8.bytesPerLine() << Qt::endl;
//uchar * pUchar = qimg8.scanLine(0);
qDebug() << u8"Address of scanLine(0): " << pUchar << Qt::endl;
qDebug() << u8"Value of scanLine(0): " << *pUchar << Qt::endl;
for(int y = 0;y<10;y++) //print the value of each point
{
qDebug() << "Value of " << y+1 << "Rows: " << qimg8.pixelIndex(0,y) << qimg8.pixelIndex(1,y)
         <<qimg8.pixelIndex(2,y) <<qimg8.pixelIndex(3,y) <<qimg8.pixelIndex(4,y)
         <<qimg8.pixelIndex(5,y) <<qimg8.pixelIndex(6,y) <<qimg8.pixelIndex(7,y)
         <<qimg8.pixelIndex(8,y)  << Qt::endl;
}

以上输出结果为:

Value of  1 Rows:  11 12 13 14 15 16 17 18 19 
Value of  2 Rows:  21 22 23 24 25 26 27 28 29 
Value of  3 Rows:  31 32 33 34 35 36 37 38 39 
Value of  4 Rows:  41 42 43 44 45 46 47 48 49 
Value of  5 Rows:  51 52 53 54 55 56 57 58 59 
Value of  6 Rows:  61 62 63 64 65 66 67 68 69 
Value of  7 Rows:  71 72 73 74 75 76 77 78 79 
Value of  8 Rows:  81 82 83 84 85 86 87 88 89 
Value of  9 Rows:  91 92 93 94 95 96 97 98 99 
Value of  10 Rows:  255 255 255 255 255 255 255 255 255 

图像显示如下:

如果在Debug模式下可以看到Qt中图像的信息为:

Format_Indexed8并不能显示16位图像,在此先借用格式说明一下QImage的数据对齐,后面还会用到此类型的图像进行格式转换。我们创建的是9*10的QImage,但实际上每行有12字节,而不是9字节。这是因为QImage是32位对齐的(应该是为了执行效率),也可以说4字节对齐。可以根据公式确定 W = ( w i d t h × b i t c o u n t + 31 ) / 32 × 4 W=(width×bitcount+31)/32×4 W=(width×bitcount+31)/32×4,其中width为图像的实际宽度,bitcount是图像的位深,例如以上图像的位深为8,W是计算得到的程序中图像每行的字节数。所以创建9*10的图像时,实际的宽度 W = ( 9 × 8 + 31 ) / 8 = 12 W=(9×8+31)/8=12 W=(9×8+31)/8=12

但是注意虽然实际存储的时候可能会比原图像宽度大,但是显示并不会显示多出来的部分,例如以上图像,利用指针打印出前12个像素值:

uchar * pUchar = qimg8.scanLine(0);
for(int i = 0;i<12;i++) //print the value of each point through pointer!
{
	qDebug() << *(pUchar+i) ;
}

结果如下:11 12 13 14 15 16 17 18 19 205 205 205

可以看到,补充的值为205,但并没有显示该部分(竖白条),虽然没有显示,但是实际上在内存中是有随机值的,所以在操作像素的时候应注意对齐的情况,最好是采用scanLine()获得每一行的首指针,然后操作,避免一直用scanLine(0)的情况。

二、Format_Indexed8直接转换成Format_RGBX64

QImage img8to16 = qimg8.convertToFormat(QImage::Format_RGBX64);
//QImage img8to16 = qimg8.convertTo(QImage::Format_RGBX64);
//void QImage::convertTo(QImage::Format format, Qt::ImageConversionFlags flags = Qt::AutoColor)
*qimgHi = img8to16;

qDebug() << u8"Total bytes of Image: " << img8to16.sizeInBytes() << Qt::endl;
qDebug() << u8"Bytes per Line of Image: " << img8to16.bytesPerLine() << Qt::endl;
uchar * pGrayscale16 = img8to16.scanLine(0);

qDebug() << u8"Value of scanLine(0): " << *pGrayscale16 << Qt::endl;
qDebug() << u8"Value of qAlpha(0): " << qAlpha(img8to16.pixel(0,0)) << Qt::endl;
qDebug() << u8"Value of qRed(0): " << qRed(img8to16.pixel(0,0)) << Qt::endl;
qDebug() << u8"Value of qGreen(0): " << qGreen(img8to16.pixel(0,0)) << Qt::endl;
qDebug() << u8"Value of qBlue(0): " << qBlue(img8to16.pixel(0,0)) << Qt::endl;


for(int y = 0;y<10;y++) //print the value of each point
{
qDebug() << u8"Value of " << y+1 << "Rows: " << img8to16.pixel(0,y) << img8to16.pixel(1,y)
         <<img8to16.pixel(2,y) <<img8to16.pixel(3,y) <<img8to16.pixel(4,y)
         <<img8to16.pixel(5,y) <<img8to16.pixel(6,y) <<img8to16.pixel(7,y)
         <<img8to16.pixel(8,y)  << Qt::endl;
}

for(int i = 0;i<200;i++) //print the value of each point through pointer!
{
qDebug() << *(pGrayscale16+i) ;
//if((i+1)%9==0) qDebug() << Qt::endl;
}

以上的部分输出结果为:

Total bytes of Image:  720 
Bytes per Line of Image:  72 

Value of scanLine(0):  11 
Value of qAlpha(0):  255 
Value of qRed(0):  11 
Value of qGreen(0):  11 
Value of qBlue(0):  11 

Value of  1 Rows:  4278913803 4278979596 4279045389 4279111182 4279176975 4279242768 4279308561 4279374354 4279440147 
......
    
11 11 11 11 11 11 255 255 
12 12 12 12 12 12 255 255
13 13 13 13 13 13 255 255
14 14 14 14 14 14 255 255
15 15 15 15 15 15 255 255
16 16 16 16 16 16 255 255
17 17 17 17 17 17 255 255
18 18 18 18 18 18 255 255
19 19 19 19 19 19 255 255   //第一行

21 21 21 21 21 21 255 255
......

可以看到将Format_Indexed8直接转换成Format_RGBX64之后,每行变成了72字节,这是因为对于RGBX64,The image is stored using a 64-bit halfword-ordered RGBA format (16-16-16-16),即位深为64(不用对齐了), 64 ∗ 9 / 8 = 72 64*9/8=72 649/8=72,此时的格式转换相当于将8位图像(0~255)映射到16位(0~65535)。虽然只是简单的复制了数据,例如对第一个数据点(原来为11),转换之后为:11 11 11 11 11 11 255 255 ,对应RRGGBBAA,但是这样直接转为Format_RGBX64是可以的。(11*256+11,二进制形式没有变化)

另外直接转换成Format_Grayscale16之后,除了每行字节变为20字节以外,数据的组织形式仍以简单的复制进行,例如同样打印第一行数据得到的结果为:

11 11 12 12 13 13 14 14 15 15 16 16 17 17 18 18 19 19 15 255

最后的15和255同样是数据对齐生成的随机值,此时的数据对齐为: ( 9 ∗ 16 + 31 ) / 32 × 4 = 20 (9*16+31)/32×4=20 (916+31)/32×4=20

三、Format_RGBX64详解

除了将8位图像转换成16位图像, 也可以自己给16位图像赋值

新建一个Format_RGBX64格式图像:

QImage qimgRGB16(9,10,QImage::Format_RGBX64); //新建一个10行9列的QImage数组,会自动数据对齐,每行20字节

for(int y =0;y<10;y++)
{
    for(int x=0;x<9;x++)
    {
        //qimgRGB16.setPixel(x, y, qRgb(20, 20, 120));  //结果同下
        qimgRGB16.setPixelColor(x,y, QColor::fromRgba64(5140, 5140, 30840));
    }
}

uchar * pRGB16 = qimgRGB16.scanLine(0);
int y = 0;
qDebug() << u8"Value of " << y+1 << "Rows: " << qBlue(qimgRGB16.pixel(0,y)) << qimgRGB16.pixel(1,y)
         <<qimgRGB16.pixelColor(2,y) <<qimgRGB16.pixel(3,y) <<qimgRGB16.pixel(4,y)
         <<qimgRGB16.pixel(5,y) <<qimgRGB16.pixel(6,y) <<qimgRGB16.pixel(7,y)
         <<qimgRGB16.pixel(8,y)  << Qt::endl;
}

for(int i = 0;i<24;i++) 
{
	qDebug() << *(pRGB16+i) ;
}

以上的输出结果为:

Value of  1 Rows:  120 4279506040 QColor(ARGB 1, 0.0784314, 0.0784314, 0.470588) 4279506040 4279506040 4279506040 4279506040 4279506040 4279506040
    
20 20 20 20 120 120 255 255
20 20 20 20 120 120 255 255
20 20 20 20 120 120 255 255

我们是通过setPixelColor这个函数给图像赋值的(效率很低),上述赋值为:setPixelColor(x,y, QColor::fromRgba64(5140, 5140, 30840));即会把(5140,5140,30840)这个RGB值赋给指定像素点,这样当按照字节打印值得时候就打印出了20 20 20 20 120 120 255 255的结果,20二进制为:0001 0100,而两个20为:0001 0100 0001 0100,其对应的值为5140,说明这是正确的。该赋值相当于setPixel(x, y, qRgb(20, 20, 120));相同的原因因该是Qt默认qRgb只支持8位,另外在实验中qRgb超过255就会出问题。

另外在输出的时候同样有所不同,qBlue(qimgRGB16.pixel(0,0))qimgRGB16.pixel(1,0)qimgRGB16.pixelColor(2,0)这三种输出同样的像素点的结果分别为:1204279506040QColor(ARGB 1, 0.0784314, 0.0784314, 0.470588),第一个120刚才也说了应该是qRgb默认的8位深度;4279506040对应的十六进制为:FF141478正好对应ARGB,即255、20、20、120;而QColor(ARGB 1, 0.0784314, 0.0784314, 0.470588)这种形式也可以换算,例如0.0784314*65536=5140,0.0784314*256=20。所以可以通过setpixel或者setpixelColor的方法设置图像某个像素点的数值,利用以上方法可以得某点的数值,但是setpixel或者setpixelColor都是官方所不推荐的(效率慢),官方声明如下,另外该方法得到某点处的像素值也需要通过换算,极为麻烦。

void QImage::setPixelColor(const QPoint &position, const QColor &color)

Sets the color at the given position to color.
If position is not a valid coordinate pair in the image, or the image's format is either monochrome or paletted, the result is undefined.
Warning: This function is expensive due to the call of the internal detach() function called within; if performance is a concern, we recommend the use of scanLine() or bits() to access pixel data directly.
This function was introduced in Qt 5.6.
void QImage::setPixel(const QPoint &position, uint index_or_rgb)

Sets the pixel index or color at the given position to index_or_rgb.
If the image's format is either monochrome or paletted, the given index_or_rgb value must be an index in the image's color table, otherwise the parameter must be a QRgb value.
If position is not a valid coordinate pair in the image, or if index_or_rgb >= colorCount() in the case of monochrome and paletted images, the result is undefined.
Warning: This function is expensive due to the call of the internal detach() function called within; if performance is a concern, we recommend the use of scanLine() or bits() to access pixel data directly.
See also pixel() and Pixel Manipulation.

所以最好使用官方锁推荐的操作像素的方法:scanLine(),这也是以上为什么多次用到该函数的原因,下面以Format_Grayscale16为例进行分析。

四、Format_Grayscale16格式

QImage qimg16(9,10,QImage::Format_Grayscale16); //新建一个10行9列的QImage数组,会自动数据对齐,每行20字节

//第一种赋值
for(int y =0;y<10;y++)
{
    for(int x=0;x<9;x++)
    {
        QColor c(50, 50, 120);
        qimg16.setPixel(x,y, c.rgba());   //Note: (x,y) means coordinate,不是行列下标!!!!   (y+1)*6000+x+1
    }
}

uchar * pGrays16 = qimg16.scanLine(0);

int y = 0;
qDebug() << "Value of 1 Rows: " << qimg16.pixelColor(0,y) <<  qimg16.pixel(0,y)  << qimg16.pixel(1,y)
             <<qimg16.pixel(2,y) <<qimg16.pixel(3,y) <<qimg16.pixel(4,y)
             <<qimg16.pixel(5,y) <<qimg16.pixel(6,y) <<qimg16.pixel(7,y)
             <<qimg16.pixel(8,y)  << Qt::endl;

for(int i = 0;i<200;i++) //print the value of each point through pointer!
{
	qDebug() << *(pGrays16+i) ;
    //if((i+1)%9==0) qDebug() << Qt::endl;
}

以上输出的结果为:

Value of  1 Rows:  QColor(ARGB 1, 0.235294, 0.235294, 0.235294) 4282137660 4282137660 4282137660 4282137660 4282137660 4282137660 4282137660 4282137660 4282137660 


60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 205 205

与之前的类似,唯一不同的是有了数据对齐,位深不同。Grayscale16为灰度图,没有ARGB通道,所以当我们使用QColor c(50, 50, 120);
qimg16.setPixel(x,y, c.rgba());赋值的时候,Qt会自动将该RGB值转换成灰度值,这里试过几个不同的值转成灰度值,大概系数应该是0.34R+0.5G+0.15B,没有仔细的算(因为是生成的灰度值是经过取整的),所以灰度图不应该使用qRgb的形式来赋值,因为计算机存储一般用的是小端表示,所以除了上述方法还可以直接指定数值,例如:

uchar * pGrays16 = qimg16.scanLine(0);
for(int i = 0;i<200;) 
{
	*(pGrays16+i)=1;
    i = i+2;
}

for(int i = 1;i<200;) 
{
    *(pGrays16+i)=255;
    i = i+2;
}

以上赋值方法是分别在高8位和低8位两个字节赋值,同样此种操作复杂。

所以!!操作像素怎么使用官方推荐的scanLine()函数呢,因为scanLine()函数的原型为:uchar *QImage::scanLine(int i),也就是该函数返回一个uchar*类型的指针,因为我们操作的是16位图像, 每个像素占两个字节,所以应该将指针变成ushort*类型即可!如下:

QImage qimg16(9,10,QImage::Format_Grayscale16); //新建一个10行9列的QImage数组,会自动数据对齐,每行20字节

for(int i = 0;i<10;i++) //行
{
    uchar * pGrays16Tmp = qimg16.scanLine(i);
    ushort * pushortTmp = reinterpret_cast<ushort*>(pGrays16Tmp);
    for(int j=0; j<9;j++) //列
    {
        *(pushortTmp+j)= 52726;  //赋值
    }
}

uchar * pGrays16 = qimg16.scanLine(0);
ushort * pushort = reinterpret_cast<ushort*>(pGrays16);
for(int i = 0;i<100;i++) //print the value of each point through pointer!
{
    qDebug() << *(pushort+i) ;  //输出像素值
    //if((i+1)%9==0) qDebug() << Qt::endl;
}

输出结果为:

52726 52726 52726 52726 52726 52726 52726 52726 52726 52685
......

这样就完成了16位图像的操作,最后的52685是对齐的随机数,所以之前说读取像素的时候最好不要用scanLine(0)从头到尾!

1.该程序用QT开发,实现图片导入、显示、缩放、拖动及处理(冷暖色、灰度、亮度、饱和、模糊、锐化)。 经实测,我写的这个软件在导入10000*7096像素的超大图片的时候,缩放的速度比2345看图软件还快,2345缩放超大图会卡顿,但本软件不会^_^ 关于程序中缩放拖动部分的说面参见我的博客https://blog.csdn.net/weixin_43935474/article/details/89327314; 2.载入图片后,鼠标移动的时候可以显示鼠所在点的图像的坐标以及灰度; 3.缩放的时候,图片右上角可以显示当前图片的缩放比例; 4.用户可导入16深的tiff灰度图文件(一般来说是由相机拍摄的灰度图数据),导入16深的tiff的时候,用户需要先点击界面左上角的checkbox,然后再导入tiff图片,否则图片解析不出来。 注:Qt自带的QImage只能导入8深的tiff灰度图,如果用qtQImage导入16深的灰度图,图像数据会被强制转换成argb格式的图像数据就被更改了,所以我自己编写一个解析tiff文件的功能,我翻阅了很多博客,其中如下链接给我的帮助最大: https://blog.csdn.net/chenlu5201314/article/details/56276903 上述博客作为详细解析tiff文件结构的说明文档,写的非常详细,我也是根据上面的内容,自己编写了一个解析tiff文件的类(当然功能很少,只能解析符合特定条件的tiff文件) //************************************************************ //by Bruce Xu //注:解析tiff的类只解析特定的tiff文件! //1.解析的tiff文件中只存在一幅图,如果文件中存在多幅图,本类不支持解析! //2.图像数据为816深度的灰度图,如果是其他类型的图片,本类不支持解析! //3.图片没有被压缩过! //************************************************************
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值