剛才在提問區解答了一個問題,即如何給圖片加半透明度浮水印,實際上由於透明度演算法是最早被發現也是最簡單和基本的演算法之一,因此這個方法實際上很直觀。這裡單獨發帖再來解釋下這個問題。首先我們強調一下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, 0, 0, new Rectangle(x, y, bm2.Width, bm2.Height), GraphicsUnit.Pixel);
g2.DrawImage(wmImg, new Rectangle(0,0,bm2.Width,bm2.Height),0, 0,bm2.Width,bm2.Height,GraphicsUnit.Pixel,att);
g2.Dispose();
。。。。這裡的代碼和上面的方法代碼相同,因此省略
return bm1;
}
以上兩種效果的:
(1) (2)
原始碼的下載連結:(該項目裡還包括我對Photoshop中置換濾鏡的類比代碼,以及在水傳輸速率效控制項原理解釋那篇文中的水波置換圖產生器。)
http://files.cnblogs.com/hoodlum1980/PsFilters.rar