...ein schnelles Alpha-blending für 2 Bilder benutzen?

Autor: Yurii Zhukow
Homepage: http://www.infoteh.ru/yz

Kategorie: Grafik

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.

 

printed from
www.swissdelphicenter.ch
developers knowledge base