C#图像处理之-给你的图片打上LOGO

做图片处理软件,肯定会遇到这样的需求:如何把一张图片放在另一张图片上,比如在大图片中打上公司或个人的Logo等等。

其实原理很简单。假设我们有一张 800 * 600 的大图片(Bitmap b0),想在(10,10)的位置上打上Logo(Bitmap b1),Logo图片大小是 150 * 30。很简单,我们以大图片的(10,10)位置作为起始位置,做一个150 * 30 的矩形,把Logo图片整个替换这个矩形。

老规矩,还是用BitmapData来帮助我们实现这个功能。

  1. // 取以大图(X,Y)为起点,w * h 的矩形区域   
  2. BitmapData srcData = b0.LockBits(newRectangle(X,Y,w,h),ImageLockMode.WriteOnly,PixelFormat.Format24bppRgb);   
  3.   
  4. // 取整个Logo   
  5. BitmapData dstData = b1.LockBits(new Rectangle(0,0,w,h),ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);   
  6.   
  7. unsafe  
  8. {   
  9. byte* pIn = (byte*)srcData.Scan0.ToPointer();     
  10. byte* pLogo = (byte*)dstData.Scan0.ToPointer();   
  11.   
  12. for (int y = 0; y < h; y++)   
  13. {   
  14. for (int x = 0; x < w; x++)   
  15. {   
  16.   
  17. // 复制Logo图的像素      
  18. pIn[0] = (byte)pLogo[0];   
  19. pIn[1] = (byte)pLogo[1];   
  20. pIn[2] = (byte)pLogo[2];   
  21.   
  22. pIn += 3;   
  23. pLogo += 3;   
  24. }   
  25.   
  26. pIn += srcData.Stride - w * 3;   
  27. pLogo += dstData.Stride - w * 3;   
  28. }   
  29.   
  30. b0.UnlockBits(srcData);   
  31. b1.UnlockBits(dstData);   
  32. }  
这样,为图片打Logo的功能就实现了。当然,还可以有别的方法,比如用BitBlt这个API函数,或者使用GDI+的DrawImage函数。

但这样有个小问题,Logo区域永远是个矩形。假如我的Logo是个圆形,只希望在大图上出现圆形的Logo而不是整块矩形,那又该如何实现呢?

这个问题其实可以这样理解:我们让Logo的背景变透明。而所谓透明,就是两张图片叠加时,上层图片“透明”区域中的像素,仍然使用下层图的相应像素来代替。但在RGB模式下,并没有“透明”这个概念(我们的讨论都在用PixelFormat.Format24bppRgb,PixelFormat.Format32bppArgb中的Alpha分量支持“透明度”),因此我们需要自己规定一种颜色,凡是这个颜色的,都作为“透明”处理。

比如,我们的Logo是黑底的,那么就指定黑色作为透明色。

修改算法也很简单,凡是遇到Logo图中像素颜色是黑色(当然这是个参数,可以自己改)的,就用大图相应位置的颜色来代替,否则还是使用Logo的像素色。下面是算法:
  1. // 因为有可能要回写,所以原图的 ImageLockMode 这次是 ReadWrite   
  2. BitmapData srcData = b0.LockBits(new Rectangle(X, Y, w, h), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);   
  3.   
  4. BitmapData dstData = b1.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);   
  5.   
  6. unsafe  
  7. {   
  8. byte* pIn = (byte*)srcData.Scan0.ToPointer();   
  9. byte* pLogo = (byte*)dstData.Scan0.ToPointer();   
  10.   
  11. for (int y = 0; y < h; y++)   
  12. {   
  13. for (int x = 0; x < w; x++)   
  14. {   
  15.   
  16. // 判断当前点是否透明色   
  17. // 取得当前色   
  18. Color c = Color.FromArgb(pLogo[2], pLogo[1], pLogo[0]);   
  19. if (!ColorIsSimilar(c, TransColor, delta))   
  20. {   
  21. // 不是透明色,b0当前位置的颜色用b1对应位置的来代替   
  22. pIn[0] = (byte)pLogo[0];   
  23. pIn[1] = (byte)pLogo[1];   
  24. pIn[2] = (byte)pLogo[2];   
  25. }   
  26.   
  27. pIn += 3;   
  28. pLogo += 3;   
  29. }   
  30.   
  31. pIn += srcData.Stride - w * 3;   
  32. pLogo += dstData.Stride - w * 3;   
  33. }   
  34.   
  35. b0.UnlockBits(srcData);   
  36. b1.UnlockBits(dstData);   
  37. }  
我们在上面的算法中出现了另一个小算法,判断颜色C0,C1是否相等。当然如果两个颜色的RGB分量都相等那就肯定是相等了,但考虑到容差方面的因素,我们需要一个更灵活的算法。

我们知道,一个颜色由RGB三个分量组成,每个分量取值[0,255]的整数。这样,任意一个颜色都是这个有限空间中的一个点。两个颜色的“相似度”是否在阀值范围内的问题,最简单的思路也就可以认为求证空间两点的距离是否在阀值范围内。

