博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
图像(层)正常混合模式详解(上)
阅读量:4993 次
发布时间:2019-06-12

本文共 11476 字,大约阅读时间需要 38 分钟。

    在图像处理过程中,图像的合成操作是使用频率最高的,如图像显示、图像拷贝、图像拼接以及的图层拼合叠加等。

    图像合成,其实也就是图像像素颜色的混合,在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种情况)来分别进行代码实现,也可完善和补充上面的文字叙述:

  1 
//
---------------------------------------------------------------------------
  2 
  3 
//
 定义ARGB像素结构
  4 
typedef union
  5 {
  6     ARGB Color;
  7     
struct
  8     {
  9         BYTE Blue;
 10         BYTE Green;
 11         BYTE Red;
 12         BYTE Alpha;
 13     };
 14 }ARGBQuad, *PARGBQuad;
 15 
 16 typedef    
struct
 17 {
 18     INT width;
 19     INT height;
 20     PARGBQuad dstScan0;
 21     PARGBQuad srcScan0;
 22     INT dstOffset;
 23     INT srcOffset;
 24 }ImageCpyData, *PImageCpyData;
 25 
 26 typedef VOID (*MixerProc)(PImageCpyData, INT);
 27 
 28 
#define PixelAlphaFlag  0x10000
 29 
//
---------------------------------------------------------------------------
 30 
//
 source alpha = false, dest alpha = false, alpha < 255
 31 
static VOID Mixer0(PImageCpyData cpyData, INT alpha)
 32 {
 33     PARGBQuad pd = cpyData->dstScan0;
 34     PARGBQuad ps = cpyData->srcScan0;
 35     
for (INT y = 
0; y < cpyData->height; y ++, pd += cpyData->dstOffset, ps += cpyData->srcOffset)
 36     {
 37         
for (INT x = 
0; x < cpyData->width; x ++, pd ++, ps ++)
 38         {
 39             pd->Blue += (((ps->Blue - pd->Blue) * alpha + 
127) / 
255);
 40             pd->Green += (((ps->Green - pd->Green) * alpha + 
127) / 
255);
 41             pd->Red += (((ps->Red - pd->Red) * alpha + 
127) / 
255);
 42         }
 43     }
 44 }
 45 
//
---------------------------------------------------------------------------
 46 
//
 source alpha = false, dest alpha = false, alpha = 255
 47 
//
 source alpha = false, dest alpha = true, alpha = 255
 48 
static VOID Mixer1(PImageCpyData cpyData, INT alpha)
 49 {
 50     ARGB *pd = (ARGB*)cpyData->dstScan0;
 51     ARGB *ps = (ARGB*)cpyData->srcScan0;
 52     
for (INT y = 
0; y < cpyData->height; y ++, pd += cpyData->dstOffset, ps += cpyData->srcOffset)
 53     {
 54         
for (INT x = 
0; x < cpyData->width; x ++, *pd ++ = *ps ++);
 55     }
 56 }
 57 
//
---------------------------------------------------------------------------
 58 
//
 source alpha = false, dest alpha = true, alpha < 255
 59 
static VOID Mixer2(PImageCpyData cpyData, INT alpha)
 60 {
 61     PARGBQuad pd = cpyData->dstScan0;
 62     PARGBQuad ps = cpyData->srcScan0;
 63     
for (INT y = 
0; y < cpyData->height; y ++, pd += cpyData->dstOffset, ps += cpyData->srcOffset)
 64     {
 65         
for (INT x = 
0; x < cpyData->width; x ++, pd ++, ps ++)
 66         {
 67             pd->Blue = (pd->Blue * pd->Alpha + 
127) / 
255;
 68             pd->Green = (pd->Green * pd->Alpha + 
127) / 
255;
 69             pd->Red = (pd->Red * pd->Alpha + 
127) / 
255;
 70 
 71             pd->Blue += (((ps->Blue - pd->Blue) * alpha + 
127) / 
255);
 72             pd->Green += (((ps->Green - pd->Green) * alpha + 
127) / 
255);
 73             pd->Red += (((ps->Red - pd->Red) * alpha + 
127) / 
255);
 74             pd->Alpha += (alpha - (pd->Alpha * alpha + 
127) / 
255);
 75 
 76             pd->Blue = pd->Blue * 
255 / pd->Alpha;
 77             pd->Green = pd->Green * 
255 / pd->Alpha;
 78             pd->Red = pd->Red * 
255 / pd->Alpha;
 79         }
 80     }
 81 }
 82 
