给图片加透明度水印的方法(c#)

简介: 刚才在提问区解答了一个问题,即如何给图片加半透明度水印,实际上由于透明度算法是最早被发现也是最简单和基本的算法之一,因此这个方法实际上很直观。这里单独发帖再来解释下这个问题。首先我们强调一下opacity的概念,是“不透明度”,它表示的是两个图层之间的关系,即该属性隶属于本图层,它表示与本图层下面图层之间的像素合成关系,当opacity=100%时,表示本图层完全不透明,因此下面的像素完全被遮挡。
        刚才在提问区解答了一个问题,即如何给图片加半透明度水印,实际上由于透明度算法是最早被发现也是最简单和基本的算法之一,因此这个方法实际上很直观。这里单独发帖再来解释下这个问题。首先我们强调一下opacity的概念,是“不透明度”,它表示的是两个图层之间的关系,即该属性隶属于本图层,它表示与本图层下面图层之间的像素合成关系,当opacity=100%时,表示本图层完全不透明,因此下面的像素完全被遮挡。当opacity=0时,表示本图层完全透明,即能看到下面的图层。这是photoshop中最基本的一个算法,表达如下:
        像素结果=底部图层*(1-opacity)+本图层*opacity;
        当有多个图层x0,x1,x2混合时,表达如下:(最底层的不透明度为1)
        x=((1-k1)x0+k1*x1)(1-k2)+k2*x2= (1-k1)(1-k2)x0 + k1(1-k2)x1 + k2*x2;

        那么绘制透明度水印的方法也就非常直观了,因为类库中的ImageAttributes属性里面并没有提供像素合成的绘制选项,因此我们自己实现上面的算法。方法是:首先准备一个小的水印图片,我们先把原图在水印下面的部分绘制上去,然后在吧水印的文本或图片绘制上去,然后把原图和水印图片的内存数据锁定(防止操作系统移动内存),然后直接用上面的算法改写原图的位图数据,解锁内存即得到最终加了水印的图片。

       代码如下:下面是绘制文本类型的水印,只需提供水印文本内容,绘制起始坐标即可。注意,为了简单直观起见,代码中都没有做参数验证,例如水印是否超出原图范围,如果超出范围将引发对超过内存边界的访问限制(引发异常)。下面使用了unsafe代码,因此项目属性->Build中,应勾选允许不安全代码,否则无法编译。 在下面代码中的定位方式是非常熟悉的。再次强调的是以下的概念:
       scan0:指针,内存数据的起始地址。(换句话说,就是指向第一个扫描行第一个像素的Blue)。
       bpp:bit per pixel。
       stride:扫描行宽度,=width*bpp/8 并在结尾补0~3个字节的0,以凑齐到4字节整数倍。
       之所以横坐标乘以3是因为我们锁定的方式是24bppRgb(最后一个参数指定了数据的bpp),这意味这每个像素占据了3个字节,因此i要乘以3来跳跃到下一个像素。如果用32bppRGB锁定,则每个像素在内存占据4字节,相应的i应该乘以4。
Code-文本水印
/// <summary>
/// 给一个位图绘制水印文字(没有验证水印是否超出图片边界!)
/// </summary>
/// <param name="text">水印文本</param>
/// <param name="x">起始点</param>
/// <param name="y">起始点</param>
/// <param name="opacity">不透明度,0~1</param>
private Bitmap DrawWatermark(Image image,string text, Font font,Brush brush,int x,int y,double opacity)
{
    Bitmap bm1 
= new Bitmap(image);
    Graphics g1
=Graphics.FromImage(bm1);
    
//测量水印文字的大小,然后申请一个新的位图
    SizeF sizef=g1.MeasureString(text,font);
    Bitmap bm2
=new Bitmap((int)sizef.Width,(int)sizef.Height);
    Graphics g2
=Graphics.FromImage(bm2);
    g2.DrawImage(bm1, 
0,0,new Rectangle(x, y, bm2.Width, bm2.Height),GraphicsUnit.Pixel);
    g2.DrawString(text,font,brush,
0,0);
    BitmapData data1
=bm1.LockBits(new Rectangle(0,0,bm1.Width,bm1.Height),ImageLockMode.ReadWrite,PixelFormat.Format24bppRgb);
    BitmapData data2
=bm2.LockBits(new Rectangle(0,0,bm2.Width,bm2.Height),ImageLockMode.ReadWrite,PixelFormat.Format24bppRgb);
    
unsafe
    {
        
byte* p1=(byte*)(void*)data1.Scan0;
        
byte* p2=(byte*)(void*)data2.Scan0;
        
for(int j=0;j<bm2.Height;j++)
        {
            
for(int i=0;i<bm2.Width*3;i++)
            {
                p1[(y
+j)*data1.Stride+i]=(byte)(p1[(y+j)*data1.Stride+i]*(1-opacity)+opacity*p2[j*data2.Stride+i]);
            }
        }
        bm1.UnlockBits(data1);
        bm2.UnlockBits(data2);
    }
    
return bm1; 
}

还有一种情况是,我们事先做作好水印,它是一个图片,更多的人在photoshop中使用一个自己设计好的logo,保存为一个“画笔形状”,制作时只要选中此画笔一盖就好了。实际上这种类型的水印是一个图片,为了加这种类型的水印,引入下面的overload方法:指定水印图片和透明色。
Code-图片水印
/// <param name="image">原图</param>
/// <param name="wmImg">水印图片</param>
/// <param name="key">透明色</param>
/// <param name="x">起始点</param>
/// <param name="y"></param>
/// <param name="opacity">不透明度</param>
/// <returns></returns>
private Bitmap DrawWatermark(Image image, Bitmap wmImg, Color key,int x, int y, double opacity)
{
    Bitmap bm1 
= new Bitmap(image);     //克隆原图,它也是我们的返回值
    Bitmap bm2 
= new Bitmap(wmImg.Width, wmImg.Height);     //准备的水印图片
    Graphics g2 
= Graphics.FromImage(bm2);
    ImageAttributes att 
= new ImageAttributes();
    att.SetColorKey(key, key, ColorAdjustType.Bitmap);     //设定透明色
    g2.DrawImage(bm1, 
00new Rectangle(x, y, bm2.Width, bm2.Height), GraphicsUnit.Pixel);
    g2.DrawImage(wmImg, 
new Rectangle(0,0,bm2.Width,bm2.Height),00,bm2.Width,bm2.Height,GraphicsUnit.Pixel,att);
    g2.Dispose();
    。。。。这里的代码和上面的方法代码相同,因此省略
    
return bm1;
}
以上两种效果的截图:
(1)     (2)
          
        源代码的下载链接:(该项目里还包括我对Photoshop中置换滤镜的模拟代码,以及在水波特效控件原理解释那篇文中的水波置换图生成器。)
        http://files.cnblogs.com/hoodlum1980/PsFilters.rar
       
目录
相关文章
|
3月前
|
开发框架 .NET 程序员
C# 去掉字符串最后一个字符的 4 种方法
在实际业务中,我们经常会遇到在循环中拼接字符串的场景,循环结束之后拼接得到的字符串的最后一个字符往往需要去掉,看看 C# 提供了哪4种方法可以高效去掉字符串的最后一个字符
315 0
|
2月前
|
编译器 C#
C#多态概述:通过继承实现的不同对象调用相同的方法,表现出不同的行为
C#多态概述:通过继承实现的不同对象调用相同的方法,表现出不同的行为
116 65
|
6月前
|
数据采集 数据可视化 测试技术
C#生成Selenium测试报告:实用方法与技巧
在C#中使用Selenium进行自动化测试时,结合代理IP和ExtentReports能增强测试安全性和报告质量。安装必备工具如Selenium WebDriver、NUnit和ExtentReports。在测试设置中,配置代理(如亿牛云爬虫代理)以隐藏IP,通过ChromeOptions定制UserAgent,并添加Cookie。测试代码示例展示了如何打开网页、执行搜索并生成详细的测试报告。使用ExtentReports可创建可视化测试结果,便于团队分析。
C#生成Selenium测试报告:实用方法与技巧
|
16天前
|
JSON 程序员 C#
使用 C# 比较两个对象是否相等的7个方法总结
比较对象是编程中的一项基本技能,在实际业务中经常碰到,比如在ERP系统中,企业的信息非常重要,每一次更新,都需要比较记录更新前后企业的信息,直接比较通常只能告诉我们它们是否指向同一个内存地址,那我们应该怎么办呢?分享 7 个方法给你!
|
19天前
|
C# UED SEO
C# 异步方法async / await任务超时处理
通过使用 `Task.WhenAny`和 `Task.Delay`方法,您可以在C#中有效地实现异步任务的超时处理机制。这种方法允许您在指定时间内等待任务完成,并在任务超时时采取适当的措施,如抛出异常或执行备用操作。希望本文提供的详细解释和代码示例能帮助您在实际项目中更好地处理异步任务超时问题,提升应用程序的可靠性和用户体验。
44 3
|
2月前
|
存储 C#
【C#】大批量判断文件是否存在的两种方法效率对比
【C#】大批量判断文件是否存在的两种方法效率对比
42 1
|
2月前
|
C#
C#的方法的参数传递
C#的方法的参数传递
21 0
|
2月前
|
数据可视化 程序员 C#
C#中windows应用窗体程序的输入输出方法实例
C#中windows应用窗体程序的输入输出方法实例
46 0
|
3月前
|
C#
C#一分钟浅谈:Lambda 表达式和匿名方法
本文详细介绍了C#编程中的Lambda表达式与匿名方法,两者均可用于定义无名函数,使代码更简洁易维护。文章通过基础概念讲解和示例对比,展示了各自语法特点,如Lambda表达式的`(parameters) =&gt; expression`形式及匿名方法的`delegate(parameters)`结构。并通过实例演示了两者的应用差异,强调了在使用Lambda时应注意闭包问题及其解决策略,推荐优先使用Lambda表达式以增强代码可读性。
47 8
|
4月前
|
图形学 C# 开发者
全面掌握Unity游戏开发核心技术:C#脚本编程从入门到精通——详解生命周期方法、事件处理与面向对象设计,助你打造高效稳定的互动娱乐体验
【8月更文挑战第31天】Unity 是一款强大的游戏开发平台,支持多种编程语言,其中 C# 最为常用。本文介绍 C# 在 Unity 中的应用,涵盖脚本生命周期、常用函数、事件处理及面向对象编程等核心概念。通过具体示例,展示如何编写有效的 C# 脚本,包括 Start、Update 和 LateUpdate 等生命周期方法,以及碰撞检测和类继承等高级技巧,帮助开发者掌握 Unity 脚本编程基础,提升游戏开发效率。
84 0