图像(层)正常混合模式详解(上)
程序员文章站
2022-05-11 17:49:00
在图像处理过程中,图像的合成操作是使用频率最高的,如图像显示、图像拷贝、图像拼接以及的图层拼合叠加等。
图像合成,其实也就是图像...
在图像处理过程中,图像的合成操作是使用频率最高的,如图像显示、图像拷贝、图像拼接以及的图层拼合叠加等。
图像合成,其实也就是图像像素颜色的混合,在photoshop中,颜色混合是个很复杂的东西,不同的混合模式,将产生不同的合成效果,如果将之全部研究透彻,估计就得写一本书。因此,本文只谈谈最基本的图像合成,也就是photoshop中的正常混合模式。
只要接触过图像处理的,都知道有个图像像素混合公式:
1)dstrgb = srcrgb * alpha + dstrgb * (1 - alpha)
其中,dstrgb为目标图像素值;srcrgb为源图像素值;alpha为源图像素值混合比例(不透明度,范围0 - 1)。
其实,这个像素混合公式有很大局限性,只适合不含alpha信息的图像。
要处理包括带alpha通道图像(层)的混合,其完整的公式应该是:
2-1)srcrgb = srcrgb * srcalpha * alpha / 255 (源图像素预乘转换为pargb)
2-2)dstrgb = dstrgb * dstalpha / 255 (目标图像素预乘转换为pargb)
2-3)dstrgb = dstrgb + srcrgb - dstrgb * srcalpha * alpha / 255 (源图像素值与目标图像素值混合)
2-4)dstalpha = dstalpha + srcalpha * alpha - dstalpha * srcalpha * alpha / 255 (混合后的目标图alpha通道值)
2-5)dstrgb = dstrgb * 255 / dstalpha (混合后的目标图像素转换为argb)
其中,dstrgb为目标图像素值;srcrgb为源图像素值;dstalpha为目标图alpha通道值;srcalpha为源图alpha通道值;dstargb为含alpha目标图像素值;alpha为源图像素值混合比例(不透明度,范围0 - 1)。
将公式2中的2-1式代入2-3式,简化可得:
3-1)dstrgb = dstrgb * dstalpha / 255
3-2)dstrgb = dstrgb + (srcrgb - dstrgb) * srcalpha * alpha / 255
3-3)dstalpha = dstalpha + srcalpha * alpha - dstalpha * srcalpha * alpha / 255
3-4)dstrgb = dstrgb * 255 / dstalpha
当dstalpha=srcalpha=255时,公式3中3-1式、3-3式和3-4式没有意义,3-2式也变化为:
4)dstrgb = dstrgb + (srcrgb - dstrgb) * alpha
不难看出,公式4是公式1的变形。因此,公式1只是公式3(或者公式2)在目标图和源图都不含alpha信息(或者alpha=255)情况下的一个特例而已。
当公式4中的alpha=1时,目标图像素等于源图像素,所以,本文前面说图像拷贝其实也是图像合成的范畴。
通过上面较详细的分析,可以看出,即使是最基本正常图像混合模式也是很复杂的。其实,上面还不是完整的分析,因为按照目标图alpha信息、源图alpha信息以及源图合成比例等三个要素的完全的排列组合,最多可以派生8个公式。
下面就按正常混合模式的全部8种情况(有2项重合,实际为7种情况)来分别进行代码实现,也可完善和补充上面的文字叙述:
//---------------------------------------------------------------------------
// 定义argb像素结构
typedef union
{
argb color;
struct
{
byte blue;
byte green;
byte red;
byte alpha;
};
}argbquad, *pargbquad;
typedef struct
{
int width;
int height;
pargbquad dstscan0;
pargbquad srcscan0;
int dstoffset;
int srcoffset;
}imagecpydata, *pimagecpydata;
typedef void (*mixerproc)(pimagecpydata, int);
#define pixelalphaflag 0x10000
//---------------------------------------------------------------------------
// source alpha = false, dest alpha = false, alpha < 255
static void mixer0(pimagecpydata cpydata, int alpha)
{
pargbquad pd = cpydata->dstscan0;
pargbquad ps = cpydata->srcscan0;
for (int y = 0; y < cpydata->height; y ++, pd += cpydata->dstoffset, ps += cpydata->srcoffset)
{
for (int x = 0; x < cpydata->width; x ++, pd ++, ps ++)
{
pd->blue += (((ps->blue - pd->blue) * alpha + 127) / 255);
pd->green += (((ps->green - pd->green) * alpha + 127) / 255);
pd->red += (((ps->red - pd->red) * alpha + 127) / 255);
}
}
}
//---------------------------------------------------------------------------
// source alpha = false, dest alpha = false, alpha = 255
// source alpha = false, dest alpha = true, alpha = 255
static void mixer1(pimagecpydata cpydata, int alpha)
{
argb *pd = (argb*)cpydata->dstscan0;
argb *ps = (argb*)cpydata->srcscan0;
for (int y = 0; y < cpydata->height; y ++, pd += cpydata->dstoffset, ps += cpydata->srcoffset)
{
for (int x = 0; x < cpydata->width; x ++, *pd ++ = *ps ++);
}
}
//---------------------------------------------------------------------------
// source alpha = false, dest alpha = true, alpha < 255
static void mixer2(pimagecpydata cpydata, int alpha)
{
pargbquad pd = cpydata->dstscan0;
pargbquad ps = cpydata->srcscan0;
for (int y = 0; y < cpydata->height; y ++, pd += cpydata->dstoffset, ps += cpydata->srcoffset)
{
for (int x = 0; x < cpydata->width; x ++, pd ++, ps ++)
{
pd->blue = (pd->blue * pd->alpha + 127) / 255;
pd->green = (pd->green * pd->alpha + 127) / 255;
pd->red = (pd->red * pd->alpha + 127) / 255;
pd->blue += (((ps->blue - pd->blue) * alpha + 127) / 255);
pd->green += (((ps->green - pd->green) * alpha + 127) / 255);
pd->red += (((ps->red - pd->red) * alpha + 127) / 255);
pd->alpha += (alpha - (pd->alpha * alpha + 127) / 255);
pd->blue = pd->blue * 255 / pd->alpha;
pd->green = pd->green * 255 / pd->alpha;
pd->red = pd->red * 255 / pd->alpha;
}
}
}
//---------------------------------------------------------------------------
// source alpha = true, dest alpha = false, alpha < 255
static void mixer4(pimagecpydata cpydata, int alpha)
{
pargbquad pd = cpydata->dstscan0;
pargbquad ps = cpydata->srcscan0;
for (int y = 0; y < cpydata->height; y ++, pd += cpydata->dstoffset, ps += cpydata->srcoffset)
{
for (int x = 0; x < cpydata->width; x ++, pd ++, ps ++)
{
int alpha0 = (alpha * ps->alpha + 127) / 255;
pd->blue += (((ps->blue - pd->blue) * alpha0 + 127) / 255);
pd->green += (((ps->green - pd->green) * alpha0 + 127) / 255);
pd->red += (((ps->red - pd->red) * alpha0 + 127) / 255);
}
}
}
//---------------------------------------------------------------------------
// source alpha = true, dest alpha = false, alpha = 255
static void mixer5(pimagecpydata cpydata, int alpha)
{
pargbquad pd = cpydata->dstscan0;
pargbquad ps = cpydata->srcscan0;
for (int y = 0; y < cpydata->height; y ++, pd += cpydata->dstoffset, ps += cpydata->srcoffset)
{
for (int x = 0; x < cpydata->width; x ++, pd ++, ps ++)
{
pd->blue += (((ps->blue - pd->blue) * ps->alpha + 127) / 255);
pd->green += (((ps->green - pd->green) * ps->alpha + 127) / 255);
pd->red += (((ps->red - pd->red) * ps->alpha + 127) / 255);
}
}
}
//---------------------------------------------------------------------------
// source alpha = true, dest alpha = true, alpha < 255
static void mixer6(pimagecpydata cpydata, int alpha)
{
pargbquad pd = cpydata->dstscan0;
pargbquad ps = cpydata->srcscan0;
for (int y = 0; y < cpydata->height; y ++, pd += cpydata->dstoffset, ps += cpydata->srcoffset)
{
for (int x = 0; x < cpydata->width; x ++, pd ++, ps ++)
{
int alpha0 = (alpha * ps->alpha + 127) / 255;
if (alpha0)
{
pd->blue = (pd->blue * pd->alpha + 127) / 255;
pd->green = (pd->green * pd->alpha + 127) / 255;
pd->red = (pd->red * pd->alpha + 127) / 255;
pd->blue += (((ps->blue - pd->blue) * alpha0 + 127) / 255);
pd->green += (((ps->green - pd->green) * alpha0 + 127) / 255);
pd->red += (((ps->red - pd->red) * alpha0 + 127) / 255);
pd->alpha += (alpha0 - (pd->alpha * alpha0 + 127) / 255);
pd->blue = pd->blue * 255 / pd->alpha;
pd->green = pd->green * 255 / pd->alpha;
pd->red = pd->red * 255 / pd->alpha;
}
}
}
}
//---------------------------------------------------------------------------
// source alpha = true, dest alpha = true, alpha = 255
static void mixer7(pimagecpydata cpydata, int alpha)
{
pargbquad pd = cpydata->dstscan0;
pargbquad ps = cpydata->srcscan0;
for (int y = 0; y < cpydata->height; y ++, pd += cpydata->dstoffset, ps += cpydata->srcoffset)
{
for (int x = 0; x < cpydata->width; x ++, pd ++, ps ++)
{
if (ps->alpha)
{
pd->blue = (pd->blue * pd->alpha + 127) / 255;
pd->green = (pd->green * pd->alpha + 127) / 255;
pd->red = (pd->red * pd->alpha + 127) / 255;
pd->blue += (((ps->blue - pd->blue) * ps->alpha + 127) / 255);
pd->green += (((ps->green - pd->green) * ps->alpha + 127) / 255);
pd->red += (((ps->red - pd->red) * ps->alpha + 127) / 255);
pd->alpha += (ps->alpha - (pd->alpha * ps->alpha + 127) / 255);
pd->blue = pd->blue * 255 / pd->alpha;
pd->green = pd->green * 255 / pd->alpha;
pd->red = pd->red * 255 / pd->alpha;
}
}
}
}
//---------------------------------------------------------------------------
void imagemixer(bitmapdata *dest, const bitmapdata *source, int alpha)
{
if (alpha <= 0) return;
if (alpha > 255) alpha = 255;
imagecpydata data;
data.width = (int)(dest->width < source->width? dest->width : source->width);
data.height = (int)(dest->height < source->height? dest->height : source->height);
data.dstoffset = (dest->stride >> 2) - data.width;
data.srcoffset = (source->stride >> 2) - data.width;
data.dstscan0 = (pargbquad)dest->scan0;
data.srcscan0 = (pargbquad)source->scan0;
mixerproc proc[] = {mixer0, mixer1, mixer2, mixer1, mixer4, mixer5, mixer6, mixer7};
int index = (alpha / 255) | ((dest->reserved >> 16) << 1) | ((source->reserved >> 16) << 2);
proc[index](&data, alpha);
}
//---------------------------------------------------------------------------
函数imagemixer有三个参数,分别为目标图数据结构(借用gdi+的bitmapdata结构)指针、源图数据结构指针和源图像素混合比例(不透明度,取值范围为0 - 255,前面的公式中的取值范围0 - 1是方便描述)。函数体中的proc数组包括了图像混合的全部8种情况的子函数,而index则按混合比例、目标图alpha信息和源图alpha信息组合成子函数调用下标值(alpha信息在bitmapdata结构的保留字段中)。
当然,在实际的运用中,全部8种情况似乎是多了点,可根据情况进行适当合并取舍,以兼顾代码的复杂度和执行效率。下面是我认为比较合理的精简版imagemixer函数:
void imagemixer(bitmapdata *dest, const bitmapdata *source, int alpha)
{
if (alpha <= 0) return;
if (alpha > 255) alpha = 255;
imagecpydata data;
data.width = (int)(dest->width < source->width? dest->width : source->width);
data.height = (int)(dest->height < source->height? dest->height : source->height);
data.dstoffset = (dest->stride >> 2) - data.width;
data.srcoffset = (source->stride >> 2) - data.width;
data.dstscan0 = (pargbquad)dest->scan0;
data.srcscan0 = (pargbquad)source->scan0;
if (alpha == 255 && !(source->reserved & pixelalphaflag))
mixer1(&data, alpha);
else if (dest->reserved & pixelalphaflag)
mixer6(&data, alpha);
else
mixer4(&data, alpha);
}
//---------------------------------------------------------------------------
这个imagemixer函数只保留了3个调用子函数,其中,mixer6是完全的正常混合模式,即前面公式3的实现;mixer4为对不含alpha信息目标图的混合,即在公式4基础上稍稍扩充了的情况;而mixer1则为拷贝模式。
下面是采用bcb2007和gdi+调用imagemixer函数的例子:
//---------------------------------------------------------------------------
// 锁定gdi+位位图扫描线到data
forceinline
void lockbitmap(gdiplus::bitmap *bmp, bitmapdata *data)
{
gdiplus::rect r(0, 0, bmp->getwidth(), bmp->getheight());
bool hasalpha = bmp->getpixelformat() & pixelformatalpha;
bmp->lockbits(&r, imagelockmoderead | imagelockmodewrite,
pixelformat32bppargb, data);
if (hasalpha) data->reserved |= pixelalphaflag;
}
//---------------------------------------------------------------------------
// gdi+位图扫描线解锁
forceinline
void unlockbitmap(gdiplus::bitmap *bmp, bitmapdata *data)
{
data->reserved &= 0xff;
bmp->unlockbits(data);
}
//---------------------------------------------------------------------------
void __fastcall tform1::button1click(tobject *sender)
{
gdiplus::bitmap *dest = new gdiplus::bitmap(l"d:\\xmas_011.png");
gdiplus::bitmap *source = new gdiplus::bitmap(l"d:\\apple.png");
gdiplus::graphics *g = new gdiplus::graphics(canvas->handle);
g->drawimage(dest, 0, 0);
g->drawimage(source, dest->getwidth(), 0);
bitmapdata dst, src;
lockbitmap(dest, &dst);
lockbitmap(source, &src);
imagemixer(&dst, &src, 192);
unlockbitmap(source, &src);
unlockbitmap(dest, &dst);
g->drawimage(dest, dest->getwidth() << 1, 0);
delete g;
delete source;
delete dest;
}
//---------------------------------------------------------------------------
下面是运行效果截图:
图像合成,其实也就是图像像素颜色的混合,在photoshop中,颜色混合是个很复杂的东西,不同的混合模式,将产生不同的合成效果,如果将之全部研究透彻,估计就得写一本书。因此,本文只谈谈最基本的图像合成,也就是photoshop中的正常混合模式。
只要接触过图像处理的,都知道有个图像像素混合公式:
1)dstrgb = srcrgb * alpha + dstrgb * (1 - alpha)
其中,dstrgb为目标图像素值;srcrgb为源图像素值;alpha为源图像素值混合比例(不透明度,范围0 - 1)。
其实,这个像素混合公式有很大局限性,只适合不含alpha信息的图像。
要处理包括带alpha通道图像(层)的混合,其完整的公式应该是:
2-1)srcrgb = srcrgb * srcalpha * alpha / 255 (源图像素预乘转换为pargb)
2-2)dstrgb = dstrgb * dstalpha / 255 (目标图像素预乘转换为pargb)
2-3)dstrgb = dstrgb + srcrgb - dstrgb * srcalpha * alpha / 255 (源图像素值与目标图像素值混合)
2-4)dstalpha = dstalpha + srcalpha * alpha - dstalpha * srcalpha * alpha / 255 (混合后的目标图alpha通道值)
2-5)dstrgb = dstrgb * 255 / dstalpha (混合后的目标图像素转换为argb)
其中,dstrgb为目标图像素值;srcrgb为源图像素值;dstalpha为目标图alpha通道值;srcalpha为源图alpha通道值;dstargb为含alpha目标图像素值;alpha为源图像素值混合比例(不透明度,范围0 - 1)。
将公式2中的2-1式代入2-3式,简化可得:
3-1)dstrgb = dstrgb * dstalpha / 255
3-2)dstrgb = dstrgb + (srcrgb - dstrgb) * srcalpha * alpha / 255
3-3)dstalpha = dstalpha + srcalpha * alpha - dstalpha * srcalpha * alpha / 255
3-4)dstrgb = dstrgb * 255 / dstalpha
当dstalpha=srcalpha=255时,公式3中3-1式、3-3式和3-4式没有意义,3-2式也变化为:
4)dstrgb = dstrgb + (srcrgb - dstrgb) * alpha
不难看出,公式4是公式1的变形。因此,公式1只是公式3(或者公式2)在目标图和源图都不含alpha信息(或者alpha=255)情况下的一个特例而已。
当公式4中的alpha=1时,目标图像素等于源图像素,所以,本文前面说图像拷贝其实也是图像合成的范畴。
通过上面较详细的分析,可以看出,即使是最基本正常图像混合模式也是很复杂的。其实,上面还不是完整的分析,因为按照目标图alpha信息、源图alpha信息以及源图合成比例等三个要素的完全的排列组合,最多可以派生8个公式。
下面就按正常混合模式的全部8种情况(有2项重合,实际为7种情况)来分别进行代码实现,也可完善和补充上面的文字叙述:
//---------------------------------------------------------------------------
// 定义argb像素结构
typedef union
{
argb color;
struct
{
byte blue;
byte green;
byte red;
byte alpha;
};
}argbquad, *pargbquad;
typedef struct
{
int width;
int height;
pargbquad dstscan0;
pargbquad srcscan0;
int dstoffset;
int srcoffset;
}imagecpydata, *pimagecpydata;
typedef void (*mixerproc)(pimagecpydata, int);
#define pixelalphaflag 0x10000
//---------------------------------------------------------------------------
// source alpha = false, dest alpha = false, alpha < 255
static void mixer0(pimagecpydata cpydata, int alpha)
{
pargbquad pd = cpydata->dstscan0;
pargbquad ps = cpydata->srcscan0;
for (int y = 0; y < cpydata->height; y ++, pd += cpydata->dstoffset, ps += cpydata->srcoffset)
{
for (int x = 0; x < cpydata->width; x ++, pd ++, ps ++)
{
pd->blue += (((ps->blue - pd->blue) * alpha + 127) / 255);
pd->green += (((ps->green - pd->green) * alpha + 127) / 255);
pd->red += (((ps->red - pd->red) * alpha + 127) / 255);
}
}
}
//---------------------------------------------------------------------------
// source alpha = false, dest alpha = false, alpha = 255
// source alpha = false, dest alpha = true, alpha = 255
static void mixer1(pimagecpydata cpydata, int alpha)
{
argb *pd = (argb*)cpydata->dstscan0;
argb *ps = (argb*)cpydata->srcscan0;
for (int y = 0; y < cpydata->height; y ++, pd += cpydata->dstoffset, ps += cpydata->srcoffset)
{
for (int x = 0; x < cpydata->width; x ++, *pd ++ = *ps ++);
}
}
//---------------------------------------------------------------------------
// source alpha = false, dest alpha = true, alpha < 255
static void mixer2(pimagecpydata cpydata, int alpha)
{
pargbquad pd = cpydata->dstscan0;
pargbquad ps = cpydata->srcscan0;
for (int y = 0; y < cpydata->height; y ++, pd += cpydata->dstoffset, ps += cpydata->srcoffset)
{
for (int x = 0; x < cpydata->width; x ++, pd ++, ps ++)
{
pd->blue = (pd->blue * pd->alpha + 127) / 255;
pd->green = (pd->green * pd->alpha + 127) / 255;
pd->red = (pd->red * pd->alpha + 127) / 255;
pd->blue += (((ps->blue - pd->blue) * alpha + 127) / 255);
pd->green += (((ps->green - pd->green) * alpha + 127) / 255);
pd->red += (((ps->red - pd->red) * alpha + 127) / 255);
pd->alpha += (alpha - (pd->alpha * alpha + 127) / 255);
pd->blue = pd->blue * 255 / pd->alpha;
pd->green = pd->green * 255 / pd->alpha;
pd->red = pd->red * 255 / pd->alpha;
}
}
}
//---------------------------------------------------------------------------
// source alpha = true, dest alpha = false, alpha < 255
static void mixer4(pimagecpydata cpydata, int alpha)
{
pargbquad pd = cpydata->dstscan0;
pargbquad ps = cpydata->srcscan0;
for (int y = 0; y < cpydata->height; y ++, pd += cpydata->dstoffset, ps += cpydata->srcoffset)
{
for (int x = 0; x < cpydata->width; x ++, pd ++, ps ++)
{
int alpha0 = (alpha * ps->alpha + 127) / 255;
pd->blue += (((ps->blue - pd->blue) * alpha0 + 127) / 255);
pd->green += (((ps->green - pd->green) * alpha0 + 127) / 255);
pd->red += (((ps->red - pd->red) * alpha0 + 127) / 255);
}
}
}
//---------------------------------------------------------------------------
// source alpha = true, dest alpha = false, alpha = 255
static void mixer5(pimagecpydata cpydata, int alpha)
{
pargbquad pd = cpydata->dstscan0;
pargbquad ps = cpydata->srcscan0;
for (int y = 0; y < cpydata->height; y ++, pd += cpydata->dstoffset, ps += cpydata->srcoffset)
{
for (int x = 0; x < cpydata->width; x ++, pd ++, ps ++)
{
pd->blue += (((ps->blue - pd->blue) * ps->alpha + 127) / 255);
pd->green += (((ps->green - pd->green) * ps->alpha + 127) / 255);
pd->red += (((ps->red - pd->red) * ps->alpha + 127) / 255);
}
}
}
//---------------------------------------------------------------------------
// source alpha = true, dest alpha = true, alpha < 255
static void mixer6(pimagecpydata cpydata, int alpha)
{
pargbquad pd = cpydata->dstscan0;
pargbquad ps = cpydata->srcscan0;
for (int y = 0; y < cpydata->height; y ++, pd += cpydata->dstoffset, ps += cpydata->srcoffset)
{
for (int x = 0; x < cpydata->width; x ++, pd ++, ps ++)
{
int alpha0 = (alpha * ps->alpha + 127) / 255;
if (alpha0)
{
pd->blue = (pd->blue * pd->alpha + 127) / 255;
pd->green = (pd->green * pd->alpha + 127) / 255;
pd->red = (pd->red * pd->alpha + 127) / 255;
pd->blue += (((ps->blue - pd->blue) * alpha0 + 127) / 255);
pd->green += (((ps->green - pd->green) * alpha0 + 127) / 255);
pd->red += (((ps->red - pd->red) * alpha0 + 127) / 255);
pd->alpha += (alpha0 - (pd->alpha * alpha0 + 127) / 255);
pd->blue = pd->blue * 255 / pd->alpha;
pd->green = pd->green * 255 / pd->alpha;
pd->red = pd->red * 255 / pd->alpha;
}
}
}
}
//---------------------------------------------------------------------------
// source alpha = true, dest alpha = true, alpha = 255
static void mixer7(pimagecpydata cpydata, int alpha)
{
pargbquad pd = cpydata->dstscan0;
pargbquad ps = cpydata->srcscan0;
for (int y = 0; y < cpydata->height; y ++, pd += cpydata->dstoffset, ps += cpydata->srcoffset)
{
for (int x = 0; x < cpydata->width; x ++, pd ++, ps ++)
{
if (ps->alpha)
{
pd->blue = (pd->blue * pd->alpha + 127) / 255;
pd->green = (pd->green * pd->alpha + 127) / 255;
pd->red = (pd->red * pd->alpha + 127) / 255;
pd->blue += (((ps->blue - pd->blue) * ps->alpha + 127) / 255);
pd->green += (((ps->green - pd->green) * ps->alpha + 127) / 255);
pd->red += (((ps->red - pd->red) * ps->alpha + 127) / 255);
pd->alpha += (ps->alpha - (pd->alpha * ps->alpha + 127) / 255);
pd->blue = pd->blue * 255 / pd->alpha;
pd->green = pd->green * 255 / pd->alpha;
pd->red = pd->red * 255 / pd->alpha;
}
}
}
}
//---------------------------------------------------------------------------
void imagemixer(bitmapdata *dest, const bitmapdata *source, int alpha)
{
if (alpha <= 0) return;
if (alpha > 255) alpha = 255;
imagecpydata data;
data.width = (int)(dest->width < source->width? dest->width : source->width);
data.height = (int)(dest->height < source->height? dest->height : source->height);
data.dstoffset = (dest->stride >> 2) - data.width;
data.srcoffset = (source->stride >> 2) - data.width;
data.dstscan0 = (pargbquad)dest->scan0;
data.srcscan0 = (pargbquad)source->scan0;
mixerproc proc[] = {mixer0, mixer1, mixer2, mixer1, mixer4, mixer5, mixer6, mixer7};
int index = (alpha / 255) | ((dest->reserved >> 16) << 1) | ((source->reserved >> 16) << 2);
proc[index](&data, alpha);
}
//---------------------------------------------------------------------------
函数imagemixer有三个参数,分别为目标图数据结构(借用gdi+的bitmapdata结构)指针、源图数据结构指针和源图像素混合比例(不透明度,取值范围为0 - 255,前面的公式中的取值范围0 - 1是方便描述)。函数体中的proc数组包括了图像混合的全部8种情况的子函数,而index则按混合比例、目标图alpha信息和源图alpha信息组合成子函数调用下标值(alpha信息在bitmapdata结构的保留字段中)。
当然,在实际的运用中,全部8种情况似乎是多了点,可根据情况进行适当合并取舍,以兼顾代码的复杂度和执行效率。下面是我认为比较合理的精简版imagemixer函数:
void imagemixer(bitmapdata *dest, const bitmapdata *source, int alpha)
{
if (alpha <= 0) return;
if (alpha > 255) alpha = 255;
imagecpydata data;
data.width = (int)(dest->width < source->width? dest->width : source->width);
data.height = (int)(dest->height < source->height? dest->height : source->height);
data.dstoffset = (dest->stride >> 2) - data.width;
data.srcoffset = (source->stride >> 2) - data.width;
data.dstscan0 = (pargbquad)dest->scan0;
data.srcscan0 = (pargbquad)source->scan0;
if (alpha == 255 && !(source->reserved & pixelalphaflag))
mixer1(&data, alpha);
else if (dest->reserved & pixelalphaflag)
mixer6(&data, alpha);
else
mixer4(&data, alpha);
}
//---------------------------------------------------------------------------
这个imagemixer函数只保留了3个调用子函数,其中,mixer6是完全的正常混合模式,即前面公式3的实现;mixer4为对不含alpha信息目标图的混合,即在公式4基础上稍稍扩充了的情况;而mixer1则为拷贝模式。
下面是采用bcb2007和gdi+调用imagemixer函数的例子:
//---------------------------------------------------------------------------
// 锁定gdi+位位图扫描线到data
forceinline
void lockbitmap(gdiplus::bitmap *bmp, bitmapdata *data)
{
gdiplus::rect r(0, 0, bmp->getwidth(), bmp->getheight());
bool hasalpha = bmp->getpixelformat() & pixelformatalpha;
bmp->lockbits(&r, imagelockmoderead | imagelockmodewrite,
pixelformat32bppargb, data);
if (hasalpha) data->reserved |= pixelalphaflag;
}
//---------------------------------------------------------------------------
// gdi+位图扫描线解锁
forceinline
void unlockbitmap(gdiplus::bitmap *bmp, bitmapdata *data)
{
data->reserved &= 0xff;
bmp->unlockbits(data);
}
//---------------------------------------------------------------------------
void __fastcall tform1::button1click(tobject *sender)
{
gdiplus::bitmap *dest = new gdiplus::bitmap(l"d:\\xmas_011.png");
gdiplus::bitmap *source = new gdiplus::bitmap(l"d:\\apple.png");
gdiplus::graphics *g = new gdiplus::graphics(canvas->handle);
g->drawimage(dest, 0, 0);
g->drawimage(source, dest->getwidth(), 0);
bitmapdata dst, src;
lockbitmap(dest, &dst);
lockbitmap(source, &src);
imagemixer(&dst, &src, 192);
unlockbitmap(source, &src);
unlockbitmap(dest, &dst);
g->drawimage(dest, dest->getwidth() << 1, 0);
delete g;
delete source;
delete dest;
}
//---------------------------------------------------------------------------
下面是运行效果截图:
左边是目标图,中间是源图,右边是源图按不透明度192进行的正常混合。
本文代码未作过多优化。
摘自 闲人阿发伯的业余心得