三元光柵操作
根據在網上的搜索總結得到兩種方案,最常見的繪制帶有透明背景的圖像的方案都是采用如下的源圖像和掩碼圖像疊加來消去邊緣部分:
IMAGE img[2];
loadimage(&img[0], "sun1.png", 100, 100); // 掩碼圖像
loadimage(&img[1], "sun0.png", 100, 100); // 源圖像
putimage(0, 0, &img[0], NOTSRCERASE); // 掩碼圖與背景或取反
putimage(0, 0, &img[1], SRCINVERT); // 源圖像與背景異或
直觀理解:由於按位運算是依次對某一位做運算,因此只需要考察一位數字的變化
// 不妨假設白色為 1,黑色為 0
// 需要變成透明的部分,需要顯示最初的背景色
// 背景色為 a,~a 表示反 a,掩碼此部分是黑色 0,源圖像此部分是白色 1
~ (a | 0); //-> ~a 或取反
(~a) ^ 1; //-> a 異或
// 結果仍為背景色
// 需要顯示源圖像的部分
// 背景色為 a,源圖像此部分為 b,掩碼此部分是白色 1
~ (a | 1); //-> 0 或取反(可以看到第一部分與 a 無關)
0 ^ b; //-> b 異或
// 結果為源圖像
然而,上面的方案雖然足夠解決問題,但是未免太過繁瑣,不僅需要源圖像,還需要花時間來制作掩碼圖。最糟糕的就是技術問題導致掩碼圖和原圖不能完全重合,繪制出的圖像有黑邊。
於是,經過長期搜索,終於找到一種不需要掩碼圖,而是直接對圖像進行處理的方案。
優化方案
此方案使用了貝葉斯定理來對圖像的每個像素進行計算,原版代碼的具體來源因為時間比較久,已經找不到了。這里的方案和原版本有所不同,因為實際需求,我根據原版本添加了透明度參數 AA ,並且修改了部分代碼使得它與我的需求相契合。
函數聲明為:
void drawAlpha(
IMAGE* image, // 圖像指針
int x, int y, // 輸出坐標
int width, int height, // 輸出尺寸
int pic_x, int pic_y, // 圖像中的位置
double AA = 1 // 透明度
);
也就是說它會在 x,y
位置輸出從圖像中 pic_x,pic_y
位置開始,寬高為 width,height
的部分,且透明度為 AA
。
函數定義部分:
// 繪圖函數,補充透明度 AA
void drawAlpha(IMAGE* image, int x, int y, int width, int height, int pic_x, int pic_y, double AA = 1)
{
// 變量初始化
DWORD* dst = GetImageBuffer(); // GetImageBuffer() 函數,用於獲取繪圖設備的顯存指針, EasyX 自帶
DWORD* draw = GetImageBuffer();
DWORD* src = GetImageBuffer(image); // 獲取 picture 的顯存指針
int imageWidth = image->getwidth(); // 獲取圖片寬度
int imageHeight = image->getheight(); // 獲取圖片寬度
int dstX = 0; // 在 繪圖區域 顯存里像素的角標
int srcX = 0; // 在 image 顯存里像素的角標
// 實現透明貼圖 公式: Cp=αp*FP+(1-αp)*BP , 貝葉斯定理來進行點顏色的概率計算
for (int iy = 0; iy < height; iy++)
{
for (int ix = 0; ix < width; ix++)
{
// 防止越界
if (ix + pic_x >= 0 && ix + pic_x < imageWidth && iy + pic_y >= 0 && iy + pic_y < imageHeight &&
ix + x >= 0 && ix + x < WindowWidth && iy + y >= 0 && iy + y < WindowHeight)
{
// 獲取像素角標
int srcX = (ix + pic_x) + (iy + pic_y) * imageWidth;
dstX = (ix + x) + (iy + y) * WindowWidth;
int sa = ((src[srcX] & 0xff000000) >> 24) * AA; // 0xAArrggbb; AA 是透明度
int sr = ((src[srcX] & 0xff0000) >> 16); // 獲取 RGB 里的 R
int sg = ((src[srcX] & 0xff00) >> 8); // G
int sb = src[srcX] & 0xff; // B
// 設置對應的繪圖區域像素信息
int dr = ((dst[dstX] & 0xff0000) >> 16);
int dg = ((dst[dstX] & 0xff00) >> 8);
int db = dst[dstX] & 0xff;
draw[dstX] = ((sr * sa / 255 + dr * (255 - sa) / 255) << 16) //公式: Cp=αp*FP+(1-αp)*BP ; αp=sa/255 , FP=sr , BP=dr
| ((sg * sa / 255 + dg * (255 - sa) / 255) << 8) //αp=sa/255 , FP=sg , BP=dg
| (sb * sa / 255 + db * (255 - sa) / 255); //αp=sa/255 , FP=sb , BP=db
}
}
}
}
注意其中 WindowWidth 和 WindowHeight 都是預先定義的全局變量。實際應用時,先在頭文件中定義窗口尺寸,然后就可以直接使用
IMAGE img;
loadimage(&img, "sun.png", 100, 100);
int x = 100, y = 100;
int width = 50, height = 50;
int pic_x = 50, pic_y = 50;
drawAlpha(img, x, y, width, height, pic_x, pic_y, 0.8);
還可以調整透明度為 0.8 ,比方案 1 更為實用。