• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            兔子的技術博客

            兔子

               :: 首頁 :: 聯(lián)系 :: 聚合  :: 管理
              202 Posts :: 0 Stories :: 43 Comments :: 0 Trackbacks

            留言簿(10)

            最新評論

            閱讀排行榜

            評論排行榜

            原文:http://blog.csdn.net/maozefa/archive/2008/08/04/2767721.aspx

               [b]自從文章[/b]《GDI+ 在Delphi程序的應用 -- 可調節(jié)的文字陰影特效》發(fā)表后,不少人問我怎樣實現(xiàn)文字描邊。由于我只是個業(yè)余編程愛好者,加上文化底蘊差,只要涉及算法和編程理論方面的東西,我就無能為力了,所以直到目前,我也不知道具體的描邊算法是怎樣的(網上搜索過N次,也沒找到答案,可能這方面的東西是要賣錢的)。

                因問得人多了,有時我也思索和研究一下,總算找了個方法可以實現(xiàn),雖然同專業(yè)的圖像軟件(如PhotoShop)文字描邊效果相比差強人意,但可以湊合湊合,作為研究心得,將代碼貼在這里備查。

             

                在《GDI+ 在Delphi程序的應用 -- 可調節(jié)的文字陰影特效》一文的內容的基礎上,對文字陰影效果代碼進行了改進和擴充,擴充的功能有2點:一是由原來只能產生黑色陰影擴充為任意顏色陰影;二是可以對陰影進行擴展。有了這2個功能,利用陰影效果也就可以進行文字描邊了,推而廣之,也可實現(xiàn)圖像的描邊。下面是具體的代碼內容:

             

            // 備份圖像。Data: GDI+位圖數(shù)據(jù),32位ARGB格式; Dest: 備份目標; Color: 陰影顏色

            procedure BackImage(Data: TBitmapData; Dest: Pointer; Color: TARGB);
            asm
                push    esi
                push    edi

                mov     esi, [eax + 16]   // esi = Data.Scan0
                mov     edi, edx          // esi = Dest
                mov     edx, ecx          // edx = Color & 0xffffff
                and     edx, 0FFFFFFh
                mov     ecx, [eax]        // ecx = Data.Height * Data.Width
                imul    ecx, [eax + 4]
                cld

              @Loop:                       // for (; ecx >= 0; ecx --)
                or      [esi], edx
                movsd                      //   *edi++ = *esi++ & 0xff000000 | edx
                loop    @Loop

                pop     edi
                pop     esi
            end;

            // 擴展。Data: GDI+位圖數(shù)據(jù),32位ARGB格式; Source: 復制的源
            // ExpMatrix: 卷積矩陣; MatrixSize:矩陣大小
            procedure MakeExpand(Data: TBitmapData; Source, ExpMatrix: Pointer;
                MatrixSize: LongWord);
            var
              Radius, mSize, rSize: LongWord;
              x, y: LongWord;
              Width, Height: Integer;
              Matrix: Pointer;
              Stride: LongWord;
            asm
                push    esi
                push    edi
                push    ebx

                mov     esi, edx          // esi = Source
                mov     edi, [eax + 16]   // edi = Data.Scan0 + 3 (Alpha byte)
                add     edi, 3
                add     ecx, 3
                mov     Matrix, ecx       // Matrix = ExpMatrix + 3 (Alpha byte)
                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     Stride, eax
                mov     mSize, eax
                shl     edx, 2
                sub     mSize, edx        // mSize = Data.Stride - MatrixSize * 4
                add     eax, 4
                imul    eax, ecx
                add     eax, 3
                add     esi, eax          // esi = esi + (Data.Stride * Radius + Radius * 4 + 3)
                shl     ecx, 3
                mov     rSize, ecx        // rSize = Radius * 2 * 4

                mov     y, 0              // for (y = 0; y < Height; y ++)
              @yLoop:                     // {
                mov     x, 0              //   for (x = 0; x < Width; x ++)
              @xLoop:                     //   {
                test    [esi], 0ffh       //     if (*esi != 0)
                jz      @NextPixel        //     {
                test    [esi - 4], 0ffh
                jz      @001
                test    [esi + 4], 0ffh
                jz      @001
                mov     ebx, Stride
                test    [esi + ebx], 0ffh
                jz      @001
                neg     ebx
                test    [esi + ebx], 0ffh
                jnz     @NextPixel
              @001:
                push    edi               //       Save(edi)
                mov     ebx, Matrix       //       ebx = Matrix
                mov     edx, MatrixSize   //       for (I = 0; I < MatrixSize; I ++)
              @Loop3:                     //       {
                mov     ecx, MatrixSize   //         for (J = 0; J <= MatrixSize; J ++)
              @Loop4:                     //         {
                mov      al, [ebx]        //           *edi = max(*ebx, *edi)
                cmp     al, [edi]
                jb      @002
                mov     [edi], al
              @002:
                add     edi, 4            //           edi += 4
                add     ebx, 4            //           ebx += 4
                loop    @Loop4            //         }
                add     edi, mSize        //         edi += mSize
                dec     edx
                jnz     @Loop3            //       }
                pop     edi               //       Reset(edi)
              @NextPixel:                 //     }
                add     edi, 4            //     edi += 4
                add     esi, 4            //     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(Data: TBitmapData; Buf: Pointer; Radius: LongWord);
            var
              Gauss: array of Integer;
              Q: Double;
              x, y, n, z: Integer;
              p: PInteger;
            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;
              MakeShadow(Data, Buf, Gauss, n, z);
            end;

            procedure GdipBorder(Data: TBitmapData; Buf: Pointer; Expand: LongWord; Color: TARGB);
            var
              bmp: TGpBitmap;
              bg: TGpGraphics;
              Data1: TBitmapData;
              Size: Integer;
            begin
              Size := Expand shl 1 + 1;
              bmp := TGpBitmap.Create(Size, Size, pf32bppARGB);
              bg := TGpGraphics.Create(bmp);
              try
                // 制造一個直徑=Size,消除鋸齒后的圓作為描邊(或擴展)的位圖畫筆
                bg.SmoothingMode := smAntiAlias;
                bg.PixelOffsetMode := pmHalf;
                bg.FillEllipse(Brushs[Color], 0, 0, Size, Size);
                Data1 := bmp.LockBits(GpRect(0, 0, Size, Size), [imRead], pf32bppARGB);
                try
                  // 用位圖畫筆擴展圖像
                  MakeExpand(Data, Buf, Data1.Scan0, Size);
                finally
                  bmp.UnlockBits(Data1);
                end;
              finally
                bg.Free;
                bmp.Free;
              end;
            end;

            procedure DrawShadow(const g: TGpGraphics; const Bitmap: TGpBitmap;
                const layoutRect: TGpRectF; ShadowSize, Distance: LongWord;
                Angle: Single; Color: TARGB; Expand: LongWord);
            var
              dr, sr: TGpRectF;
              Data: TBitmapData;
              Buf: Pointer;
              SaveScan0: Pointer;
            begin
              Data := Bitmap.LockBits(GpRect(0, 0, Bitmap.Width, Bitmap.Height),
                                      [imRead, imWrite], pf32bppARGB);
              GetMem(Buf, Data.Height * Data.Stride);
              try
                BackImage(Data, Buf, Color);
                if Expand > ShadowSize then
                  Expand := ShadowSize;
                if Expand <> 0 then            // 處理文字陰影擴展
                  if Expand <> ShadowSize then
                  begin
                    SaveScan0 := Data.Scan0;
                    Data.Scan0 := Buf;
                    GdipBorder(Data, SaveScan0, Expand, Color);
                    Data.Scan0 := SaveScan0;
                  end else
                    GdipBorder(Data, Buf, Expand, Color);
                if Expand <> ShadowSize then   // 處理文字陰影效果
                  GdipShadow(Data, Buf, ShadowSize - Expand);
              finally
                FreeMem(Buf);
                Bitmap.UnlockBits(Data);
              end;
              sr := GpRect(0.0, 0.0, Data.Width, Data.Height);
            //  sr := GpRect(0.0, 0.0, layoutRect.Width + ShadowSize * 2 + 2,
            //               layoutRect.Height + ShadowSize * 2 + 2);
              dr := GpRect(layoutRect.Point, sr.Size);
              // 根據(jù)角度計算陰影位圖在目標畫布的偏移量
              Offset(dr, Cos(PI * Angle / 180) * Distance - ShadowSize - 1,
                     Sin(PI * Angle / 180) * Distance - ShadowSize - 1);
              // 輸出陰影位圖到目標畫布
              g.DrawImage(Bitmap, dr, sr.X, sr.Y, sr.Width, sr.Height, utPixel);
            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;
                Color: TARGB = $C0000000; Expand: LongWord = 0;
                const format: TGpStringFormat = nil); overload;
            var
              Bmp: TGpBitmap;
              Bg: TGpGraphics;
            begin
              // 建立透明的32位ARGB陰影位圖,大小為layoutRect長、寬度 + ShadowSize * 2 + 2
              Bmp := TGpBitmap.Create(Round(layoutRect.Width + 0.5) + ShadowSize shl 1 + 2,
                                      Round(layoutRect.Height + 0.5) + ShadowSize shl 1 + 2,
                                      pf32bppARGB);
              Bg := TGpGraphics.Create(Bmp);
              try
                Bg.TextRenderingHint := thAntiAlias;
                // 以Color不透明度的黑色畫刷,在ShadowSize + 1處輸出文字到位圖畫布。
                // 方便黑色以外的陰影顏色替換(直接用Color畫,模糊處理后很難看)
                Bg.DrawString(str, font, Brushs[Color and $FF000000],
                              GpRect(ShadowSize + 1, ShadowSize + 1,
                              layoutRect.Width, layoutRect.Height), format);
                DrawShadow(g, Bmp, layoutRect, ShadowSize, Distance, Angle, Color, Expand);
              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;
                Color: TARGB = $C0000000; Expand: LongWord = 0;
                const format: TGpStringFormat = nil); overload;
            begin
              DrawShadowString(g, str, font, g.MeasureString(str, font, origin, format),
                               ShadowSize, Distance, Angle, Color, Expand, format);
            end;
                上面代碼中MakeShadow過程的代碼在《GDI+ 在Delphi程序的應用 -- 可調節(jié)的文字陰影特效》一文中,本文沒有貼出。由于代碼中已經有了較詳細的注釋,故不再解釋。下面貼出測試代碼:


            procedure TextPaint(g: TGpGraphics);
            var
              brush: TGpLinearGradientBrush;
              font: TGpFont;
              fontFamily: TGpFontFamily;
              r: TGpRect;
            begin
              fontFamily := TGpFontFamily.Create({'Times New Roman'}'華文行楷');
              font := TGpFont.Create(fontFamily, 55, [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, 60, $C0000000, 1);
              DrawShadowString(g, '文字陰影特效', font, GpPoint(10, r.Height / 3), 1, 0, 60, $FFFF0000, 1);

            //  DrawShadowString(g, '文字陰影特效', font, GpPoint(10, r.Height / 3), 5, 12, 60, $C0000000, 1);
            //  DrawShadowString(g, '文字陰影特效', font, GpPoint(10, r.Height / 3), 2, 3, 60, $FFc00000, 1);
              g.TextRenderingHint := thAntiAlias;
              g.DrawString('文字陰影特效', font, Brushs.White, 10, r.Height / 3);
              font.Free;
              fontFamily.Free;
              Brush.Free;
            end;
                以下是測試代碼效果圖,圖一和圖二都是文字描邊(1個像素的邊框)加陰影效果,其中圖一沒進行陰影擴展,即上面的15行的代碼最后一個參數(shù)為0,圖二是加了1個像素的陰影擴展效果(上述代碼的“正宗”輸出):

             圖一

             

            圖二

             

                利用改進的陰影效果,不僅可實現(xiàn)文字描邊,也可顯示類似立體文字的效果(改變顯示距離),上面測試代碼中,被注釋的2句代碼輸出效果如下:

             

            圖三


                至于圖像的描邊,似乎沒有文字的描邊效果好,究其原因,主要是圖像的輪廓看起來好像是圓潤平滑的,其實有很多半影鋸齒,在Photoshop中,通過先選區(qū)后描邊,可能對選區(qū)邊緣作了處理,所以效果相當好(專業(yè)的軟件,肯定有很好的算法)。下面是我對一張小圖片作的描邊處理代碼和輸出效果圖:

            // 圖像描邊
            // g: 文字輸出的畫布; Image: 圖像; x, y: 圖像輸出原點
            // BorderWidth: 總的邊框寬度; Color: 邊框顏色;
            // Expand: 邊框擴散大小; Attributes: 圖像顯示屬性
            procedure DrawImageBorder(const g: TGpGraphics; const Image: TGpImage;
                x, y: Single; BorderWidth: LongWord; Color: TARGB = kcWhite;
                Expand: LongWord = 0; const Attributes: TGpImageAttributes = nil);
            var
              Bmp: TGpBitmap;
              Bg: TGpGraphics;
              ColorMatrix: TColorMatrix;
              Attr: TGpImageAttributes;
              layoutRect: TGpRectF;
            begin
              Bmp := TGpBitmap.Create(Image.Width + BorderWidth shl 1 + 2,
                                      Image.Height + BorderWidth shl 1 + 2,
                                      pf32bppARGB);
              Bg := TGpGraphics.Create(Bmp);
              Attr := Attributes;
              if Attr = nil then
                Attr := TGpImageAttributes.Create;
              try
                FillChar(ColorMatrix, Sizeof(TColorMatrix), 0);
                ColorMatrix[3, 3] := 1;
                ColorMatrix[4, 4] := 1;
                // 利用顏色矩陣將圖像輸出為黑色,以便邊框顏色替換
                Attr.SetColorMatrix(ColorMatrix);
                layoutRect := GpRect(x, y, Image.Width, Image.Height);
                Bg.DrawImage(Image,
                             GpRect(BorderWidth + 1, BorderWidth + 1, layoutRect.Width, layoutRect.Height),
                             0, 0, layoutRect.Width, layoutRect.Height, utPixel, Attr);
                DrawShadow(g, Bmp, layoutRect, BorderWidth, 0, 0, Color, BorderWidth - Expand);
              finally
                if Attributes <> nil then
                  Attr.ClearColorMatrix
                else
                  Attr.Free;
                Bg.Free;
                Bmp.Free;
              end;
            end;

            procedure ImagePaint(g: TGpGraphics);
            var
              brush: TGpLinearGradientBrush;
              r: TGpRect;
              Image: TGpImage;
              Attributes: TGpImageAttributes;
            begin
              r := GpRect(Form1.PaintBox1.ClientRect);
              brush := TGpLinearGradientBrush.Create(r, kcBlue, kcAliceBlue, 90);
              g.FillRectangle(Brush, r);
              Image := TGpImage.Create('..\..\Media\Watermark.bmp');
              // 畫原圖
              g.TranslateTransform(20, r.Height / 3);
              g.DrawImage(Image, 0, 0, Image.Width, Image.Height);
              // 設置圖像透明色
              Attributes := TGpImageAttributes.Create;
              Attributes.SetColorKey($ff00ff00, $ff00ff00);
              // 畫2個像素的描邊圖
              g.TranslateTransform(Image.Width + 20, 0);
              DrawImageBorder(g, Image, 0, 0, 2, kcWhite, 0, Attributes);
              g.DrawImage(Image, GpRect(0.0, 0, Image.Width, Image.Height),
                          0.0, 0.0, Image.Width, Image.Height, utPixel, Attributes);
              // 畫5個像素的描邊圖,其中擴散3像素
              g.TranslateTransform(Image.Width + 20, 0);
              DrawImageBorder(g, Image, 0, 0, 5, kcWhite, 3, Attributes);
              g.DrawImage(Image, GpRect(0.0, 0, Image.Width, Image.Height),
                          0.0, 0.0, Image.Width, Image.Height, utPixel, Attributes);
              Attributes.Free;
              Brush.Free;
              Image.Free;
            end;

             

             

            圖四

                上面的效果圖中,左邊是原圖,中間是2個像素的描邊圖,右邊是5個像素的描邊圖,其中有3像素的模糊擴散。從圖中可以看出,我以$ff00ff00為透明色處理圖像四個角后,在中間和右邊的描邊圖中,還是很明顯的看到四個角有很淡的綠色,正是這個原因,在中間圖的圓角描邊有明顯的鋸齒。

             

                最后作幾點說明:

                1、本文純屬業(yè)余學習和研究的心得,并非什么正宗的算法;

                2、因為本文代碼是學習時即興寫的,并非優(yōu)化代碼,而且是以過程形式出現(xiàn)的,有興趣的朋友可以自己進行優(yōu)化改進,寫成類或者元件更好(由于算法和功能都不是很完善,所以我沒寫成類的形式);

                3、例子中的GDI+版本系本人自己改寫的,與網上流通的版本不完全兼容,如需使用本版本,請參照《GDI+ for VCL基礎 -- GDI+ 與 VCL 》一文的下載地址,并請留意后面的修改說明。

                4、如有好的建議,請來信:maozefa@hotmail.com

             

                更新(2008-8-5 12:50):在MakeExpand過程中,是按圖象逐點用位圖畫筆矩陣填充的,每個像素點都要進行矩陣大小的操作,最小的1像素擴展的矩陣大小為3 * 3,可見擴展速度是不大理想的。今天對代碼作了一點修改,對每個象素點都進行了判斷,如果是邊界像素,則作畫筆矩陣填充,否則直接跳過,這樣一來,速度應該提高不少(沒作測試,增加的代碼用紅色標出,有興趣者可以測試)。

             
            原作者相關文章:
            GDI+ 在Delphi程序的應用 -- 可調節(jié)的文字陰影特效
            GDI+在Delphi程序的應用 – Photoshop色相/飽和度/明度功能

            posted on 2009-09-07 10:53 會飛的兔子 閱讀(2043) 評論(0)  編輯 收藏 引用 所屬分類: 框架/圖像/算法
            伊人色综合久久天天网 | 久久99热狠狠色精品一区| 男女久久久国产一区二区三区| 狠狠88综合久久久久综合网| 久久久久中文字幕| 久久久久亚洲国产| 91精品观看91久久久久久| 久久亚洲AV成人无码| 99久久国产综合精品网成人影院| 久久精品极品盛宴观看| 成人免费网站久久久| 亚洲人成精品久久久久| 久久精品夜色噜噜亚洲A∨| 国产麻豆精品久久一二三| 欧美大战日韩91综合一区婷婷久久青草| 亚洲级αV无码毛片久久精品| 国产呻吟久久久久久久92| 国产亚洲精久久久久久无码 | 少妇人妻综合久久中文字幕| 精品久久久久久无码专区 | 国产精品久久99| 无码专区久久综合久中文字幕| 久久无码人妻精品一区二区三区 | 亚洲精品无码久久千人斩| 三级片免费观看久久| 久久亚洲欧洲国产综合| 久久久久综合网久久| 久久精品国产秦先生| 99久久99久久精品免费看蜜桃| 久久人人爽人人爽人人片av麻烦| 午夜精品久久久久久影视777| 色欲综合久久躁天天躁| 久久99精品久久久久久水蜜桃| 91精品日韩人妻无码久久不卡| 久久久久久久99精品免费观看| 国产精品欧美久久久天天影视 | 久久久久亚洲精品无码网址| 精品人妻伦九区久久AAA片69| 久久99久久成人免费播放| 亚洲国产一成久久精品国产成人综合 | 国产成年无码久久久免费|