在图像处理过程中,图像的合成操作是使用频率最高的,如图像显示、图像拷贝、图像拼接以及的图层拼合叠加等。
    图像合成,其实也就是图像像素颜色的混合,在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进行的正常混合。

本文代码未作过多优化。

转载于:https://my.oschina.net/u/246509/blog/651326

图像(层)正常混合模式详解(上)相关推荐

  1. Photoshop----图层混合模式详解

    [PS图层混合模式详解] 图层的混合模式决定其像素如何与图像中的下层像素进行混合.使用混合模式可以创建各种特殊效果. 默认情况下,图层组的混合模式是"穿透",表示图层组没有自己的混 ...

  2. matlab对图像操作函数的详解(笔记1)

    matlab对图像操作函数的详解 一. 读写图像文件 1. imread imread函数用于读入各种图像文件,如:a=imread('e:\w01.tif') 注:计算机E盘上要有w01相应的.ti ...

  3. 目标检测特殊层:PSROIPooling详解

    1. Position Sensitive ROI-Pooling 简介 Position Sensitive ROI Pooling(位置敏感的候选区域池化)是检测框架R-FCN的主要创新点.一般来 ...

  4. sync.Map低层工作原理详解

    sync.Map低层工作原理详解 目录 为什么需要sync.Map?适合什么场景? sync.Map内部实现基本原理及结构体分析 sync.Map低层工作原理 1. 为什么需要sync.Map?适合什 ...

  5. android拍照保存照片方向,Android:Camera2开发详解(上):实现预览、拍照、保存照片等功能...

    android.jpg 前言 在前几篇文章中介绍了如何调用系统相机拍照和使用Camera1的实现自定义相机拍照.人脸检测等功能 文章传送门: 接下来的几篇文章中,我将给大家介绍如何使用Camera2实 ...

  6. [Python从零到壹] 四十五.图像增强及运算篇之图像灰度非线性变换详解

    欢迎大家来到"Python从零到壹",在这里我将分享约200篇Python系列文章,带大家一起去学习和玩耍,看看Python这个有趣的世界.所有文章都将结合案例.代码和作者的经验讲 ...

  7. [Python从零到壹] 三十六.图像处理基础篇之图像算术与逻辑运算详解

    欢迎大家来到"Python从零到壹",在这里我将分享约200篇Python系列文章,带大家一起去学习和玩耍,看看Python这个有趣的世界.所有文章都将结合案例.代码和作者的经验讲 ...

  8. 搬砖:新一代基于UDP的低延时网络传输层协议——QUIC详解

    技术扫盲:新一代基于UDP的低延时网络传输层协议--QUIC详解 本文来自腾讯资深研发工程师罗成的技术分享,主要介绍 QUIC 协议产生的背景和核心特性等. 1.写在前面 如果你的 App,在不需要任 ...

  9. android开发自动拍照,Android:Camera2开发详解(上):实现预览、拍照、保存照片等功能...

    android.jpg 前言 在前几篇文章中介绍了如何调用系统相机拍照和使用Camera1的实现自定义相机拍照.人脸检测等功能 文章传送门: 接下来的几篇文章中,我将给大家介绍如何使用Camera2实 ...

  10. 高斯模糊java代码_Java编程实现高斯模糊和图像的空间卷积详解

    高斯模糊 高斯模糊(英语:Gaussian Blur),也叫高斯平滑,是在Adobe Photoshop.GIMP以及Paint.NET等图像处理软件中广泛使用的处理效果,通常用它来减少图像杂讯以及降 ...

最新文章

  1. tensorflow量化感知训练_tensorflow
  2. 用ADO.NET的ExecuteScalar方法返回单一值-资讯动态
  3. Linux下安装Redis(三分钟搞定)
  4. 【转载】修改host不重启浏览器的方法
  5. 要做互联星空的SP接口,一点头绪都没有
  6. 北美互联网公司“隐藏福利”大盘点:看完我彻底酸了...
  7. linux安装audit软件,linux audit工具
  8. Oracle从入门到精通
  9. Xcode8 - apploader 上传失败 - ERROR ITMS-90168: The binary you uploaded was invalid.
  10. 实对称矩阵必可正交对角化证明
  11. 路虎:独到所处,揽胜极致.
  12. <hr> 标签:定义水平线
  13. git 仓库分支多文件夹管理
  14. Tracking相关的文章
  15. 除了《千与千寻》,宫崎骏漫画的花卉治愈了观众,而花艺学院治愈了你一片温情
  16. 如何将卫星影像或者航拍影像叠加到CAD设计图上(Auto CAD版)
  17. SVN mac 破解版
  18. 合唱队形(动态规划)
  19. 淘宝年货节红包来了,2021年淘宝年货节红包开抢时间表
  20. eai java,EAI与SOA

热门文章

  1. web聊天室项目开发过程及重难点整理
  2. SugarCRM源码分析之缓存
  3. php 接收复杂json,php解析复杂json的实例 - 火车api的应用
  4. 【fecmall】fecyo-1.5.1开源版本发布 - 重构优惠券部分
  5. Ubuntu20.04实时显示CPU、内存、网速
  6. 关闭删库跑路的后门,打造高可用的MySQL
  7. java for循环延迟_Java 锁粗化与循环问题
  8. Oracle日志挖掘之LogMiner
  9. webrtc入门之客户端连麦demo-apprtc
  10. Android GPS定位记录发送功能