Qt 2D绘图(7):QImage类(操纵像素、扫描线、图像转换)

https://blog.csdn.net/hyongilfmmm/article/details/83236903

Qt 2D绘图(7):QImage类(操纵像素、扫描线、图像转换)

QImage的基本函数的使用与QPixmap类似,对这些函数的讲解本文从略。

1、QImage图像的存储
存储为QImage的图像,每个像素使用一个整数表示,QImage默认支持的文件格式与QPixmap相同。
单色图像的存储:具有1位深度的图像(即单色图像)被存储到最多拥有两种颜色的颜色表中。单色图像有两种类型:大端(MSB)或小端(LSB)位顺序。
8位深度图像的存储:使用一个8位的索引把8位图像存储到颜色表中,因此8位图像的每个像素占据一字节(8位)的存储空间,即每个像素的颜色与颜色表中某个索引号的颜色相对应。
颜色表使用QVector存储,QRgb类型是使用typedef定义的一个unsigned int类型,该类型包含一个0xAARRGGBB格式的四元组ARGB格式。
32位图像没有颜色表,每个像素直接包含一个QRgb类型的值,共有3种类型的32位图像,分别是RGB(即0xffRRGGBB),ARGB和预乘的ARGB,在预乘格式中,红、绿、蓝通道需乘以alpha分量除以255(详见下一点对alpha的讲解)。
2、对alpha通道的处理(预乘alpha)
带alpha通道的图像有两种处理alpha通道的方法,一种是直接alpha,别一种是预乘alpha,使用预乘alpha通道的图像通常会更快。
直接alpha图像的RGB数值是原始的数值,而预乘alpha图像的RGB数值是乘以alpha通道后得到的数值,比如ARGB = (a , r , g, b);则预乘alpha后的值为(a, a * r, a * g, a * b);

Qt 预乘alpha通道图像的算法是把红、绿、蓝通道的数值乘以alpha通道的数值再除以255,比如使用ARGB32格式表示的0x7F00004E,使用预乘ARGB32格式应表示为0x7F000027,算法为(0x4E * 0x7F) / 0xFF ≈ 0x27,把(0x4E * 0x7F) / 0xFF 变换一下为0x4E * ( 0x7F / 0xFF ) 其中 0x7F / 0xFF是以小数表示的alpha值。再如对于透明度为80% (1-33/FF)的ARGB32格式表示的 0x3337304B,使用预乘ARGB32格式应为0x330B170F,以0x37 为例计算0x37 * (0x33/0xFF),转换为10进制后为55 * 0.2 = 11=0xB。
3、表12-26为QImage类需要使用到的描述图像格式的枚举QImage::Format(完整的枚举可参阅帮助文档)

在这里插入图片描述


8、与颜色表和操控像素有关的函数
QRgb类型与qRgb()函数:QRgb类型相当于是使用typedef重命名的unsigned int类型,该类型用于保存颜色,其格式为#AARRGGBB的ARGB四元组。qRgb()是一个用于设置QRgb类型颜色的全局函数,比如qRgb(111,1,1)表示返回一个红色的QRgb类型的颜色。

在这里插入图片描述

在这里插入图片描述
示例12.36:颜色表及像素操控(结果见图12-76)

//m.h文件的内容
#ifndef M_H
#define M_H
#include<QtWidgets>
class B:public QWidget{    Q_OBJECT
public:    B(QWidget *p1=0):QWidget(p1){}
void paintEvent(QPaintEvent *e){
           QPainter pr;
          QImage pi(200,200,QImage::Format_Mono);    //创建一个单色图像)
            QRgb r1=qRgb(111,1,1);      //红色,注意:颜色是使用qRgb()创建的
            QRgb r2=qRgb(1,111,1);      //绿色
            QRgb r3=qRgb(111,111,1);    //黄色
    //绘制单色图未填充时的默认样式
            pr.begin(this);    pr.drawImage(11,11,pi);    pr.end();
            qDebug()<<pi.colorTable();    //输出默认颜色表QVector(4278190080, 4294967295),其中
//4278190080=0xFF00 0000,4294967295=0xFFFF FFFF
            pi.fill(Qt::color1);           //使用单色图的颜色表中的索引为1的颜色(绿色)填充图像pi。
 //以下步骤用于设置颜色表
            QVector<QRgb> v;   v.append(r1);    v.append(r2);
            pi.setColorCount(2);           //设置颜色表中的颜色数量
            pi.setColorTable(v);           //设置一个新颜色表(包含红色和绿色)
//使用单色图的新颜色表逐像素绘制一个矩形(22,22,128,100)
            pr.begin(&pi);
            for(int j=22;j<122;j++)
                for(int i=22;i<150;i++){
                pi.setPixel(i,j,0);}    //使用新颜色表中索引为0的颜色(红色)填充位于(i,j)处的像素。
            pr.end();

            pr.begin(this);
            pr.drawImage(222,11,pi);      //在QWidget中绘制pi
            qDebug()<<pi.colorTable();    //输出新设置的颜色表QVector(4285464833, 4278284033),
//其中4285464833=0xFF6F 0101,4278284033=0xFF01 6F01,0x6F=111
            pi.setColor(1,r3); //把新颜色表中索引为1的颜色更改为r3(黄色),注意:setColor()函数
//改变的是颜色表中的颜色,setPixel()才能改变图像中某个位置的像素颜色。
            pr.drawImage(444,11,pi);      //在QWidget重新绘制pi
            qDebug()<<pi.colorTable();    //输出更改后的颜色表QVector(4285464833, 4285492993)
                              //其中4285492993=0xFF6F 6F01,0x6F=111
         pr.end();    }};