空间两点距离公式:r2 = (x0-x1)2 + (y0-y1)2 + (z0-z1)2

下面是我的算法。
  1. /// <summary>   
  2. /// 颜色是否近似   
  3. /// </summary>   
  4. /// <param name="c0">颜色0</param>   
  5. /// <param name="c1">颜色1</param>   
  6. /// <param name="delta">容差</param>   
  7. /// <returns>是/否</returns>   
  8. public static bool ColorIsSimilar(Color c0, Color c1, int delta)   
  9. {   
  10. int r0, r1, g0, g1, b0, b1;   
  11.   
  12. r0 = c0.R;   
  13. r1 = c1.R;   
  14.   
  15. g0 = c0.G;   
  16. g1 = c1.G;   
  17.   
  18. b0 = c0.B;   
  19. b1 = c1.B;   
  20.   
  21. if   ((r0-r1)*(r0-r1) + (g0-g1) * (g0-g1) + (b0-b1) * (b0-b1) <= delta * delta)   
  22. {   
  23. return true;   
  24. }   
  25. else  
  26. {   
  27. return false;   
  28. }               
  29.   
  30. }  
最后再来看看一种情况。我们需要Logo半透明的呈现,貌似水印样的效果。有了上面两种处理图片的经验,不难认识到,大图b0的指定区域中需要混合原始像素和Logo图片b1像素后的新像素填充。

不难想象,新像素值应该结合了大图和Logo图两方面因素的。Logo图越透明,大图受的影响越小。所以这里我们可以采用加权平均的方法来得到混合后的新值。

新值 = (1 - 不透明度%)* 大图像素值 + 不透明度% * Logo图像素值

完整的函数如下:
  1. /// <summary>   
  2. /// 合并两张图片,支持不透明度和透明色   
  3. /// </summary>   
  4. /// <param name="b0">图片一</param>   
  5. /// <param name="b1">图片二</param>   
  6. /// <param name="X">起始坐标X</param>   
  7. /// <param name="Y">起始坐标Y</param>   
  8. /// <param name="b1_alpha">图片二的不透明度</param>   
  9. /// <param name="TransColor">被作为透明色处理的颜色</param>   
  10. /// <param name="delta">透明色的容差</param>   
  11. /// <returns>合并后的图片</returns>   
  12. public static Bitmap KiMerge(Bitmap b0, Bitmap b1, int X, int Y, int b1_alpha,Color TransColor,int delta)   
  13. {   
  14. if (b0.Equals(null) || b1.Equals(null))   
  15. {   
  16. return null;   
  17. }   
  18.   
  19. int w0 = b0.Width;   
  20. int h0 = b0.Height;   
  21.   
  22. int w1 = b1.Width;   
  23. int h1 = b1.Height;   
  24.   
  25. int w, h;   
  26.   
  27. if (X + w1 > w0)   
  28. {   
  29. w = w0 - X;   
  30. }   
  31. else  
  32. {   
  33. w = w1;   
  34. }   
  35.   
  36. if (Y + h1 > h0)   
  37. {   
  38. h = h0 - Y;   
  39. }   
  40. else  
  41. {   
  42. h = h1;   
  43. }   
  44.   
  45. BitmapData srcData = b0.LockBits(new Rectangle(X, Y, w, h), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);   
  46. BitmapData dstData = b1.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);   
  47.   
  48. unsafe  
  49. {   
  50. byte* pIn = (byte*)srcData.Scan0.ToPointer();   
  51. byte* pLogo = (byte*)dstData.Scan0.ToPointer();   
  52.   
  53. for (int y = 0; y < h; y++)   
  54. {   
  55. for (int x = 0; x < w; x++)   
  56. {   
  57.   
  58. // 判断透明色   
  59. Color c = Color.FromArgb(pLogo[2], pLogo[1], pLogo[0]);   
  60. if (!ColorIsSimilar(c, TransColor, delta))   
  61. {                               
  62.   
  63. float bili = (float)b1_alpha / (float)255; // 不是透明色,加权平均   
  64. float inbili = 1.0f - bili;   
  65.   
  66. int r, g, b;   
  67.   
  68. b = (int)(pIn[0] * inbili + pLogo[0] * bili);   
  69. g = (int)(pIn[1] * inbili + pLogo[1] * bili);   
  70. r = (int)(pIn[2] * inbili + pLogo[2] * bili);   
  71.   
  72. pIn[0] = (byte)b;   
  73. pIn[1] = (byte)g;   
  74. pIn[2] = (byte)r;   
  75. }   
  76. pIn += 3;   
  77. pLogo += 3;   
  78. }   
  79.   
  80. pIn += srcData.Stride - w * 3;   
  81. pLogo += dstData.Stride - w * 3;   
  82. }   
  83.   
  84. b0.UnlockBits(srcData);   
  85. b1.UnlockBits(dstData);   
  86. }   
  87.   
  88. return b0;   
  89. }  
当然方法有很多,这里只是从原理的角度阐述了一下我的观点,谁有更好的方法请让我知道。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值