首页  编辑  

快速的图像AlphaBlend

Tags: /超级猛料/Picture.图形图像编程/图片处理/   Date Created:

快速的图像AlphaBlend

unit BMixer;

{

--------------------------------------------------------------------------------

 Here we will mix two bitmaps - Background and Foreground (Sprite).

 For more acceleration we will use

 Transparency map and ScanLine maps for all bitmaps we use.

 This will be described above.

--------------------------------------------------------------------------------

}

interface

uses Windows, Graphics, Types;

type

 PPointer = ^Pointer;  // This type will describe an array of pointers

type

 TYZBitMap = record     // This type we will use to store ScanLines of our bitmaps

   RowMap: PPointer;  // to accelerate access to them during drawing

   Width: Integer;

   Height: Integer;

 end;

var

 TransMap: pbyte;

 // Transparency Map - actually it is an array[0..255,0..255] of byte

 // where first index is color byte (R, G or B channel)

 // and second index is it`s alpha value over black

 FG, BG, Alpha: TBitMap;

 FGScans, BGScans, AlphaScans: TYZBitMap;

                    {

                        I used global instances of this values

                        only for simplification of the example

                        actually it would be better to create

                        a descendant of TBitmap, incapsulate them into it

                        and initialize them while Bitmap is loaded, created or resized.

                        So, here we will use 3 bitmaps, named

                        'BG'        (Background Bitmap),

                        'FG'        (Our Sprite) and

                        'Alpha'     (Transparency bitmap for Foreground Bitmap).

                    }

procedure Init(); // This must be called only once - at the beginning of all drawings

procedure AlphaDraw(ABG, AFG, AAlpha: TYZBitMap; SrcRect: TRect;

 DstPoint: TPoint; Transparency: Byte = 255);

implementation

//------------------------------------------------------------------------------

// This is the body of BuildTransparencyMap :

procedure BuildTransparencyMap(var P: PByte);

var

 i, j: Integer;

 pb: pbyte;

 x: Byte;

begin

 if P <> nil then freemem(p);

 getmem(p, 65536);

 pb := P;

 for i := 0 to 255 do for j := 0 to 255 do

   begin

     x   := round(i * j / 255) mod 256;

     pb^ := x;

     Inc(pb);

   end;

end;

//------------------------------------------------------------------------------

// Here is body of InitBitmapScans :

// We will fill a TYZBitMap structure for future use in AlphaDraw()

procedure InitBitmapScans(B: TBitmap; var Map: TYZBitmap);

var

 I, X: Integer;

 P: PPointer;

begin

 B.PixelFormat := pf24bit; // Ensure that our bitmap has 24bit depth color map

 Map.Width     := B.Width;

 Map.Height    := B.Height;

 if Map.RowMap <> nil then FreeMem(Map.RowMap);

 Map.RowMap := nil;

 if Map.Height = 0 then Exit;

 GetMem(Map.RowMap, Map.Height * SizeOf(Pointer));

 P := Map.RowMap;

 for i := 0 to Map.Height - 1 do

 begin

   p^ := B.ScanLine[i];

   Inc(p);

 end;

end;

//------------------------------------------------------------------------------

procedure Init();

begin

 // This is an initialization procedure

 // which must be called at the beginning of all drawings - only once.

 BuildTransparencymap(TransMap);

 // Then we must prepare ScanLine Tables for our bitmaps

 InitBitmapScans(BG, BGScans);

 InitBitmapScans(FG, FGScans);

 InitBitmapScans(Alpha, AlphaScans);

end;

//------------------------------------------------------------------------------

// WOW! This is what we want!

procedure AlphaDraw(  // Params:

 ABG: TYZBitMap; // BG scanlines record

 AFG: TYZBitMap; // FG scanlines record

 AAlpha: TYZBitMap; // Alpha scanlines record

 SrcRect: TRect;     // A rectangle to copy from FG-Bitmap

 DstPoint: TPoint;    // A TopLeft point in Background bitmap to put

 Transparency: Byte =

 255 // Global Transparency of our Sprite (this will be combined with Alpha channel)

 );