#endif // M_H

//m.cpp文件内容
#include "m.h"
int main(int argc, char *argv[]){    QApplication app(argc,argv);
   B w;    w.resize(444,333);    w.show();    return app.exec();  }

在这里插入图片描述
9、与扫描线有关的函数
扫描线是指把图像按水平方向分割成很多条线,每条线就是一条扫描线,说简单点,一条扫描线就是图像中的一行,比如对于300*200的图像,共有200条扫描线(即,有200行)。
注:以下各函数的区别为,返回类型包含const的函数未使用隐式共享,未包含const的函数使用了隐式共享。

在这里插入图片描述
示例12.37:扫描线的使用(结果见图12-77)

void paintEvent(QPaintEvent *e){
    QPainter pr;
    QImage pi(200,200,QImage::Format_ARGB32);
    QImage pi1(200,200,QImage::Format_ARGB32);
    pi.fill(qRgb(1,111,1));     pi1.fill(qRgb(1,111,1));   //使用绿色填充背景
//使用扫描线函数返回的指针逐像素绘制一个矩形
//获取指向第一行第一个像素的指针,并将其强制转换为QRgb*以方便修改。
        QRgb *pu=(QRgb*)pi.bits();
for(int i=0;i<20000;i++)     //逐像素设置每个像素的颜色,pi每行有200个像素,
//循环2万次意味着设置100行像素的颜色。
                {   *pu=qRgb(111,1,1);pu++; }
        QRgb *pu1=(QRgb*)pi1.scanLine(19);  //获取第20行扫描线第一个元素的指针
            for(int i=0;i<20000;i++){    *pu1=qRgb(111,1,1);pu1++;  }
    pr.begin(this);
    pr.drawImage(11,11,pi);   pr.drawImage(222,11,pi1);
qDebug()<<pi1.bytesPerLine();  //输出800(字节),因为pi是32位的ARGB图像,
//每个像素占据4字节,每一行有200个像素,
//因此每个扫描线占据800个字节大小。
    pr.end();}
在这里插入图片描述
10、与图像格式或类型转换有关的函数

在这里插入图片描述

在这里插入图片描述

示例12.38:图像转换(结果见图12-78)

void paintEvent(QPaintEvent *e){
    QPainter pr(this);
    QImage pi2("F:/1z.png");
    qDebug()<<pi2.format();                                   //输出5(即ARGB32格式)
    pr.drawImage(11,11,pi2);                                   //绘制原始图像
    QImage pi3= pi2.convertToFormat(QImage::Format_Grayscale8);     //转换为灰度图
    pr.drawImage(11,222,pi3);
    QImage pi4= pi2.rgbSwapped();                               //交换红色和蓝色通道
    pr.drawImage(222,222,pi4);
pi2.invertPixels();    //反转所有像素的颜色,注意,该函数会破坏pi2(但不会破坏原始文件1z.png)
    pr.drawImage(222,11,pi2);
    pr.end();        }
在这里插入图片描述

QImage

https://blog.csdn.net/lengyuezuixue/article/details/80656358

一些基本操作方法

获取图像的首地址:

    const uchar *QImage::bits() const

获取图像的总字节数

    int QImage::byteCount() const

获取图像每行字节数

    int QImage::bytesPerLine() const  

还可以这样计算(width:图像宽度,img.depth是图图像深度):

    int bytePerLine = (width * img.depth()  +  31) / 32 * 4;

