Unit BMixer.pas
unit BMixer;
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();
procedure AlphaDraw(ABG, AFG, AAlpha: TYZBitMap; SrcRect: TRect;
DstPoint: TPoint; Transparency: Byte = 255);
implementation
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;
procedure InitBitmapScans(B: TBitmap; var Map: TYZBitmap);
var
I, X: Integer;
P: PPointer;
begin
B.PixelFormat := pf24bit;
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
BuildTransparencymap(TransMap);
InitBitmapScans(BG, BGScans);
InitBitmapScans(FG, FGScans);
InitBitmapScans(Alpha, AlphaScans);
end;
procedure AlphaDraw(
ABG: TYZBitMap;
AFG: TYZBitMap;
AAlpha: TYZBitMap;
SrcRect: TRect;
DstPoint: TPoint;
Transparency: Byte =
255
);
var
dstRect: TRect;
srcp, mskp, dstp: pbyte;
i, x: Integer;
wdt, hgt: Word;
skt: Byte;
srcleft, dstleft: Word;
offs: TRect;
begin
`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;
// Lets 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 EBX
push ECX
push EDX
push EDI
push ESI
mov EDI,srcp
mov ESI,dstp
mov EDX,mskp
xor eax,eax
mov AX,LOWORD(srcrect.top)
shl AX,2
add EDI,EAX
add EDX,EAX
mov AX,LOWORD(dstrect.top)
shl AX,2
add ESI,EAX
mov BX,hgt
@vloop:
push BX
mov BX,wdt
push EDI
push ESI
push EDX
mov EDI,[EDI]
mov ESI,[ESI]
mov EDX,[EDX]
mov AX,srcleft
add EDI,EAX
add EDX,EAX
mov AX,dstleft
add ESI,EAX
@loop:
`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
end;
end;
end.
Unit 1:
uses BMixer
//
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 := clSilver;
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;
Keine Kommentare:
Kommentar veröffentlichen