var

 dstRect: TRect;

 srcp, mskp, dstp: pbyte;

 i, x: Integer;

 wdt, hgt: Word;

 skt: Byte;

 srcleft, dstleft: Word;

 offs: TRect;

begin

 // Okay! Let`s do it!

 // First of all, we must ensure,

 // that our drawing areas do not cross borders of bitmaps

 wdt := ABG.Width;

 hgt := ABG.Height;

 // Let`s calculate output (Destination, or DST) rect (Where our Sprite will be shown on a BG)

 dstRect.Left := dstpoint.x;

 dstRect.Top  := dstpoint.y;

 dstRect.Right  := dstpoint.x + srcrect.Right - srcrect.Left;

 dstRect.Bottom := dstpoint.y + srcrect.Bottom - srcrect.Top;

 // Validate Source (SRC) rect

 offs := rect(0,0,0,0);

 if srcrect.Left < 0 then offs.Left := offs.Left - srcrect.Left;

 if srcrect.Top < 0 then offs.Top := offs.Top - srcrect.Top;

 if srcrect.Right >= wdt then offs.Right := offs.Right - (srcrect.Right - wdt + 1);

 if srcrect.Bottom >= hgt then offs.Bottom := offs.Bottom - (srcrect.Bottom - hgt + 1);

 srcrect.Left   := srcrect.Left + offs.Left;

 srcrect.Top    := srcrect.Top + offs.Top;

 srcrect.Right  := srcrect.Right + offs.Right;

 srcrect.Bottom := srcrect.Bottom + offs.Bottom;

 // We also must update DST rect if SRC was changed

 dstrect.Left   := dstrect.Left + offs.Left;

 dstrect.Top    := dstrect.Top + offs.Top;

 dstrect.Right  := dstrect.Right + offs.Right;

 dstrect.Bottom := dstrect.Bottom + offs.Bottom;

 offs := rect(0,0,0,0);

 // Now, validate DST rect again - it can also be invalid

 if dstrect.Left < 0 then offs.Left := offs.Left - dstrect.Left;

 if dstrect.Top < 0 then offs.Top := offs.Top - dstrect.Top;

 if dstrect.Right >= wdt then offs.Right := offs.Right - (dstrect.Right - wdt + 1);

 if dstrect.Bottom >= hgt then offs.Bottom := offs.Bottom - (dstrect.Bottom - hgt + 1);

 // Update SRC rect again

 srcrect.Left   := srcrect.Left + offs.Left;

 srcrect.Top    := srcrect.Top + offs.Top;

 srcrect.Right  := srcrect.Right + offs.Right;

 srcrect.Bottom := srcrect.Bottom + offs.Bottom;

 dstrect.Left   := dstrect.Left + offs.Left;

 dstrect.Top    := dstrect.Top + offs.Top;

 dstrect.Right  := dstrect.Right + offs.Right;

 dstrect.Bottom := dstrect.Bottom + offs.Bottom;

 // Hmmm... Nay be our DST-rect or/and SRC-rect are invalid?

 if (dstrect.Top >= dstrect.Bottom) or (srcrect.Top >= srcrect.Bottom) or

   (dstrect.Left >= dstrect.Right) or (srcrect.Left >= srcrect.Right) then

   Exit; // Then exit!

 srcp := pbyte(AFG.RowMap); // prepare our pointers

 mskp := pbyte(AAlpha.RowMap);

 dstp := pbyte(ABG.RowMap);

 wdt := (dstrect.Right - dstrect.Left + 1) * 3;

 // here is actual width of a scanrow in bytes

 hgt     := (dstrect.Bottom - dstrect.Top + 1);

 srcleft := srcrect.Left * 3; // actual left offset in Sprite

 dstleft := dstrect.Left * 3; // actual left offset in BG

 // FINE! Let`s Dance!

 asm

     push EAX         // Push-Push

     push EBX

     push ECX

     push EDX

     push EDI

     push ESI

     mov EDI,srcp     // first Sprite scanline

     mov ESI,dstp     // --"-- Background scanline

     mov EDX,mskp     // and Alpha too!

     xor eax,eax

     mov AX,LOWORD(srcrect.top) // find needed scanlines

     shl AX,2

     add EDI,EAX

     add EDX,EAX

     mov AX,LOWORD(dstrect.top)

     shl AX,2

     add ESI,EAX

     mov BX,hgt   // BX - is our vertical lines counter

  @vloop:         // begin vertical loop

     push BX

     mov BX,wdt   // now BX becomes our horizontal bytes counter

     push EDI     // Push again

     push ESI

     push EDX

     mov EDI,[EDI]

     mov ESI,[ESI]

     mov EDX,[EDX]

     mov AX,srcleft  // move to the left rect sides

     add EDI,EAX

     add EDX,EAX

     mov AX,dstleft

     add ESI,EAX

   @loop:

     // Here I must note, that this routine doesn`t work with colors as triades

     // Instead of it, we will work with each byte separately

     // Thats why Alpha bitmap must be also 24bit depth RGB image

     // where all R, G and B are equal in each pixel (desaturated)

     mov ECX,&TransMap

     mov AH,[EDX]

     mov AL,&Transparency

     neg AL

     dec AL

     sub AH,AL  // calculate sprite pixel opacity (using global Transparency)

     jnc @skip  // if result is less than zero

     xor AH,AH  // then force it to be the ZERO!

   @skip:

     mov AL,[EDI]

     add ECX,EAX

     mov AL,[ECX]

     mov &skt,AL

     mov ECX,&TransMap // calculate inverted transparency for BG

     mov AH,[EDX]

     mov AL,&Transparency

     neg AL

     dec AL

     sub AH,AL

     jnc @skip2

     xor AH,AH

   @skip2:

     neg AH

     dec AH

     mov AL,[ESI]

     add ECX,EAX

     mov AL,[ECX]

     add AL,&skt      // Finally, the result of this mixing will be the same as

                      // COLOR = ( FG_COLOR * Alpha ) + ( BG_Color * ( 255 - Alpha ) )

     mov [ESI],AL

     inc EDI

     inc ESI

     inc EDX

     dec BX

     jnz @loop       // horizontal loop

     pop EDX

     pop ESI

     pop EDI

     add EDI,4      // next scanline

     add ESI,4

     add EDX,4

     pop BX         // BX becomes vertical loop counter again!

     dec BX

     jnz @vloop     // vertical loop

     pop ESI

     pop EDI

     pop EDX

     pop ECX

     pop EBX

     pop EAX

                    // Thats all!

   end;

