Delphi 7 : Center form position on multiple monitors - delphi

I have a TForm and I set the "Position" to poMainFormCenter.
When I open that form, it is displayed correctly in the center of the Main Form.
But, on multiple screens (2 monitors), when I put the application in the secondary monitor, that form is not displayed in the center of the Main Form.
It is displayed still in the primary monitor, positioned in in the edge of the screen.
There is nothing fancy on my app, I only set that Position property.
Anyone know how to fix this?
I am using Delphi 7 and Windows XP SP3.

Jlouro has the right idea except for looking at the mouse. Screen.Monitors[] contains information on each screen.
I have a standard procedure that goes through the list of monitors and figures out where the upper left corner is to decide what monitor to put it on. While my code does not center (I was simply after ensuring that the window is entirely within whatever monitor it came up on) the idea remains the same. Note that you must consider the case where the window shows up not on ANY monitor--I handle that by throwing it to the first monitor. (This would come about when the saved position is on a monitor that doesn't exist anymore--either removed or running on a different machine.)
It's been a long time since I messed with this, it hasn't given me any trouble in ages and so I haven't tested it on anything more recent than XP/Delphi 7.
Note that this is only about ensuring that the form is visible and entirely on one monitor, there is no attempt to center it.
Function PointInBox(x, y, x1, y1, x2, y2 : Integer) : Boolean;
Begin
Result := (X >= X1) And (X <= X2) And (Y >= Y1) And (Y <= Y2);
End;
Function Overlapping(x11, y11, x12, y12, x21, y21, x22, y22 : Integer) : Boolean;
Var
tx1, ty1, tx2, ty2 : Integer;
Begin
Tx1 := Max(x11, x21);
Tx2 := Min(x12, x22);
Ty1 := Max(y11, y21);
Ty2 := Min(y12, y22);
Result := (Tx1 < Tx2) And (Ty1 < Ty2);
End;
Function GetWhere(Form : TForm) : Integer;
Var
Loop : Integer;
Where : Integer;
Begin
Where := -1;
For Loop := 1 to Screen.MonitorCount do
With Screen.Monitors[Loop - 1] do
If PointInBox(Form.Left, Form.Top, Left, Top, Left + Width - 1, Top + Height - 1) then
Where := Loop - 1;
If Where = -1 then // Top left corner is wild, check for anything
For Loop := 1 to Screen.MonitorCount do
With Screen.Monitors[Loop - 1] do
If Overlapping(Form.Left, Form.Top, Form.Left + Form.Width - 1, Form.Top + Form.Height - 1, Left, Top, Left + Width - 1, Top + Height - 1) then
Where := Loop - 1;
Result := Where;
End;
Procedure GetLimits(Where : Integer; var X, Y, WWidth, WHeight : Integer);
Var
R : TRect;
Begin
If Where < 0 then
Begin
SystemParametersInfo(Spi_GetWorkArea, 0, #R, 0);
X := R.Left;
Y := R.Top;
WWidth := R.Right - R.Left + 1;
WHeight := R.Bottom - R.Top + 1;
End
Else With Screen.Monitors[Where] do
Begin
X := Left;
Y := Top;
WWidth := Width;
WHeight := Height;
End;
End;
Procedure EnsureValidDisplay(Form : TForm);
Var
Left : Integer;
Top : Integer;
Width : Integer;
Height : Integer;
Where : WindowPlacement;
Begin
GetLimits(GetWhere(Form), Left, Top, Width, Height);
Where.Length := SizeOf(Where);
Where.Flags := 0;
GetWindowPlacement(Form.Handle, #Where);
If Form.Left < Left then
Where.rcNormalPosition.Left := Left
Else If Form.Left + Form.Width > Left + Width then
Where.rcNormalPosition.Left := Left + Width - Form.Width;
If Form.Top < Top then
Where.rcNormalPosition.Top := Top
Else If Form.Top + Form.Height > Top + Height then
Where.rcNormalPosition.Top := Top + Height - Form.Height;
If Form.Width > Width then
Where.rcNormalPosition.Right := Where.rcNormalPosition.Left + Width
Else
Where.rcNormalPosition.Right := Where.rcNormalPosition.Left + Form.Width;
If Form.Height > Height then
Where.rcNormalPosition.Bottom := Where.rcNormalPosition.Top + Height
Else
Where.rcNormalPosition.Bottom := Where.rcNormalPosition.Top + Form.Height;
SetWindowPlacement(Form.Handle, #Where);
End;

None of the other answers here mention the cause of the problem in the first place, which is a bug in the VCL. From forms.pas on my system, with some snipping for brevity:
procedure TCustomForm.CMShowingChanged(var Message: TMessage);
var
X, Y: Integer;
NewActiveWindow: HWnd;
CenterForm: TCustomForm;
begin
if (FPosition = poScreenCenter) or
((FPosition = poMainFormCenter) and (FormStyle = fsMDIChild)) then
begin
if FormStyle = fsMDIChild then
begin
X := (Application.MainForm.ClientWidth - Width) div 2;
Y := (Application.MainForm.ClientHeight - Height) div 2;
end else
begin
X := (Screen.Width - Width) div 2;
Y := (Screen.Height - Height) div 2;
end;
if X < 0 then X := 0;
if Y < 0 then Y := 0;
SetBounds(X, Y, Width, Height);
if Visible then SetWindowToMonitor;
end
else if FPosition in [poMainFormCenter, poOwnerFormCenter] then
begin
CenterForm := Application.MainForm;
if (FPosition = poOwnerFormCenter) and (Owner is TCustomForm) then
CenterForm := TCustomForm(Owner);
if Assigned(CenterForm) then
begin
X := ((CenterForm.Width - Width) div 2) + CenterForm.Left;
Y := ((CenterForm.Height - Height) div 2) + CenterForm.Top;
end else
begin
X := (Screen.Width - Width) div 2;
Y := (Screen.Height - Height) div 2;
end;
if X < 0 then X := 0;
if Y < 0 then Y := 0;
SetBounds(X, Y, Width, Height);
if Visible then SetWindowToMonitor;
end
else if FPosition = poDesktopCenter then
begin
if FormStyle = fsMDIChild then
begin
X := (Application.MainForm.ClientWidth - Width) div 2;
Y := (Application.MainForm.ClientHeight - Height) div 2;
end else
begin
X := (Screen.DesktopWidth - Width) div 2;
Y := (Screen.DesktopHeight - Height) div 2;
end;
if X < 0 then X := 0;
if Y < 0 then Y := 0;
SetBounds(X, Y, Width, Height);
end;
The key to this bug seems to be the following snippets, repeated several times in the function:
if X < 0 then X := 0;
if Y < 0 then Y := 0;
So, if you try to center the form on a monitor to the left or above the primary monitor (remember that origin is at upper-left corner of primary monitor), it will get snapped to the primary monitor from this check. It seems that this code wasn't updated when VCL was updated to support multiple monitors. Which is amusing, since two lines later are calls to SetWindowToMonitor.
The code was probably there from when only single monitors were supported in Windows 95 / Windows NT 4.0. In a single-monitor environment, negative coordinates are always off-screen, and it makes sense to snap to onscreen coordinates, which are always positive. However, the code fails miserably in the presence of multiple monitors, which allows for negative onscreen coordinates.
Working around this bug is left as an exercise to the reader. There are a number of possible solutions.

I use this on the create event:
C_FollowMouse :BOOLEAN=TRUE; // Global Const - Follow mouse. Opens App in the monitor where the mouse is.
C_Monitor :BYTE=0; // Default Monitor
Procedure TfrmMain.ScreenPOS;
Var pt:tpoint;
_lMonitor :BYTE;
Begin
if NOT Screen.MonitorCount > 1 then Begin
Position := poScreenCenter;
Exit;
End;
_lMonitor := C_Monitor;
if C_FollowMouse then Begin
_lMonitor := 0;
getcursorpos(pt);
if pt.X < 0 then
_lMonitor := 1;
End;
Left:= Screen.Monitors[_lMonitor].Left + Round( (Screen.Monitors[_lMonitor].Width - Width ) / 2);
Top:=Screen.Monitors[_lMonitor].Top + Round( (Screen.Monitors[_lMonitor].Height - Height ) / 2)
End;
Just tested it with 2 monitors. Is all I have.
If you have more, post back the changes.

I was able to workaround this by using the code below on the Form OnActivate:
Self.Left := MainForm.Left + ((MainForm.Width div 2) - (Self.Width div 2));
Self.Top := MainForm.Top + ((MainForm.Height div 2) - (Self.Height div 2));
MainForm is the "main" form of the application.

Hey David you can use poOwnerFormCenter Instead of poMainFormCenter. It will Solve your problem. Read this post.

I know this is an old thread but I was just trying to fix this problem with regards to modal dialog forms and found the following to work (after reading the above post by James Johnson)
On OnActivate:
X := (Application.MainForm.ClientWidth - Width) div 2;
Y := (Application.MainForm.ClientHeight - Height) div 2;
self.SetBounds(x,y,self.width,self.height);

Related

Scan Line Out of Range Error for Bitmap. TJanDrawImage Component for a Paint-like Program

I am using the free JansDraw Components and when the executable runs, it throws AV error. I could locate the error to specifically the loop block of the code (not the initial block of assignment statements).
procedure TjanDrawImage.colorcircle(var bm:TBitmap;center:tpoint;radius,mode:integer);
var p,p0,p1:pbytearray;
dx,x,y,w,h,i,j,sum,c:integer;
cm,tm:tbitmap;
Rs,Rd:trect;
begin
x:=center.x;
y:=center.y;
w:=bm.width;
h:=bm.height;
cm:=tbitmap.create;
cm.width:=2*radius;
cm.height:=2*radius;
cm.PixelFormat :=FPixelFormat;
tm:=tbitmap.create;
tm.width:=2*radius;
tm.height:=2*radius;
tm.PixelFormat :=FPixelFormat;
tm.canvas.brush.color:=clblack;
tm.canvas.Ellipse (0,0,tm.width-1,tm.height-1);
tm.transparent:=true;
tm.TransparentColor :=clblack;
Rd:=rect(0,0,cm.width,cm.height);
Rs:=rect(x-radius,y-radius,x+radius,y+radius);
cm.canvas.CopyRect (Rd,bm.canvas,RS);
for j:=0 to cm.height-1 do begin
p:=cm.scanline[j];
if j>0 then p0:=cm.scanline[j-1];
if j<(h-1) then p1:=cm.scanline[j+1];
for i:=0 to cm.width-1 do begin
case mode of
0: //blue
begin
p[i*3+1]:=0;
p[i*3+2]:=0;
end;
1: //green
begin
p[i*3]:=0;
p[i*3+2]:=0;
end;
2: //red
begin
p[i*3]:=0;
p[i*3+1]:=0;
end;
3: //not blue
begin
p[i*3]:=0;
end;
4: //not green
begin
p[i*3+1]:=0;
end;
5: //not red
begin
p[i*3+2]:=0;
end;
6: //half blue
begin
p[i*3]:=p[i*3]*9 div 10;
end;
7: //half green
begin
p[i*3+1]:=p[i*3+1]*9 div 10;
end;
8: //half red
begin
p[i*3+2]:=p[i*3+2]*9 div 10;
end;
9:// darker
begin
p[i*3]:=round(p[i*3]*10 /11);
p[i*3+1]:=round(p[i*3+1]*10 / 11);
p[i*3+2]:=round(p[i*3+2]*10 /11);
end;
10:// lighter
begin
p[i*3]:=round(p[i*3]*11 / 10);
p[i*3+1]:=round(p[i*3+1]*11 / 10);
p[i*3+2]:=round(p[i*3+2]*11 / 10);
end;
11:// gray
begin
sum:=round((p[i*3]+p[i*3+1]+p[i*3+2])/ 3);
p[i*3]:=sum;
p[i*3+1]:=sum;
p[i*3+2]:=sum;
end;
12:// mix
begin
c:=p[i*3];
p[i*3]:=p[i*3+1];
p[i*3+1]:=p[i*3+2];
p[i*3+2]:=c;
end;
13://smooth
begin
if ((j>0) and (j<(h-1))and (i>0)and (i<(w-1))) then begin
p[i*3]:=round((p[(i-1)*3]+p[(i+1)*3]+p0[i*3]+p1[i*3]) /4);
p[i*3+1]:=round((p[(i-1)*3+1]+p[(i+1)*3+1]+p0[i*3+1]+p1[i*3+1]) /4);
p[i*3+2]:=round((p[(i-1)*3+2]+p[(i+1)*3+2]+p0[i*3+2]+p1[i*3+2]) / 4);
end;
end;
end;
end;
end;
cm.canvas.Draw (0,0,tm);
cm.transparent:=true;
cm.transparentcolor:=clwhite;
bm.Canvas.draw(x-radius,y-radius,cm);
cm.free;
tm.free;
end;
A linked question which is helpful is this - implementing scan line of bitmap corectly. It suggests to cast the pointers to NativeInt. The OP changed his code after answers, making it difficult to correlate old code with new code. I understand that my problem is due to some hard coded sequential access of pointers but I am really beginner to make sense of scan line or pointers. If you help me port this, these components will continue to be useful to everyone.
update after comment from #Renate Schaaf:
all the brush modes of the janDrawImage are working now, except for the below one. I was expecting a bigger problem but that didn't turn out to be the case. So modified the title of the question. #Renate Schaaf Can you please help fix the below one too. I tried but failed.
procedure TjanDrawImage.rimple(src,dst:tbitmap;amount:extended);
var ca,sa,a,dx,dy,r,rx,ry,sr,fr:extended;
w,h,x,y,cx,cy,i,j,c,ci:NativeInt;
p1,p2:pbytearray;
begin
w:=src.width;
h:=src.height;
cx:=w div 2;
cy:=h div 2;
if amount<1 then amount:=1;
fr:=cx/amount;
for y:=0 to h-1 do begin
p1:=src.ScanLine[y];
for x:=0 to w-1 do begin
dx:=x-cx;dy:=-(y-cx);
r:=sqrt(sqr(dx)+sqr(dy));
sr:=fr*sin(r/cx*amount*2*pi);
if (r+sr<cx) and (r+sr>0) then begin
a:=arctan2(dy,dx);
sincos(a,sa,ca);
i:=cx+round((r+sr)*ca);
j:=cy+round((r+sr)*sa);
p2:=dst.scanline[j];
c:=x*3;ci:=i*3;
p2[ci]:=p1[c];
p2[ci+1]:=p1[c+1];
p2[ci+2]:=p1[c+2];
end;
end;
end;
end;
When you do scanline operations, you always need to make sure that your pixel location is within the boundaries of your bitmap, particularly if you make geometric transformations.
So, in the last example you must clamp j to [0,h-1] and i to [0,w-1] using max(min(..)).
Also, you should set the size and pixelformat of src and dst to the same at the beginning.
I didn't really bother to find out what this ripple is supposed to do, but when I run an example it doesn't look like it's doing whatever it does right. For geometric transformations you need to work backwards, running through the pixels of the destination and figure out which pixel of the source needs to go there. Otherwise you end up with a destination that has holes, like here.
Edit:
Since I'm stuck with my project: I think this is the routine you really want to use. Note that I just switched the roles of src and dst and corrected some errors. It now adds a water ripple effect to the bitmap.
procedure rimple(src, dst: TBitmap; amount: extended);
var
ca, sa, a, dx, dy, r, sr, fr: extended;
w, h, x, y, cx, cy, i, j, c, ci: NativeInt;
p1, p2: pbytearray;
bits: integer;
begin
Assert(src.PixelFormat in [pf24bit, pf32bit],
'Device independent bitmap needed');
dst.PixelFormat := src.PixelFormat;
bits := 3;
if src.PixelFormat = pf32bit then
bits := 4;
w := src.width;
h := src.height;
dst.SetSize(w, h);
cx := w div 2;
cy := h div 2;
// in case somebody enters a negative amount
if abs(amount) < 1 then
amount := 1;
fr := cx / amount;
for y := 0 to h - 1 do
begin
// switched src and dst
p1 := dst.scanline[y]; // src.scanline[y];
for x := 0 to w - 1 do
begin
dx := x - cx;
// Corrected from dy:=-(y-cx)
dy := (y - cy);
r := sqrt(sqr(dx) + sqr(dy));
sr := fr * sin(r / cx * amount * 2 * pi);
// Omitted the following check
// if (r + sr < cx) and (r + sr > 0) then
begin
a := arctan2(dy, dx);
sincos(a, sa, ca);
i := max(min(cx + round((r + sr) * ca), w - 1), 0);
j := max(min(cy + round((r + sr) * sa), h - 1), 0);
// switched src and dst
p2 := src.scanline[j];
c := x * bits;
ci := i * bits;
p1[c] := p2[ci];
p1[c + 1] := p2[ci + 1];
p1[c + 2] := p2[ci + 2];
end;
end;
end;
end;

Open form at cursor position, Delphi

I am trying to figure out how to position a Form to open at a given mouse location, despite my monitor settings.
In the Form's OnCreate event, I have this:
procedure TSplashScreen.FormCreate(Sender: TObject);
Var
oMousePos: TPoint;
nLeft, nTop: Integer;
begin
Scaled := false;
PixelsPerInch := Screen.PixelsPerInch;
Scaled := true;
//Position:=poScreenCenter;
//center form for 2nd monitor //zzz
if (Screen.MonitorCount > 1) then //zzz
begin
GetCursorPos(oMousePos);
if (oMousePos.X > Screen.Width) or (oMousePos.X < 0) then
begin
Self.Position := poDesigned;
nLeft := Screen.Monitors[1].Left + Round(Screen.Monitors[1].Width / 2) - Round(Self.Width / 2);
nTop := Screen.Monitors[1].Top + Round(Screen.Monitors[1].Height / 2) - Round(Self.Height / 2);
Self.Left := nLeft;
Self.Top := nTop;
end;
end;
end;
When I have 2 monitors, and monitor 1 is set as primary monitor, the Form will open at the mouse cursor.
However, if I set monitor 2 to primary, the Form will always open on monitor 2.
If you just want to position the Form on the same monitor that the mouse cursor is currently in, use the Win32 API MonitorFromPoint() function (which is wrapped by the VCL's TScreen.MonitorFromPoint() method), eg:
procedure TSplashScreen.FormCreate(Sender: TObject);
var
r: TRect;
begin
if (Screen.MonitorCount > 1) then
begin
r := Screen.MonitorFromPoint(Mouse.CursorPos).WorkareaRect;
Self.Position := poDesigned;
Self.Left := r.Left + ((r.Width - Width) div 2);
Self.Top := r.Top + ((r.Height - Height) div 2);
{ alternatively:
Self.SetBounds(
r.Left + ((r.Width - Width) div 2),
r.Top + ((r.Height - Height) div 2),
Width, Height);
}
end else begin
Self.Position := poScreenCenter;
end;
end;

Delphi - Image move randomly inside desktop coordinates [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 6 years ago.
Improve this question
I want to created a small application, which should move images smoothly into the desktop coordinates.
I was wondering how can I limit that the image remains inside the desktop?
I have try like that to move the image:
procedure TForm1.Timer1Timer(Sender: TObject);
Var
X, Y :Integer;
begin
X:= random(2+1);
Y:= random(2+1);
Image1.Left:= Image1.Left + X;
Image1.Top:= Image1.Top + Y;
Image1.Refresh;
end;
Any help is appreciated.
Thanks.
Is you image placed over Windows Desktop - or over you TForm1 ? I guess the latter. So you would have to care about the WINDOW size, not the DESKTOP size.
type TForm1=class(TForm)
....
private
ImageMovesLeft, ImageMovesUp: Boolean;
end;
.....
procedure TForm1.Timer1Timer(Sender: TObject);
Var
dX, dY, NewLeft, NewTop :Integer;
FormSize: TRect;
begin
dX := random(2+1); // did you really mean "random(3)" or "1+random(2)" ???
dY := random(2+1);
FormSize := Self.ClientRect;
FormSize.Bottom := FormSize.Bottom - Image1.Height - 1;
FormSize.Right := FormSize.Right - Image1.Width - 1;
// now we have the "box" in which the Image's topleft corner must be
If ImageMovesLeft then dX := -dX;
If ImageMovesUp then dY := -dY;
NewLeft := Image1.Left + dX;
NewTop := Image1.Top + dY;
if ( NewTop >= FormSize.Top ) and ( NewTop <= FormSize.Bottom ) then begin
Image1.Top := NewTop; // we fit into the allowed box
end else begin
ImageMovesUp := not ImageMovesUp; // we did not fit and have to bounce back
end;
if ( NewLeft >= FormSize.Left ) and ( NewLeft <= FormSize.Right ) then begin
Image1.Left := NewLeft; // we fit into the allowed box
end else begin
ImageMovesLeft := not ImageMovesLeft; // we did not fit and have to bounce back
end;
end;
PS. In an unlikely case you really do need the Windows DESKTOP coordinates and not your Form coordinates you can get them at
http://docwiki.embarcadero.com/Libraries/XE7/en/Vcl.Forms.TScreen.DesktopRect
But to use that information you would have to solve another problem - how to place your Image1 over desktop and not over the form, which is much more complex for you. So I do not think you really meant Desktop....
UPD. The code above if very simple and easy to understand, but it makes few implicit assumptions to work correctly. Those assumptions are:
The window(form) size is fixed once for all, it would never be resized.
The imagebox size is fixed once for all, it would never be resized.
The window is larger than an imagebox in both dimensions.
Only our procedure can move the imagebox, there is nothing else that can move it.
Given those assumptions ( natural for fixed screen size computers many many years ago ) there is no need to analyze if the moving object got too left o too right, too above or too below. It only matters if the new coordinate is correct or not - if it is no more correct, then "bouncing" - reversing the direction without looking which one it was - is enough. But if, for example, user can suddenly resize the window and make it so small that the imagebox would fall outside of it - then this method would stuck infinitely switching directions, because the coordinates would always be incorrect given those very small changes "smooth" movement allows to have.
To adapt to possible sudden and large changes in geometry there can be a number of approaches, but the most simple one would be to make two changes: distinction between two cases of wrong coordinates (too little or too large now would be different cases) and instant jumps of the image into the allowed box when needed, even if the jump would be large and not-smooth.
procedure TForm1.Timer1Timer(Sender: TObject);
var
dX, dY, NewLeft, NewTop :Integer;
FormSize: TRect;
begin
dX := random(2+1); // did you really mean "random(3)" or "1+random(2)" ???
dY := random(2+1);
FormSize := Self.ClientRect;
FormSize.Bottom := FormSize.Bottom - Image1.Height - 1;
FormSize.Right := FormSize.Right - Image1.Width - 1;
// now we have the "box" in which the Image's topleft corner must be
If ImageMovesLeft then dX := -dX;
If ImageMovesUp then dY := -dY;
NewLeft := Image1.Left + dX;
NewTop := Image1.Top + dY;
if NewLeft > FormSize.Right then begin
ImageMovesLeft := True;
NewLeft := FormSize.Right;
end;
if NewLeft < FormSize.Left then begin
ImageMovesLeft := False;
NewLeft := FormSize.Left;
end;
if NewTop > FormSize.Bottom then begin
ImageMovesUp := True;
NewTop := FormSize.Bottom;
end;
if NewTop < FormSize.Top then begin
ImageMovesUp := False;
NewTop := FormSize.Top;
end;
Image1.Top := NewTop;
Image1.Left := NewLeft;
end;
UPD. Several controls moving.
type TControlledObject = record
obj: TControl;
MovesLeft, MovesUp: Boolean;
end;
type TForm1=class(TForm)
....
private
images: array of TControlledObject;
end;
procedure TForm1.FormShow(....);
begin
SetLength(images, 3);
with images[0] do begin
obj := Self.Image1;
MovesLeft := random >= 0.5;
MovesUp := random >= 0.5;
end;
with images[1] do begin
obj := Self.Image2;
MovesLeft := random >= 0.5;
MovesUp := random >= 0.5;
end;
with images[2] do begin
obj := Self.Image3;
MovesLeft := random >= 0.5;
MovesUp := random >= 0.5;
end;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var
i: Integer
begin
for i := 0 to Length(images)-1 do
MoveImage(images[i]);
end;
procedure TForm1.MoveImage(var ImgRec: TControlledObject);
var .....
begin
dX := random(2+1); // did you really mean "random(3)" or "1+random(2)" ???
dY := random(2+1);
FormSize := Self.ClientRect;
FormSize.Bottom := FormSize.Bottom - ImgRec.obj.Height - 1;
FormSize.Right := FormSize.Right - ImgRec.obj.Width - 1;
// now we have the "box" in which the Image's topleft corner must be
If ImgRec.MovesLeft then dX := -dX;
If ImgRec.MovesUp then dY := -dY;
....and so on. Finish the conversion from one to many as your home task.

Image "fade" in / out (Not opacity)

I want to "fade" in / out an image. But not opacity-wise.
Pictures say more than words:
Original image:
Desired image:
How can I do that programatically? Not the way like "use Bitmap.Canvas" but the mathematical approach. ("For dummies" if possible ... :D)
I want the image to have a fade-in / -out area, not linear increasing but "curvy". I guess it has something to do with Bezier curves? If yes, how would I setup the points to get a curve like that?
Or what would be your approach here?
Thanks for any help! :)
Here is roughly how you would go about doing it (as you said, you're looking for the logic and not the full implementation)
Create the basic shape outline: Create a partial sine-wave, such that the semi-period (half-wavelength: P1 = L/2) equals the length (x-coordinate size) of your image.
Add Overtones: Add to it another sine-function. This time with wavelength given by P2 = P1 / 2 + rnd where rnd is a random real number in the interval (-P1/4 , +P1 / 4)
Repeat: Now P2 becomes the new P1.
That way you can generate the 'wavy-waves' by modulating the main wave and you will get the top boundary.
You can change the sign and get the lower boundary.
The word you might be looking for (for the shape, i.e.) is Overtones. You could look up more on generating overtones for optics or acoustics.
This example for adding overtones to a straight line would give a better idea. The code above adds up these sinusoidal waves of randomly shortening periods to create the wave-on-wave effect (source)
Thanks again, hnk. You got me the right ideas. :)
I did a little work and ended up with this:
procedure GenerateOverlayEdges(ABitmap: TBitmap; ARadiusX, ARadiusY: Integer);
const
MAX_ANGLE = 90;
var
ShapePosition, ShapeIndex, ShapesCount,
Angle, X, Y, RadiusX, RadiusY, CenterY: Integer;
PolyPoints: Array of TPoint;
begin
ABitmap.Canvas.Pen.Color := clBlack;
ABitmap.Canvas.Brush.Color := clBlack;
//ABitmap.Canvas.Pen.Color := clWhite;
//ABitmap.Canvas.Brush.Color := clWhite;
RadiusX := ARadiusX;
RadiusY := ARadiusY;
CenterY := Round(ABitmap.Height / 2);
ShapesCount := Ceil(CenterY / RadiusY);
for ShapePosition := 1 to 4 do // 1 = TopLeft, 2 = BottomLeft, 3 = TopRight, 4 = BottomRight
for ShapeIndex := 0 to ShapesCount - 1 do // Index of current "stair"
begin
Finalize(PolyPoints);
SetLength(PolyPoints, MAX_ANGLE + 1);
for Angle := 0 to MAX_ANGLE do
begin
case ShapePosition of
1, 3:
begin
RadiusX := Abs(RadiusX);
RadiusY := Abs(RadiusY);
end;
2, 4:
begin
RadiusX := Abs(RadiusX);
RadiusY := -Abs(RadiusY);
end;
end;
X := Trunc(RadiusX * Cos(Angle * 2 * Pi / 360));
Y := Trunc(RadiusY * Sin(Angle * 2 * Pi / 360));
case ShapePosition of
1:
begin
X := X + ShapeIndex * RadiusX;
Y := Y + CenterY - RadiusY - ShapeIndex * RadiusY;
end;
2:
begin
X := X + ShapeIndex * RadiusX;
Y := Y + CenterY - RadiusY + ShapeIndex * Abs(RadiusY);
end;
3:
begin
X := ABitmap.Width - X - ShapeIndex * Abs(RadiusX);
Y := Y + CenterY - RadiusY - ShapeIndex * RadiusY;
end;
4:
begin
X := ABitmap.Width - X - ShapeIndex * Abs(RadiusX);
Y := Y + CenterY + Abs(RadiusY) + ShapeIndex * Abs(RadiusY);
end;
end;
// Add points as part of a Polyon
PolyPoints[Angle] := Point(X, Y);
end;
// Set Y to the Y borders for the very first and last point of the polygon so we will get a "closed" shape
case ShapePosition of
1, 3:
begin
PolyPoints[0] := Point(PolyPoints[0].X, 0);
PolyPoints[Angle - 1] := Point(PolyPoints[Angle - 1].X, 0);
end;
2, 4:
begin
PolyPoints[0] := Point(PolyPoints[0].X, ABitmap.Height);
PolyPoints[Angle - 1] := Point(PolyPoints[Angle - 1].X, ABitmap.Height);
end;
end;
// Draw the poly points ... and fill the background at the same time
ABitmap.Canvas.Polygon(PolyPoints);
end;
Finalize(PolyPoints);
end;
Usage:
var
Bmp: TBitmap;
begin
Bmp := TBitmap.Create;
try
Bmp.LoadFromFile('C:\Temp\Osc 2.bmp'); // Original Oscilloscope Image
GenerateOverlayEdges(Bmp, 15, 20);
Bmp.SaveToFile('C:\Temp\Osc 3.bmp');
finally
Bmp.Free;
end;
end;
By changing the RadiusX and RadiusY parameters of the GenerateOverlayEdges function I can adjust the results:
8x8:
15x20:
20x10:

FastReport - How to displaying data in the form of table?

How can i display data in the form of table in the FastReport ?
Edit
I mean ,I want to create a report like this : (with tabular format).
The easiest way to use FR wizard
from FR File menu > new > Standard report wizard
when you reach the "Layout" page, choose tabular from layout then OK
I think you need to build the grid yourself. Here's a bit of code that builds a grid layout to get you started. You will need to adjust the column widths and add the formatting code (memo.frame) to get your desired look.
procedure CreateHeader(frxDataset: TfrxDBDataSet; Page: TfrxReportPage);
var
i: Integer;
X, Y, ThisWidth: Extended;
HeaderMemo: TfrxCustomMemoView;
Column: TcxGridDBColumn;
begin
Band := TfrxPageHeader.Create(Page);
Band.CreateUniqueName;
Band.SetBounds(0, 0, 0, fr01cm * 7);
Band.Height := edtHeightHeader.Value;
HeaderMemo := CreateMemo(Band);
HeaderMemo.SetBounds(0, 0, PageWidth, 0);
// Set memo style
// Or just add a frame HeaderMemo.Frame....
HeaderMemo.Style := 'Header line';
X := 0;
Y := 0;
Memo := CreateMemo(Band);
Memo.SetBounds(0, Y, X, fr01cm * 6);
Memo.Height := Band.Height - 1;
for i := 0 to pred(frxDataset.Fields.Count) do
begin
ThisWidth := 100;
Memo := CreateMemo(Band);
Memo.SetBounds(X, Y, ThisWidth, fr01cm * 6);
Memo.Text := frxDataset.Fields[i].FieldName;
// Set memo style
// Or just add a frame HeaderMemo.Frame....
Memo.Style := 'Header';
Memo.Height := Band.Height - 1;
X := X + ThisWidth;
end;
HeaderMemo.Height := Band.Height;
end;
procedure CreateFastReportDataBand(frxDataset: TfrxDBDataSet; Page: TfrxReportPage);
var
i: Integer;
X, Y, ThisWidth: Extended;
begin
Band := TfrxMasterData.Create(Page);
Band.CreateUniqueName;
Band.SetBounds(0, CurY, 0, 0);
Band.Height := edtHeightData.Value;
TfrxMasterData(Band).frxDataset := frxDataset;
X := 0;
Y := 0;
for i := 0 to pred(frxDataset.Fields.Count) do
begin
ThisWidth := 100;
Memo := CreateMemo(Band);
Memo.SetBounds(X, Y, ThisWidth, fr01cm * 5);
Memo.Dataset := frxDataset;
Memo.DataField := frxDataset.Fields[i].FieldName;
// Set memo style
// Or just add a frame HeaderMemo.Frame....
Memo.Style := 'Data';
Memo.Height := Band.Height - 1;
X := X + ThisWidth;
end;
end;
It should work ok, but I've not had a chance to test since decoupling it from my application.
It will be possible using Framing Property of Memos.

Resources