...use a fast alpha-blend for two Bitmaps?
Author: Yurii Zhukow
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