I need develop a circular progress bar in delphi 2007, I can't use third-party components (company policy).
I'm using a Canvas, drawing an arc, that's works fine, but the image is at a very low resolution. It's possible to improve the resolution in canvas drawing?
Code sample:
procedure TForm1.DrawPieSlice(const Canvas: TCanvas; const Center: TPoint;
const Radius: Integer; const StartDegrees, StopDegrees: Double);
//Get it in http://delphidabbler.com/tips/148
const
Offset = 90;
var
X1, X2, X3, X4: Integer;
Y1, Y2, Y3, Y4: Integer;
begin
X1 := Center.X - Radius;
Y1 := Center.Y - Radius;
X2 := Center.X + Radius;
Y2 := Center.Y + Radius;
X4 := Center.X + Round(Radius * Cos(DegToRad(Offset + StartDegrees)));
Y4 := Center.y - Round(Radius * Sin(DegToRad(Offset + StartDegrees)));
X3 := Center.X + Round(Radius * Cos(DegToRad(Offset + StopDegrees)));
Y3 := Center.y - Round(Radius * Sin(DegToRad(Offset + StopDegrees)));
Canvas.Arc(X1, Y1, X2, Y2, X3, Y3, X4, Y4);
end;
procedure TForm1.SpinEdit1Change(Sender: TObject);
var
Center: TPoint;
Bitmap: TBitmap;
Radius: Integer;
p: Pointer;
begin
Label1.Caption:= SpinEdit1.Text+'%';
Bitmap := TBitmap.Create;
try
Bitmap.Width := Image1.Width;
Bitmap.Height := Image1.Height;
Bitmap.PixelFormat := pf24bit;
Bitmap.HandleType := bmDIB;
Bitmap.ignorepalette := true;
Bitmap.Canvas.Brush.Color := clBlack;
Bitmap.Canvas.Pen.Color := clHighlight;
Bitmap.Canvas.Pen.Width := 10;
Center := Point(Bitmap.Width div 2, Bitmap.Height div 2);
Radius := 61;
DrawPieSlice(Bitmap.Canvas, Center, Radius,0,round(SpinEdit1.Value * -3.6));
Image1.Picture.Graphic := Bitmap;
finally
Bitmap.Free;
end;
end;
Result:
I am open to proposals for other solutions.
If you are not allowed to use any third-party graphic library with anti-aliasing possibilities, consider using GDI+, which is included in Windows, and Delphi has a wrapper for it.
uses
..., GDIPAPI, GDIPOBJ, GDIPUTIL //included in Delphi standard modules
var
graphics: TGPGraphics;
SolidPen: TGPPen;
begin
graphics := TGPGraphics.Create(Canvas.Handle);
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
SolidPen := TGPPen.Create(MakeColor(255, 0, 0, 255), 31);
SolidPen.SetStartCap(LineCapRound);
SolidPen.SetEndCap(LineCapRound);
graphics.DrawArc(SolidPen, 100, 100, 100, 100, 0, 270);
graphics.Free;
SolidPen.Free;
Not sure if Direct2D units already exist in Delphi 2007, but it maybe a better option to use Direct2D since it is rendered using GPU, not CPU.
uses Vcl.Direct2D, Winapi.D2D1;
...
var
D2DCanvas: TDirect2DCanvas;
begin
if TDirect2DCanvas.Supported then
begin
D2DCanvas := TDirect2DCanvas.Create(PaintBox.Canvas, PaintBox.ClientRect);
try
D2DCanvas.RenderTarget.BeginDraw;
D2DCanvas.RenderTarget.SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
D2DCanvas.Pen.Color := TColors.Blue;
D2DCanvas.Pen.Width := 10;
D2DCanvas.Arc(100, 100, 200, 200, 100, 150, 150, 100);
D2DCanvas.RenderTarget.EndDraw;
finally
D2DCanvas.Free;
end;
end
end;
a very simple solution would be to draw your circle at a higher resolution (like 1.5x or 2x) on a temp bitmap and then resize it to your resolution (because the resize process will add antialias to your circle) and finally draw directly this bitmap to the canvas. in fact it's like this that many algorithm work to add antialias.
you may use the following unit (work in progress)
All you need to do is to add it to your "uses" and the supported TCanvas operations will be converted to GDI+
The "magic" is done by a TCanvas class helper that overrides the functions
supports: ellipse, polygon, polyline, lineTo
arc is not yet supported - because I did not need it so far...
unit uAntiAliasedCanvas;
interface
uses Graphics, types, UITypes, GdiPlus;
type TAntiAliasedCanvas = class helper for TCanvas
private
function Graphics : IGPGraphics;
function Pen : IGPPen;
function Brush: IGPBrush;
function path(const points : array of TPoint; close : boolean = false) : TGPGraphicsPath;
function TGPcolorFromVCLColor(color : TColor) : TGPColor;
private
class var antiAliased : boolean;
public
procedure Ellipse(X1, Y1, X2, Y2: Integer);
procedure Polyline(const Points: array of TPoint);
procedure Polygon(const Points: array of TPoint);
procedure lineTo(x,y : integer);
class procedure setAntiAliasing(value : boolean);
end;
implementation
{ TAntiAliasedCanvas }
uses WinAPI.Windows;
function TAntiAliasedCanvas.Brush: IGPBrush;
begin
result := TGPSolidBrush.Create(
TGPColor.Create(
TGPcolorFromVCLColor(
(inherited brush).color)));
end;
procedure TAntiAliasedCanvas.Ellipse(X1, Y1, X2, Y2: Integer);
begin
if antiAliased then
begin
Graphics.fillEllipse(brush, X1, Y1, 1+X2-X1, 1+Y2-Y1);
Graphics.drawEllipse(Pen, X1, Y1, 1+X2-X1, 1+Y2-Y1)
end
else
inherited Ellipse(X1, Y1, X2, Y2)
end;
function TAntiAliasedCanvas.Graphics: IGPGraphics;
begin
result := TGPGraphics.Create(Handle);
result.SmoothingMode := SmoothingModeAntiAlias
end;
procedure TAntiAliasedCanvas.lineTo(x, y: integer);
begin
if antiAliased then
graphics.DrawLine(pen, penPos.X, penPos.Y, X, Y)
else
inherited lineTo(x,y)
end;
function TAntiAliasedCanvas.path(const points: array of TPoint;
close : boolean = false): TGPGraphicsPath;
var
GPPoints: array of TGPPointF;
ptTypes : array of byte;
i : integer;
begin
setLength(GPPoints, length(points) + ord(close));
setLength(ptTypes, length(points) + ord(close));
for i := 0 to high(Points) + ord(close) do
with points[i mod length(points)] do
begin
GPPoints[i] := TGPPointF.Create(x,y);
ptTypes[i] := byte(PathPointTypeLine);
end;
result := TGPGraphicsPath.Create(GPPoints,ptTypes)
end;
function TAntiAliasedCanvas.pen: IGPpen;
begin
result := TGPpen.Create(
TGPColor.Create(
TGPcolorFromVCLColor(
(inherited pen).color)),
(inherited pen).width);
end;
procedure TAntiAliasedCanvas.Polygon(const Points: array of TPoint);
var
aPath : TGPGraphicsPath;
aPen : IGPPen;
begin
if antiAliased then
begin
aPath := path(points, true);
graphics.FillPath(brush, aPath);
aPen := pen();
aPen.SetLineJoin(LineJoinRound);
graphics.DrawPath(aPen, aPath);
end
else
inherited Polygon(points);
end;
procedure TAntiAliasedCanvas.Polyline(const Points: array of TPoint);
var
aPen : IGPPen;
begin
if antiAliased then
begin
aPen := pen();
aPen.SetLineJoin(LineJoinRound);
graphics.DrawPath(aPen, path(points))
end
else
inherited polyline(points)
end;
class procedure TAntiAliasedCanvas.setAntiAliasing(value: boolean);
begin
antiAliased := value
end;
function TAntiAliasedCanvas.TGPcolorFromVCLColor(color: TColor): TGPColor;
begin
if Color < 0 then
color := GetSysColor(Color and $000000FF);
result := TGPColor.Create(
color and $FF,
(color and $FF00) shr 8,
(color and $FF0000) shr 16)
end;
begin
TCanvas.setAntiAliasing(true)
end.
Related
My little code creates modern art to the full screen size Form canvas with different kind of shapes.
I can make ellipse, rectangle and line work but not the polycon. Can any one help me? (Version: Delphi community edition )
uses .... GDIPAPI, GDIPOBJ, GDIPUTIL;
procedure TForm1.Button1Click(Sender: TObject);
var
graphics: TGPGraphics;
SolidPen: TGPPen;
SolidBrush : TGPBrush;
x,y,x2,y2,x3,y3 : integer;
begin
graphics := TGPGraphics.Create(Canvas.Handle);
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
SolidPen := TGPPen.Create(MakeColor(255, random(255), random(255), random(255)), random(4)+1);
SolidBrush := TGPSolidBrush.Create(MakeColor(255, random(255), random(255), random(255)));
SolidPen.SetStartCap(LineCapRound);
SolidPen.SetEndCap(LineCapRound);
//POLYCON, not working.------------------
// PROBLEM HERE: it's complaining: 'Oridinal type required' ,
// 'incompatible type: integer and TPoint'
x:= 150; y := 50; x2 := 50; y2 := 250; x3 := 250; y3 := 250;
graphics.FillPolygon(SolidBrush, [Point(x, y), Point(x2, y2), Point(x3, y3)]);
graphics.DrawPolygon(SolidPen, [Point(x, y), Point(x2, y2), Point(x3, y3)]);
//--------------------------------------------
// ELLIPSE, ok
x := random(Form1.width); y := random(Form1.height); x2 := random(200); y2 := random(200);
graphics.FillEllipse(SolidBrush,x, y, x2, y2);
graphics.DrawEllipse(SolidPen,x, y, x2, y2);
// RECTANGLE, ok
x := random(Form1.width); y := random(Form1.height); x2 := random(200); y2 := random(200);
graphics.FillRectangle(SolidBrush, x, y, x2, y2);
graphics.DrawRectangle(SolidPen, x, y, x2, y2);
// LINE, ok
x := random(Form1.width); y := random(Form1.height); x2 := random(Form1.width); y2 := random(Form1.height);
graphics.DrawLine(SolidPen, x, y, x2, y2);
procedure TForm1.FormCreate(Sender: TObject);
begin
Form1.Height := Screen.Height;
Form1.Width := Screen.Width;
end;
You are not passing the points of the polygon correctly.
Looking at the two overloaded DrawPolygon() declarations:
function TGPGraphics.DrawPolygon(pen: TGPPen; points: PGPPointF; count: Integer): TStatus;
function TGPGraphics.DrawPolygon(pen: TGPPen; points: PGPPoint; count: Integer): TStatus;
you can see that the points are passed as a PGPPointF or PGPPoint. The definitions of these types are found in Winapi.GDIPAPI and there we see that the coordinates are either single or integer
Since you are using integer coordinates look at the definition of PGPPoint in Winapi.GDIPAPI
type
PGPPoint = ^TGPPoint;
TGPPoint = record
X : Integer;
Y : Integer;
end;
TPointDynArray = array of TGPPoint;
function MakePoint(X, Y: Integer): TGPPoint; overload;
{$EXTERNALSYM MakePoint}
So, declare a variable
ArrOfPoint: TPointDynArray;
and fill it with your points:
SetLength(ArrOfPoint, 3);
ArrOfPoint[0] := MakePoint(x, y);
ArrOfPoint[1] := MakePoint(x2, y2);
ArrOfPoint[2] := MakePoint(x3, y3);
Finally replace your call to e.g. DrawPolygon() with
graphics.DrawPolygon(SolidPen, PGPPoint(#ArrOfPoint[0]), 3);
meaning you pass the address of the first point as a PGPPoint type.
So, the corrected working code for the polygon is following (at the moment):
uses
... GDIPAPI, GDIPOBJ, GDIPUTIL;
procedure TForm1.Button1Click(Sender: TObject);
var
graphics: TGPGraphics;
SolidPen: TGPPen;
SolidBrush : TGPBrush;
ArrOfPoint: TPointDynArray;
x, y, x2, y2, x3, y3 : integer;
begin
graphics := TGPGraphics.Create(Canvas.Handle);
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
SolidPen := TGPPen.Create(MakeColor(255, random(255), random(255), random(255)), random(4)+1);
SolidBrush := TGPSolidBrush.Create(MakeColor(255, random(255), random(255), random(255)));
x := random(Form1.Width); y := random(Form1.Height);
x2 := random(Form1.Width); y2 := random(Form1.Height);
x3 := random(Form1.Width); y3 := random(Form1.Height);
SetLength(ArrOfPoint, 3);
ArrOfPoint[0] := MakePoint(x, y);
ArrOfPoint[1] := MakePoint(x2, y2);
ArrOfPoint[2] := MakePoint(x3, y3);
graphics.FillPolygon(SolidBrush,PGPPoint(#ArrOfPoint[0]), 3);
graphics.DrawPolygon(SolidPen, PGPPoint(#ArrOfPoint[0]), 3);
end;
I receive a bitmap via TCameraComponent.SampleBufferReady event. Then I need to crop the received image so that I get a, for instance, recangular image.
I calculate the necessary parameters in the following method:
procedure TPersonalF.SampleBufferReady(Sender: TObject;
const ATime: TMediaTime);
var
BMP: TBitmap;
X, Y, W, H: Word;
begin
Try
BMP := TBitmap.Create;
CameraComponent.SampleBufferToBitmap(BMP, true);
if BMP.Width >= BMP.Height then //landscape
begin
W:=BMP.Height;
H:=W;
Y:=0;
X:=trunc((BMP.Width-BMP.Height)/2);
end
else //portrait
begin
W:=BMP.Width;
H:=W;
X:=0;
Y:=trunc((BMP.Height-BMP.Width)/2);
end;
CropBitmap(BMP, Image1.Bitmap, X,Y,W,H);
Finally
BMP.Free;
End;
end;
I found an answer by #RRUZ delphi-how-do-i-crop-a-bitmap-in-place, but it requires a VCL API handle and is uses a Windows GDI function:
procedure CropBitmap(InBitmap, OutBitMap: TBitmap; X, Y, W, H: Word);
begin
OutBitMap.PixelFormat := InBitmap.PixelFormat;
OutBitMap.Width := W;
OutBitMap.Height := H;
BitBlt(OutBitMap.Canvas.Handle, 0, 0, W, H, InBitmap.Canvas.Handle, X,
Y, SRCCOPY);
end;
My project is using FMX, and I plan to port it to Android platform in the future. So I am expecting to get problems if I use handles. How can I solve this problem?
Assuming you can guarantee that InBitmap and OutBitMap exist (if not, you can handle error checking yourself)
procedure CropBitmap(InBitmap, OutBitMap: TBitmap; X, Y, W, H: Word);
var
iRect : TRect;
begin
OutBitMap.PixelFormat := InBitmap.PixelFormat;
OutBitMap.Width := W;
OutBitMap.Height := H;
iRec.Left := 0;
iRect.Top := 0;
iRect.Width := W;
iRect.Height := H;
OutBitMap.CopyFromBitmap( InBitMap, iRect, 0, 0 );
end;
It is the same as the original but uses Firemonkey CopyFromBitmap which is similar to the Windows rather cryptically named BitBlt.
I want to draw Text on a canvas. To do the rotation I used the following code from https://forums.embarcadero.com/thread.jspa?messageID=440010
//bm is a TImage
a := 45;
c:= bm.Canvas;
CurrentM := c.Matrix;
a:=-radian(a);
m.m11:= cos(a); m.m12:=sin(a); m.m13:=0;
m.m21:=-sin(a); m.m22:=cos(a); m.m23:=0;
m.m31:=0; m.m32:=0; m.m33:=1;
c.setmatrix(M);
c.BeginScene;
c.filltext(rectf(100,100,5000,5000), 'test rotated string', false,1,[],ttextalign.taLeading,ttextalign.taLeading);
c.EndScene;
This works fine. I have set my rectangle's right and bottom to 5000 so that I do not have to be worried about my rectangle being to small.
The problem is that I now want to change my TextAlignment properties. So to draw text from right to left I had to adjust my rectangle and then draw it in the following way:
c.BeginScene;
c.filltext(rectf((100 - 5000),100,100,5000), 'test rotated string', false,1,[],ttextalign.taTrailing,ttextalign.taLeading);
c.EndScene;
So basically I moved the x value of my rectangle's TopLeft and moved it back 5000 (again I am using 5000 to make sure my text fit). I then set the x value of my rectangle's bottom right to where the x value was in my previous example's rectangle's TopLeft.
This work fine for a 0 degree rotation, but as soon as I change the degrees I does not draw my text at the correct place. I assume this is because the text will rotate around the rectangle's TopLeft position (which is altered to make the text write from right to left).
I assume this is because the text will rotate around the rectangle's TopLeft position
No, the rotation is centered around the current origin of the canvas. By default, that is coordinate 0, 0, but could be altered by the currently set deformation matrix. The typical way to go is: choose a rotation center, move the origin to that center, rotate, move back to the changed origin, and then draw. See TControl.MatrixChanged for reference. But there are many other ways.
Hereby an example of how to paint text from the lower left to the upper right within a form:
procedure TForm1.FormPaint(Sender: TObject; Canvas: TCanvas;
const ARect: TRectF);
var
Angle: Single;
R: TRectF;
S: String;
H: Single;
Matrix: TMatrix;
begin
Canvas.Fill.Color := TAlphaColors.Black;
Angle := -ArcTan2(ClientHeight, ClientWidth);
R := ClientRect;
S := 'Text from bottom-left...';
H := Canvas.TextHeight(S);
Matrix := CreateRotationMatrix(Angle);
Matrix.m31 := Sin(Angle) * (ClientHeight - H);
Matrix.m32 := ClientHeight * (1 - Cos(Angle));
Canvas.SetMatrix(Matrix);
Canvas.FillText(R, S, False, 1, [], TTextAlign.taLeading,
TTextAlign.taTrailing);
S := '...to top-right';
Matrix.m31 := ClientWidth * (1 - Cos(Angle)) + Sin(Angle) * H;
Matrix.m32 := -Sin(Angle) * ClientWidth;
Canvas.SetMatrix(Matrix);
Canvas.FillText(R, S, False, 1, [], TTextAlign.taTrailing,
TTextAlign.taLeading);
end;
Update:
This code does not yet take an already shifted origin into account.
In response to your comment, the following code draws text from coordinate 50, 100 down, 90° rotated around that point, using the method explained above, on a PaintBox which is arbitrarily positioned on the form.
procedure TForm1.PaintBox1Paint(Sender: TObject; Canvas: TCanvas);
const
S = 'Hello World';
var
R: TRectF;
OriginalMatrix: TMatrix;
ShiftMatrix: TMatrix;
RotationMatrix: TMatrix;
ShiftBackMatrix: TMatrix;
Matrix: TMatrix;
begin
PaintBox1.Canvas.Fill.Color := TAlphaColors.Black;
R.Right := 50;
R.Bottom := 100;
R.Left := R.Right - 5000;
R.Top := R.Bottom - 5000;
OriginalMatrix := PaintBox1.Canvas.Matrix;
ShiftMatrix := IdentityMatrix;
ShiftMatrix.m31 := -R.Right;
ShiftMatrix.m32 := -R.Bottom;
RotationMatrix := CreateRotationMatrix(DegToRad(-90));
ShiftBackMatrix := IdentityMatrix;
ShiftBackMatrix.m31 := R.Right;
ShiftBackMatrix.m32 := R.Bottom;
Matrix := MatrixMultiply(RotationMatrix, ShiftBackMatrix);
Matrix := MatrixMultiply(ShiftMatrix, Matrix);
Matrix := MatrixMultiply(Matrix, OriginalMatrix);
PaintBox1.Canvas.SetMatrix(Matrix);
PaintBox1.Canvas.FillText(R, S, False, 1, [], TTextAlign.taTrailing,
TTextAlign.taTrailing);
PaintBox1.Canvas.SetMatrix(OriginalMatrix);
end;
Which can be reduced to:
procedure TForm1.PaintBox1Paint(Sender: TObject; Canvas: TCanvas);
const
S = 'Hello World';
var
R: TRectF;
SaveMatrix: TMatrix;
Matrix: TMatrix;
begin
PaintBox1.Canvas.Fill.Color := TAlphaColors.Black;
R := RectF(-Canvas.TextWidth(S), -Canvas.TextHeight(S), 0, 0);
SaveMatrix := PaintBox1.Canvas.Matrix;
Matrix := CreateRotationMatrix(DegToRad(-90));
Matrix.m31 := 50;
Matrix.m32 := 100;
PaintBox1.Canvas.MultyMatrix(Matrix);
PaintBox1.Canvas.FillText(R, S, False, 1, [], TTextAlign.taTrailing,
TTextAlign.taTrailing);
PaintBox1.Canvas.SetMatrix(SaveMatrix);
end;
Which in turn evolves into this general routine:
procedure DrawRotatedText(Canvas: TCanvas; const P: TPointF; RadAngle: Single;
const S: String; HTextAlign, VTextAlign: TTextAlign);
var
W: Single;
H: Single;
R: TRectF;
SaveMatrix: TMatrix;
Matrix: TMatrix;
begin
W := Canvas.TextWidth(S);
H := Canvas.TextHeight(S);
case HTextAlign of
TTextAlign.taCenter: R.Left := -W / 2;
TTextAlign.taLeading: R.Left := 0;
TTextAlign.taTrailing: R.Left := -W;
end;
R.Width := W;
case VTextAlign of
TTextAlign.taCenter: R.Top := -H / 2;
TTextAlign.taLeading: R.Top := 0;
TTextAlign.taTrailing: R.Top := -H;
end;
R.Height := H;
SaveMatrix := Canvas.Matrix;
Matrix := CreateRotationMatrix(RadAngle);
Matrix.m31 := P.X;
Matrix.m32 := P.Y;
Canvas.MultyMatrix(Matrix);
Canvas.FillText(R, S, False, 1, [], HTextAlign, VTextAlign);
Canvas.SetMatrix(SaveMatrix);
end;
procedure TForm1.PaintBox1Paint(Sender: TObject; Canvas: TCanvas);
begin
PaintBox1.Canvas.Fill.Color := TAlphaColors.Black;
DrawRotatedText(PaintBox1.Canvas, PointF(50, 100), DegToRad(-90),
'Hello world', TTextAlign.taTrailing, TTextAlign.taTrailing);
end;
procedure TmsLineWithArrow.DoDrawTo(const aCanvas: TCanvas;
const aOrigin: TPointF);
var
l_Proxy : TmsShape;
l_OriginalMatrix: TMatrix;
l_Matrix: TMatrix;
l_Angle : Single;
l_CenterPoint : TPointF;
l_TextRect : TRectF;
begin
inherited;
aCanvas.BeginScene;
if (StartPoint <> FinishPoint) then
begin
l_OriginalMatrix := aCanvas.Matrix;
try
l_Proxy := TmsSmallTriangle.Create(FinishPoint);
try
// in Radian
l_Angle := GetArrowAngleRotation;
// create a point around which will rotate
l_CenterPoint := TPointF.Create(FinishPoint.X, FinishPoint.Y);
l_Matrix := l_OriginalMatrix;
l_Matrix := l_Matrix * TMatrix.CreateTranslation(-l_CenterPoint.X,-l_CenterPoint.Y);
l_Matrix := l_Matrix * TMatrix.CreateRotation(l_Angle);
l_Matrix := l_Matrix * TMatrix.CreateTranslation(l_CenterPoint.X,l_CenterPoint.Y);
aCanvas.SetMatrix(l_Matrix);
l_Proxy.DrawTo(aCanvas, aOrigin);
finally
FreeAndNil(l_Proxy);
end;//try..finally
finally
aCanvas.SetMatrix(l_OriginalMatrix);
aCanvas.EndScene;
end;
end;//(StartPoint <> FinishPoint)
end;
This code working in XE5 Firemonkey application.
all source here
https://bitbucket.org/ingword/mindstream
For people interested, here you will find the C++Builder version I use for FMX (tested on 10.2 / Tokyo) :
Function :
// Draw rotated text on pMainBitmap ; Rot = should be multiple of 90° !
void DrawRotatedText( TBitmap * pMainBitmap, TRectF TextDestRect, String StrTxt, int Rot )
(add of course "{" and "}" between the following code... always an error with the editor to post this answer...)
int SizeTextW = pMainBitmap->Canvas->TextWidth( StrTxt );
int SizeTextH = pMainBitmap->Canvas->TextHeight( StrTxt );
int SizeTextMax = (SizeTextW>SizeTextH)?SizeTextW:SizeTextH;
TRectF TheTextRect;
TheTextRect.init( 0, 0, SizeTextMax, SizeTextMax );
TBitmap * pBitmapText = new TBitmap( SizeTextMax, SizeTextMax );
if ( pBitmapText )
{
/* background color used */
pBitmapText->Clear( claBlack );
pBitmapText->Canvas->BeginScene();
// use same color than main bitmap for text
pBitmapText->Canvas->Fill->Color = pMainBitmap->Canvas->Fill->Color;
pBitmapText->Canvas->FillText(TheTextRect, StrTxt, false, 100,
TFillTextFlags()/* << TFillTextFlag::RightToLeft*/, TTextAlign::Center,
TTextAlign::Center);
pBitmapText->Canvas->EndScene();
// Canvas->EndScene must be done before doing bitmap rotate/flip...!
if( Rot==180 )
pBitmapText->FlipVertical( );
else if ( Rot!=0 )
pBitmapText->Rotate( Rot );
int PosSrcX = 0;
int PosSrcY = 0;
if ( SizeTextW>SizeTextH )
PosSrcX = (SizeTextMax-SizeTextH)/2;
else
PosSrcY = (SizeTextMax-SizeTextW)/2;
TheTextRect.init( PosSrcX, PosSrcY, PosSrcX+SizeTextH, PosSrcY+SizeTextW );
int iPosDestX = TextDestRect.left;
int iPosDestY = TextDestRect.top;
if ( (TextDestRect.right-TextDestRect.left)>SizeTextH )
iPosDestX = iPosDestX+(TextDestRect.right-TextDestRect.left-SizeTextH)/2;
if ( (TextDestRect.bottom-TextDestRect.top)>SizeTextW )
iPosDestY = iPosDestY + (TextDestRect.bottom-TextDestRect.top-SizeTextW)/2;
TextDestRect.left = iPosDestX;
TextDestRect.top = iPosDestY;
TextDestRect.right = TextDestRect.left+SizeTextH;
TextDestRect.bottom = TextDestRect.top+SizeTextW;
pMainBitmap->Canvas->DrawBitmap( pBitmapText, TheTextRect, TextDestRect, 100, true );
delete( pBitmapText );
}
When designing a form in the IDE, non-visual components (eg TMainMenus, TDatamodules) can be freely placed and positioned. The position is persisted, so that on reloading the form these components appear in the correct place.
But, TComponent does not have Top or Left properties!
So, how can my code access the 'designed position' of non visual components?
This can be accessed at runtime, but it's sort of a hack. (Mostly because it's implemented as sort of a hack.)
The Left and Top properties are set up as Word-size values, and the two of them are packed together into a Longint called TComponent.FDesignInfo. You can obtain its value with the DesignInfo property. Have a look at TComponent.DefineProperties to get a look into how it's used.
And also:
How to set DesignInfo to a point like (-100,-100)?
Objective: Put the icon out of visual area, hide it on design-time.
Note: It is very usefull when for example creating simple visual components derived directly from TComponent, i have in mind a very simple label (taht is allways aligned to top, has allways left=0, top is auto-calculated, bla bla bla) that only stores it's caption property into the .dfm file; and also any localizer will only see that caption property.
SOLUTION is to Override ReadState with code like this:
procedure TMyComponent.ReadState(Reader:TReader);
var
NewDesignInfo:LongRec;
begin
inherited ReadState(Reader);
NewDesignInfo.Hi:=Word(-100); // Hide design-time icon (top position = -100)
NewDesignInfo.Lo:=Word(-100); // Hide design-time icon (left position = -100)
DesignInfo:=Longint(NewDesignInfo); // Set the design-icon position out of visual area
end;
Hope help others!
This worked for me. Source: CnPack CnAlignSizeWizard.pas.
procedure SetNonVisualPos(Form: TCustomForm; Component: TComponent; X, Y: Integer);
const
NonvisualClassNamePattern = 'TContainer';
csNonVisualSize = 28;
csNonVisualCaptionSize = 14;
csNonVisualCaptionV = 30;
var
P: TSmallPoint;
H1, H2: HWND;
Offset: TPoint;
function HWndIsNonvisualComponent(hWnd: hWnd): Boolean;
var
AClassName: array[0..256] of Char;
begin
AClassName[GetClassName(hWnd, #AClassName, SizeOf(AClassName) - 1)] := #0;
Result := string(AClassName) = NonvisualClassNamePattern;
end;
procedure GetComponentContainerHandle(AForm: TCustomForm; L, T: Integer; var H1, H2: hWnd; var Offset: TPoint);
var
R1, R2: TRect;
P: TPoint;
ParentHandle: hWnd;
AControl: TWinControl;
I: Integer;
begin
ParentHandle := AForm.Handle;
AControl := AForm;
if AForm.ClassNameIs('TDataModuleForm') then // ÊÇ DataModule
begin
for I := 0 to AForm.ControlCount - 1 do
if AForm.Controls[I].ClassNameIs('TComponentContainer')
and (AForm.Controls[I] is TWinControl) then
begin
AControl := AForm.Controls[I] as TWinControl;
ParentHandle := AControl.Handle;
Break;
end;
end;
H2 := 0;
H1 := GetWindow(ParentHandle, GW_CHILD);
H1 := GetWindow(H1, GW_HWNDLAST);
while H1 <> 0 do
begin
if HWndIsNonvisualComponent(H1) and GetWindowRect(H1, R1) then
begin
P.x := R1.Left;
P.y := R1.Top;
P := AControl.ScreenToClient(P);
if (P.x = L) and (P.y = T) and (R1.Right - R1.Left = csNonVisualSize)
and (R1.Bottom - R1.Top = csNonVisualSize) then
begin
H2 := GetWindow(ParentHandle, GW_CHILD);
H2 := GetWindow(H2, GW_HWNDLAST);
while H2 <> 0 do
begin
if HWndIsNonvisualComponent(H2) and GetWindowRect(H2, R2) then
begin
if (R2.Top - R1.Top = csNonVisualCaptionV) and (Abs(R2.Left + R2.Right - R1.Left - R1.Right) <= 1)
and (R2.Bottom - R2.Top = csNonVisualCaptionSize) then
begin
Offset.x := R2.Left - R1.Left;
Offset.y := R2.Top - R1.Top;
Break;
end;
end;
H2 := GetWindow(H2, GW_HWNDPREV);
end;
Exit;
end;
end;
H1 := GetWindow(H1, GW_HWNDPREV);
end;
end;
begin
P := TSmallPoint(Component.DesignInfo);
GetComponentContainerHandle(Form, P.x, P.y, H1, H2, Offset);
Component.DesignInfo := Integer(PointToSmallPoint(Point(X, Y)));
if H1 <> 0 then
SetWindowPos(H1, 0, X, Y, 0, 0, SWP_NOSIZE or SWP_NOZORDER);
if H2 <> 0 then
SetWindowPos(H2, 0, X + Offset.x, Y + Offset.y, 0, 0, SWP_NOSIZE or SWP_NOZORDER);
end;
Use sample:
SetNonVisualPos(TCustomForm(Designer.Root),MyComponent,10,10);
Getting this error while I am trying to run /compile/build a Proiject
Incompatible Types : “TBitmap” and “TObject”
The cursor is pointing to Bitmap := FSectionList.BackgroundBitmap
Kindly help me figure it out.
Struck here like a ambulance in heavy traffic
Here is the part of the code:-
procedure ThtmlViewer.DoBackground1(ACanvas: TCanvas; ATop, AWidth, AHeight, FullHeight: integer);
var
ARect: TRect;
Bitmap, Mask: TBitmap;
PRec: PtPositionRec;
BW, BH, X, Y, X2, Y2, IW, IH, XOff, YOff: integer;
Fixed: boolean;
begin
ARect := Rect(0, 0, AWidth, AHeight);
Bitmap := FSectionList.BackgroundBitmap;
if FSectionList.ShowImages and Assigned(Bitmap) then
begin
Mask := FSectionList.BackgroundMask;
BW := Bitmap.Width;
BH := Bitmap.Height;
PRec := FSectionList.BackgroundPRec;
Fixed := PRec[1].Fixed;
if Fixed then
begin {fixed background}
XOff := 0;
YOff := 0;
IW := AWidth;
IH := AHeight;
end
else
begin {scrolling background}
XOff := 0;
YOff := ATop;
IW := AWidth;
IH := FullHeight;
end;
CalcBckgrndLoctionAndTilng(PRec, ARect, XOff, YOff, IW, IH, BW, BH, X, Y, X2, Y2);
DrwBckgrnd(ACanvas, ARect, X, Y, X2, Y2, Bitmap, Mask, BW, BH, PaintPanel.Color);
end
else
begin {no background image, show color only}
DrwBckgrnd(ACanvas, ARect, 0,0,0,0, Nil, Nil, 0, 0, PaintPanel.Color);
end;
end;
Thanks and Regards
Vas
I'm only guessing, but from the error message and the name of FSectionList, it's some kind of List which holds generic TObject instances and BackgroundBitmap is one of them.
You would need to cast it back as a TBitmap:
Bitmap := FSectionList.BackgroundBitmap as TBitMap;
It looks like there's some confusion for the compiler between the TBitmap defined in Windows.pas and the TBitmap class defined in Graphics.pas. It seems to think you're trying to assign a Graphics.TBitmap to a Windows.TBitmap.
You can fix it by changing the declaration of Bitmap to either Windows.TBitmap or Graphics.TBitmap. You didn't include any info on FSectionList, but what's causing the problem is probably the line
var
Bitmap, Mask: TBitmap;
Change that to one of the following:
Bitmap, Mask: Graphics.TBitmap;
or
Bitmap, Mask: Windows: TBitmap;
I can't tell you which to use, because I don't know what FSectionList is holding there; adding one of them and then trying to compile should decide for you. I'd suspect you'll need Windows, though, based on the error message.