end;

//==============================================================================

// HERE is an example of how to use it all

// Image1 - Sprite Bitmap is here

// Image2 - Alpha channel for Sprite

// Image3 - we will draw result here

procedure TForm1.FormCreate(Sender: TObject);

var

 i: Integer;

 c: tcolor;

 B: Byte;

begin

 FG        := TBitmap.Create;

 FG.Width  := Image1.Picture.Bitmap.Width;

 FG.Height := Image1.Picture.Bitmap.Height;

 BG        := TBitmap.Create;

 BG.Width  := FG.Width;

 BG.Height := FG.Height;

 BG.Canvas.Brush.Color := clBtnFace;

 BG.Canvas.FillRect(rect(0,0,BG.Width, BG.Height));

 Alpha        := TBitmap.Create;

 Alpha.Width  := FG.Width;

 Alpha.Height := FG.Height;

 Alpha.Canvas.Draw(0,0,Image2.Picture.Bitmap);

 Init;

 Image3.Width  := BG.Width;

 Image3.Height := BG.Height;

 AlphaDraw(BGScans, FGScans, AlphaScans, rect(0,0,FG.Width, FG.Height), point(0,0), 255);

 Image3.Canvas.Draw(0,0,BG);

end;

procedure TForm1.FormDestroy(Sender: TObject);

begin

 BG.Destroy;

 FG.Destroy;

 Alpha.Destroy;

end;

//==============================================================================

end.