Hi Marin,
> If I have two bitmaps and want to put the second above the first with some
> transparency, what can I do?
> I want to achieve an effect like in Photoshop, when you use layer's
> transparency lower than 100%.
try this MMX code for blending which is taken from the next version of my
soon-to-be-available Virtual Treeview:
type
// Describes the mode how to blend pixels.
TBlendMode = (
bmConstantAlpha, // apply given constant alpha
bmPerPixelAlpha, // use alpha value of the source pixel
bmMasterAlpha // use alpha value of source pixel and multiply it with
the constant alpha value
);
procedure AlphaBlendLineConstant(Source, Destination: Pointer; Count: Integer;
ConstantAlpha, Bias: Integer);
// Blends a line of Count pixels from Source to Destination using a constant
alpha value.
// The layout of a pixel must be BGRA where A is ignored (but is calculated as
the other components).
// ConstantAlpha must be in the range 0..255 where 0 means totally transparent
(destination pixel only)
// and 255 totally opaque (source pixel only).
// Bias is an additional value which gets added to every component and must be
in the range -128..127
//
// EAX contains Source
// EDX contains Destination
// ECX contains Count
// ConstantAlpha and Bias are on the stack
asm
PUSH ESI // save used registers
PUSH EDI
MOV ESI, EAX // ESI becomes the actual source pointer
MOV EDI, EDX // EDI becomes the actual target pointer
// Load MM6 with the constant alpha value (replicate it for every
component).
// Expand it to word size.
MOV EAX, [ConstantAlpha]
DB $0F, $6E, $F0 /// MOVD MM6, EAX
DB $0F, $61, $F6 /// PUNPCKLWD MM6, MM6
DB $0F, $62, $F6 /// PUNPCKLDQ MM6, MM6
// Load MM5 with the bias value.
MOV EAX, [Bias]
DB $0F, $6E, $E8 /// MOVD MM5, EAX
DB $0F, $61, $ED /// PUNPCKLWD MM5, MM5
DB $0F, $62, $ED /// PUNPCKLDQ MM5, MM5
// Load MM4 with 128 to allow for saturated biasing.
MOV EAX, 128
DB $0F, $6E, $E0 /// MOVD MM4, EAX
DB $0F, $61, $E4 /// PUNPCKLWD MM4, MM4
DB $0F, $62, $E4 /// PUNPCKLDQ MM4, MM4
@1: // The pixel loop calculates an entire pixel in one run.
// Note: The pixel byte values are expanded into the higher bytes of a
word due
// to the way unpacking works. We compensate for this with an
extra shift.
DB $0F, $EF, $C0 /// PXOR MM0, MM0, clear source
pixel register for unpacking
DB $0F, $60, $06 /// PUNPCKLBW MM0, [ESI], unpack source
pixel byte values into words
DB $0F, $71, $D0, $08 /// PSRLW MM0, 8, move higher
bytes to lower bytes
DB $0F, $EF, $C9 /// PXOR MM1, MM1, clear target
pixel register for unpacking
DB $0F, $60, $0F /// PUNPCKLBW MM1, [EDI], unpack target
pixel byte values into words
DB $0F, $6F, $D1 /// MOVQ MM2, MM1, make a copy of
the shifted values, we need them again
DB $0F, $71, $D1, $08 /// PSRLW MM1, 8, move higher
bytes to lower bytes
// calculation is: target = (alpha * (source - target) + 256 * target) /
256
DB $0F, $F9, $C1 /// PSUBW MM0, MM1, source - target
DB $0F, $D5, $C6 /// PMULLW MM0, MM6, alpha *
(source - target)
DB $0F, $FD, $C2 /// PADDW MM0, MM2, add target (in
shifted form)
DB $0F, $71, $D0, $08 /// PSRLW MM0, 8, divide by 256
// Bias is accounted for by conversion of range 0..255 to -128..127,
// doing a saturated add and convert back to 0..255.
DB $0F, $F9, $C4 /// PSUBW MM0, MM4
DB $0F, $ED, $C5 /// PADDSW MM0, MM5
DB $0F, $FD, $C4 /// PADDW MM0, MM4
DB $0F, $67, $C0 /// PACKUSWB MM0, MM0, convert words
to bytes with saturation
DB $0F, $7E, $07 /// MOVD [EDI], MM0, store the
result
@3:
ADD ESI, 4
ADD EDI, 4
DEC ECX
JNZ @1
POP EDI
POP ESI
end;
file://------------------------------------------------------------------------------
----------------------------------------
procedure AlphaBlendLinePerPixel(Source, Destination: Pointer; Count, Bias:
Integer);
// Blends a line of Count pixels from Source to Destination using the alpha
value of the source pixels.
// The layout of a pixel must be BGRA.
// Bias is an additional value which gets added to every component and must be
in the range -128..127
//
// EAX contains Source
// EDX contains Destination
// ECX contains Count
// Bias is on the stack
asm
PUSH ESI // save used registers
PUSH EDI
MOV ESI, EAX // ESI becomes the actual source pointer
MOV EDI, EDX // EDI becomes the actual target pointer
// Load MM5 with the bias value.
MOV EAX, [Bias]
DB $0F, $6E, $E8 /// MOVD MM5, EAX
DB $0F, $61, $ED /// PUNPCKLWD MM5, MM5
DB $0F, $62, $ED /// PUNPCKLDQ MM5, MM5
// Load MM4 with 128 to allow for saturated biasing.
MOV EAX, 128
DB $0F, $6E, $E0 /// MOVD MM4, EAX
DB $0F, $61, $E4 /// PUNPCKLWD MM4, MM4
DB $0F, $62, $E4 /// PUNPCKLDQ MM4, MM4
@1: // The pixel loop calculates an entire pixel in one run.
// Note: The pixel byte values are expanded into the higher bytes of a
word due
// to the way unpacking works. We compensate for this with an
extra shift.
DB $0F, $EF, $C0 /// PXOR MM0, MM0, clear source
pixel register for unpacking
DB $0F, $60, $06 /// PUNPCKLBW MM0, [ESI], unpack source
pixel byte values into words
DB $0F, $71, $D0, $08 /// PSRLW MM0, 8, move higher
bytes to lower bytes
DB $0F, $EF, $C9 /// PXOR MM1, MM1, clear target
pixel register for unpacking
DB $0F, $60, $0F /// PUNPCKLBW MM1, [EDI], unpack target
pixel byte values into words
DB $0F, $6F, $D1 /// MOVQ MM2, MM1, make a copy of
the shifted values, we need them again
DB $0F, $71, $D1, $08 /// PSRLW MM1, 8, move higher
bytes to lower bytes
// Load MM6 with the source alpha value (replicate it for every
component).
// Expand it to word size.
DB $0F, $6F, $F0 /// MOVQ MM6, MM0
DB $0F, $69, $F6 /// PUNPCKHWD MM6, MM6
DB $0F, $6A, $F6 /// PUNPCKHDQ MM6, MM6
// calculation is: target = (alpha * (source - target) + 256 * target) /
256
DB $0F, $F9, $C1 /// PSUBW MM0, MM1, source - target
DB $0F, $D5, $C6 /// PMULLW MM0, MM6, alpha *
(source - target)
DB $0F, $FD, $C2 /// PADDW MM0, MM2, add target (in
shifted form)
DB $0F, $71, $D0, $08 /// PSRLW MM0, 8, divide by 256
// Bias is accounted for by conversion of range 0..255 to -128..127,
// doing a saturated add and convert back to 0..255.
DB $0F, $F9, $C4 /// PSUBW MM0, MM4
DB $0F, $ED, $C5 /// PADDSW MM0, MM5
DB $0F, $FD, $C4 /// PADDW MM0, MM4
DB $0F, $67, $C0 /// PACKUSWB MM0, MM0, convert words
to bytes with saturation
DB $0F, $7E, $07 /// MOVD [EDI], MM0, store the
result
@3:
ADD ESI, 4
ADD EDI, 4
DEC ECX
JNZ @1
POP EDI
POP ESI
end;
file://------------------------------------------------------------------------------
----------------------------------------
procedure AlphaBlendLineMaster(Source, Destination: Pointer; Count: Integer;
ConstantAlpha, Bias: Integer);
// Blends a line of Count pixels from Source to Destination using the source
pixel and a constant alpha value.
// The layout of a pixel must be BGRA.
// ConstantAlpha must be in the range 0..255.
// Bias is an additional value which gets added to every component and must be
in the range -128..127
//
// EAX contains Source
// EDX contains Destination
// ECX contains Count
// ConstantAlpha and Bias are on the stack
asm
PUSH ESI // save used registers
PUSH EDI
MOV ESI, EAX // ESI becomes the actual source pointer
MOV EDI, EDX // EDI becomes the actual target pointer
// Load MM6 with the constant alpha value (replicate it for every
component).
// Expand it to word size.
MOV EAX, [ConstantAlpha]
DB $0F, $6E, $F0 /// MOVD MM6, EAX
DB $0F, $61, $F6 /// PUNPCKLWD MM6, MM6
DB $0F, $62, $F6 /// PUNPCKLDQ MM6, MM6
// Load MM5 with the bias value.
MOV EAX, [Bias]
DB $0F, $6E, $E8 /// MOVD MM5, EAX
DB $0F, $61, $ED /// PUNPCKLWD MM5, MM5
DB $0F, $62, $ED /// PUNPCKLDQ MM5, MM5
// Load MM4 with 128 to allow for saturated biasing.
MOV EAX, 128
DB $0F, $6E, $E0 /// MOVD MM4, EAX
DB $0F, $61, $E4 /// PUNPCKLWD MM4, MM4
DB $0F, $62, $E4 /// PUNPCKLDQ MM4, MM4
@1: // The pixel loop calculates an entire pixel in one run.
// Note: The pixel byte values are expanded into the higher bytes of a
word due
// to the way unpacking works. We compensate for this with an
extra shift.
DB $0F, $EF, $C0 /// PXOR MM0, MM0, clear source
pixel register for unpacking
DB $0F, $60, $06 /// PUNPCKLBW MM0, [ESI], unpack source
pixel byte values into words
DB $0F, $71, $D0, $08 /// PSRLW MM0, 8, move higher
bytes to lower bytes
DB $0F, $EF, $C9 /// PXOR MM1, MM1, clear target
pixel register for unpacking
DB $0F, $60, $0F /// PUNPCKLBW MM1, [EDI], unpack target
pixel byte values into words
DB $0F, $6F, $D1 /// MOVQ MM2, MM1, make a copy of
the shifted values, we need them again
DB $0F, $71, $D1, $08 /// PSRLW MM1, 8, move higher
bytes to lower bytes
// Load MM7 with the source alpha value (replicate it for every
component).
// Expand it to word size.
DB $0F, $6F, $F8 /// MOVQ MM7, MM0
DB $0F, $69, $FF /// PUNPCKHWD MM7, MM7
DB $0F, $6A, $FF /// PUNPCKHDQ MM7, MM7
DB $0F, $D5, $FE /// PMULLW MM7, MM6, source alpha *
master alpha
DB $0F, $71, $D7, $08 /// PSRLW MM7, 8, divide by 256
// calculation is: target = (alpha * master alpha * (source - target) +
256 * target) / 256
DB $0F, $F9, $C1 /// PSUBW MM0, MM1, source - target
DB $0F, $D5, $C7 /// PMULLW MM0, MM7, alpha *
(source - target)
DB $0F, $FD, $C2 /// PADDW MM0, MM2, add target (in
shifted form)
DB $0F, $71, $D0, $08 /// PSRLW MM0, 8, divide by 256
// Bias is accounted for by conversion of range 0..255 to -128..127,
// doing a saturated add and convert back to 0..255.
DB $0F, $F9, $C4 /// PSUBW MM0, MM4
DB $0F, $ED, $C5 /// PADDSW MM0, MM5
DB $0F, $FD, $C4 /// PADDW MM0, MM4
DB $0F, $67, $C0 /// PACKUSWB MM0, MM0, convert words
to bytes with saturation
DB $0F, $7E, $07 /// MOVD [EDI], MM0, store the
result
@3:
ADD ESI, 4
ADD EDI, 4
DEC ECX
JNZ @1
POP EDI
POP ESI
end;
file://------------------------------------------------------------------------------
----------------------------------------
procedure EMMS;
// Reset MMX state to use the FPU for other tasks again.
asm
DB $0F, $77 /// EMMS
end;
file://------------------------------------------------------------------------------
----------------------------------------
function GetBitmapBitsFromDeviceContext(DC: HDC; var Width, Height: Integer):
Pointer;
// Helper function used to retrieve the bitmap selected into the given device
context. If there is a bitmap then
// the function will return a pointer to its bits otherwise nil is returned.
// Additionally the dimensions of the bitmap are returned.
var
Bitmap: HBITMAP;
DIB: TDIBSection;
begin
Result := nil;
Width := 0;
Height := 0;
Bitmap := GetCurrentObject(DC, OBJ_BITMAP);
if Bitmap <> 0 then
begin
if GetObject(Bitmap, SizeOf(DIB), @DIB) = SizeOf(DIB) then
begin
Result := DIB.dsBm.bmBits;
Width := DIB.dsBmih.biWidth;
Height := DIB.dsBmih.biHeight;
end;
end;
end;
file://------------------------------------------------------------------------------
----------------------------------------
function CalculateScanline(Bits: Pointer; Width, Height, Row: Integer): Pointer;
// Helper function to calculate the start address for the given row.
begin
if Height > 0 then // bottom-up DIB
Row := Height - Row - 1;
// Return DWORD aligned address of the requested scanline.
Integer(Result) := Integer(Bits) + Row * ((Width * 32 + 31) and not 31) div 8;
end;
file://------------------------------------------------------------------------------
----------------------------------------
procedure AlphaBlend(Source, Destination: HDC; R: TRect; Target: TPoint; Mode:
TBlendMode; ConstantAlpha, Bias: Integer);
// Optimized alpha blend procedure using MMX instructions to perform as quick as
possible.
// For this procedure to work properly it is important that both source and
target bitmap use the 32 bit color format.
// R describes the source rectangle to work on.
// Target is the place (upper left corner) in the target bitmap where to blend
to. Note that source width + X offset
// must be less or equal to the target width. Similar for the height.
// If Mode is bmConstantAlpha then the blend operation uses the given
ConstantAlpha value for all pixels.
// if Mode is bmPerPixelAlpha then each pixel is blended using its individual
alpha value (the alpha value of the source).
// if Mode is bmMasterAlpha then each pixel is blended using its individual
alpha value multiplied by ConstantAlpha.
// Bias can have a value which gets added to every pixel (in the range
of -128..127).
// CAUTION: This procedure does not check whether MMX instructions are actually
available!
var
Y: Integer;
SourceRun,
TargetRun: PByte;
SourceBits,
DestBits: Pointer;
SourceWidth,
SourceHeight,
DestWidth,
DestHeight: Integer;
begin
if not IsRectEmpty(R) then
begin
// Note: it is tempting to optimize the special cases for constant alpha 0
and 255 by just ignoring soure
// (alpha = 0) or simply do a blit (alpha = 255). But this does not
take the bias into account.
case Mode of
bmConstantAlpha:
begin
// Get a pointer to the bitmap bits for the source and target device
contexts.
// Note: this supposes that both contexts do actually have bitmaps
assigned!
SourceBits := GetBitmapBitsFromDeviceContext(Source, SourceWidth,
SourceHeight);
DestBits := GetBitmapBitsFromDeviceContext(Destination, DestWidth,
DestHeight);
if Assigned(SourceBits) and Assigned(DestBits) then
begin
for Y := 0 to R.Bottom - R.Top - 1 do
begin
SourceRun := CalculateScanline(SourceBits, SourceWidth,
SourceHeight, Y + R.Top);
Inc(SourceRun, 4 * R.Left);
TargetRun := CalculateScanline(DestBits, DestWidth, DestHeight, Y
+ Target.Y);
Inc(TargetRun, 4 * Target.X);
AlphaBlendLineConstant(SourceRun, TargetRun, R.Right - R.Left,
ConstantAlpha, Bias);
end;
end;
EMMS;
end;
bmPerPixelAlpha:
begin
SourceBits := GetBitmapBitsFromDeviceContext(Source, SourceWidth,
SourceHeight);
DestBits := GetBitmapBitsFromDeviceContext(Destination, DestWidth,
DestHeight);
if Assigned(SourceBits) and Assigned(DestBits) then
begin
for Y := 0 to R.Bottom - R.Top - 1 do
begin
SourceRun := CalculateScanline(SourceBits, SourceWidth,
SourceHeight, Y + R.Top);
Inc(SourceRun, 4 * R.Left);
TargetRun := CalculateScanline(DestBits, DestWidth, DestHeight, Y
+ Target.Y);
Inc(TargetRun, 4 * Target.X);
AlphaBlendLinePerPixel(SourceRun, TargetRun, R.Right - R.Left,
Bias);
end;
end;
EMMS;
end;
bmMasterAlpha:
begin
SourceBits := GetBitmapBitsFromDeviceContext(Source, SourceWidth,
SourceHeight);
DestBits := GetBitmapBitsFromDeviceContext(Destination, DestWidth,
DestHeight);
if Assigned(SourceBits) and Assigned(DestBits) then
begin
for Y := 0 to R.Bottom - R.Top - 1 do
begin
SourceRun := CalculateScanline(SourceBits, SourceWidth,
SourceHeight, Y + R.Top);
Inc(SourceRun, 4 * Target.X);
TargetRun := CalculateScanline(DestBits, DestWidth, DestHeight, Y
+ Target.Y);
AlphaBlendLineMaster(SourceRun, TargetRun, R.Right - R.Left,
ConstantAlpha, Bias);
end;
end;
EMMS;
end;
end;
end;
end;
file://------------------------------------------------------------------------------
----------------------------------------
Source and Destination HDC are simply the handles of the bitmap canvases (e.g.
SourceBMP.Canvas.Handle) etc.
Ciao, Mike
--