//
---------------------------------------------------------------------------
 83 
//
 source alpha = true, dest alpha = false, alpha < 255
 84 
static VOID Mixer4(PImageCpyData cpyData, INT alpha)
 85 {
 86     PARGBQuad pd = cpyData->dstScan0;
 87     PARGBQuad ps = cpyData->srcScan0;
 88     
for (INT y = 
0; y < cpyData->height; y ++, pd += cpyData->dstOffset, ps += cpyData->srcOffset)
 89     {
 90         
for (INT x = 
0; x < cpyData->width; x ++, pd ++, ps ++)
 91         {
 92             INT alpha0 = (alpha * ps->Alpha + 
127) / 
255;
 93             pd->Blue += (((ps->Blue - pd->Blue) * alpha0 + 
127) / 
255);
 94             pd->Green += (((ps->Green - pd->Green) * alpha0 + 
127) / 
255);
 95             pd->Red += (((ps->Red - pd->Red) * alpha0 + 
127) / 
255);
 96         }
 97     }
 98 }
 99 
//
---------------------------------------------------------------------------
100 
//
 source alpha = true, dest alpha = false, alpha = 255
101 
static VOID Mixer5(PImageCpyData cpyData, INT alpha)
102 {
103     PARGBQuad pd = cpyData->dstScan0;
104     PARGBQuad ps = cpyData->srcScan0;
105     
for (INT y = 
0; y < cpyData->height; y ++, pd += cpyData->dstOffset, ps += cpyData->srcOffset)
106     {
107         
for (INT x = 
0; x < cpyData->width; x ++, pd ++, ps ++)
108         {
109             pd->Blue += (((ps->Blue - pd->Blue) * ps->Alpha + 
127) / 
255);
110             pd->Green += (((ps->Green - pd->Green) * ps->Alpha + 
127) / 
255);
111             pd->Red += (((ps->Red - pd->Red) * ps->Alpha + 
127) / 
255);
112         }
113     }
114 }
115 
//
---------------------------------------------------------------------------
116 
//
 source alpha = true, dest alpha = true, alpha < 255
117 
static VOID Mixer6(PImageCpyData cpyData, INT alpha)
118 {
119     PARGBQuad pd = cpyData->dstScan0;
120     PARGBQuad ps = cpyData->srcScan0;
121     
for (INT y = 
0; y < cpyData->height; y ++, pd += cpyData->dstOffset, ps += cpyData->srcOffset)
122     {
123         
for (INT x = 
0; x < cpyData->width; x ++, pd ++, ps ++)
124         {
125             INT alpha0 = (alpha * ps->Alpha + 
127) / 
255;
126             
if (alpha0)
127             {
128                 pd->Blue = (pd->Blue * pd->Alpha + 
127) / 
255;
129                 pd->Green = (pd->Green * pd->Alpha + 
127) / 
255;
130                 pd->Red = (pd->Red * pd->Alpha + 
127) / 
255;
131 
132                 pd->Blue += (((ps->Blue - pd->Blue) * alpha0 + 
127) / 
255);
133                 pd->Green += (((ps->Green - pd->Green) * alpha0 + 
127) / 
255);
134                 pd->Red += (((ps->Red - pd->Red) * alpha0 + 
127) / 
255);
135                 pd->Alpha += (alpha0 - (pd->Alpha * alpha0 + 
127) / 
255);
136 
137                 pd->Blue = pd->Blue * 
255 / pd->Alpha;
138                 pd->Green = pd->Green * 
255 / pd->Alpha;
139                 pd->Red = pd->Red * 
255 / pd->Alpha;
140             }
141         }
142     }
143 }
144 
//
---------------------------------------------------------------------------
145 
//
 source alpha = true, dest alpha = true, alpha = 255