存入图像,格式为R,G,B,A(0,1,2,3)

    QImage::Format_RGB32

存入图像,格式为R,G,B(0,1,2)

    QImage::Format_RGB888

存入图像,此时需要设定一张颜色表QVector<QRgb>,如下

    QImage::Format_Indexed8  

灰度颜色表:

    QVector<QRgb> vtrColor;

    for(int k = 0;k < 256;++k)

    {
      vtrColor.push_back( qRgb(k,k,k) );

    }

灰度图

    普通彩色图片中每个像素中有R、G、B三个分量,而每个分量有256种(0~255)值可以选择,这样一个像素点就可以有1600多万(255255255)的颜色变化范围。

    而灰度图就是R、G、B三分分量的值相同的一种特殊彩色图像。即R=G=B.

位深

    位深是指存储每个像素所用的位数.

    如上面介绍的QImage方法,img.depth(),该方法可以返回当前图像的位深.

常规处理

unsigned char *grayData;
    QImage img;
    img.load (m_strPath+"/2.jpg");
 
    QPixmap pixmap(QPixmap::fromImage (img));
 
    ui->label->setPixmap (pixmap);
 
    unsigned char *data = img.bits ();
 
    int w = img.width ();
    int h = img.height ();
 
    int bytePerLine = (w * 24 + 31) / 8;
    //存储处理后的数据
    grayData = new unsigned char [bytePerLine * h];
    unsigned char r,g,b;
    for ( int i = 0; i < h; i++ )
    {
        for ( int j = 0; j < w; j++ )
        {
            r =  *(data + 2);
            g = *(data + 1);
            b = *data;
 
            grayData[i * bytePerLine + j * 3] = (r * 30 + g*59 +b*11)/100;
            grayData[i*bytePerLine+j*3+1]=(r*30+g*59+b*11)/100;
            grayData[i*bytePerLine+j*3+2]=(r*30+g*59+b*11)/100;
 
            data+=4;
 
            //ui->textEdit->append (QString("%1 %2 %3").arg (r).arg (g).arg (b));
        }
    }
 
    QImage grayImage(grayData,w,h,bytePerLine,QImage::Format_RGB888);
 
    QPixmap pixmap2(QPixmap::fromImage (grayImage));
 
    ui->label_2->setPixmap (pixmap2);

QImage总结

https://www.cnblogs.com/nmgxbc/p/4876909.html

嗯,这个QImage的问题研究好久了,有段时间没用,忘了,已经被两次问到了,突然有点解释不清楚,我汗颜,觉得有必要重新总结下了,不然无颜对自己了。

图像的数据是以字节为单位保存的,每一行的字节数必须是4的整数倍,不足的补0。

(因为我们使用的是32操作系统,因此数据是按照32位对齐的,所以每行的字节数必须是4的整数倍也就是说每行的数据位必须是32位的整数倍。)这里是按照我的理解的,貌似错了,修正一下,最近在看数据对齐,这段话先忽略了,没有删掉,是因为,想留个足迹,等我找到合适的答案再贴上来。不过,图像的数据确实是按32位对齐的。

如果不是整数倍,则根据公式: W = ( w * bitcount + 31 )/32 * 4;

注:  w是图像的宽度,bitcount是图像的位深,即32、24等, 计算得到的W是程序中图像每行的字节数。

这里讲述QImage的32、24、8位图。

图像格式:QImage::Format_RGB32 ,QImage::Format_RGB888,QImage::Format_Indexed8。

构造图像:

    (1)、QImage myImage1 = QImage(filename);  根据文件名打开图像,如果图像本身是32、24位的,程序中图像是32位的,如果图像本身是8位、1位的,程序中对应为8位、1位。

   (2)、QImage myImage2 = QImage(width, height, QImage::Format_…); 根据图像宽高来构造一幅图像,程序会自动根据图像格式对齐图像数据。

操作图像:按照(2)的方式构造图像,在Debug下,如果不给图像myImage2初值,图像不是黑的, 但release下,则构造好的图像默认为黑色。

 

好了,现在我们需要对图像数据操作,32位图像无疑是最简单的,因为它数据是对齐的。用width表示图像宽度,height表示图像高度。

首先熟悉几个函数:

a、uchar* bits();       可以获取图像的首地址

b、int  byteCount();  图像的总字节数 

c、int  bytesPerLine(); 图像每行字节数

