C#圖像處理之-給你的圖片打上LOGO

  做圖片處理軟件,肯定會遇到這樣的需求:如何把一張圖片放在另一張圖片上,比如在大圖片中打上公司或個人的Logo等等。
  其實原理很簡單。假設我們有一張 800 * 600 的大圖片(Bitmap b0),想在(10,10)的位置上打上Logo(Bitmap b1),Logo圖片大小是 150 * 30。很簡單,我們以大圖片的(10,10)位置作爲起始位置,做一個150 * 30 的矩形,把Logo圖片整個替換這個矩形。
  老規矩,還是用BitmapData來幫助我們實現這個功能。view plaincopy to clipboardprint?
  // 取以大圖(X,Y)爲起點,w * h 的矩形區域
  BitmapData srcData = b0.LockBits(new Rectangle(X,Y,w,h),ImageLockMode.WriteOnly,PixelFormat.Format24bppRgb);
  
  // 取整個Logo
  BitmapData dstData = b1.LockBits(new Rectangle(0,0,w,h),ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
  
  unsafe
  {
  byte* pIn = (byte*)srcData.Scan0.ToPointer();
  byte* pLogo = (byte*)dstData.Scan0.ToPointer();
  
  for (int y = 0; y < h; y++)
  {
  for (int x = 0; x < w; x++)
  {
  
  // 複制Logo圖的像素
  pIn[0] = (byte)pLogo[0];
  pIn[1] = (byte)pLogo[1];
  pIn[2] = (byte)pLogo[2];
  
  pIn += 3;
  pLogo += 3;
  }
  
  pIn += srcData.Stride - w * 3;
  pLogo += dstData.Stride - w * 3;
  }
  
  b0.UnlockBits(srcData);
  b1.UnlockBits(dstData);
  }
  // 取以大圖(X,Y)爲起點,w * h 的矩形區域
  BitmapData srcData = b0.LockBits(new Rectangle(X,Y,w,h),ImageLockMode.WriteOnly,PixelFormat.Format24bppRgb);
  // 取整個Logo
  BitmapData dstData = b1.LockBits(new Rectangle(0,0,w,h),ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
  unsafe
  {
  byte* pIn = (byte*)srcData.Scan0.ToPointer();
  byte* pLogo = (byte*)dstData.Scan0.ToPointer();
  for (int y = 0; y < h; y++)
  {
  for (int x = 0; x < w; x++)
  {
  // 複制Logo圖的像素
  pIn[0] = (byte)pLogo[0];
  pIn[1] = (byte)pLogo[1];
  pIn[2] = (byte)pLogo[2];
  pIn += 3;
  pLogo += 3;
  }
  pIn += srcData.Stride - w * 3;
  pLogo += dstData.Stride - w * 3;
  }
  b0.UnlockBits(srcData);
  b1.UnlockBits(dstData);
  }這樣,爲圖片打Logo的功能就實現了。當然,還可以有別的方法,比如用BitBlt這個API函數,或者使用GDI+的DrawImage函數。
  但這樣有個小問題,Logo區域永遠是個矩形。假如我的Logo是個圓形,只希望在大圖上出現圓形的Logo而不是整塊矩形,那又該如何實現呢?
  這個問題其實可以這樣理解:我們讓Logo的背景變透明。而所謂透明,就是兩張圖片疊加時,上層圖片「透明」區域中的像素,仍然使用下層圖的相應像素來代替。但在RGB模式下,並沒有「透明」這個概念(我們的討論都在用PixelFormat.Format24bppRgb,PixelFormat.Format32bppArgb中的Alpha分量支持「透明度」),因此我們需要自己規定一種顔色,凡是這個顔色的,都作爲「透明」處理。
  比如,我們的Logo是黑底的,那麽就指定黑色作爲透明色。
  修改算法也很簡單,凡是遇到Logo圖中像素顔色是黑色(當然這是個參數,可以自己改)的,就用大圖相應位置的顔色來代替,否則還是使用Logo的像素色。下面是算法:view plaincopy to clipboardprint?
  // 因爲有可能要回寫,所以原圖的 ImageLockMode 這次是 ReadWrite
  BitmapData srcData = b0.LockBits(new Rectangle(X, Y, w, h), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
  
  BitmapData dstData = b1.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
  
  unsafe
  {
  byte* pIn = (byte*)srcData.Scan0.ToPointer();
  byte* pLogo = (byte*)dstData.Scan0.ToPointer();
  
  for (int y = 0; y < h; y++)
  {
  for (int x = 0; x < w; x++)
  {
  
  // 判斷當前點是否透明色
  // 取得當前色
  Color c = Color.FromArgb(pLogo[2], pLogo[1], pLogo[0]);
  if (!ColorIsSimilar(c, TransColor, delta))
  {
  // 不是透明色,b0當前位置的顔色用b1對應位置的來代替
  pIn[0] = (byte)pLogo[0];
  pIn[1] = (byte)pLogo[1];
  pIn[2] = (byte)pLogo[2];
  }
  
  pIn += 3;
  pLogo += 3;
  }
  
  pIn += srcData.Stride - w * 3;
  pLogo += dstData.Stride - w * 3;
  }
  
  b0.UnlockBits(srcData);
  b1.UnlockBits(dstData);
  }
  // 因爲有可能要回寫,所以原圖的 ImageLockMode 這次是 ReadWrite
  BitmapData srcData = b0.LockBits(new Rectangle(X, Y, w, h), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
  BitmapData dstData = b1.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
  unsafe
  {
  byte* pIn = (byte*)srcData.Scan0.ToPointer();
  byte* pLogo = (byte*)dstData.Scan0.ToPointer();
  for (int y = 0; y < h; y++)
  {
  for (int x = 0; x < w; x++)
  {
  // 判斷當前點是否透明色
  // 取得當前色
  Color c = Color.FromArgb(pLogo[2], pLogo[1], pLogo[0]);
  if (!ColorIsSimilar(c, TransColor, delta))
  {
  // 不是透明色,b0當前位置的顔色用b1對應位置的來代替
  pIn[0] = (byte)pLogo[0];
  pIn[1] = (byte)pLogo[1];
  pIn[2] = (byte)pLogo[2];
  }
  pIn += 3;
  pLogo += 3;
  }
  pIn += srcData.Stride - w * 3;
  pLogo += dstData.Stride - w * 3;
  }
  b0.UnlockBits(srcData);
  b1.UnlockBits(dstData);
  }我們在上面的算法中出現了另一個小算法,判斷顔色C0,C1是否相等。當然如果兩個顔色的RGB分量都相等那就肯定是相等了,但考慮到容差方面的因素,我們需要一個更靈活的算法。
  我們知道,一個顔色由RGB三個分量組成,每個分量取值[0,255]的整數。這樣,任意一個顔色都是這個有限空間中的一個點。兩個顔色的「相似度」是否在閥值範圍內的問題,最簡單的思路也就可以認爲求證空間兩點的距離是否在閥值範圍內。
  空間兩點距離公式:r2 = (x0-x1)2 + (y0-y1)2 + (z0-z1)2
  下面是我的算法。view plaincopy to clipboardprint?
  /// <summary>
  /// 顔色是否近似
  /// </summary>
  /// <param name="c0">顔色0</param>
  /// <param name="c1">顔色1</param>
  /// <param name="delta">容差</param>
  /// <returns>是/否</returns>
  public static bool ColorIsSimilar(Color c0, Color c1, int delta)
  {
  int r0, r1, g0, g1, b0, b1;
  
  r0 = c0.R;
  r1 = c1.R;
  
  g0 = c0.G;
  g1 = c1.G;
  
  b0 = c0.B;
  b1 = c1.B;
  
  if ((r0-r1)*(r0-r1) + (g0-g1) * (g0-g1) + (b0-b1) * (b0-b1) <= delta * delta)
  {
  return true;
  }
  else
  {
  return false;
  }
  
  }
  /// <summary>
  /// 顔色是否近似
  /// </summary>
  /// <param name="c0">顔色0</param>
  /// <param name="c1">顔色1</param>
  /// <param name="delta">容差</param>
  /// <returns>是/否</returns>
  public static bool ColorIsSimilar(Color c0, Color c1, int delta)
  {
  int r0, r1, g0, g1, b0, b1;
  r0 = c0.R;
  r1 = c1.R;
  g0 = c0.G;
  g1 = c1.G;
  b0 = c0.B;
  b1 = c1.B;
  if ((r0-r1)*(r0-r1) + (g0-g1) * (g0-g1) + (b0-b1) * (b0-b1) <= delta * delta)
  {
  return true;
  }
  else
  {
  return false;
  }
  }最後再來看看一種情況。我們需要Logo半透明的呈現,貌似水印樣的效果。有了上面兩種處理圖片的經驗,不難認識到,大圖b0的指定區域中需要混合原始像素和Logo圖片b1像素後的新像素填充。
  不難想象,新像素值應該結合了大圖和Logo圖兩方面因素的。Logo圖越透明,大圖受的影響越小。所以這裏我們可以采用加權平均的方法來得到混合後的新值。
  新值 = (1 - 不透明度%)* 大圖像素值 + 不透明度% * Logo圖像素值
  完整的函數如下:view plaincopy to clipboardprint?
  /// <summary>
  /// 合並兩張圖片,支持不透明度和透明色
  /// </summary>
  /// <param name="b0">圖片一</param>
  /// <param name="b1">圖片二</param>
  /// <param name="X">起始坐標X</param>
  /// <param name="Y">起始坐標Y</param>
  /// <param name="b1_alpha">圖片二的不透明度</param>
  /// <param name="TransColor">被作爲透明色處理的顔色</param>
  /// <param name="delta">透明色的容差</param>
  /// <returns>合並後的圖片</returns>
  public static Bitmap KiMerge(Bitmap b0, Bitmap b1, int X, int Y, int b1_alpha,Color TransColor,int delta)
  {
  if (b0.Equals(null) || b1.Equals(null))
  {
  return null;
  }
  
  int w0 = b0.Width;
  int h0 = b0.Height;
  
  int w1 = b1.Width;
  int h1 = b1.Height;
  
  int w, h;
  
  if (X + w1 > w0)
  {
  w = w0 - X;
  }
  else
  {
  w = w1;
  }
  
  if (Y + h1 > h0)
  {
  h = h0 - Y;
  }
  else
  {
  h = h1;
  }
  
  BitmapData srcData = b0.LockBits(new Rectangle(X, Y, w, h), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
  BitmapData dstData = b1.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
  
  unsafe
  {
  byte* pIn = (byte*)srcData.Scan0.ToPointer();
  byte* pLogo = (byte*)dstData.Scan0.ToPointer();
  
  for (int y = 0; y < h; y++)
  {
  for (int x = 0; x < w; x++)
  {
  
  // 判斷透明色
  Color c = Color.FromArgb(pLogo[2], pLogo[1], pLogo[0]);
  if (!ColorIsSimilar(c, TransColor, delta))
  {
  
  float bili = (float)b1_alpha / (float)255; // 不是透明色,加權平均
  float inbili = 1.0f - bili;
  
  int r, g, b;
  
  b = (int)(pIn[0] * inbili + pLogo[0] * bili);
  g = (int)(pIn[1] * inbili + pLogo[1] * bili);
  r = (int)(pIn[2] * inbili + pLogo[2] * bili);
  
  pIn[0] = (byte)b;
  pIn[1] = (byte)g;
  pIn[2] = (byte)r;
  }
  pIn += 3;
  pLogo += 3;
  }
  
  pIn += srcData.Stride - w * 3;
  pLogo += dstData.Stride - w * 3;
  }
  
  b0.UnlockBits(srcData);
  b1.UnlockBits(dstData);
  }
  
  return b0;
  }
  /// <summary>
  /// 合並兩張圖片,支持不透明度和透明色
  /// </summary>
  /// <param name="b0">圖片一</param>
  /// <param name="b1">圖片二</param>
  /// <param name="X">起始坐標X</param>
  /// <param name="Y">起始坐標Y</param>
  /// <param name="b1_alpha">圖片二的不透明度</param>
  /// <param name="TransColor">被作爲透明色處理的顔色</param>
  /// <param name="delta">透明色的容差</param>
  /// <returns>合並後的圖片</returns>
  public static Bitmap KiMerge(Bitmap b0, Bitmap b1, int X, int Y, int b1_alpha,Color TransColor,int delta)
  {
  if (b0.Equals(null) || b1.Equals(null))
  {
  return null;
  }
  int w0 = b0.Width;
  int h0 = b0.Height;
  int w1 = b1.Width;
  int h1 = b1.Height;
  int w, h;
  if (X + w1 > w0)
  {
  w = w0 - X;
  }
  else
  {
  w = w1;
  }
  if (Y + h1 > h0)
  {
  h = h0 - Y;
  }
  else
  {
  h = h1;
  }
  BitmapData srcData = b0.LockBits(new Rectangle(X, Y, w, h), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
  BitmapData dstData = b1.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
  unsafe
  {
  byte* pIn = (byte*)srcData.Scan0.ToPointer();
  byte* pLogo = (byte*)dstData.Scan0.ToPointer();
  for (int y = 0; y < h; y++)
  {
  for (int x = 0; x < w; x++)
  {
  // 判斷透明色
  Color c = Color.FromArgb(pLogo[2], pLogo[1], pLogo[0]);
  if (!ColorIsSimilar(c, TransColor, delta))
  {
  float bili = (float)b1_alpha / (float)255; // 不是透明色,加權平均
  float inbili = 1.0f - bili;
  int r, g, b;
  b = (int)(pIn[0] * inbili + pLogo[0] * bili);
  g = (int)(pIn[1] * inbili + pLogo[1] * bili);
  r = (int)(pIn[2] * inbili + pLogo[2] * bili);
  pIn[0] = (byte)b;
  pIn[1] = (byte)g;
  pIn[2] = (byte)r;
  }
  pIn += 3;
  pLogo += 3;
  }
  pIn += srcData.Stride - w * 3;
  pLogo += dstData.Stride - w * 3;
  }
  b0.UnlockBits(srcData);
  b1.UnlockBits(dstData);
  }
  return b0;
  }當然方法有很多,這裏只是從原理的角度闡述了一下我的觀點,誰有更好的方法請讓我知道。