146 
static VOID Mixer7(PImageCpyData cpyData, INT alpha)
147 {
148     PARGBQuad pd = cpyData->dstScan0;
149     PARGBQuad ps = cpyData->srcScan0;
150     
for (INT y = 
0; y < cpyData->height; y ++, pd += cpyData->dstOffset, ps += cpyData->srcOffset)
151     {
152         
for (INT x = 
0; x < cpyData->width; x ++, pd ++, ps ++)
153         {
154             
if (ps->Alpha)
155             {
156                 pd->Blue = (pd->Blue * pd->Alpha + 
127) / 
255;
157                 pd->Green = (pd->Green * pd->Alpha + 
127) / 
255;
158                 pd->Red = (pd->Red * pd->Alpha + 
127) / 
255;
159 
160                 pd->Blue += (((ps->Blue - pd->Blue) * ps->Alpha + 
127) / 
255);
161                 pd->Green += (((ps->Green - pd->Green) * ps->Alpha + 
127) / 
255);
162                 pd->Red += (((ps->Red - pd->Red) * ps->Alpha + 
127) / 
255);
163                 pd->Alpha += (ps->Alpha - (pd->Alpha * ps->Alpha + 
127) / 
255);
164 
165                 pd->Blue = pd->Blue * 
255 / pd->Alpha;
166                 pd->Green = pd->Green * 
255 / pd->Alpha;
167                 pd->Red = pd->Red * 
255 / pd->Alpha;
168             }
169         }
170     }
171 }
172 
//
---------------------------------------------------------------------------
173 
174 VOID ImageMixer(BitmapData *dest, CONST BitmapData *source, INT alpha)
175 {
176     
if (alpha <= 
0
return;
177     
if (alpha > 
255) alpha = 
255;
178 
179     ImageCpyData data;
180     data.width = (INT)(dest->Width < source->Width? dest->Width : source->Width);
181     data.height = (INT)(dest->Height < source->Height? dest->Height : source->Height);
182     data.dstOffset = (dest->Stride >> 
2) - data.width;
183     data.srcOffset = (source->Stride >> 
2) - data.width;
184     data.dstScan0 = (PARGBQuad)dest->Scan0;
185     data.srcScan0 = (PARGBQuad)source->Scan0;
186 
187     MixerProc proc[] = {Mixer0, Mixer1, Mixer2, Mixer1, Mixer4, Mixer5, Mixer6, Mixer7};
188     INT index = (alpha / 
255) | ((dest->Reserved >> 
16) << 
1) | ((source->Reserved >> 
16) << 
2);
189     proc[index](&data, alpha);
190 }
191 
//
---------------------------------------------------------------------------

    函数ImageMixer有三个参数,分别为目标图数据结构(借用GDI+的BitmapData结构)指针、源图数据结构指针和源图像素混合比例(不透明度,取值范围为0 - 255,前面的公式中的取值范围0 - 1是方便描述)。函数体中的proc数组包括了图像混合的全部8种情况的子函数,而index则按混合比例、目标图Alpha信息和源图Alpha信息组合成子函数调用下标值(Alpha信息在BitmapData结构的保留字段中)。

    当然,在实际的运用中,全部8种情况似乎是多了点,可根据情况进行适当合并取舍,以兼顾代码的复杂度和执行效率。下面是我认为比较合理的精简版ImageMixer函数:

 
 1 VOID ImageMixer(BitmapData *dest, CONST BitmapData *source, INT alpha)
 2 {
 3     if (alpha <= 0return;
 4     if (alpha > 255) alpha = 255;
 5 
 6     ImageCpyData data;
 7     data.width = (INT)(dest->Width < source->Width? dest->Width : source->Width);
 8     data.height = (INT)(dest->Height < source->Height? dest->Height : source->Height);
 9     data.dstOffset = (dest->Stride >> 2) - data.width;
10     data.srcOffset = (source->Stride >> 2) - data.width;
11     data.dstScan0 = (PARGBQuad)dest->Scan0;
12     data.srcScan0 = (PARGBQuad)source->Scan0;
13 
14     if (alpha == 255 && !(source->Reserved & PixelAlphaFlag))
15         Mixer1(&data, alpha);
16     else if (dest->Reserved & PixelAlphaFlag)
17         Mixer6(&data, alpha);
18     else
19         Mixer4(&data, alpha);
20 }
21 //---------------------------------------------------------------------------
  这个ImageMixer函数只保留了3个调用子函数,其中,Mixer6是完全的正常混合模式,即前面公式3的实现;Mixer4为对不含Alpha信息目标图的混合,即在公式4基础上稍稍扩充了的情况;而Mixer1则为拷贝模式。

    下面是采用BCB2007和GDI+调用ImageMixer函数的例子:

 
 1 
//
---------------------------------------------------------------------------
 2 
 3 
//
 锁定GDI+位位图扫描线到data
 4 
FORCEINLINE
 5 VOID LockBitmap(Gdiplus::Bitmap *bmp, BitmapData *data)
 6 {
 7     Gdiplus::Rect r(
0
0, bmp->GetWidth(), bmp->GetHeight());
 8     BOOL hasAlpha = bmp->GetPixelFormat() & PixelFormatAlpha;
 9     bmp->LockBits(&r, ImageLockModeRead | ImageLockModeWrite,
10         PixelFormat32bppARGB, data);
11     
if (hasAlpha) data->Reserved |= PixelAlphaFlag;
12 }
13 
//
---------------------------------------------------------------------------
14 
15 
//
 GDI+位图扫描线解锁
16 
FORCEINLINE
17 VOID UnlockBitmap(Gdiplus::Bitmap *bmp, BitmapData *data)
18 {
19     data->Reserved &= 
0xff;
20     bmp->UnlockBits(data);
21 }
22 
//
---------------------------------------------------------------------------
23 
24 
void __fastcall TForm1::Button1Click(TObject *Sender)
25 {
26     Gdiplus::Bitmap *dest =  
new Gdiplus::Bitmap(L
"
d:\\xmas_011.png
");
27     Gdiplus::Bitmap *source =  
new Gdiplus::Bitmap(L
"
d:\\Apple.png
");
28 
29     Gdiplus::Graphics *g = 
new Gdiplus::Graphics(Canvas->Handle);
30     g->DrawImage(dest, 
0
0);
31     g->DrawImage(source, dest->GetWidth(), 
0);
32 
33     BitmapData dst, src;
34     LockBitmap(dest, &dst);
35     LockBitmap(source, &src);
36     ImageMixer(&dst, &src, 
192);
37     UnlockBitmap(source, &src);
38     UnlockBitmap(dest, &dst);
39 
40     g->DrawImage(dest, dest->GetWidth() << 
1
0);
41 
42     delete g;
43     delete source;
44     delete dest;
45 }
46 
//
---------------------------------------------------------------------------

    下面是运行效果截图:

    左边是目标图,中间是源图,右边是源图按不透明度192进行的正常混合。

    本文代码未作过多优化。

    水平有限,错误在所难免,欢迎指正和指导。邮箱地址:

转载于:https://www.cnblogs.com/maozefa/archive/2012/01/04/2312547.html

你可能感兴趣的文章
SVGImageView
查看>>
Android UI 优化 使用<include/>和 <merge />标签
查看>>
linux命令--使用fsck修复文件系统
查看>>
洛谷 P2324 [SCOI2005]骑士精神
查看>>
leetcode(64)最小路径和
查看>>
Select文字居右显示
查看>>
mycat操作MySQL第一篇:全局表
查看>>
MySQL数据库表分区
查看>>
python多个装饰器的执行顺序
查看>>
岗顶-一图一世界
查看>>
一步步构造自己的vue2.0+webpack环境
查看>>
分页类
查看>>
Python装饰器的个人小理解
查看>>
为什么百万医疗险越来越多,到底选哪款?
查看>>
如何检测 51单片机IO口的下降沿
查看>>
扫描识别控件Dynamic .NET TWAIN使用教程:如何将事件添加到应用程序中
查看>>
创建和修改主键 (SQL)
查看>>
2018-2019 ICPC, NEERC, Southern Subregional Contest(训练记录)
查看>>
20145233 《信息安全系统设计基础》第7周学习总结
查看>>
linux设备驱动程序第3版学习笔记(例程2--hellop.c)
查看>>