1、算法敘述
算法參考自:【RGBA alpha 透明度混合算法】 ,下面的敘述和實現中有一些個人修改在里面。
1.1、透明度混合算法1
R1、G1、B1、Alpha1 為前景顏色值,R2、G2、B2、Alpha2 為背景顏色值,則:
Alpha = 1 - (1 - Alpha1) * ( 1 - Alpha2)
R = (R1 * Alpha1 + R2 * Alpha2 * (1-Alpha1))/Alpha
G = (G1 * Alpha1 + G2 * Alpha2 * (1-Alpha1))/Alpha
B = (B1 * Alpha1 + B2 * Alpha2 * (1-Alpha1))/Alpha
這里的Alpha取值范圍是[0,1],需要使用到浮點計算(實數計算)。對於我們常見的8位圖像,我們可以將其值域改為[0,255]進行計算,具體的見下面測試代碼。
1.2、AlphaBlend算法介紹
混合算法目前在常用到的算法是AlphaBlend。
計算公式如下:假設一幅圖像是A,另一幅透明的圖像是B,那么透過B去看A,看上去的圖像C就是B和A的混合圖像,
設B圖像的透明度為alpha(取值為0-1,1為完全透明,0為完全不透明).
Alpha混合公式如下:
R(C)=(1-alpha)*R(B) + alpha*R(A)
G(C)=(1-alpha)*G(B) + alpha*G(A)
B(C)=(1-alpha)*B(B) + alpha*B(A)
R(x)、G(x)、B(x)分別指顏色x的RGB分量原色值。從上面的公式可以知道,Alpha其實是一個決定混合透明度的數值。
這里只對B圖的Alpha進行了處理,但A圖本身如果也有透明通道的,也需要進行一樣的處理,即
A(C)=(1-alpha)*A(B) + alpha*A(A)
1.3、簡易Alpha混合算法
首先,要能取得上層與下層顏色的 RGB三基色,然后用r,g,b 為最后取得的顏色值;r1、g1、b1是上層的顏色值;r2、g2、b2是下層顏色值,若Alpha=上層透明度,則:
-
當Alpha=50%時
r = r1/2 + r2/2; g = g1/2 + g2/2; b = b1/2 + b2/2; -
當Alpha<50%時
r = r1 - r1/Alpha + r2/Alpha; g = g1 - g1/Alpha + g2/Alpha; b = b1 - b1/Alpha + b2/Alpha; -
當Alpha>50%時
r = r1/Alpha + r2 - r2/Alpha; g = g1/Alpha + g2 - g2/Alpha; b = b1/Alpha + b2 - b2/Alpha;
這個算法比較簡單,我也沒有去做實現。簡單來說這里就是看透明度高是否超過50%,來決定是上下層圖像誰占主導地位。
2、算法實現代碼和測試
實現其實是很簡單的,只是注意下面沒有實數的計算,都是使用的整數計算,要注意移位與抹零的操作別出錯。
下面的rgba1表示前景圖(我的代碼里是水印圖)的一個像素值,是RGBA8888格式的。rgba2表示背景圖的一個像素值。a1表示rgba1中的Alpha分量值,a2表示rgba2中的Alpha分量值。
2.1、透明度混合算法1實現代碼
// 如果alpha的值域是[0,1],這里相當於將其拉伸為[0,255]
// 所以相當於是 Alpha = 1 - (1 - Alpha1) * ( 1 - Alpha2)乘以了兩次255
// 當a1和a2都接近於0的時候,會導致計算得到的A值不為0,導致疊加不正常
uint32_t A = (0xffff - (0xff - a1)*(0xff - a2));
// 下面左邊部分少左移8位,相當於乘以了255
uint32_t R = (((rgba1 << 8 &0xff00U) * a1 + (rgba2 >> 0 &0xffU) * a2 *(0xff - a1))/A)&0xffU;
uint32_t G = (((rgba1 >> 0 &0xff00U) * a1 + (rgba2 >> 8 &0xffU) * a2 *(0xff - a1))/A)&0xffU;
uint32_t B = (((rgba1 >> 8 &0xff00U) * a1 + (rgba2 >> 16&0xffU) * a2 *(0xff - a1))/A)&0xffU;
2.1、AlphaBlend算法實現代碼
uint32_t A = a1;
uint32_t R = (((rgba1 >> 0 &0xffU) * A + (rgba2 >> 0 &0xffU) *(0xff - A)) >> 8)&0xffU;
uint32_t G = (((rgba1 >> 8 &0xffU) * A + (rgba2 >> 8 &0xffU) *(0xff - A)) >> 8)&0xffU;
uint32_t B = (((rgba1 >> 16&0xffU) * A + (rgba2 >> 16&0xffU) *(0xff - A)) >> 8)&0xffU;
A = ((a1 * A + a2 *(0xff - A)) >> 8)&0xffU; // 必須對Alpha波段也處理
2.3、測試截圖