1、QImage::Format_RGB32,存入格式为B,G,R,A 对应 0,1,2,3

    QImage::Format_RGB888,存入格式为R, G, B 对应 0,1,2

    QImage::Format_Indexed8,需要设定颜色表,QVector<QRgb>

    灰度图像颜色表设定:

    QVector<QRgb>  colorTable;

    for(int k=0;k<256;++k)
    {

           colorTable.push_back( qRgb(k,k,k) );

    }

2、QImage  image32 = QImage(width, height, QImage::Format_32);

     QImage  image24 = QImage(width, height, QImage::Format_24);

     QImage  image8 = QImage(width, height, QImage::Format_8);

     image8.setColorTable(colorTable);

3、需要取每个像素处理,采用指针取值,行扫的方式:

    int  lineNum_32 = 0;     //行数

    int  pixelsub_32 = 0;    //像素下标

    uchar*    imagebits_32 = image32.bits();         //获取图像首地址,32位图

    uchar*    imagebits24 = image24.bits();

    uchar*    imagebits8 = image8.bits();

    for(int i=0; i<height; ++i)

    {   

           //按照通常的理解,我们会如下处理,取每行

           lineNum_32 = i * width * 4;                  //对于任意图像,这句没有问题

           // lineNum_24 = i * width * 3;             //??当width不是4的整数倍时,这句取不到每行开头

           // lineNum_8 = i * width;                 //??当width不是4的整数倍时,这句取不到每行开头

         for(int j=0; j<width; ++j)

         {

               int r_32 = imagebits_32[ lineNum_32 + j * 4 + 2]; 

               int g_32 = imagebits_32[ lineNum_32 + j * 4 + 1];

               int b_32 = imagebits_32[ lineNum _32 + j * 4];

 

               // int r_24 = imagebits_24[ lineNum_24 + j * 3];        //注意区别32位的图

               // int g_24 = imagebits_24[ lineNum_24 + j *3 + 1];

               // int b_24 = imagebits_24[ lineNum_24 + j * 3 + 2];

               // int gray_8 = imagebits_8[ lineNum_8 + j];

               ……

               //自己的操作

         }

    }

//??出问题了,因为实际的图像数据并不是以width为真实宽度的,解决,有两种方法:

 

第一种方法:自己计算实际的宽度

修改为:

// 获取每行的字节数

int  W_32 = ( width * 32 + 31 )/32 * 4;           //注意这里没有四舍五入,所以不要随意换算

int  W_24 = ( width * 24 + 31 )/32 * 4;

int  W_8 = ( width * 8 + 31)/32 * 4;

       //也可以使用QT函数来获取,功能和上面一样

{

        int  W_32 = image32.bytesPerLine();

        int  W_24 = image24.bytesPerLine();

        int  W_8 = image8.bytesPerLine();

}

for(int i=0;  i<height;  ++i)

{   

           //现在可以按照通常的理解,取每行

           lineNum_32 = i * W_32;               //注意,这里不再需要乘倍数了(4, 3等)

           // lineNum_24 = i * W_24;            

           // lineNum_8 = i * W_8;                   

         for(int j=0; j<width; ++j)

         {

               //这里的操作同上面的一样

         }

}

 

第二种方法:采用scanLine(int)来获取每行的首地址,

for(int i=0; i<height; ++i)

{   

       imagebits_32 = image32.scanLine(i);

       imagebits_24 = image24.scanLine(i);

       imagebits_8 = image8.scanLine(i);

       for(int j=0; j<width; ++j)

      {

               int r_32 = imagebits_32[ j * 4 + 2]; 

               int g_32 = imagebits_32[ j * 4 + 1];

               int b_32 = imagebits_32[ j * 4];

 

               // int r_24 = imagebits_24[ j * 3];       

               // int g_24 = imagebits_24[ j *3 + 1];

               // int b_24 = imagebits_24[ j * 3 + 2];

               // int gray_8 = imagebits_8[ j ];

               ……

               //自己的操作

      }

}

OK,上述两种方法的索引就不会出现图像数据偏移的问题

4、大家注意到QImage的这个构造函数了吧,QImage::QImage ( uchar * data, int width, int height, Format format )

     嗯,这个函数就是从uchar* 的数据来构造图像,一般我们都可能先将图像数据存在uchar*中,

     uchar* data32 = new uchar[ width * height * 4];      

     uchar* data24 = new uchar[ width * height * 3];

     uchar* data8 = new uchar[ width * height];

     从data32构造图像,不会有任何问题,但是当width不是4的整数倍时,你就不可能从data24和data8构造出自己想要的数据,程序会挂掉的,因为这两个数组的数据量根本不够一幅图(还记得数据补齐不)。

