I am using LineDDA to draw animated selection:
procedure TFormMain.DrawMarchingAnts;
begin
AMarchingAntsCounter := AMarchingAntsCounterStart;
// Use LineDDA to draw each of the 4 edges of the rectangle
LineDDA(AMarchingAntsPointA.X, AMarchingAntsPointA.Y, AMarchingAntsPointB.X, AMarchingAntsPointA.Y,
#MarchingAnts, LongInt(
AMarchingAntsCanvas));
LineDDA(AMarchingAntsPointB.X, AMarchingAntsPointA.Y, AMarchingAntsPointB.X, AMarchingAntsPointB.Y,
#MarchingAnts, LongInt(
AMarchingAntsCanvas));
LineDDA(AMarchingAntsPointB.X, AMarchingAntsPointB.Y, AMarchingAntsPointA.X, AMarchingAntsPointB.Y,
#MarchingAnts, LongInt(
AMarchingAntsCanvas));
LineDDA(AMarchingAntsPointA.X, AMarchingAntsPointB.Y, AMarchingAntsPointA.X, AMarchingAntsPointA.Y,
#MarchingAnts, LongInt(
AMarchingAntsCanvas));
if AMarchingAntsPointB.X > AMarchingAntsPointA.X then
ARubberbandVisible := True
else
ARubberbandVisible := False;
end;
Is there a function to add animated ellipses to the corners of the rect for grip points?
You want an animated "marching ants" circle? Then create a custom pen style. For example, as follows:
unit Unit1;
interface
uses
Windows, Classes, Graphics, Controls, Forms, ExtCtrls, Math;
type
TForm1 = class(TForm)
Timer1: TTimer;
procedure FormPaint(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
FMarkBrush: LOGBRUSH;
FMarkPen: HPEN;
FPenStyle: array[0..1] of Integer;
FStartAngle: Single;
end;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
FPenStyle[0] := 4;
FPenStyle[1] := 4;
FMarkBrush.lbStyle := BS_SOLID;
FMarkBrush.lbColor := ColorToRGB(clBlue);
FMarkPen := ExtCreatePen(PS_GEOMETRIC or PS_USERSTYLE, 1, FMarkBrush, 2,
#FPenStyle);
Canvas.Pen.Handle := FMarkPen;
end;
procedure TForm1.FormPaint(Sender: TObject);
var
X: Integer;
Y: Integer;
begin
Canvas.FillRect(Rect(0, 0, 50, 50));
X := Round(25 * (1 + Cos(FStartAngle)));
Y := Round(25 * (1 + Sin(FStartAngle)));
Canvas.Arc(0, 0, 50, 50, X, Y, X, Y);
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
FStartAngle := FStartAngle + DegToRad(5);
Invalidate;
end;
end.
Related
I would need to color an image as in the example below.
I would need to apply this transformation in memory, after loading the image from a file.
An example of what I would like to achieve can be found at the following link (from which I took the attached image). Another site that implements the functionality that interests me: link
The color of the filter must be customizable.
I also have the ImageEn libraries available from which I started to do some tests, using the CastColorRange function, which however does not give me the expected result
var
FIMageEn: TImageEn;
...
procedure TTest.ApplyColorMask(const ARGBFilter: TRGB);
begin
FIMageEn.Proc.CastColorRange(FProcOverrideColorStartRange, // BeginColor
FProcOverrideColorEndRange, // EndColor
ARGBFilter); // Filter
end;
The problem with the piece of code shown above is that the function requires a range of colors in rgb format, but since the images are all different from each other, I don't know what range to set
You don't need a third-party library for this.
It looks like the desired transformation is to set the per-pixel hue (H) to a fixed value, preserving saturation (S) and value (V in the HSV colour model).
So, you merely need some RGB<->HSV conversion functions. Personally, I use my own, but I bet you can find plenty examples on the web.
Having access to such conversion functions, the rest is easy:
unit Unit6;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs;
type
TForm1 = class(TForm)
procedure FormResize(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormPaint(Sender: TObject);
procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
procedure FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
protected
private
FBitmap, FBitmap2: TBitmap;
FX: Integer;
public
procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;
end;
var
Form1: TForm1;
implementation
uses
Math, ascolors;
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
FBitmap := TBitmap.Create;
FBitmap.LoadFromFile('K:\sample.bmp');
FBitmap2 := TBitmap.Create;
FBitmap2.Assign(FBitmap);
FBitmap2.PixelFormat := pf32bit;
{$POINTERMATH ON}
for var y := 0 to FBitmap2.Height - 1 do
begin
var sl: PRGBQuad := FBitmap2.ScanLine[y];
for var x := 0 to FBitmap2.Width - 1 do
begin
var ColorRgb := TRGB.Create(sl[x].rgbRed / 255, sl[x].rgbGreen / 255, sl[x].rgbBlue / 255);
var ColorHsv := THSV(ColorRgb);
ColorHsv.Hue := 0;
ColorRgb := TRGB(ColorHsv);
sl[x].rgbRed := Round(255 * ColorRgb.Red);
sl[x].rgbGreen := Round(255 * ColorRgb.Green);
sl[x].rgbBlue := Round(255 * ColorRgb.Blue);
end;
end;
FX := FBitmap.Width div 2;
ClientWidth := FBitmap.Width;
ClientHeight := FBitmap.Height;
end;
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
FX := X;
Invalidate;
end;
procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
begin
if csLButtonDown in ControlState then
begin
FX := X;
Invalidate;
end;
end;
procedure TForm1.FormPaint(Sender: TObject);
begin
BitBlt(
Canvas.Handle,
0,
0,
Min(FBitmap.Width, FX),
FBitmap.Height,
FBitmap.Canvas.Handle,
0,
0,
SRCCOPY
);
BitBlt(
Canvas.Handle,
FX,
0,
Max(0, FBitmap.Width - FX),
FBitmap.Height,
FBitmap2.Canvas.Handle,
FX,
0,
SRCCOPY
);
end;
procedure TForm1.FormResize(Sender: TObject);
begin
Invalidate;
end;
procedure TForm1.WMEraseBkgnd(var Message: TWMEraseBkgnd);
begin
Message.Result := 1;
end;
end.
I want to create a slide effect: one bitmap is painted from right to left on a form's canvas. For this I use BitBlt.
I call this function in a Timer (20ms):
var ViewPort: TRect;
ViewPort.Left := 0;
ViewPort.Top := 0;
ViewPort.Width := 1400;
ViewPort.Height := 900;
x: integer := spnStep.Value; //SpinBox.Value = 10
procedure TfrmTester.Slide(BMP: TBitmap; ViewPort: TRect);
begin
Inc(x, spnStep.Value);
if x >= ViewPort.Width then
begin
x:= ViewPort.Width;
Timer.Enabled:= FALSE;
end;
BitBlt(frmTester.Canvas.Handle,
ViewPort.Width-x, 0, // X, Y
x, ViewPort.Height, // cX, cY
BMP.Canvas.Handle, 0, 0, SRCCOPY);
end;
However, the image does not progress smoothly. It has some kind of flicker, but not the kind of flicker that we know in the VCL. It is difficult to describe it. It is like the image moves two pixels forward and then one pixel backward.
How to make the image move smoothly?
Could the actually be caused by the refresh rate of the monitor?
Update: I don't know why, but it is caused by the timer.
If I call Slide() in a 'for' loop then the animation is smooth.
I know that the timer has an accuracy of ~15ms, but I still don't get it why it makes the image to shimmer.
If I add a sleed(1) inside the loop the shimmer effect appears again, and it is even worse. It really looks like the image is drawn twice.
First, you should only paint on the form in the form's OnPaint handler. I don't know if you do that or not, but you should do so.
Second, you cannot really rely on the temporal distance between successive WM_TIMER messages being very precise or even constant. So it is better to check the actual time each time you paint. For instance, you may use the formula Position = Original Position + Velocity × Time known from school physics.
Also, to avoid flickering, you should probably handle WM_ERASEBKGND.
Putting these together,
unit AnimatedRabbit;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms, Dialogs, ExtCtrls;
type
TMainForm = class(TForm)
Timer1: TTimer;
procedure FormCreate(Sender: TObject);
procedure FormPaint(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
private
FRabbit: TBitmap;
FStartTime: TDateTime;
const
Speed = -100;
function GetRabbitLeft: Double;
protected
procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;
public
end;
var
MainForm: TMainForm;
implementation
uses
DateUtils;
{$R *.dfm}
procedure TMainForm.FormCreate(Sender: TObject);
begin
FRabbit := TBitmap.Create;
FRabbit.LoadFromFile('K:\rabbit.bmp');
FStartTime := Now;
end;
procedure TMainForm.FormDestroy(Sender: TObject);
begin
FreeAndNil(FRabbit);
end;
procedure TMainForm.FormPaint(Sender: TObject);
var
x: Integer;
begin
x := Round(GetRabbitLeft);
BitBlt(
Canvas.Handle,
x,
0,
FRabbit.Width,
FRabbit.Height,
FRabbit.Canvas.Handle,
0,
0,
SRCCOPY
);
Canvas.Brush.Color := Color;
if x > 0 then
Canvas.FillRect(Rect(0, 0, x - 1, ClientHeight));
if x + FRabbit.Width < ClientWidth then
Canvas.FillRect(Rect(x + FRabbit.Width, 0, ClientWidth, ClientHeight));
end;
function TMainForm.GetRabbitLeft: Double;
begin
Result := ClientWidth + Speed * SecondSpan(Now, FStartTime);
end;
procedure TMainForm.Timer1Timer(Sender: TObject);
begin
Invalidate;
if GetRabbitLeft + FRabbit.Width < 0 then
Timer1.Enabled := False;
end;
procedure TMainForm.WMEraseBkgnd(var Message: TWMEraseBkgnd);
begin
Message.Result := 1;
end;
end.
I think this is as good as you can make it using GDI (a graphics API from the 1980s). I bet it will look better in Direct2D (or OpenGL, if you prefer that).
Update
After further investigation, I suspect that the usual timer isn't good enough. The problem is two-fold: (1) The best FPS obtainable by a normal timer is too low. (2) The fact that the duration between two consecutive WM_TIMER messages isn't constant causes visual issues.
If I instead use a high-resolution multimedia timer, ignoring the fact that they are deprecated, I get a nicer result:
unit AnimatedRabbit;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms, Dialogs, ExtCtrls;
const
WM_DOINVALIDATE = WM_USER + 1;
type
TMainForm = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormPaint(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FRabbit: TBitmap;
FStartTime: TDateTime;
FMMEvent: Cardinal;
const
Speed = -100;
function GetRabbitLeft: Double;
protected
procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;
procedure WMDoInvalidate(var Message: TMessage); message WM_DOINVALIDATE;
public
end;
var
MainForm: TMainForm;
implementation
uses
DateUtils, MMSystem, Math;
{$R *.dfm}
procedure RepaintFunc(wTimerID: UINT; msg: UINT; dwUser: NativeUINT;
dw1, dw2: NativeUINT); stdcall;
begin
PostMessage(MainForm.Handle, WM_DOINVALIDATE, 0, 0);
end;
procedure TMainForm.FormCreate(Sender: TObject);
const
TargetResolution = 1;
var
tc: TTimeCaps;
res: Cardinal;
begin
FRabbit := TBitmap.Create;
FRabbit.LoadFromFile('K:\rabbit.bmp');
FStartTime := Now;
if timeGetDevCaps(#tc, SizeOf(tc)) <> TIMERR_NOERROR then
Exit;
res := EnsureRange(TargetResolution, tc.wPeriodMin, tc.wPeriodMax);
if timeBeginPeriod(res) <> TIMERR_NOERROR then
Exit;
FMMEvent := timeSetEvent(10, res, RepaintFunc, 0, TIME_PERIODIC);
end;
procedure TMainForm.FormDestroy(Sender: TObject);
begin
timeKillEvent(FMMEvent);
FreeAndNil(FRabbit);
end;
procedure TMainForm.FormPaint(Sender: TObject);
var
x: Integer;
begin
x := Round(GetRabbitLeft);
BitBlt(
Canvas.Handle,
x,
0,
FRabbit.Width,
FRabbit.Height,
FRabbit.Canvas.Handle,
0,
0,
SRCCOPY
);
Canvas.Brush.Color := Color;
if x > 0 then
Canvas.FillRect(Rect(0, 0, x - 1, ClientHeight));
if x + FRabbit.Width < ClientWidth then
Canvas.FillRect(Rect(x + FRabbit.Width, 0, ClientWidth, ClientHeight));
end;
function TMainForm.GetRabbitLeft: Double;
begin
Result := ClientWidth + Speed * SecondSpan(Now, FStartTime);
end;
procedure TMainForm.WMDoInvalidate(var Message: TMessage);
begin
Invalidate;
end;
procedure TMainForm.WMEraseBkgnd(var Message: TWMEraseBkgnd);
begin
Message.Result := 1;
end;
end.
Update 2
And here is the non-deprecated version:
unit AnimatedRabbit;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms, Dialogs, ExtCtrls;
const
WM_DOINVALIDATE = WM_USER + 1;
type
TMainForm = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormPaint(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FRabbit: TBitmap;
FStartTime: TDateTime;
FTimer: THandle;
const
Speed = -100;
function GetRabbitLeft: Double;
protected
procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;
procedure WMDoInvalidate(var Message: TMessage); message WM_DOINVALIDATE;
public
end;
var
MainForm: TMainForm;
implementation
uses
DateUtils, Math;
{$R *.dfm}
procedure RepaintFunc(Context: Pointer; Success: Boolean); stdcall;
begin
PostMessage(MainForm.Handle, WM_DOINVALIDATE, 0, 0);
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
FRabbit := TBitmap.Create;
FRabbit.LoadFromFile('K:\rabbit.bmp');
FStartTime := Now;
if not CreateTimerQueueTimer(FTimer, 0, RepaintFunc, nil, 0, 10, 0) then
RaiseLastOSError;
end;
procedure TMainForm.FormDestroy(Sender: TObject);
begin
DeleteTimerQueueTimer(0, FTimer, INVALID_HANDLE_VALUE);
FreeAndNil(FRabbit);
end;
procedure TMainForm.FormPaint(Sender: TObject);
var
x: Integer;
begin
x := Round(GetRabbitLeft);
BitBlt(
Canvas.Handle,
x,
0,
FRabbit.Width,
FRabbit.Height,
FRabbit.Canvas.Handle,
0,
0,
SRCCOPY
);
Canvas.Brush.Color := Color;
if x > 0 then
Canvas.FillRect(Rect(0, 0, x - 1, ClientHeight));
if x + FRabbit.Width < ClientWidth then
Canvas.FillRect(Rect(x + FRabbit.Width, 0, ClientWidth, ClientHeight));
end;
function TMainForm.GetRabbitLeft: Double;
begin
Result := ClientWidth + Speed * SecondSpan(Now, FStartTime);
end;
procedure TMainForm.WMDoInvalidate(var Message: TMessage);
begin
Invalidate;
end;
procedure TMainForm.WMEraseBkgnd(var Message: TWMEraseBkgnd);
begin
Message.Result := 1;
end;
end.
Also, I previously said that the precise result depends on CPU, GPU, OS, and monitor. But it also depends on the eye and brain. The thing that makes this animation require such a high-quality timer is the fact that the motion is a simple translation with constant velocity, and the eye + brain can easily spot any imperfection. If we had animated a bouncing ball or SHM, an old-school timer would have been enough.
You should not be drawing on the Form's Canvas from outside of its OnPaint event at all. All of the drawing should be in the OnPaint event only. Have your timer save the desired information into variables that the Form can access, and then Invalidate() the Form, and let its OnPaint event draw the image using the latest saved information.
Alternatively, simply display your BMP inside a TImage control, and then have the timer set that control's Left/Top/Width/Height properties as needed. Let the TImage handle the drawing of the image for you.
You can use AnimateWindow
Here's the DFM. Just add client aligned TPicture inside the TPanel
object Form30: TForm30
Left = 0
Top = 0
Caption = 'Form30'
ClientHeight = 337
ClientWidth = 389
Color = clBtnFace
DoubleBuffered = True
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
object Panel1: TPanel
Left = 0
Top = 0
Width = 389
Height = 289
Align = alTop
BevelOuter = bvNone
Color = clRed
FullRepaint = False
ParentBackground = False
ShowCaption = False
TabOrder = 0
Visible = False
end
object Button1: TButton
Left = 136
Top = 304
Width = 75
Height = 25
Caption = 'Button1'
TabOrder = 1
OnClick = Button1Click
end
end
And the Button1.OnClick handler:
procedure TForm30.Button1Click(Sender: TObject);
begin
AnimateWindow(Panel1.Handle, 1000, AW_SLIDE or AW_HOR_POSITIVE or AW_ACTIVATE);
end;
I do not know how to call an interactive panel of tools like TeamViewer has. My question is very objective: How can I create a interactive panel where the panel will hide/show at any moment?
Example:
EDIT:
I found a possible solution (code below). Now I want to insert a "Button" glued on the right side and below Panel. How can I make this?
procedure TForm1.btn1Click(Sender: TObject);
begin
AnimateWindow(Panel1.Handle, 800, AW_SLIDE or AW_VER_NEGATIVE or AW_HIDE);
end;
procedure TForm1.btn2Click(Sender: TObject);
begin
AnimateWindow(Panel1.Handle, 800, AW_SLIDE or AW_VER_POSITIVE or AW_ACTIVATE);
end;
type
TForm1 = class(TForm)
pnl1: TPanel;
btn1: TButton;
procedure btn1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.btn1Click(Sender: TObject);
begin
if btn1.Caption = 'H' then
begin
btn1.Top := 0;
btn1.Caption := 'S';
AnimateWindow(Pnl1.Handle, 400, AW_SLIDE or AW_VER_NEGATIVE or AW_HIDE);
end
else
begin
btn1.Top:= pnl1.Height;
btn1.Caption := 'H';
AnimateWindow(Pnl1.Handle, 400, AW_SLIDE or AW_VER_POSITIVE or AW_ACTIVATE);
end;
end;
end.
This was my solution:
I'm still using AnimateWindow api.
On Button properties, set right = 0
When Panel is visible, the Button have top := Panel.Height
By last, when Panel is no-visible (hidden), Button have top := 0
Try this:
unit NP;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls;
type
TMainFrm = class(TForm)
Timer1: TTimer;
Timer2: TTimer;
procedure FormCreate(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
procedure Timer2Timer(Sender: TObject);
private
public
end;
var
MainFrm: TMainFrm;
Range: integer;
implementation
{$R *.dfm}
procedure TMainFrm.FormCreate(Sender: TObject);
begin
Width := 255;
Height := Screen.Height;
Left := 0 - Width;
Top := 0;
Range := 0;
Timer1.Enabled := True;
Timer2.Enabled := True;
MainFrm.Show;
end;
procedure TMainFrm.Timer1Timer(Sender: TObject);
var
pos: TPoint;
begin
GetCursorPos(pos);
if (pos.X < 10) and (MainFrm.Left < 0) then
begin
Range := 20;
MainFrm.Show;
end;
if (Range <> 0) then
MainFrm.Left := MainFrm.Left + Range;
if MainFrm.Left < 0 - MainFrm.Width then
begin
Range := 0;
MainFrm.Left := 0 - MainFrm.Width;
MainFrm.Hide;
end;
if (Range = 20) and (MainFrm.Left >= 0) then
begin
Range := 0;
MainFrm.Left := 0;
end;
end;
procedure TMainFrm.Timer2Timer(Sender: TObject);
var
pos: TPoint;
begin
GetCursorPos(pos);
if pos.X > MainFrm.Width then
Range := -20;
end;
end.
Axel
I've been searching this quite a while but couldn't get the answer.
I want to draw a polygon on an image, but I want to do this with by creating points;
With the MouseCursor create this specific points, and with a button draw a line along these points;
I found this:
var
Poly: array of TPoint;
begin
// Allocate dynamic array of TPoint
SetLength(Poly, 6);
// Set array elements
Poly[0] := Point(10, 10);
Poly[1] := Point(30, 5);
Poly[2] := Point(100, 20);
Poly[3] := Point(120, 100);
Poly[4] := Point(50, 120);
Poly[5] := Point(10, 60);
// Pass to drawing routine
Canvas.Polygon(Poly);
// Redim if needed
SetLength(Poly, 7);
Poly[6] := Point(1, 5);
// Pass to drawing routine
Canvas.Polygon(Poly);
end;
This is what I want, but the difference is the Point[1], Point[2], etc is given by the user with a MouseEvent.
You might superimpose a Paintbox over your image and use a code like this
unit Unit3;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls, StdCtrls;
type
TPointArray=array of TPoint;
TForm3 = class(TForm)
Image1: TImage;
PaintBox1: TPaintBox;
Button1: TButton;
procedure PaintBox1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure PaintBox1Paint(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
{ Private-Deklarationen }
FPointArray:TPointArray;
public
{ Public-Deklarationen }
end;
var
Form3: TForm3;
implementation
{$R *.dfm}
procedure TForm3.Button1Click(Sender: TObject);
begin
PaintBox1.Visible := false;
Image1.Canvas.Polygon(FPointArray);
end;
procedure TForm3.PaintBox1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
SetLength(FPointArray,Length(FPointArray)+1);
FPointArray[High(FPointArray)].X := X;
FPointArray[High(FPointArray)].Y := Y;
Paintbox1.Invalidate;
end;
procedure TForm3.PaintBox1Paint(Sender: TObject);
var
i:Integer;
begin
PaintBox1.Canvas.Brush.Style := bsClear; //as suggested by TLama
PaintBox1.Canvas.Polygon(FPointArray);
for I := 0 to High(FPointArray) do
begin
PaintBox1.Canvas.TextOut(FPointArray[i].X-5,FPointArray[i].y-5,IntToStr(i));
end;
end;
end.
Make an array of points managed by your form. Declare a dynamic-array field in your form class:
private
FPoly: array of TPoint;
In your OnClick event, lengthen the array and append a new coordinate to it:
procedure TFruitForm.ImageClick(Sender: TObject);
var
p: TPoint;
begin
p := ...;
SetLength(FPoly, Length(FPoly) + 1);
FPoly[High(FPoly)] := p;
end;
To assign p, see How do I get the coordinates of the mouse when a control is clicked?
Instead of an array, you might also consider using a generic list: TList<TPoint>.
I'm trying to make a cropping tool that will look as follow:
Original Image:
Crop tool - This is what I want:
Notice that the cropping area is showing the original colors, and around the colors are dim.
What I did is to place a TShape over my TImage with properties:
object Shape1: TShape
Brush.Color = clSilver
Pen.Mode = pmMask
Pen.Style = psDot
end
I plan to use the TShape to make the re-sizing/coping control.
This is how it looks in Delphi:
As you can see, it does not looks good (colors palette looks dithered), but the main problem that I need the dim area to be around the crop area, not in the center. I have tried to cover the whole TImage with another TShpae, tried different Pen.Mode combinations but there are no good results, and I think my method/approach is bad.
Do you have any ideas on how to achieve the desired behavior?
a little part is missing here, but should not be a problem to add...
unit Unit3;
// 20121108 by Thomas Wassermann
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls, jpeg;
type
TForm3 = class(TForm)
Image1: TImage;
PaintBox1: TPaintBox;
procedure FormCreate(Sender: TObject);
procedure PaintBox1Paint(Sender: TObject);
procedure PaintBox1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
procedure PaintBox1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
private
{ Private-Deklarationen }
FDownPoint, FCurrentPoint: TPoint;
public
{ Public-Deklarationen }
end;
var
Form3: TForm3;
implementation
uses Math;
{$R *.dfm}
procedure TForm3.FormCreate(Sender: TObject);
begin
PaintBox1.BringToFront;
end;
type
pRGBQuadArray = ^TRGBQuadArray;
TRGBQuadArray = ARRAY [0 .. $EFFFFFF] OF TRGBQuad;
Procedure SetAlpha(bmp: TBitMap; Alpha: Byte; R: TRect);
var
pscanLine32: pRGBQuadArray;
i, j: Integer;
begin
bmp.PixelFormat := pf32Bit;
bmp.HandleType := bmDIB;
bmp.ignorepalette := true;
bmp.alphaformat := afDefined;
for i := 0 to bmp.Height - 1 do
begin
pscanLine32 := bmp.Scanline[i];
for j := 0 to bmp.Width - 1 do
begin
if (j >= R.Left) and (j <= R.Right) and (i >= R.Top) and (i <= R.Bottom) then
begin
pscanLine32[j].rgbReserved := 0;
pscanLine32[j].rgbBlue := 0;
pscanLine32[j].rgbRed := 0;
pscanLine32[j].rgbGreen := 0;
end
else
begin
pscanLine32[j].rgbReserved := Alpha;
pscanLine32[j].rgbBlue := Alpha;
pscanLine32[j].rgbRed := Alpha;
pscanLine32[j].rgbGreen := Alpha;
end;
end;
end;
end;
procedure TForm3.PaintBox1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
FDownPoint.X := X;
FDownPoint.Y := Y;
FCurrentPoint := FDownPoint;
PaintBox1.Invalidate;
end;
procedure TForm3.PaintBox1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
if ssLeft in Shift then
begin
FCurrentPoint.X := X;
FCurrentPoint.Y := Y;
PaintBox1.Invalidate;
end;
end;
procedure TForm3.PaintBox1Paint(Sender: TObject);
var
bmp: TBitMap;
SelRect: TRect;
begin
bmp := TBitMap.Create;
try
bmp.Width := PaintBox1.Width;
bmp.Height := PaintBox1.Height;
if (FCurrentPoint.X = FDownPoint.X) and (FCurrentPoint.Y = FDownPoint.Y) then
SelRect := PaintBox1.BoundsRect
else
begin
SelRect.Left := Min(FCurrentPoint.X, FDownPoint.X);
SelRect.Top := Min(FCurrentPoint.Y, FDownPoint.Y);
SelRect.Right := Max(FCurrentPoint.X, FDownPoint.X);
SelRect.Bottom := Max(FCurrentPoint.Y, FDownPoint.Y);
end;
SetAlpha(bmp, 140, SelRect);
PaintBox1.Canvas.Draw(0, 0, bmp);
finally
bmp.Free;
end;
end;
end.
The attempt on this solution is to use a overlying paintbox, same clientrect as the image, for all the drawing and selection. By using the coordinates generated by mouse/down/move a semitransparent bitmap is created, which is full transparent in the selected rect. After generation it's painted on the paintbox. Further paintings could be done there e.g. frames, anchors, crosshair. Any user action would have to be caught in mousedown, depending of the selected part ,e.g. an anchor a sizing of the rect could be done.
Usually I'd prefer GDI+ for requests like this, but as shown, no additional units are required. Source: http://www.bummisoft.de/download/transparenteauswahl.zip