In Firemonkey we can use a TShadowEffect to draw a nice looking shadow.
This shadow also adjusts its opacity and translucency so it displays the correct component beneath it if a control is overlapping.
Without TShadowEffect:
With TShadowEffect:
Is there a way to draw the same shadow effect in VCL forms without embedding a FMX form?
My idea was to create a TGraphicControl and place it underneath the target control. The shadow control will stick to the target control. The steps of drawing the shadow are as follow:
We create an off screen Bitmap and draw a RoundRect
Then apply Gaussian Blur convolution kernel:
see http://www.concepto.ch/delphi/uddf/pages/graphics.htm#graphics9 (unit GBlur2). (EDIT: Link is dead)
Finally we make it 32 bit alpha semi transparent gray scale. depending on the amount of darkness:
And draw it via AlphaBlend on the TGraphicControl canvas.
GBlur2.pas (Author unknown)
unit GBlur2;
interface
uses
Windows, Graphics;
type
PRGBTriple = ^TRGBTriple;
TRGBTriple = packed record
b: byte; {easier to type than rgbtBlue}
g: byte;
r: byte;
end;
PRow = ^TRow;
TRow = array[0..1000000] of TRGBTriple;
PPRows = ^TPRows;
TPRows = array[0..1000000] of PRow;
const
MaxKernelSize = 100;
type
TKernelSize = 1..MaxKernelSize;
TKernel = record
Size: TKernelSize;
Weights: array[-MaxKernelSize..MaxKernelSize] of single;
end;
{the idea is that when using a TKernel you ignore the Weights except
for Weights in the range -Size..Size.}
procedure GBlur(theBitmap: TBitmap; radius: double);
implementation
uses
SysUtils;
procedure MakeGaussianKernel(var K: TKernel; radius: double; MaxData, DataGranularity: double);
{makes K into a gaussian kernel with standard deviation = radius. For the current application
you set MaxData = 255 and DataGranularity = 1. Now the procedure sets the value of K.Size so
that when we use K we will ignore the Weights that are so small they can't possibly matter. (Small
Size is good because the execution time is going to be propertional to K.Size.)}
var
j: integer;
temp, delta: double;
KernelSize: TKernelSize;
begin
for j := Low(K.Weights) to High(K.Weights) do
begin
temp := j / radius;
K.Weights[j] := exp(-temp * temp / 2);
end;
{now divide by constant so sum(Weights) = 1:}
temp := 0;
for j := Low(K.Weights) to High(K.Weights) do
temp := temp + K.Weights[j];
for j := Low(K.Weights) to High(K.Weights) do
K.Weights[j] := K.Weights[j] / temp;
{now discard (or rather mark as ignorable by setting Size) the entries that are too small to matter.
This is important, otherwise a blur with a small radius will take as long as with a large radius...}
KernelSize := MaxKernelSize;
delta := DataGranularity / (2 * MaxData);
temp := 0;
while (temp < delta) and (KernelSize > 1) do
begin
temp := temp + 2 * K.Weights[KernelSize];
dec(KernelSize);
end;
K.Size := KernelSize;
{now just to be correct go back and jiggle again so the sum of the entries we'll be using is exactly 1}
temp := 0;
for j := -K.Size to K.Size do
temp := temp + K.Weights[j];
for j := -K.Size to K.Size do
K.Weights[j] := K.Weights[j] / temp;
end;
function TrimInt(Lower, Upper, theInteger: integer): integer;
begin
if (theInteger <= Upper) and (theInteger >= Lower) then
result := theInteger
else if theInteger > Upper then
result := Upper
else
result := Lower;
end;
function TrimReal(Lower, Upper: integer; x: double): integer;
begin
if (x < upper) and (x >= lower) then
result := trunc(x)
else if x > Upper then
result := Upper
else
result := Lower;
end;
procedure BlurRow(var theRow: array of TRGBTriple; K: TKernel; P: PRow);
var
j, n: integer;
tr, tg, tb: double; {tempRed, etc}
w: double;
begin
for j := 0 to High(theRow) do
begin
tb := 0;
tg := 0;
tr := 0;
for n := -K.Size to K.Size do
begin
w := K.Weights[n];
{the TrimInt keeps us from running off the edge of the row...}
with theRow[TrimInt(0, High(theRow), j - n)] do
begin
tb := tb + w * b;
tg := tg + w * g;
tr := tr + w * r;
end;
end;
with P[j] do
begin
b := TrimReal(0, 255, tb);
g := TrimReal(0, 255, tg);
r := TrimReal(0, 255, tr);
end;
end;
Move(P[0], theRow[0], (High(theRow) + 1) * Sizeof(TRGBTriple));
end;
procedure GBlur(theBitmap: TBitmap; radius: double);
var
Row, Col: integer;
theRows: PPRows;
K: TKernel;
ACol: PRow;
P: PRow;
begin
if (theBitmap.HandleType <> bmDIB) or (theBitmap.PixelFormat <> pf24Bit) then
raise exception.Create('GBlur only works for 24-bit bitmaps');
MakeGaussianKernel(K, radius, 255, 1);
GetMem(theRows, theBitmap.Height * SizeOf(PRow));
GetMem(ACol, theBitmap.Height * SizeOf(TRGBTriple));
{record the location of the bitmap data:}
for Row := 0 to theBitmap.Height - 1 do
theRows[Row] := theBitmap.Scanline[Row];
{blur each row:}
P := AllocMem(theBitmap.Width * SizeOf(TRGBTriple));
for Row := 0 to theBitmap.Height - 1 do
BlurRow(Slice(theRows[Row]^, theBitmap.Width), K, P);
{now blur each column}
ReAllocMem(P, theBitmap.Height * SizeOf(TRGBTriple));
for Col := 0 to theBitmap.Width - 1 do
begin
{first read the column into a TRow:}
for Row := 0 to theBitmap.Height - 1 do
ACol[Row] := theRows[Row][Col];
BlurRow(Slice(ACol^, theBitmap.Height), K, P);
{now put that row, um, column back into the data:}
for Row := 0 to theBitmap.Height - 1 do
theRows[Row][Col] := ACol[Row];
end;
FreeMem(theRows);
FreeMem(ACol);
ReAllocMem(P, 0);
end;
end.
ShadowBox.pas
unit ShadowBox;
interface
uses Messages, Windows, SysUtils, Classes, Controls, Graphics, StdCtrls;
type
TShadowBox = class(TGraphicControl)
private
FControl: TControl;
FControlWndProc: TWndMethod;
procedure SetControl(AControl: TControl);
procedure ControlWndProc(var Message: TMessage);
procedure AdjustBounds;
protected
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
procedure Paint; override;
public
destructor Destroy; override;
published
property Control: TControl read FControl write SetControl;
end;
implementation
uses GBlur2;
destructor TShadowBox.Destroy;
begin
SetControl(nil);
inherited;
end;
procedure TShadowBox.SetControl(AControl: TControl);
begin
if AControl = Self then Exit;
if FControl <> AControl then
begin
if FControl <> nil then
begin
FControl.WindowProc := FControlWndProc;
FControl.RemoveFreeNotification(Self);
end;
FControl := AControl;
if FControl <> nil then
begin
FControlWndProc := FControl.WindowProc;
FControl.WindowProc := ControlWndProc;
FControl.FreeNotification(Self);
end else
FControlWndProc := nil;
if FControl <> nil then
begin
Parent := FControl.Parent;
AdjustBounds;
end;
end;
end;
procedure TShadowBox.ControlWndProc(var Message: TMessage);
begin
if Assigned(FControlWndProc) then
FControlWndProc(Message);
case Message.Msg of
CM_VISIBLECHANGED:
Visible := FControl.Visible;
WM_WINDOWPOSCHANGED:
begin
if Parent <> FControl.Parent then
Parent := FControl.Parent;
AdjustBounds;
end;
end;
end;
procedure TShadowBox.Notification(AComponent: TComponent; Operation: TOperation);
begin
inherited;
if (Operation = opRemove) and (AComponent = FControl) then
begin
FControl := nil;
FControlWndProc := nil;
end;
end;
procedure TShadowBox.AdjustBounds;
begin
if FControl <> nil then
begin
SetBounds(FControl.Left - 8, FControl.Top - 8, FControl.Width + 16, FControl.Height + 16);
if FControl is TWinControl then
BringToFront
else
SendToBack;
end;
end;
procedure PrepareBitmap32Shadow(Bitmap: TBitmap; Darkness: Byte=100);
var
I, J: Integer;
Pixels: PRGBQuad;
Color: COLORREF;
begin
for I := 0 to Bitmap.Height - 1 do
begin
Pixels := PRGBQuad(Bitmap.ScanLine[I]);
for J := 0 to Bitmap.Width - 1 do
begin
with Pixels^ do
begin
Color := RGB(rgbRed, rgbGreen, rgbBlue);
case Color of
$FFFFFF: rgbReserved := 0; // white = transparent
$000000: rgbReserved := 255; // black = opaque
else
rgbReserved := 255 - ((rgbRed + rgbGreen + rgbBlue) div 3); // intensity of semi transparent
end;
rgbRed := Darkness; rgbGreen := Darkness; rgbBlue := Darkness; // darkness
// pre-multiply the pixel with its alpha channel
rgbRed := (rgbRed * rgbReserved) div $FF;
rgbGreen := (rgbGreen * rgbReserved) div $FF;
rgbBlue := (rgbBlue * rgbReserved) div $FF;
end;
Inc(Pixels);
end;
end;
end;
{$IFDEF VER130} // D5
const
AC_SRC_ALPHA = $01;
{$ENDIF}
procedure TShadowBox.Paint;
var
Bitmap: TBitmap;
BlendFunction: TBlendFunction;
begin
Bitmap := TBitmap.Create;
try
Bitmap.PixelFormat := pf24bit;
Bitmap.Width := Width;
Bitmap.Height := Height;
Bitmap.Canvas.Pen.Color := clBlack;
Bitmap.Canvas.Brush.Color := clBlack;
Bitmap.Canvas.RoundRect(5, 5, Width - 5, Height - 5, 10, 10);
GBlur(Bitmap, 3); // Radius
Bitmap.PixelFormat := pf32bit;
Bitmap.IgnorePalette := True;
Bitmap.HandleType := bmDIB;
PrepareBitmap32Shadow(Bitmap, 150); // Darkness
BlendFunction.BlendOp := AC_SRC_OVER;
BlendFunction.BlendFlags := 0;
BlendFunction.SourceConstantAlpha := 255;
BlendFunction.AlphaFormat := AC_SRC_ALPHA;
Windows.AlphaBlend(
Canvas.Handle, // HDC hdcDest
0, // int xoriginDest
0, // int yoriginDest
Bitmap.Width, // int wDest
Bitmap.Height, // int hDest
Bitmap.Canvas.Handle, // HDC hdcSrc
0, // int xoriginSrc
0, // int yoriginSrc
Bitmap.Width, // int wSrc
Bitmap.Height, // int hSrc
BlendFunction); // BLENDFUNCTION
finally
Bitmap.Free;
end;
end;
end.
Usage:
uses ShadowBox;
...
procedure TForm1.FormCreate(Sender: TObject);
begin
with TShadowBox.Create(Self) do
Control := Edit1;
with TShadowBox.Create(Self) do
Control := Shape1;
with TShadowBox.Create(Self) do
Control := Panel1;
end;
Related
How to rotate and draw a transperent PNG image using GDI+ directly on TChart?
Currently I load PNG image to a bitmap and use two bitmaps for GDI+ rotation (it works very fast) and then make the target bitmap transparent and draw it on TChart's canvas (it works slow when transparency is on).
procedure TPitchDisplay._chartAfterDraw(Sender: TObject);
var
c: TCanvas3D;
pw, ph: Integer;
r: TRect;
bmp: TBitmap;
rotAngle: Integer;
begin
_setKnobBounds();
c := _chart.Canvas;
_drawCircle(c, _knob.CenterPoint.X, _knob.CenterPoint.Y, Round(_knob.YRadius * 0.9));
c.StretchDrawQuality := sqHigh;
pw := 450;
ph := Round((pw / _shipBitmap.Width) * _shipBitmap.Height);
r.Left := _chart.ChartXCenter - pw div 2;
r.Top := Round(_chart.Height / 3);
r.Width := pw;
r.Height := ph;
//Draw water
c.StretchDraw(Rect(0, r.Top + Round(ph / 1.5), _chart.Width, _chart.Height), _waterBitmap);
//Draw rotated ship over it
bmp := TBitmap.Create();
try
bmp.Width := Round(_shipBitmap.Width * 1.2);
bmp.Height := Round(_shipBitmap.Height * 1.7);
bmp.TransparentColor := clBlack;
bmp.Transparent := True;
bmp.TransparentMode := TTransparentMode.tmFixed;
if VarIsNumeric(_pitchBox.Value) then
rotAngle := _pitchBox.Value
else
rotAngle := 0;
TGraphUtils.RotateBitmap(_shipBitmap, bmp, 0.5, 0.7, rotAngle, False, clBlack);
c.StretchDraw(r, bmp);
finally
bmp.Free();
end;
end;
Rotation routine:
class procedure TGraphUtils.RotateBitmap(srcBmp, tgtBmp: TBitmap; rotateAtX, rotateAtY: Single; Degs: Integer; AdjustSize: Boolean;
BkColor: TColor = clNone);
var
Tmp: TGPBitmap;
Matrix: TGPMatrix;
C: Single;
S: Single;
NewSize: TSize;
Graphs: TGPGraphics;
P: TGPPointF;
attr: TGPImageAttributes;
begin
Tmp := TGPBitmap.Create(srcBmp.Handle, srcBmp.Palette);
Matrix := TGPMatrix.Create();
try
Matrix.RotateAt(Degs, MakePoint(rotateAtX * srcBmp.Width, rotateAtY * srcBmp.Height));
if AdjustSize then begin
C := Cos(DegToRad(Degs));
S := Sin(DegToRad(Degs));
NewSize.cx := Round(srcBmp.Width * Abs(C) + srcBmp.Height * Abs(S));
NewSize.cy := Round(srcBmp.Width * Abs(S) + srcBmp.Height * Abs(C));
tgtBmp.Width := NewSize.cx;
tgtBmp.Height := NewSize.cy;
end;
Graphs := TGPGraphics.Create(tgtBmp.Canvas.Handle);
attr := TGPImageAttributes.Create;
try
Graphs.Clear(ColorRefToARGB(ColorToRGB(BkColor)));
Graphs.SetTransform(Matrix);
//attr.SetColorKey($FFFFFF {TGPColor.Blue}, $FFFFFF {TGPColor.Blue}, ColorAdjustTypeBitmap);
Graphs.DrawImage(Tmp, (Cardinal(tgtBmp.Width) - Tmp.GetWidth) div 2,
(Cardinal(tgtBmp.Height) - Tmp.GetHeight) div 2);
finally
attr.Free();
Graphs.Free();
end;
finally
Matrix.Free();
Tmp.Free();
end;
end;
procedure TForm1.Chart1AfterDraw(Sender: TObject);
var
GPImage: TGPImage;
GPGraphics: TGPGraphics;
Matrix: TGPMatrix;
c: TCanvas3d;
begin
c := Chart1.Canvas;
GPImage := TGPImage.Create('e:\2.png');
GPGraphics := TGPGraphics.Create(C.Handle);
Matrix := TGPMatrix.Create;
Matrix.Rotate(30);
Matrix.Scale(0.5, 0.5);
GPGraphics.SetTransform(Matrix);
GPGraphics.DrawImage(GPImage, 150, 0);
Matrix.Free;
GPGraphics.Free;
GPimage.Free;
end;
Not precise example of output into form center:
IM := TGPMatrix.Create;
IM := Matrix.Clone;
IM.Invert;
pc := MakePoint(Width div 2 - GPImage.GetWidth() div 2,
Height div 2 - Integer(GPImage.GetHeight) div 2);
Im.TransformPoints(pgppoINT(#pc));
GPGraphics.DrawImage(GPImage, pc);
How can I use animated GIF in Firemonky. I can load the gif using Timage but it's not animating. I am using Delphi 10.2 tokyo.
Maybe a bit late, but found a simple solution on this page :
http://www.raysoftware.cn/?p=559
Download the file http://www.raysoftware.cn/wp-content/uploads/2016/12/FMXGif.rar, uncompress, and take the file FMX.GifUtils out, and put in your the directory of your application
Put a image component on your form with name Image1
Put the file FMX.GifUtils in your use on top
Declare in your form in private :
FGifPlayer: TGifPlayer;
in the create of your form:
FGifPlayer := TGifPlayer.Create(Self);
FGifPlayer.Image := Image1;
FGifPlayer.LoadFromFile('youfilename.gif');
FGifPlayer.Play;
That's it;
Use TBitmapListAnimation.
Place TImage on Form
Place TBitmapListAnimation into TImage like on screenshot:
Set properties in TBitmapListAnimation
AnimationBitmap -
You can use online convertorsm that split gif into frames.
http://ezgif.com/split
http://www.photojoiner.net/merge-photos/editor/#
Set another properties:
AnimationCount = 8, AnimationRowCount = 1,
Enabled = True
Duration in seconds,
PropertyName = Bitmap.
Please vote if you like this answer.
P.s. How to create an animation bitmap from a list of images to use in TBitmapListAnimation?
Download this app, here is also a topic.
Here is another one solution.
Unit from previous answer http://www.raysoftware.cn, but with fixed bugs
unit FMX.GifUtils;
interface
uses
System.Classes, System.SysUtils, System.Types, System.UITypes,
FMX.Types, FMX.Objects, FMX.Graphics, System.Generics.Collections;
const
alphaTransparent = $00;
GifSignature: array [0 .. 2] of Byte = ($47, $49, $46); // GIF
VerSignature87a: array [0 .. 2] of Byte = ($38, $37, $61); // 87a
VerSignature89a: array [0 .. 2] of Byte = ($38, $39, $61); // 89a
GIF_DISPOSAL_UNSPECIFIED = 0;
GIF_DISPOSAL_LEAVE = 1;
GIF_DISPOSAL_BACKGROUND = 2;
GIF_DISPOSAL_PREVIOUS = 3;
type
TGifVer = (verUnknow, ver87a, ver89a);
TInternalColor = packed record
case Integer of
0:
(
{$IFDEF BIGENDIAN}
R, G, B, A: Byte;
{$ELSE}
B, G, R, A: Byte;
{$ENDIF}
);
1:
(Color: TAlphaColor;
);
end;
{$POINTERMATH ON}
PInternalColor = ^TInternalColor;
{$POINTERMATH OFF}
TGifRGB = packed record
R: Byte;
G: Byte;
B: Byte;
end;
TGIFHeader = packed record
Signature: array [0 .. 2] of Byte; // * Header Signature (always "GIF") */
Version: array [0 .. 2] of Byte; // * GIF format version("87a" or "89a") */
// Logical Screen Descriptor
ScreenWidth: word; // * Width of Display Screen in Pixels */
ScreenHeight: word; // * Height of Display Screen in Pixels */
Packedbit: Byte; // * Screen and Color Map Information */
BackgroundColor: Byte; // * Background Color Index */
AspectRatio: Byte; // * Pixel Aspect Ratio */
end;
TGifImageDescriptor = packed record
Left: word; // * X position of image on the display */
Top: word; // * Y position of image on the display */
Width: word; // * Width of the image in pixels */
Height: word; // * Height of the image in pixels */
Packedbit: Byte; // * Image and Color Table Data Information */
end;
TGifGraphicsControlExtension = packed record
BlockSize: Byte; // * Size of remaining fields (always 04h) */
Packedbit: Byte; // * Method of graphics disposal to use */
DelayTime: word; // * Hundredths of seconds to wait */
ColorIndex: Byte; // * Transparent Color Index */
Terminator: Byte; // * Block Terminator (always 0) */
end;
TGifReader = class;
TPalette = TArray<TInternalColor>;
TGifFrameItem = class;
TGifFrameList = TObjectList<TGifFrameItem>;
{ TGifReader }
TGifReader = class(TObject)
protected
FHeader: TGIFHeader;
FPalette: TPalette;
FScreenWidth: Integer;
FScreenHeight: Integer;
FInterlace: Boolean;
FBitsPerPixel: Byte;
FBackgroundColorIndex: Byte;
FResolution: Byte;
FGifVer: TGifVer;
public
function Read(Stream: TStream; var AFrameList: TGifFrameList): Boolean;
overload; virtual;
function Read(FileName: string; var AFrameList: TGifFrameList): Boolean;
overload; virtual;
function ReadRes(Instance: THandle; ResName: string; ResType: PChar;
var AFrameList: TGifFrameList): Boolean; overload; virtual;
function ReadRes(Instance: THandle; ResId: Integer; ResType: PChar;
var AFrameList: TGifFrameList): Boolean; overload; virtual;
function Check(Stream: TStream): Boolean; overload; virtual;
function Check(FileName: string): Boolean; overload; virtual;
public
constructor Create; virtual;
destructor Destroy; override;
property Header: TGIFHeader read FHeader;
property ScreenWidth: Integer read FScreenWidth;
property ScreenHeight: Integer read FScreenHeight;
property Interlace: Boolean read FInterlace;
property BitsPerPixel: Byte read FBitsPerPixel;
property Background: Byte read FBackgroundColorIndex;
property Resolution: Byte read FResolution;
property GifVer: TGifVer read FGifVer;
end;
TGifFrameItem = class
FDisposalMethod: Integer;
FPos: TPoint;
FTime: Integer;
FDisbitmap: TBitmap;
fBackColor : TalphaColor;
public
destructor Destroy; override;
property Bitmap : TBitmap read FDisbitmap;
end;
implementation
uses
Math;
function swap16(x: UInt16): UInt16; inline;
begin
Result := ((x and $FF) shl 8) or ((x and $FF00) shr 8);
end;
function swap32(x: UInt32): UInt32; inline;
begin
Result := ((x and $FF) shl 24) or ((x and $FF00) shl 8) or
((x and $FF0000) shr 8) or ((x and $FF000000) shr 24);
end;
function LEtoN(Value: word): word; overload;
begin
Result := swap16(Value);
end;
function LEtoN(Value: Dword): Dword; overload;
begin
Result := swap32(Value);
end;
procedure MergeBitmap(const Source, Dest: TBitmap; SrcRect: TRect;
DestX, DestY: Integer);
var
I, J, MoveBytes: Integer;
SrcData, DestData: TBitmapData;
lpColorSrc, lpColorDst: PInternalColor;
begin
With Dest do
begin
if Map(TMapAccess.Write, DestData) then
try
if Source.Map(TMapAccess.Read, SrcData) then
try
if SrcRect.Left < 0 then
begin
Dec(DestX, SrcRect.Left);
SrcRect.Left := 0;
end;
if SrcRect.Top < 0 then
begin
Dec(DestY, SrcRect.Top);
SrcRect.Top := 0;
end;
SrcRect.Right := Min(SrcRect.Right, Source.Width);
SrcRect.Bottom := Min(SrcRect.Bottom, Source.Height);
if DestX < 0 then
begin
Dec(SrcRect.Left, DestX);
DestX := 0;
end;
if DestY < 0 then
begin
Dec(SrcRect.Top, DestY);
DestY := 0;
end;
if DestX + SrcRect.Width > Width then
SrcRect.Width := Width - DestX;
if DestY + SrcRect.Height > Height then
SrcRect.Height := Height - DestY;
if (SrcRect.Left < SrcRect.Right) and (SrcRect.Top < SrcRect.Bottom)
then
begin
MoveBytes := SrcRect.Width * SrcData.BytesPerPixel;
for I := 0 to SrcRect.Height - 1 do
begin
lpColorSrc := SrcData.GetPixelAddr(SrcRect.Left,
SrcRect.Top + I);
lpColorDst := DestData.GetPixelAddr(DestX, DestY + I);
for J := 0 to SrcRect.Width - 1 do
if lpColorSrc[J].A <> 0 then
begin
lpColorDst[J] := lpColorSrc[J];
end;
end;
end;
finally
Source.Unmap(SrcData);
end;
finally
Unmap(DestData);
end;
end;
end;
{ TGifReader }
function TGifReader.Read(FileName: string;
var AFrameList: TGifFrameList): Boolean;
var
fs: TFileStream;
begin
Result := False;
fs := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
Result := Read(fs, AFrameList);
except
end;
fs.DisposeOf;
end;
function TGifReader.ReadRes(Instance: THandle; ResName: string; ResType: PChar;
var AFrameList: TGifFrameList): Boolean;
var
res: TResourceStream;
begin
res := TResourceStream.Create(HInstance, ResName, ResType);
Result := Read(res, AFrameList);
res.DisposeOf;
end;
function TGifReader.ReadRes(Instance: THandle; ResId: Integer; ResType: PChar;
var AFrameList: TGifFrameList): Boolean;
var
res: TResourceStream;
begin
res := TResourceStream.CreateFromID(HInstance, ResId, ResType);
Result := Read(res, AFrameList);
res.DisposeOf;
end;
function TGifReader.Read(Stream: TStream;
var AFrameList: TGifFrameList): Boolean;
var
LDescriptor: TGifImageDescriptor;
LGraphicsCtrlExt: TGifGraphicsControlExtension;
LIsTransparent: Boolean;
LGraphCtrlExt: Boolean;
LFrameWidth: Integer;
LFrameHeight: Integer;
LLocalPalette: TPalette;
LScanLineBuf: TBytes;
procedure ReadPalette(Stream: TStream; Size: Integer; var APalette: TPalette);
Var
RGBEntry: TGifRGB;
I: Integer;
c: TInternalColor;
begin
SetLength(APalette, Size);
For I := 0 To Size - 1 Do
Begin
Stream.Read(RGBEntry, SizeOf(RGBEntry));
With APalette[I] do
begin
R := RGBEntry.R or (RGBEntry.R shl 8);
G := RGBEntry.G or (RGBEntry.G shl 8);
B := RGBEntry.B or (RGBEntry.B shl 8);
A := $FF;
end;
End;
end;
function ProcHeader: Boolean;
var
c: TInternalColor;
begin
Result := False;
With FHeader do
begin
if (CompareMem(#Signature, #GifSignature, 3)) and
(CompareMem(#Version, #VerSignature87a, 3)) or
(CompareMem(#Version, #VerSignature89a, 3)) then
begin
FScreenWidth := FHeader.ScreenWidth;
FScreenHeight := FHeader.ScreenHeight;
FResolution := Packedbit and $70 shr 5 + 1;
FBitsPerPixel := Packedbit and 7 + 1;
FBackgroundColorIndex := BackgroundColor;
if CompareMem(#Version, #VerSignature87a, 3) then
FGifVer := ver87a
else if CompareMem(#Version, #VerSignature89a, 3) then
FGifVer := ver89a;
Result := True;
end
else
Raise Exception.Create('Unknown GIF image format');
end;
end;
function ProcFrame: Boolean;
var
LineSize: Integer;
LBackColorIndex: Integer;
begin
Result := False;
With LDescriptor do
begin
LFrameWidth := Width;
LFrameHeight := Height;
FInterlace := ((Packedbit and $40) = $40);
end;
if LGraphCtrlExt then
begin
LIsTransparent := (LGraphicsCtrlExt.Packedbit and $01) <> 0;
If LIsTransparent then
LBackColorIndex := LGraphicsCtrlExt.ColorIndex;
end
else
begin
LIsTransparent := FBackgroundColorIndex <> 0;
LBackColorIndex := FBackgroundColorIndex;
end;
LineSize := LFrameWidth * (LFrameHeight + 1);
SetLength(LScanLineBuf, LineSize);
If LIsTransparent then
begin
LLocalPalette[LBackColorIndex].A := alphaTransparent;
end;
Result := True;
end;
function ReadAndProcBlock(Stream: TStream): Byte;
var
Introducer, Labels, SkipByte: Byte;
begin
Stream.Read(Introducer, 1);
if Introducer = $21 then
begin
Stream.Read(Labels, 1);
Case Labels of
$FE, $FF:
// Comment Extension block or Application Extension block
while True do
begin
Stream.Read(SkipByte, 1);
if SkipByte = 0 then
Break;
Stream.Seek(Int64( SkipByte), soFromCurrent);
end;
$F9: // Graphics Control Extension block
begin
Stream.Read(LGraphicsCtrlExt, SizeOf(LGraphicsCtrlExt));
LGraphCtrlExt := True;
end;
$01: // Plain Text Extension block
begin
Stream.Read(SkipByte, 1);
Stream.Seek(Int64( SkipByte), soFromCurrent);
while True do
begin
Stream.Read(SkipByte, 1);
if SkipByte = 0 then
Break;
Stream.Seek(Int64( SkipByte), soFromCurrent);
end;
end;
end;
end;
Result := Introducer;
end;
function ReadScanLine(Stream: TStream; AScanLine: PByte): Boolean;
var
OldPos, UnpackedSize, PackedSize: longint;
I: Integer;
Data, Bits, Code: Cardinal;
SourcePtr: PByte;
InCode: Cardinal;
CodeSize: Cardinal;
CodeMask: Cardinal;
FreeCode: Cardinal;
OldCode: Cardinal;
Prefix: array [0 .. 4095] of Cardinal;
Suffix, Stack: array [0 .. 4095] of Byte;
StackPointer: PByte;
Target: PByte;
DataComp: TBytes;
B, FInitialCodeSize, FirstChar: Byte;
ClearCode, EOICode: word;
begin
DataComp := nil;
try
try
Stream.Read(FInitialCodeSize, 1);
OldPos := Stream.Position;
PackedSize := 0;
Repeat
Stream.Read(B, 1);
if B > 0 then
begin
Inc(PackedSize, B);
Stream.Seek(Int64(B), soFromCurrent);
CodeMask := (1 shl CodeSize) - 1;
end;
until B = 0;
SetLength(DataComp, 2 * PackedSize);
SourcePtr := #DataComp[0];
Stream.Position := OldPos;
Repeat
Stream.Read(B, 1);
if B > 0 then
begin
Stream.ReadBuffer(SourcePtr^, B);
Inc(SourcePtr, B);
end;
until B = 0;
SourcePtr := #DataComp[0];
Target := AScanLine;
CodeSize := FInitialCodeSize + 1;
ClearCode := 1 shl FInitialCodeSize;
EOICode := ClearCode + 1;
FreeCode := ClearCode + 2;
OldCode := 4096;
CodeMask := (1 shl CodeSize) - 1;
UnpackedSize := LFrameWidth * LFrameHeight;
for I := 0 to ClearCode - 1 do
begin
Prefix[I] := 4096;
Suffix[I] := I;
end;
StackPointer := #Stack;
FirstChar := 0;
Data := 0;
Bits := 0;
while (UnpackedSize > 0) and (PackedSize > 0) do
begin
Inc(Data, SourcePtr^ shl Bits);
Inc(Bits, 8);
while Bits >= CodeSize do
begin
Code := Data and CodeMask;
Data := Data shr CodeSize;
Dec(Bits, CodeSize);
if Code = EOICode then
Break;
if Code = ClearCode then
begin
CodeSize := FInitialCodeSize + 1;
CodeMask := (1 shl CodeSize) - 1;
FreeCode := ClearCode + 2;
OldCode := 4096;
Continue;
end;
if Code > FreeCode then
Break;
if OldCode = 4096 then
begin
FirstChar := Suffix[Code];
Target^ := FirstChar;
Inc(Target);
Dec(UnpackedSize);
OldCode := Code;
Continue;
end;
InCode := Code;
if Code = FreeCode then
begin
StackPointer^ := FirstChar;
Inc(StackPointer);
Code := OldCode;
end;
while Code > ClearCode do
begin
StackPointer^ := Suffix[Code];
Inc(StackPointer);
Code := Prefix[Code];
end;
FirstChar := Suffix[Code];
StackPointer^ := FirstChar;
Inc(StackPointer);
Prefix[FreeCode] := OldCode;
Suffix[FreeCode] := FirstChar;
if (FreeCode = CodeMask) and (CodeSize < 12) then
begin
Inc(CodeSize);
CodeMask := (1 shl CodeSize) - 1;
end;
if FreeCode < 4095 then
Inc(FreeCode);
OldCode := InCode;
repeat
Dec(StackPointer);
Target^ := StackPointer^;
Inc(Target);
Dec(UnpackedSize);
until StackPointer = #Stack;
end;
Inc(SourcePtr);
Dec(PackedSize);
end;
finally
DataComp := nil;
end;
except
end;
Result := True;
end;
function WriteScanLine(var Img: TBitmap; AScanLine: PByte): Boolean;
Var
Row, Col: Integer;
Pass, Every: Byte;
P: PByte;
function IsMultiple(NumberA, NumberB: Integer): Boolean;
begin
Result := (NumberA >= NumberB) and (NumberB > 0) and
(NumberA mod NumberB = 0);
end;
var
PLine: PInternalColor;
Data: TBitmapData;
begin
Result := False;
P := AScanLine;
if Img.Map(TMapAccess.Write, Data) then
begin
try
If FInterlace then
begin
For Pass := 1 to 4 do
begin
Case Pass of
1:
begin
Row := 0;
Every := 8;
end;
2:
begin
Row := 4;
Every := 8;
end;
3:
begin
Row := 2;
Every := 4;
end;
4:
begin
Row := 1;
Every := 2;
end;
end;
Repeat
PLine := Data.GetScanline(Row);
for Col := 0 to Img.Width - 1 do
begin
PLine[Col] := LLocalPalette[P^];
Inc(P);
end;
Inc(Row, Every);
until Row >= Img.Height;
end;
end
else
begin
for Row := 0 to Img.Height - 1 do
begin
PLine := Data.GetScanline(Row);
for Col := 0 to Img.Width - 1 do
begin
PLine[Col] := LLocalPalette[P^];
Inc(P);
end;
end;
end;
Result := True;
finally
Img.Unmap(Data);
end;
end;
end;
procedure RenderFrame(const Index : integer; const aFrames : array of TGifFrameItem; const aDisplay : TBitmap);
var
I, First, Last: Integer;
begin
Last := Index;
First := Max(0, Last);
aDisplay.Clear(aFrames[Index].fBackColor);
while First > 0 do
begin
if (fScreenWidth = aFrames[First].Bitmap.Width) and (fScreenHeight = aFrames[First].Bitmap.Height) then
begin
if (aFrames[First].FDisposalMethod = GIF_DISPOSAL_BACKGROUND) and (First < Last) then
Break;
end;
Dec(First);
end;
for I := First to Last - 1 do
begin
case aFrames[I].FDisposalMethod of
GIF_DISPOSAL_UNSPECIFIED,
GIF_DISPOSAL_LEAVE:
begin
// Copy previous raw frame onto screen
MergeBitmap(aFrames[i].Bitmap, aDisplay, aFrames[i].Bitmap.Bounds,
aFrames[i].FPos.X, aFrames[i].FPos.Y);
end;
GIF_DISPOSAL_BACKGROUND:
if (I > First) then
begin
// Restore background color
aDisplay.ClearRect(TRectF.Create(aFrames[i].FPos.X, aFrames[i].FPos.Y,
aFrames[i].FPos.X + aFrames[i].Bitmap.Width,
aFrames[i].FPos.Y + aFrames[i].Bitmap.Height),
aFrames[i].fBackColor);
end;
GIF_DISPOSAL_PREVIOUS: ; // Do nothing - previous state is already on screen
end;
end;
MergeBitmap(aFrames[Index].Bitmap, aDisplay, aFrames[Index].Bitmap.Bounds, aFrames[Index].FPos.X, aFrames[Index].FPos.Y);
end;
var
Introducer: Byte;
ColorTableSize: Integer;
tmp: TBitmap;
LFrame: TGifFrameItem;
FrameIndex: Integer;
I: Integer;
LBC : integer;
LFrames : array of TGifFrameItem;
rendered : array of TBitmap;
begin
Result := False;
if not Check(Stream) then
Exit;
AFrameList.Clear;
FGifVer := verUnknow;
FPalette := nil;
LScanLineBuf := nil;
try
Stream.Position := 0;
Stream.Read(FHeader, SizeOf(FHeader));
{$IFDEF BIGENDIAN}
with FHeader do
begin
ScreenWidth := LEtoN(ScreenWidth);
ScreenHeight := LEtoN(ScreenHeight);
end;
{$ENDIF}
if (FHeader.Packedbit and $80) = $80 then
begin
ColorTableSize := FHeader.Packedbit and 7 + 1;
ReadPalette(Stream, 1 shl ColorTableSize, FPalette);
end;
if not ProcHeader then
Exit;
FrameIndex := 0;
SetLength(LFrames, 0);
while True do
begin
LLocalPalette := nil;
Repeat
Introducer := ReadAndProcBlock(Stream);
until (Introducer in [$2C, $3B]);
if Introducer = $3B then
Break;
Stream.Read(LDescriptor, SizeOf(LDescriptor));
{$IFDEF BIGENDIAN}
with FDescriptor do
begin
Left := LEtoN(Left);
Top := LEtoN(Top);
Width := LEtoN(Width);
Height := LEtoN(Height);
end;
{$ENDIF}
if (LDescriptor.Packedbit and $80) <> 0 then
begin
ColorTableSize := LDescriptor.Packedbit and 7 + 1;
ReadPalette(Stream, 1 shl ColorTableSize, LLocalPalette);
end
else
begin
LLocalPalette := Copy(FPalette, 0, Length(FPalette));
end;
if not ProcFrame then
Exit;
LFrame := TGifFrameItem.Create;
LFrame.FTime := 10 * LGraphicsCtrlExt.DelayTime;
LFrame.FDisbitmap := TBitmap.Create(LFrameWidth, LFrameHeight);
LFrame.FPos := Point(LDescriptor.Left, LDescriptor.Top);
LFrame.FDisposalMethod := 7 and (LGraphicsCtrlExt.Packedbit shr 2);
if not ReadScanLine(Stream, #LScanLineBuf[0]) then
Exit;
if not WriteScanLine(LFrame.FDisbitmap, #LScanLineBuf[0]) then
Exit;
if LGraphCtrlExt then
begin
LIsTransparent := (LGraphicsCtrlExt.Packedbit and $01) <> 0;
If LIsTransparent then
LBC := LGraphicsCtrlExt.ColorIndex
else
LBC := FBackgroundColorIndex;
end
else
LBC := FBackgroundColorIndex;
LFrame.fBackColor := LLocalPalette[LBC].Color;
Inc(FrameIndex);
SetLength(LFrames, FrameIndex);
LFrames[FrameIndex - 1] := LFrame;
end;
SetLength(rendered, Length(LFrames));
for I := 0 to Length(LFrames) - 1 do
begin
tmp := TBitmap.Create(FScreenWidth, FScreenHeight);
RenderFrame(I, LFrames, tmp);
rendered[i] := tmp;
end;
for I := 0 to Length(LFrames) - 1 do
begin
LFrames[i].Bitmap.Assign(rendered[i]);
FreeAndNil(rendered[i]);
AFrameList.Add(LFrames[i]);
end;
Result := True;
finally
LLocalPalette := nil;
LScanLineBuf := nil;
rendered := nil;
LFrames := nil;
end;
end;
function TGifReader.Check(Stream: TStream): Boolean;
var
OldPos: Int64;
begin
try
OldPos := Stream.Position;
Stream.Read(FHeader, SizeOf(FHeader));
Result := (CompareMem(#FHeader.Signature, #GifSignature, 3)) and
(CompareMem(#FHeader.Version, #VerSignature87a, 3)) or
(CompareMem(#FHeader.Version, #VerSignature89a, 3));
Stream.Position := OldPos;
except
Result := False;
end;
end;
function TGifReader.Check(FileName: string): Boolean;
var
fs: TFileStream;
begin
Result := False;
fs := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
Result := Check(fs);
except
end;
fs.DisposeOf;
end;
constructor TGifReader.Create;
begin
inherited Create;
end;
destructor TGifReader.Destroy;
begin
inherited Destroy;
end;
{ TGifFrameItem }
destructor TGifFrameItem.Destroy;
begin
if FDisbitmap <> nil then
begin
FDisbitmap.DisposeOf;
FDisbitmap := nil;
end;
inherited Destroy;
end;
end.
I would like to know if it is possible to make the Form transparent (AlphaBlend) without the Border (where the minimize button is and all that) being transparent too? If it is, how can I do it?
It is possible, but any controls you put on such form will not be visible.
Here is some base code to get you started, but I have never used it on forms with border, so there might be possible issues with border functionality. Most likely, you will have to create bitmap that will include window border and set alpha for that part to 255;
procedure PremultiplyBitmap(Bitmap: TBitmap);
var
Row, Col: Integer;
p: PRGBQuad;
begin
Bitmap.AlphaFormat := afPremultiplied;
for Row := 0 to Bitmap.Height - 1 do
begin
Col := Bitmap.Width;
p := Bitmap.ScanLine[Row];
while (Col > 0) do
begin
p.rgbBlue := p.rgbReserved * p.rgbBlue div 255;
p.rgbGreen := p.rgbReserved * p.rgbGreen div 255;
p.rgbRed := p.rgbReserved * p.rgbRed div 255;
inc(p);
dec(Col);
end;
end;
end;
procedure PremultiplyBitmapAlpha(Bitmap: TBitmap; Alpha: byte);
var
Row, Col: Integer;
p: PRGBQuad;
begin
Bitmap.AlphaFormat := afPremultiplied;
for Row := 0 to Bitmap.Height - 1 do
begin
Col := Bitmap.Width;
p := Bitmap.ScanLine[Row];
while (Col > 0) do
begin
p.rgbReserved := Alpha;
p.rgbBlue := p.rgbReserved * p.rgbBlue div 255;
p.rgbGreen := p.rgbReserved * p.rgbGreen div 255;
p.rgbRed := p.rgbReserved * p.rgbRed div 255;
inc(p);
dec(Col);
end;
end;
end;
procedure BlendForm(Form: TCustomForm; Bmp: TBitmap);
var
BitmapPos: TPoint;
BitmapSize: TSize;
BlendFunction: TBlendFunction;
begin
BitmapPos := Point(0, 0);
BitmapSize.cx := Bmp.Width;
BitmapSize.cy := Bmp.Height;
BlendFunction.BlendOp := AC_SRC_OVER;
BlendFunction.BlendFlags := 0;
BlendFunction.SourceConstantAlpha := 255;
BlendFunction.AlphaFormat := AC_SRC_ALPHA;
UpdateLayeredWindow(Form.Handle, 0, nil, #BitmapSize, Bmp.Canvas.Handle, #BitmapPos, 0, #BlendFunction, ULW_ALPHA);
end;
procedure TForm1.CreateWnd;
var
ExStyle: DWORD;
begin
inherited;
ExStyle := GetWindowLong(Handle, GWL_EXSTYLE);
if (ExStyle and WS_EX_LAYERED = 0) then
SetWindowLong(Handle, GWL_EXSTYLE, ExStyle or WS_EX_LAYERED);
end;
procedure TForm1.FormCreate(Sender: TObject);
var
Bmp: TBitmap;
begin
Bmp := TBitmap.Create;
try
Bmp.PixelFormat := pf32bit;
Bmp.Width := ClientWidth;
Bmp.Height := ClientHeight;
PremultiplyBitmapAlpha(Bmp, 200);
BlendForm(Form1, Bmp);
finally
Bmp.Free;
end;
end;
Bitmaps you use have to be 32-bit bitmaps. If you want to blend whole bitmap with some alpha value, you can use PremultiplyBitmapAlpha procedure, and if your bitmap has alpha channel you can then use PremultiplyBitmap procedure.
For speed improvements you can use premultiplied byte table like this:
var
PreMult: array[byte, byte] of byte;
procedure InitializePreMult;
var
Row, Col: Integer;
begin
// precalculate all possible values of a*b
for Row := 0 to 255 do
for Col := Row to 255 do
PreMult[Row, Col] := Row*Col div 255;
end;
and then PremultiplyBitmap procedure would use that lookup table:
procedure PremultiplyBitmap(Bitmap: TBitmap);
var
Row, Col: integer;
p: PRGBQuad;
begin
for Row := 0 to Bitmap.Height-1 do
begin
Col := Bitmap.Width;
p := Bitmap.ScanLine[Row];
while (Col > 0) do
begin
p.rgbBlue := PreMult[p.rgbReserved, p.rgbBlue];
p.rgbGreen := PreMult[p.rgbReserved, p.rgbGreen];
p.rgbRed := PreMult[p.rgbReserved, p.rgbRed];
inc(p);
dec(Col);
end;
end;
end;
How can I replace color on TCanvas on Delphi XE2? The following code works incredibly slow:
for y := ARect.Top to ARect.Top + ARect.Height - 1 do
for x := ARect.Left to ARect.Left + ARect.Width - 1 do
if Canvas.Pixels[x, y] = FixedColor then
Canvas.Pixels[x, y] := Canvas.Pixels[ARect.Left, ARect.Top];
Here are two function (with and without tolerance) to replace the color:
Bonus:
Code to test the functions also provided. Load your image in a TImage control, then use the MouseUp event to change the color under mouse.
procedure ReplaceColor(BMP: TBitmap; OldColor, NewColor: TColor);
VAR
x, y: Integer;
R,G,B: Byte;
R_,G_,B_: Byte;
aPixel: PRGBTriple;
begin
R:= GetRValue(OldColor);
G:= GetGValue(OldColor);
B:= GetBValue(OldColor);
R_:= GetRValue(NewColor);
G_:= GetGValue(NewColor);
B_:= GetBValue(NewColor);
BMP.PixelFormat := pf24bit;
for y := 0 to BMP.Height - 1 do
for x := 0 to BMP.Width - 1 do
begin
aPixel := BMP.ScanLine[y];
Inc(aPixel, x);
if (aPixel^.rgbtRed = R)
AND (aPixel^.rgbtGreen = G)
AND (aPixel^.rgbtBlue = B) then
begin
aPixel^.rgbtRed := R_;
aPixel^.rgbtGreen := G_;
aPixel^.rgbtBlue := B_;
end;
end;
end;
procedure ReplaceColor(BMP: TBitmap; OldColor, NewColor: TColor; ToleranceR, ToleranceG, ToleranceB: Byte);
VAR
x, y: Integer;
R,G,B: Byte;
R_,G_,B_: Byte;
aPixel: PRGBTriple;
begin
R:= GetRValue(OldColor);
G:= GetGValue(OldColor);
B:= GetBValue(OldColor);
R_:= GetRValue(NewColor);
G_:= GetGValue(NewColor);
B_:= GetBValue(NewColor);
BMP.PixelFormat := pf24bit;
for y := 0 to BMP.Height - 1 do
for x := 0 to BMP.Width - 1 do
begin
aPixel := BMP.ScanLine[y];
Inc(aPixel, x);
if (abs(aPixel^.rgbtRed - R)< ToleranceR)
AND (abs(aPixel^.rgbtGreen- G)< ToleranceG)
AND (abs(aPixel^.rgbtBlue - B)< ToleranceB) then
begin
aPixel^.rgbtRed := R_;
aPixel^.rgbtGreen := G_;
aPixel^.rgbtBlue := B_;
end;
end;
end;
procedure TfrmTester.imgOnMouseUp(Sender: TObject; Shift: TShiftState; X, Y: Integer);
VAR
PixelClr: TColor;
BMP: TBitmap;
begin
// Collect the new color, under mouse pointer
PixelClr:= imgReplaceOrig.Picture.Bitmap.Canvas.Pixels[x, y];
if PixelClr< 0 then EXIT;
Label1.Caption := 'x'+IntToStr(X)+':y='
+ IntToStr(Y)
+' r'+ IntToStr(GetRValue(Pixel))
+', g'+ IntToStr(GetGValue(Pixel))
+', b'+ IntToStr(GetBValue(Pixel));
BMP:= TBitmap.Create;
BMP.Assign(imgReplaceOrig.Picture.Bitmap);
// Replace the color
cGraphUtil.ReplaceColor(BMP, PixelClr, clBlue, 44, 44, 44);
imgReplace.Picture.Assign(BMP);
FreeAndNil(BMP);
end;
var
aBitmap: TBitmap;
x, y: Integer;
aPixel: PRGBTriple;
...
aBitmap := TBitmap.Create;
try
aBitmap.PixelFormat := pf24bit;
aBitmap.Height := ARect.Height;
aBitmap.Width := ARect.Width;
aBitmap.Canvas.CopyRect(TRect.Create(0, 0, aBitmap.Width, aBitmap.Height), Canvas, ARect);
for y := 0 to aBitmap.Height - 1 do
for x := 0 to aBitmap.Width - 1 do
begin
aPixel := aBitmap.ScanLine[y];
Inc(aPixel, x);
if (aPixel^.rgbtRed = GetRValue(FixedColor)) and (aPixel^.rgbtGreen = GetGValue(FixedColor)) and (aPixel^.rgbtBlue = GetBValue(FixedColor)) then
aPixel^ := PRGBTriple(aBitmap.ScanLine[y])^;
end;
Canvas.Draw(ARect.Left, ARect.Top, aBitmap);
finally
aBitmap.Free;
end;
I want to create a special kind of selection, in which the image darkened and in part which user is selecting, the real Image is shown. You can see an Example:
I found two approaches for implementing this:
Implementing a control which show the darkened image.
When user drag an ellipse over this control, an ellipse copy the real image (Image which is NOT Darkened) into the control canvas.
In this scenario When he/she try to Resize the ellipse to SMALLER SIZE, first the whole rectangular area of ellipse darkened and then real image draw in the new Smaller Ellipse.
Same as Approach 1, but instead of drawing on the canvas of the control, we create a new control which show the real image. In this case, all messages send to the new control, SHOULD pass to the parent control. Because if user try to resize the ellipse to smaller size, WM_MOVE messages sent to this control, instead of the parent control.
Can please, someone show me the right direction for implementing this. I think that approach 1 is very hard to implement because it cause lot's of Flicker. Unless I implement a way to only repaint the changed part by InvalidateRect function.
Here is the code of the class TScreenEmul which is implemented by me, until now. It works but it has flicker.
unit ScreenEmul;
interface
uses Classes, Types, Windows, Messages, Graphics, Controls, SysUtils, Dialogs, ExtCtrls;
const
PixelCountMax = 32768;
type
PRGBTripleArray = ^TRGBTripleArray;
TRGBTripleArray = array[0..PixelCountMax-1] of TRGBTriple;
TScreenEmul = class(TCustomControl)
private
LastRect, DrawRect: TRect;
DrawStart: TPoint;
MouseDown: Boolean;
Backup, Darken: TBitmap;
FBitmap: TBitmap;
procedure BitmapChange(Sender: TObject);
procedure WMLButtonDown(var Message: TWMLButtonDown); message WM_LBUTTONDOWN;
procedure WMMouseMove(var Message: TWMMouseMove); message WM_MOUSEMOVE;
procedure WMLButtonUp(var Message: TWMLButtonUp); message WM_LBUTTONUP;
procedure WMEraseBkgnd(var Message: TWMEraseBkgnd ); message WM_ERASEBKGND;
procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
procedure DarkenBitmap(B: TBitmap);
procedure RestoreImage;
procedure CalculateDrawRect(X, Y: Integer);
procedure SetBitmap(const Value: TBitmap);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
property Bitmap: TBitmap read FBitmap write SetBitmap;
end;
implementation
{ TScreenEmul }
function AlphaBlend(Color1, Color2: TRGBTriple; Alpha: Byte): TRGBTriple; overload;
var
rPrimary : Real; // Primary (Color1) Intensity
rSecondary: Real;// Secondary (Color2) Intensity
begin
rPrimary:=((Alpha+1)/$100);
rSecondary:=(($100-Alpha)/$100);
with Result do
begin
rgbtBlue := Trunc(Color1.rgbtBlue * rPrimary + Color2.rgbtBlue * rSecondary);
rgbtGreen := Trunc(Color1.rgbtGreen * rPrimary + Color2.rgbtGreen * rSecondary);
rgbtRed := Trunc(Color1.rgbtRed * rPrimary + Color2.rgbtRed * rSecondary);
end;
end;
procedure TScreenEmul.BitmapChange(Sender: TObject);
begin
FreeAndNil(Backup);
Backup := TBitmap.Create;
Backup.Assign(FBitmap);
DarkenBitmap(FBitmap);
Darken := TBitmap.Create;
Darken.Assign(FBitmap);
end;
procedure TScreenEmul.CalculateDrawRect(X, Y: Integer);
begin
if X >= DrawStart.X then
begin
if DrawRect.Left <> DrawStart.X then DrawRect.Left := DrawStart.X;
DrawRect.Right := X
end
else
begin
if DrawRect.Right <> DrawStart.X then DrawRect.Right := DrawStart.X;
DrawRect.Left := X;
end;
if Y >= DrawStart.Y then
begin
if DrawRect.Top <> DrawStart.Y then DrawRect.Top := DrawStart.Y;
DrawRect.Bottom := Y;
end
else
begin
if DrawRect.Bottom <> DrawStart.Y then DrawRect.Bottom := DrawStart.Y;
DrawRect.Top := Y;
end;
end;
constructor TScreenEmul.Create(AOwner: TComponent);
begin
inherited;
MouseDown := False;
FBitmap := TBitmap.Create;
FBitmap.OnChange := BitmapChange;
DoubleBuffered := True;
end;
procedure TScreenEmul.DarkenBitmap(B: TBitmap);
var
I, J: Integer;
Row: PRGBTripleArray;
rgbBlack: tagRGBTRIPLE;
begin
rgbBlack.rgbtBlue := 0; rgbBlack.rgbtGreen := 0; rgbBlack.rgbtRed := 0;
for I := 0 to B.Height - 1 do
begin
Row := B.ScanLine[I];
for J := 0 to B.Width - 1 do
Row[J] := AlphaBlend(Row[J], rgbBlack, 150);
end;
end;
destructor TScreenEmul.Destroy;
begin
FBitmap.Free;
inherited;
end;
procedure TScreenEmul.RestoreImage;
begin
BitBlt(FBitmap.Canvas.Handle,
LastRect.Left, LastRect.Top, RectWidth(LastRect), RectHeight(LastRect),
Darken.Canvas.Handle, LastRect.Left, LastRect.Top, SRCCOPY);
end;
procedure TScreenEmul.SetBitmap(const Value: TBitmap);
begin
FBitmap := Value;
FBitmap.OnChange := BitmapChange;
end;
procedure TScreenEmul.WMEraseBkgnd(var Message: TWMEraseBkgnd);
begin
Message.Result := LResult(False);
end;
procedure TScreenEmul.WMLButtonDown(var Message: TWMLButtonDown);
begin
MouseDown := True;
with DrawRect do
begin
Left := Message.XPos;
Top := Message.YPos;
Right := Left;
Bottom := Top;
end;
DrawStart.X := DrawRect.Top;
DrawStart.Y := DrawRect.Left;
end;
procedure TScreenEmul.WMLButtonUp(var Message: TWMLButtonUp);
begin
MouseDown := False;
RestoreImage;
InvalidateRect(Self.Handle, DrawRect, False);
end;
procedure TScreenEmul.WMMouseMove(var Message: TWMMouseMove);
begin
if not MouseDown then Exit;
CalculateDrawRect(Message.XPos, Message.YPos);
RestoreImage;
BitBlt(
FBitmap.Canvas.Handle,
DrawRect.Left, DrawRect.Top, RectWidth(DrawRect), RectHeight(DrawRect),
Backup.Canvas.Handle,
DrawRect.Left, DrawRect.Top,
SRCCOPY);
InvalidateRect(Self.Handle, DrawRect, False);
LastRect := DrawRect;
end;
procedure TScreenEmul.WMPaint(var Message: TWMPaint);
var
B: TBitmap;
Rct: TRect;
X, Y: Integer;
FullRepaint: Boolean;
begin
inherited;
FullRepaint := GetUpdateRect(Self.Handle, Rct, False);
if not FullRepaint then
begin
Canvas.Draw(0, 0, FBitmap);
end
else
begin
B := TBitmap.Create;
B.SetSize(RectWidth(Rct), RectHeight(Rct));
FBitmap.Canvas.CopyRect(Rect(0, 0, B.Width, B.Height), B.Canvas, Rct);
Canvas.Draw(0, 0, B);
FreeAndNil(B);
end;
end;
end.
For using this class:
var
ScreenEmul: TScreenEmul;
begin
ScreenEmul := TScreenEmul.Create(Self);
ScreenEmul.Parent := Self;
ScreenEmul.Align := alClient;
ScreenEmul.Bitmap.LoadFromFile('C:\img13.bmp');
I solved the problem. I answer the question for the record:
1- WMEraseBkgnd should return True to prevent painting background. I mistakenly returned False.
2- I inherited the WMPaint method which is not correct. I also copy the updated Rect into new Bitmap and then draw the bitmap into canvas which was slow the painting process. Here is full fixed source code:
unit ScreenEmul;
interface
uses Classes, Types, Windows, Messages, Graphics, Controls, SysUtils, Dialogs, ExtCtrls;
const
PixelCountMax = 32768;
type
PRGBTripleArray = ^TRGBTripleArray;
TRGBTripleArray = array[0..PixelCountMax-1] of TRGBTriple;
TScreenEmul = class(TCustomControl)
private
LastRect, DrawRect: TRect;
DrawStart: TPoint;
MouseDown: Boolean;
Backup, Darken: TBitmap;
FBitmap: TBitmap;
procedure BitmapChange(Sender: TObject);
procedure WMLButtonDown(var Message: TWMLButtonDown); message WM_LBUTTONDOWN;
procedure WMMouseMove(var Message: TWMMouseMove); message WM_MOUSEMOVE;
procedure WMLButtonUp(var Message: TWMLButtonUp); message WM_LBUTTONUP;
procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;
procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
procedure DarkenBitmap(B: TBitmap);
procedure RestoreImage;
procedure CalculateDrawRect(X, Y: Integer);
procedure SetBitmap(const Value: TBitmap);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
property Bitmap: TBitmap read FBitmap write SetBitmap;
end;
implementation
{ TScreenEmul }
function AlphaBlend(Color1, Color2: TRGBTriple; Alpha: Byte): TRGBTriple; overload;
var
rPrimary : Real; // Primary (Color1) Intensity
rSecondary: Real;// Secondary (Color2) Intensity
begin
rPrimary:=((Alpha+1)/$100);
rSecondary:=(($100-Alpha)/$100);
with Result do
begin
rgbtBlue := Trunc(Color1.rgbtBlue * rPrimary + Color2.rgbtBlue * rSecondary);
rgbtGreen := Trunc(Color1.rgbtGreen * rPrimary + Color2.rgbtGreen * rSecondary);
rgbtRed := Trunc(Color1.rgbtRed * rPrimary + Color2.rgbtRed * rSecondary);
end;
end;
procedure TScreenEmul.BitmapChange(Sender: TObject);
begin
FreeAndNil(Backup);
Backup := TBitmap.Create;
Backup.Assign(FBitmap);
DarkenBitmap(FBitmap);
Darken := TBitmap.Create;
Darken.Assign(FBitmap);
end;
procedure TScreenEmul.CalculateDrawRect(X, Y: Integer);
begin
if X >= DrawStart.X then
begin
if DrawRect.Left <> DrawStart.X then DrawRect.Left := DrawStart.X;
DrawRect.Right := X
end
else
begin
if DrawRect.Right <> DrawStart.X then DrawRect.Right := DrawStart.X;
DrawRect.Left := X;
end;
if Y >= DrawStart.Y then
begin
if DrawRect.Top <> DrawStart.Y then DrawRect.Top := DrawStart.Y;
DrawRect.Bottom := Y;
end
else
begin
if DrawRect.Bottom <> DrawStart.Y then DrawRect.Bottom := DrawStart.Y;
DrawRect.Top := Y;
end;
end;
constructor TScreenEmul.Create(AOwner: TComponent);
begin
inherited;
MouseDown := False;
FBitmap := TBitmap.Create;
FBitmap.OnChange := BitmapChange;
DoubleBuffered := True;
end;
procedure TScreenEmul.DarkenBitmap(B: TBitmap);
var
I, J: Integer;
Row: PRGBTripleArray;
rgbBlack: tagRGBTRIPLE;
begin
rgbBlack.rgbtBlue := 0; rgbBlack.rgbtGreen := 0; rgbBlack.rgbtRed := 0;
for I := 0 to B.Height - 1 do
begin
Row := B.ScanLine[I];
for J := 0 to B.Width - 1 do
Row[J] := AlphaBlend(Row[J], rgbBlack, 150);
end;
end;
destructor TScreenEmul.Destroy;
begin
FBitmap.Free;
inherited;
end;
procedure TScreenEmul.RestoreImage;
begin
BitBlt(FBitmap.Canvas.Handle,
LastRect.Left, LastRect.Top, RectWidth(LastRect), RectHeight(LastRect),
Darken.Canvas.Handle, LastRect.Left, LastRect.Top, SRCCOPY);
end;
procedure TScreenEmul.SetBitmap(const Value: TBitmap);
begin
FBitmap := Value;
FBitmap.OnChange := BitmapChange;
end;
procedure TScreenEmul.WMEraseBkgnd(var Message: TWMEraseBkgnd);
begin
Message.Result := LResult(True);
end;
procedure TScreenEmul.WMLButtonDown(var Message: TWMLButtonDown);
begin
MouseDown := True;
with DrawRect do
begin
Left := Message.XPos;
Top := Message.YPos;
Right := Left;
Bottom := Top;
end;
DrawStart.X := DrawRect.Top;
DrawStart.Y := DrawRect.Left;
end;
procedure TScreenEmul.WMLButtonUp(var Message: TWMLButtonUp);
begin
MouseDown := False;
RestoreImage;
InvalidateRect(Self.Handle, DrawRect, False);
end;
procedure TScreenEmul.WMMouseMove(var Message: TWMMouseMove);
begin
if not MouseDown then Exit;
CalculateDrawRect(Message.XPos, Message.YPos);
RestoreImage;
BitBlt(
FBitmap.Canvas.Handle,
DrawRect.Left, DrawRect.Top, RectWidth(DrawRect), RectHeight(DrawRect),
Backup.Canvas.Handle,
DrawRect.Left, DrawRect.Top,
SRCCOPY);
InvalidateRect(Self.Handle, DrawRect, False);
LastRect := DrawRect;
end;
procedure TScreenEmul.WMPaint(var Message: TWMPaint);
var
Rct: TRect;
FullRepaint: Boolean;
begin
FullRepaint := GetUpdateRect(Self.Handle, Rct, False);
if not FullRepaint then
Canvas.Draw(0, 0, FBitmap)
else
BitBlt(Canvas.Handle, Rct.Left, Rct.Top, RectWidth(Rct), RectHeight(Rct), FBitmap.Canvas.Handle, Rct.Left, Rct.Top, SRCCOPY);
end;
end.
I have done someting similar... here are extracts of my code (only one bitmap in memory):
Grab screen ...
Type
GrabScreen = (GTSCREEN);
[...]
procedure PGrabScreen(bm: TBitMap; gt : GrabScreen);
var
DestRect, SourceRect: TRect;
h: THandle;
hdcSrc : THandle;
pt : TPoint;
begin
case(gt) of
//...
GTSCREEN : h := GetDesktopWindow;
end;
if h <> 0 then
begin
try
begin
hdcSrc := GetWindowDC(h);
GetWindowRect(h, SourceRect);
end;
bm.Width := SourceRect.Right - SourceRect.Left;
bm.Height := SourceRect.Bottom - SourceRect.Top;
DestRect := Rect(0, 0, SourceRect.Right - SourceRect.Left, SourceRect.Bottom - SourceRect.Top);
StretchBlt(bm.Canvas.Handle, 0, 0, bm.Width,
bm.Height, hdcSrc,
0,0,SourceRect.Right - SourceRect.Left,
SourceRect.Bottom - SourceRect.Top,
SRCCOPY);
DrawCursor(bm,SourceRect.Left, SourceRect.Top);
finally
ReleaseDC(0, hdcSrc);
end;
end;
end;
Blur that bitmap once selection is initiated by mouse down (suggested code)
procedure BitmapBlur(var theBitmap: TBitmap);
var
x, y: Integer;
yLine,
xLine: PByteArray;
begin
for y := 1 to theBitmap.Height -2 do begin
yLine := theBitmap.ScanLine[y -1];
xLine := theBitmap.ScanLine[y];
for x := 1 to theBitmap.Width -2 do begin
xLine^[x * 3] := (
xLine^[x * 3 -3] + xLine^[x * 3 +3] +
yLine^[x * 3 -3] + yLine^[x * 3 +3] +
yLine^[x * 3] + xLine^[x * 3 -3] +
xLine^[x * 3 +3] + xLine^[x * 3]) div 8;
xLine^[x * 3 +1] := (
xLine^[x * 3 -2] + xLine^[x * 3 +4] +
yLine^[x * 3 -2] + yLine^[x * 3 +4] +
yLine^[x * 3 +1] + xLine^[x * 3 -2] +
xLine^[x * 3 +4] + xLine^[x * 3 +1]) div 8;
xLine^[x * 3 +2] := (
xLine^[x * 3 -1] + xLine^[x * 3 +5] +
yLine^[x * 3 -1] + yLine^[x * 3 +5] +
yLine^[x * 3 +2] + xLine^[x * 3 -1] +
xLine^[x * 3 +5] + xLine^[x * 3 +2]) div 8;
end;
end;
end;
Select area* on the blurred bitmap on screen (exemple:)
procedure GrabSelectedArea(Sender: TObject);
begin
Grab(image1.Picture.Bitmap, GTSCREEN);
bmp := Image1.Picture.Bitmap;
image1.Width := image1.Picture.Bitmap.Width;
image1.Height := image1.Picture.Bitmap.Height;
DoSelect := true;
end;
Doing so, reverse (offset) the blur effect for the selected area on the bitmap.
*Here the code i have for selection
procedure Image1MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var
DestRect, SourceRect : TRect;
begin
if DoSelect then begin
Image1.Canvas.Rectangle(SelX,SelY,SelX1,SelY1);
if X <= SelX then
begin
SelX1 := SelX;
SelX := X;
end
else
SelX1 := X;
if Y <= SelY then
begin
SelY1 := SelY;
SelY := Y;
end
else
SelY1 := Y;
Image1.Canvas.Pen.Mode := pmCopy;
SourceRect := Rect(SelX,SelY,SelX1,SelY1);
DestRect := Rect(0,0,SelX1-SelX,SelY1-SelY);
Image1.Canvas.CopyRect(DestRect,Image1.Canvas,SourceRect);
Image1.Picture.Bitmap.Height := SelY1-SelY;
Image1.Picture.Bitmap.Width := SelX1-SelX;
Image1.SetBounds(0,0,SelX1-SelX,SelY1-SelY);
DoSelect := false;
if FormIsFullScreen then
RestoreForm;
end;
end;
procedure Image1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if DoSelect then begin
SelX := X;
SelY := Y;
SelX1 := X;
SelY1 := Y;
with Image1.Canvas do
begin // Options shown in comments
Pen.Width := 1; // 2; // use with solid pen style
Pen.Style := psDashDotDot; // psSolid;
Pen.Mode := pmNotXOR; // pmXor;
Brush.Style := bsClear;
Pen.Color := clBlue; // clYellow;
end;
end;
end;
procedure Image1MouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
begin
if DoSelect then begin
if ssLeft in Shift then
begin
Image1.Canvas.Rectangle(SelX,SelY,SelX1,SelY1);
SelX1 := X;
SelY1 := Y;
Image1.Canvas.Rectangle(SelX,SelY,SelX1,SelY1);
end;
end;
end;
first you need to have a Bitmap into memory(hidden) which you manipulate so the "flicker" effect won't appear. Second you need to apply some darken algorithm on the bitmap the you display and copy the selection from original Bitmap to the visible Bitmap.
In other words:
OffsetBitmap(original bitmap) copy to visible Bitmap.
when selection occurs:
apply darken effect to visible Bitmap
copy the selected rectangle from OFFSETBITMAP to the visible bitmap so you will have your selection with original light intensity.
Hope this helps to some degree -- implementing this takes a bit of time which I don't have right now.