解决办法:

你需要首先计算出,你的图像的真实数据量(字节数), 可以根据QImage.byteCount()函数来获取图像的字节数,当然,你也可以自己计算,计算公式 byteCount = height * W;   这里的W就是每行的字节数,上面已经讲过了它的计算方法。

然后,你可以由QByteArray来获取转换的指针数据:

如:你的图像数据放在数组  uchar*  srcData;  中

QByteArray imageByteArray = QByteArray( (const char*)srcData,  byteCount );

uchar*  transData = (unsigned char*)imageByteArray.data();

QImage desImage = QImage(transData, width, height, QImage::Format_…);

嗯,经过上述转换后,transData中将是补齐数据的数组,由此构造的图像不会有任何问题。

 

好了,终于总结完了,有很多小的问题,我不想写了,应该够用了,如果有具体其他问题,大家再仔细想想,肯定能解决的,哈哈
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: qimage::setAlphaChannel函数是用来设置QImage对象的alpha通道的。该函数接受一个QImage对象作为参数,并将其作为图像的alpha通道。 使用方法如下: QImage image("example.png"); // 加载图像 QImage alpha("alpha.png"); // 加载alpha通道图像 image.setAlphaChannel(alpha); // 设置alpha通道 以上代码中,首先使用QImage对象加载了一个图像,然后再加载一个alpha通道图像,并使用setAlphaChannel函数将其设置为图像的alpha通道。 需要注意的是,使用setAlphaChannel函数设置的alpha通道图像必须和原始图像的大小一致。如果alpha通道图像的大小与原始图像不匹配,将会抛出异常。 ### 回答2: 在Qt中,QImage::setAlphaChannel()函数用于设置图像的Alpha通道。Alpha通道用于控制每个像素的透明度。 要使用QImage::setAlphaChannel()函数,首先需要创建一个QImage对象,并使用load()函数加载一个图像文件。然后,创建一个新的QImage对象用于存储Alpha通道信息。 接下来,可以使用QImage::convertToFormat()函数将原始图像转换为带有Alpha通道的格式。可选择的Alpha通道格式包括QImage::Format_ARGB32和QImage::Format_ARGB32_Premultiplied。 然后,使用QImage::fill()函数将新图像对象填充为全透明。这样可以确保在设置Alpha通道之前,图像的所有像素都是完全透明的。 最后,使用QImage::setAlphaChannel()函数将之前创建的Alpha通道图像对象设置为原始图像的Alpha通道。这将复制Alpha通道数据到原始图像中,并将新的Alpha通道应用于原始图像。 下面是使用QImage::setAlphaChannel()函数的示例代码: ```cpp QImage image; image.load("image.png"); QImage alphaChannelImage(image.size(), QImage::Format_ARGB32); alphaChannelImage.fill(Qt::transparent); image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied); image.setAlphaChannel(alphaChannelImage); ``` 以上代码将加载名为"image.png"的图像,并将其转换为带有预乘Alpha通道的格式。然后,创建一个新的Alpha通道图像,将其填充为全透明,并将其设置为原始图像的Alpha通道。 请注意,使用QImage::setAlphaChannel()函数时,必须确保原始图像和Alpha通道图像的大小相同,否则可能会导致未定义的行为。 ### 回答3: QImage中的setAlphaChannel()函数用于将一个图像的Alpha通道设置为另一个图像的Alpha通道。 使用该函数,需要满足以下要求: 1. 调用该函数的图像对象和传递给它的图像对象应该具有相同的大小。如果它们的大小不同,将抛出一个异常。 2. 传递给setAlphaChannel()函数的图像对象应为32位带有Alpha通道的图像。 3. 新的Alpha通道将取自传递给函数的图像对象。 下面是一个使用setAlphaChannel()函数的示例代码: QImage img1("image1.png"); // 创建图像对象img1 QImage img2("image2.png"); // 创建图像对象img2 // 检查img1和img2是否具有相同的大小 if (img1.size() != img2.size()) { throw std::runtime_error("Image sizes do not match!"); } // 将img2的Alpha通道设置到img1中 img1.setAlphaChannel(img2); 在上面的示例代码中,首先创建了两个图像对象img1和img2,分别从image1.png和image2.png加载图像数据。然后通过对图像对象的大小进行比较,确保它们具有相同的大小。最后,通过调用setAlphaChannel()函数将img2的Alpha通道设置到img1中,实现了将一个图像的Alpha通道设置为另一个图像的Alpha通道的操作。 希望对你有所帮助!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值