2.4、完整測試程序代碼
#include <QApplication>
#include <QWidget>
#include <QLineEdit>
#include <QPushButton>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QFileDialog>
#include <QWebEngineView>
#include <QXmlStreamWriter>
#include <QBuffer>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QImage bkImage,wmImage;
QImage mixImage1,mixImage2;
// 創建窗口
QWidget widget;
// 添加控件
QWebEngineView *wevView = new QWebEngineView(&widget);
QLineEdit* leBkImagePath = new QLineEdit(&widget);
QLineEdit* leWmImagePath = new QLineEdit(&widget);
QPushButton* pbSelectBkFile = new QPushButton(QStringLiteral("選擇背景圖"),&widget);
QPushButton* pbSelectWmFile = new QPushButton(QStringLiteral("選擇水印圖"),&widget);
QPushButton* pbRunMix = new QPushButton(QStringLiteral("執行疊加"),&widget);
//pbRunDetect->setEnabled(false);
QHBoxLayout* hbLayout = new QHBoxLayout;
// 設置布局
hbLayout->addWidget(leBkImagePath);
hbLayout->addWidget(pbSelectBkFile);
hbLayout->addWidget(leWmImagePath);
hbLayout->addWidget(pbSelectWmFile);
hbLayout->addWidget(pbRunMix);
QVBoxLayout* vbLayout = new QVBoxLayout(&widget);
vbLayout->addLayout(hbLayout);
vbLayout->addWidget(wevView);
// 添加處理操作
std::function<void()> updateHtmlView =
[wevView,&bkImage,&wmImage,&mixImage1,&mixImage2]()
{
QByteArray html;
{
QXmlStreamWriter writer(&html);
writer.setAutoFormatting(true);
writer.writeStartDocument();
writer.writeStartElement("html");
writer.writeStartElement("body");
writer.writeAttribute("bgcolor","gray");
QStringList imgName =
{
QStringLiteral("背景圖"),
QStringLiteral("水印圖"),
QStringLiteral("算法1結果圖"),
QStringLiteral("算法2結果圖")
};
QList<QImage*> imgRef =
{
&bkImage,&wmImage,&mixImage1,&mixImage2
};
for(int i=0;i<imgName.size();++i){
if(imgRef[i]->isNull()){continue;}
writer.writeTextElement("h2",imgName[i]);
writer.writeStartElement("img");
QBuffer buffer;
imgRef[i]->save(&buffer,"PNG");
writer.writeAttribute("src","data:image/png;base64," + buffer.data().toBase64());
writer.writeEndElement();
}
writer.writeEndElement();
writer.writeEndElement();
}
wevView->setHtml(QString::fromUtf8(html));
};
QObject::connect(pbSelectBkFile,&QPushButton::clicked,
[leBkImagePath,&bkImage,&widget,&updateHtmlView]()
{
static QString path(".");
path = QFileDialog::getOpenFileName(&widget,
QStringLiteral("選擇背景圖"),
path,
QString("Images (*.png *.jpg *.jpeg *.jfif)"));
if(path.isEmpty()){return;}
QImage image;
if(!image.load(path)){return;}
bkImage = image.convertToFormat(QImage::Format_RGBA8888);
leBkImagePath->setText(path);
updateHtmlView();
});
QObject::connect(pbSelectWmFile,&QPushButton::clicked,
[leWmImagePath,&bkImage,&wmImage,&widget,&updateHtmlView]()
{
static QString path(".");
path = QFileDialog::getOpenFileName(&widget,
QStringLiteral("選擇水印圖"),
path,
QString("Images (*.png)"));
if(path.isEmpty()){return;}
QImage image;
if(!image.load(path)){return;}
// 水印圖不能比背景圖大
int w = image.width() * 2 > bkImage.width() ? bkImage.width()/2:image.width();
int h = image.height() * w / image.width();
h = h > bkImage.height()?bkImage.height():h;
wmImage = image.scaledToHeight(h).convertToFormat(QImage::Format_RGBA8888);
leWmImagePath->setText(path);
updateHtmlView();
});
QObject::connect(pbRunMix,&QPushButton::clicked,
[&bkImage,&wmImage,&mixImage1,&mixImage2,&updateHtmlView]
{
mixImage1 = mixImage2 = bkImage;
for(int r = 0;r < wmImage.height();++r){
uint32_t* pBgLine = reinterpret_cast<uint32_t*>(bkImage.bits() + bkImage.bytesPerLine()*r);
uint32_t* pWmLine = reinterpret_cast<uint32_t*>(wmImage.bits() + wmImage.bytesPerLine()*r);
uint32_t* pM1Line = reinterpret_cast<uint32_t*>(mixImage1.bits() + mixImage1.bytesPerLine()*r);
uint32_t* pM2Line = reinterpret_cast<uint32_t*>(mixImage2.bits() + mixImage2.bytesPerLine()*r);
for(int c=0;c<wmImage.width();++c){
uint32_t rgba1 = pWmLine[c];
uint32_t rgba2 = pBgLine[c];
uint32_t a1 = rgba1 >> 24;
uint32_t a2 = rgba2 >> 24;
{
// 如果alpha的值域是[0,1],這里相當於將其拉伸為[0,255]
// 所以相當於是 Alpha = 1 - (1 - Alpha1) * ( 1 - Alpha2)乘以了兩次255
uint32_t A = (0xffff - (0xff - a1)*(0xff - a2));
// 下面左邊部分少左移8位,相當於乘以了255
uint32_t R = (((rgba1 << 8 &0xff00U) * a1 + (rgba2 >> 0 &0xffU) * a2 *(0xff - a1))/A)&0xffU;
uint32_t G = (((rgba1 >> 0 &0xff00U) * a1 + (rgba2 >> 8 &0xffU) * a2 *(0xff - a1))/A)&0xffU;
uint32_t B = (((rgba1 >> 8 &0xff00U) * a1 + (rgba2 >> 16&0xffU) * a2 *(0xff - a1))/A)&0xffU;
pM1Line[c] = R|(G<<8)|(B<<16)|((A&0xff)<<24);
}
{
uint32_t A = a1;
uint32_t R = (((rgba1 >> 0 &0xffU) * A + (rgba2 >> 0 &0xffU) *(0xff - A)) >> 8)&0xffU;
uint32_t G = (((rgba1 >> 8 &0xffU) * A + (rgba2 >> 8 &0xffU) *(0xff - A)) >> 8)&0xffU;
uint32_t B = (((rgba1 >> 16&0xffU) * A + (rgba2 >> 16&0xffU) *(0xff - A)) >> 8)&0xffU;
A = ((a1 * A + a2 *(0xff - A)) >> 8)&0xffU; // 必須對Alpha波段也處理
pM2Line[c] = R|(G<<8)|(B<<16)|(A<<24);
}
}
}
updateHtmlView();
});
widget.resize(1024,768);
widget.show();
return a.exec();
}
