|
利用GDI+輸出文字陰影效果有多種方法,最簡單的就是第一次輸出有偏移的灰色文字,第二次輸出正常文字。下面是仿C#文字輸出例子里的代碼片斷,輸出了這種帶陰影的文字: serifFontFamily := TGpFontFamily.GenericSerif; //Load the fonts we want to use titleFont := TGpFont.Create(serifFontFamily, 60); //Load the image to be used for the textured text from the exe's resource fork textImage := TGpBitmap.Create('....mediamarble.jpg'); textTextureBrush := TGpTextureBrush.Create(textImage); //Set up shadow brush - make it translucent titleShadowBrush := TGpSolidBrush.Create(ARGB(70, kcBlack)); //Draw a textured string s := 'Graphics Samples'; g.DrawString(s, titleFont, titleShadowBrush, 15, 25); g.DrawString(s, titleFont, textTextureBrush, 10, 20); 效果圖如下(作了適當(dāng)縮?。?/p>
顯然,這種文字陰影效果不太令人滿意,沒有那種半陰影的效果。 網(wǎng)上介紹了一種“借助GDI+的反走樣能力生成透明的陰影與半陰影”的文字陰影方法,其原理是將要輸出的文字輸出按一定比例縮小,以某種半灰調(diào)輸出到一個按同樣比例縮小的內(nèi)存位圖中,然后設(shè)置畫布插值模式為高質(zhì)量雙三次插值法,再將位圖放大到實際畫布大小輸出,因為雙三次插值放大使文本的邊緣產(chǎn)生Alpha模糊,這樣就出現(xiàn)陰影與半影效果,下面是這種方法的Delphi代碼: procedure PaintText(g: TGpGraphics); var brush: TGpLinearGradientBrush; font: TGpFont; fontFamily: TGpFontFamily; r: TGpRect; bmp: TGpBitmap; bg: TGpGraphics; m: TGpMatrix; begin fontFamily := TGpFontFamily.Create('Times New Roman'); font := TGpFont.Create(fontFamily, 50, [fsBold], utPixel); r := GpRect(Form1.PaintBox1.ClientRect); brush := TGpLinearGradientBrush.Create(r, kcBlue, kcAliceBlue, 90); // 填充漸變背景 g.FillRectangle(brush, r); // 建立內(nèi)存位圖,其大小是畫布的1/4 bmp := TGpBitmap.Create(r.Width shr 2, r.Height shr 2, g); bg := TGpGraphics.Create(bmp); bg.TextRenderingHint := thAntiAlias; // 按1/4縮放輸出陰影文字,并平移(3, 3) m := TGpMatrix.Create(0.25, 0, 0, 0.25, 3, 3); bg.SetTransform(m); bg.DrawString('文字陰影特效', font, Brushs[ARGB(128, 0, 0, 0)], 10, r.Height / 3); // 設(shè)置插值模式為高質(zhì)量雙三次插值法 g.InterpolationMode := imHighQualityBicubic; g.TextRenderingHint := thAntiAlias; // 放大輸出陰影位圖到畫布 g.DrawImage(bmp, r, 0, 0, bmp.Width, bmp.Height, utPixel); // 輸出正常文字 g.DrawString('文字陰影特效', font, Brushs.White, 10, r.Height / 3); m.Free; bg.Free; bmp.Free; brush.Free; font.Free; fontFamily.Free; end; 代碼中已經(jīng)作了注釋,就不再詳細(xì)講解,下面是運行效果截圖,圖的上部分是帶陰影的文字輸出截圖,下部分是單獨的陰影輸出截圖:
從效果圖,特別是從上半部分截圖看,文字的陰影效果要比前面簡單的文字陰影效果好,其陰影邊緣有一定的半影效果;但是從下半部分單獨的文字陰影輸出圖看,還是覺得不盡人意。首先是邊緣模糊效果不太明顯,其次是陰影半影部分,也就是邊緣模糊部分有太強的放大痕跡,再次就是此方法無多大調(diào)節(jié)余地,想得到更明顯的半影效果的辦法就是進(jìn)一步縮小陰影文字比例,但由此帶來的鋸齒狀顯然更突出??磥恚胍玫胶玫奈淖株幱靶Ч?,如PhotoShop式樣中的投影效果,得另辟途徑。 經(jīng)過研究,PhotoShop式樣中的投影效果其實就是一種高斯模糊效果。一般的圖像高斯模糊是對圖像各像素的RGB用高斯卷積矩陣進(jìn)行卷積處理(關(guān)于高斯模糊請看我的文章《GDI+ 在Delphi程序的應(yīng)用 -- 圖像卷積操作及高斯模糊》),而要處理文字陰影效果只需要建立一個透明的32位ARGB格式內(nèi)存位圖,將文字用一定的陰影色調(diào)輸出到位圖,然后用高斯模糊矩陣對位圖的Alpha字節(jié)進(jìn)行卷積處理,就可達(dá)到很好的效果。下面是函數(shù)代碼: // 卷積處理陰影效果。Data: GDI+位圖數(shù)據(jù),要求32位ARGB格式; Source: 復(fù)制的源
// ConvolMatrix: 卷積矩陣; MatrixSize:矩陣大小, Nuclear: 卷積核(必須大于0) procedure MakeShadow(Data: TBitmapData; Source: Pointer; ConvolMatrix: array of Integer; MatrixSize, Nuclear: LongWord); var Radius, mSize, rSize: LongWord; x, y: LongWord; Width, Height: Integer; Matrix: Pointer; asm push esi push edi push ebx mov esi, edx // esi = Source + 3 (Alpha byte) add esi, 3 mov edi, [eax + 16] // edi = Data.Scan0 mov Matrix, ecx // Matrix = ConvolMatrix mov ecx, MatrixSize mov edx, ecx dec ecx mov ebx, [eax] sub ebx, ecx mov Width, ebx // Width = Data.Width - (MatrixSize - 1) mov ebx, [eax + 4] sub ebx, ecx mov Height, ebx // Height = Data.Height - (MatrixSize - 1) shr ecx, 1 mov Radius, ecx // Radius = MatrixSize / 2 mov eax, [eax + 8] mov mSize, eax shl edx, 2 sub mSize, edx // mSize = Data.Stride - MatrixSize * 4 add eax, 4 imul eax, ecx add edi, eax // edi = edi + (Data.Stride * Radius + Radius * 4) add edi, 3 // edi += 3 (Alpha byte) shl ecx, 3 mov rSize, ecx // rSize = Radius * 2 * 4 mov ebx, Nuclear // ebx = Nuclear mov y, 0 // for (y = 0; y < Height; y ++) @yLoop: // { mov x, 0 // for (x = 0; x < Width; x ++) @xLoop: // { push esi // Save(esi) push edi // Save(edi) mov edi, Matrix // edi = Matrix xor eax, eax // eax = 0 //用卷積矩陣處理Alpha字節(jié) mov ecx, MatrixSize // for (I = 0; I < MatrixSize; I ++) @Loop3: // { push ecx mov ecx, MatrixSize // for (J = 0; J <= MatrixSize; J ++) @Loop4: // { movzx edx, [esi] // edx = *esi (Alpha byte) imul edx, [edi] add eax, edx // eax += edx * *edi add esi, 4 // esi += 4 add edi, 4 // edi ++ loop @Loop4 // } add esi, mSize // esi += mSize pop ecx loop @Loop3 // } cdq idiv ebx // eax /= ebx pop edi // Result(edi) mov [edi], al // *edi = al add edi, 4 // edi += 4 pop esi // Reset(esi) esi += 4 add esi, 4 inc x mov eax, x cmp eax, Width jl @xLoop // } add esi, rSize add edi, rSize inc y mov eax, y cmp eax, Height jl @yLoop // } pop ebx pop edi pop esi end; procedure GdipShadow(Bmp: TGpBitmap; Radius: LongWord); var Data: TBitmapData; Gauss: array of Integer; Q: Double; x, y, n, z: Integer; p: PInteger; Buf: Pointer; begin // 根據(jù)半徑計算高斯模糊矩陣 Q := Radius / 2; if Q = 0 then Q := 0.1; n := Radius shl 1 + 1; SetLength(Gauss, n * n); p := @Gauss[0]; z := 0; for x := -Radius to Radius do for y := -Radius to Radius do begin p^ := Round(Exp(-(x * x + y * y) / (2.0 * Q * Q)) / (2.0 * PI * Q * Q) * 1000.0); Inc(z, p^); Inc(p); end; Data := Bmp.LockBits(GpRect(0, 0, Bmp.Width, Bmp.Height), [imRead, imWrite], pf32bppARGB); GetMem(Buf, Data.Height * Data.Stride); try // 備份源數(shù)據(jù) Move(Data.Scan0^, Buf^, Data.Height * Data.Stride); // 高斯卷積處理陰影效果 MakeShadow(Data, Buf, Gauss, n, z); finally FreeMem(Buf); Bmp.UnlockBits(Data); end; end; // 計算并輸出文字陰影效果 // g: 文字輸出的畫布; str要輸出的文字; font: 字體; layoutRect: 限定的文字輸出范圍 // ShadowSize: 陰影大小; Distance: 陰影距離; // Angle: 陰影輸出角度(左邊平行處為0度。順時針方向) // ShadowAlpha: 陰影文字的不透明度; format: 文字輸出格式 procedure DrawShadowString(const g: TGpGraphics; const str: WideString; const font: TGpFont; const layoutRect: TGpRectF; ShadowSize, Distance: LongWord; Angle: Single = 60; ShadowAlpha: Byte = 192; const format: TGpStringFormat = nil); overload; var Bmp: TGpBitmap; Bg: TGpGraphics; dr, sr: TGpRectF; begin sr := GpRect(ShadowSize shl 1, ShadowSize shl 1, layoutRect.Width, layoutRect.Height); // 建立透明的32位ARGB陰影位圖,其大小為layoutRect長、寬度 + ShadowSize * 4 Bmp := TGpBitmap.Create(Round(sr.Width) + ShadowSize shl 2, Round(sr.Height) + ShadowSize shl 2, pf32bppARGB); Bg := TGpGraphics.Create(Bmp); try Bg.TextRenderingHint := thAntiAlias; // 以不透明度為ShadowAlpha的黑色畫刷, // 在2倍ShadowSize偏移處輸出文字到位圖畫布, Bg.DrawString(str, font, Brushs[ARGB(ShadowAlpha, kcBlack)], sr, format); // 處理文字陰影效果 GdipShadow(Bmp, ShadowSize); dr := layoutRect; // 根據(jù)角度計算陰影位圖在目標(biāo)畫布的偏移量 Offset(dr, Cos(pi * Angle / 180) * Distance, Sin(pi * Angle / 180) * Distance); // 擴大源和目標(biāo)矩形,以輸出邊緣半影部分 Inflate(dr, ShadowSize, ShadowSize); Inflate(sr, ShadowSize, ShadowSize); // 輸出陰影位圖到目標(biāo)畫布 g.DrawImage(Bmp, dr, sr.X, sr.Y, sr.Width, sr.Height, utPixel); finally Bg.Free; Bmp.Free; end; end; // 計算并輸出文字陰影效果,除以輸出點origin替代上面布局矩形外,其他參數(shù)同上 procedure DrawShadowString(const g: TGpGraphics; const str: WideString; const font: TGpFont; const origin: TGpPointF; ShadowSize, Distance: LongWord; Angle: Single = 60; ShadowAlpha: Byte = 192; const format: TGpStringFormat = nil); overload; begin DrawShadowString(g, str, font, g.MeasureString(str, font, origin, format), ShadowSize, Distance, Angle, ShadowAlpha, format); end; 代碼中已經(jīng)含比較詳細(xì)的注釋,就不再講解了,本文介紹的函數(shù)最大的特點就是陰影的大小、間隔距離、輸出角度以及不透明度可根據(jù)需要調(diào)整;陰影效果也很好,邊緣模糊均勻,線條圓潤平滑,可與一般的PhotoShop文字陰影效果相媲美;由于核心函數(shù)MakeShadow代碼采用BASM,且只處理了像素的Alpha字節(jié),邊界處理也省略了,因此處理速度還是較滿意的。為了照顧需要pascal代碼的朋友,下面給出該函數(shù)的pascal版本,速度比BASM版本慢很多,不過,通過其中的代碼和注釋可以加深了解該函數(shù)的原理: // 卷積處理陰影效果。Data: GDI+位圖數(shù)據(jù),要求32位ARGB格式; Source: 復(fù)制的源 // ConvolMatrix: 卷積矩陣; MatrixSize:矩陣大小, Nuclear: 卷積核(必須大于0) procedure MakeShadow(Data: TBitmapData; Source: Pointer; ConvolMatrix: array of Integer; MatrixSize, Nuclear: LongWord); var x, y, I, J: Integer; Width, Height, mSize, rSize: Integer; v, Radius, Count: Integer; pd, ps, ps1: PByte; begin Radius := MatrixSize shr 1; Width := Data.Width - Radius shl 1; Height := Data.Height - Radius shl 1; mSize := Data.Stride - MatrixSize shl 2; rSize := Radius shl 3; Count := MatrixSize * MatrixSize; // pd 指向目標(biāo)偏移地址為卷積半徑后像素的Alpha字節(jié) // 為簡化過程,不處理以Radius為半徑的邊界像素 pd := Data.Scan0; Inc(pd, Radius * Data.Stride + Radius * 4 + 3); // ps 指向源首像素地址的Alpha字節(jié),也就是目標(biāo)首像素第一個卷積乘數(shù)的像素點 ps := Source; Inc(ps, 3); for y := 1 to Height do begin for x := 1 to Width do begin ps1 := ps; v := 0; for I := 0 to count - 1 do begin if (I <> 0) and (I mod MatrixSize = 0) then Inc(ps1, mSize); Inc(v, ConvolMatrix[I] * ps1^); // Alpha字節(jié)卷積求和 Inc(ps1, 4); end; v := v div Nuclear; // 卷積和 / 卷積核 pd^ := v; inc(pd, 4); Inc(ps, 4); end; Inc(ps, rSize); Inc(pd, rSize); end; end;
下面給出演示代碼和效果圖: procedure TextPaint(g: TGpGraphics); var brush: TGpLinearGradientBrush; font: TGpFont; fontFamily: TGpFontFamily; r: TGpRect; begin fontFamily := TGpFontFamily.Create('Times New Roman'{'華文行楷'}); font := TGpFont.Create(fontFamily, 50, [fsBold], utPixel); r := GpRect(Form1.PaintBox1.ClientRect); brush := TGpLinearGradientBrush.Create(r, kcBlue, kcAliceBlue, 90); g.FillRectangle(Brush, r); DrawShadowString(g, '文字陰影特效', font, GpPoint(10, r.Height / 3), 5, 10); g.TextRenderingHint := thAntiAlias; g.DrawString('文字陰影特效', font, Brushs.White, 10, r.Height / 3); brush.Free; font.Free; fontFamily.Free; end; 效果圖也和上面一樣分上下兩部分,以便效果比較,并給出了2種字體的文字輸出,其中下圖為華文行楷字體。
下面是華文彩云字體,五色漸變畫刷文字:
由于本文代碼沒有做更多條件下的測試,可能存在BUG,而且算法也有待提出改進(jìn)意見,請朋友們不吝指教,來信請寄maozefa@hotmail.com。 本例子中的GDI+版本系本人自己改寫的,與網(wǎng)上流通的版本不完全兼容,如需使用本版本,請參照《GDI+ for VCL基礎(chǔ) -- GDI+ 與 VCL 》一文的下載地址,并請留意后面的修改說明。 后記1(2007.13.30):剛才發(fā)現(xiàn)函數(shù)中果然存在一點BUG,原因是把數(shù)據(jù)源備份地址通過GDI+的TBitmapData結(jié)構(gòu)的保留字段作為參數(shù)傳遞給MakeShadow函數(shù),原以為該保留字段可以使用的,沒想到GDI+ DLL內(nèi)部可能使用了該字段(看來,保留字段還是不要使用的好,呵呵),導(dǎo)致設(shè)置某些字體,或者字體大小,或者字體風(fēng)格時隨機出現(xiàn)陰影位圖清零,而無文字陰影輸出的BUG,現(xiàn)已經(jīng)修改本文代碼,請朋友們諒解并提出寶貴意見。 |
|
|