Need guidance on drawing PNGs into a Delphi XE6 TDrawGrid - delphi

I'm using a drawgrid to display a "dungeon" map. I have an array filled with data describing what types of physical features are located in each cell. The basic layout of rooms and corridors, etc. There's a PNG for each permutation.
I'm trying to "draw" the map by drawing the appropriate PNG into each cell of the drawgrid. I can do it more or less directly using DrawGrid1.Canvas.Draw(x,y,pngImage), but figuring out the exact x,y in pixels is frustrating because of the gridlines (at least, I'm finding it frustrating), and I'm not sure what other issues I'll run into down the road.
I also tried pngImage.Draw(DrawGrid1.Canvas,Rect), but once again, I have to calculate the Rect, which really seems unnecessary as the cells and PNGs are all 40x40 pixels.
Reading related articles and examples, OnDrawCell seems to be a better way, because given ARow and ACol, the Rect for the given cell is apparently precalculated (unless I'm misinterpreting what I've read in several places). But none of the examples I've found actually show how OnDrawCell is triggered. The easy answer "when something gets drawn into a cell" doesn't really explain it much better. That's what I'm trying to do.
(I've often found this to be the case with Delphi's documentation: How is explained (not always exactly for your given use case), but when or why is left shrouded in mystery..)
Of course, there are a few other things to figure out as well, like controlling the drawgrid so it doesn't erase the PNG when a cell is clicked..
Any relevant suggestions will be greatly appreciated. TFRM

Why is figuring out exact X and Y in pixels so hard? It is actually pretty simple.
In order to get left border of your rectangle you simply multiply width of you column plus width of your gridline by number of the column. And if you have gridline on the very left of the first column then add the width of your gridline.
And in order to get right border of your rectangle simply add width of the column to the previous result.
And for calculating Y you just use height instead of width
So the code would look something like this (written from my mind and untested)
const
CellWidth = 40;
CellHeight = 40;
GridLineThickness = 1;
procedure DrawCell(Row, Column: Integer; Image: Bitmap);
var Rect: TRect;
begin
Rect.Left := ((CellWidth + GridLineThickness) * Column) + GridLineThickness;
Rect.Right := Rect.Left + CellWidth;
Rect.Top := ((CellHeight + GridLineThickness) * Row) + GridLineThickness;
Rect.Bottom := Rect.Top + CellHeight;
PaintBox1.Canvas.StretchDraw(Rect, Image);
end;
And if you want an ability to zoom in or zoom out just multiply the CellWidth and CellHeight by a zoom factor. And when being zoomed out a lot just omit rendering grid lines.

Related

Why did CopyRect Flipped the second image in delphi 10.3?

I want to take a screenshot of my page and put the result into a bitmap, Because there is a scrollbar on the page, i have to take several screenshots, and i want to merge those bitmaps.
if have used this code to make a screenshot and save it: Take a screenshot of a particular area in Delphi 7
i used the code to merge them from this page http://www.delphigroups.info/2/8/309463.html
if i copied it directly it would result in the first image being used, and i white rectangle for the second. so i tried to change it a little bit, and now i'm getting both images in one file.
This is the code i use to concatenate the bitmaps:
function ConcatenateBitmaps(const MainBitmap: TBitmap; const BitmapToAdd:
TBitmap): TBitmap;
begin
Result := MainBitmap;
If BitmapToAdd.Width > MainBitmap.Width then
Result.Width := BitmapToAdd.Width;
Result.Height := MainBitmap.Height + MainBitmap.Height;
Result.Canvas.CopyRect(
Rect(0,MainBitmap.Height,BitmapToAdd.Width,BitmapToAdd.Height),
BitmapToAdd.Canvas,
Rect(0,0,BitmapToAdd.Width,BitmapToAdd.Height)
);
end;
The problem is that te second image is being flipped, vertical and horizontal;
What am i doing wrong here?
EDIT:
An example of the result, the first image is good, the second image is flipped:
as i see now, my description was wrong, it's horizontaly mirrored, and verticaly flipped
Cause and quickfix:
The problem is in this part:
Rect(0,MainBitmap.Height,BitmapToAdd.Width,BitmapToAdd.Height)
You make a rectangle of which the top is the total height of the resulting image, and the bottom is the height of the bitmap to add. So this rectangle is basically inverted (its bottom is above its top).
And it's likely deformed as well, since the height of this rectangle is not the height of the bitmap to add.
The quickfix would be:
Rect(0,Result.Height- BitmapToAdd.Height,BitmapToAdd.Width,Result.Height)
Other issues and confusion:
But I think the cause of your confusion is because you think that Result and MainBitmap are two different bitmaps, while actually they are both references to the same bitmap. The assignment you do in the beginning just copies the reference, not the actual TBitmap object.
In addition, you mix up 'height' and 'bottom'. TRect expects you to set top and bottom coordinates, not top and height. This, together with the previous issue, causes not only that the bitmap is upside down, but also that it will be stretched, and partially covering the previous images. The more images you add, the more clear that effect will be.
Personally I think it's way more efficient to modify the existing bitmap in this scenario, mainly because you would otherwise have to clean up your old bitmap all the time, plus that you have a function that magically creates bitmaps. You get the question of ownership of the bitmap objects, and with that, the risk of memory leaks, which is not good, especially when dealing with large bitmaps.
My suggested version:
So, I would just make it a procedure, where the first bitmap is modified by adding the second bitmap to it.
In the version below, I also used Canvas.ClipRect, which is for a bitmap essentially the bounding rectangle of the bitmap. And then I used OffsetRect to 'move' this rectangle(increasing its top Y and bottom Y).
By doing this in a separate variable, you can have a relatively clean version compared to the quick fix I presented above, because you can use the dimensions of MainBitmap before actually modifying it.
procedure AppendBitmap(const MainBitmap: TBitmap; const BitmapToAdd:
TBitmap);
var
TargetRect: TRect;
begin
// Widen the main bitmap if needed
if BitmapToAdd.Width > MainBitmap.Width then
MainBitmap.Width := BitmapToAdd.Width;
// Set TargetRect to the right size
TargetRect := BitmapToAdd.Canvas.ClipRect;
// And then to the right position
OffsetRect(TargetRect, 0, MainBitmap.Height);
// Make room for the bitmap to add
MainBitmap.Height := MainBitmap.Height + BitmapToAdd.Height;
// Draw it in the created space
MainBitmap.Canvas.CopyRect(
TargetRect,
BitmapToAdd.Canvas,
BitmapToAdd.Canvas.ClipRect
);
end;
And if you like, you can make a wrapper function with the signature of the original, that creates a copy of the main image and returns that. Note though, that MainBitmap and the result of this function are no longer the same bitmap, and you have to make sure to properly free both of them when you're done.
function ConcatenateBitmaps(const MainBitmap: TBitmap; const BitmapToAdd:
TBitmap): TBitmap;
begin
Result := TBitmap.Create;
Result.Assign(MainBitmap);
AppendBitmap(Result, BitmapToAdd);
end;
PS: I like questions like this from which I learn something. I never realized you could flip an image by flipping the rect passed to CopyRect. :D

Delphi FireMonkey app can not draw simple black rectangle

Just create simple FireMokey HD app, put TImage with align=alclient on the form and trying to draw simple black rect:
procedure TForm8.FormCreate(Sender: TObject);
var
c: TCanvas;
begin
Image.Bitmap := TBitmap.Create(ClientWidth, ClientHeight);
c := Image.Bitmap.Canvas;
c.BeginScene;
try
c.Clear(claWhite);
c.Stroke.Color := claBlack;
c.Stroke.Kind := TBrushKind.bkSolid;
c.DrawRect(
TRectF.Create(7,7,ClientWidth-7,ClientHeight-7),
0,0,
[],
1
);
finally
c.EndScene;
end;
end;
And it doesn't work. Color of the rect is not black, it is kind of gray. There some changes of the color in corners. Did i need to set some other properties or what is wrong here ?
I tried different opacity values (1,100,255,65535), picture doesn't change at all and there is no information in the help what the hell this option means.
Zoomed left-top corner:
Also tried to use polygons as it described in example. Same problem - rounded corners and gray color instead of black (Opacity property of image is 1, all properties as by default):
procedure TForm8.Button2Click(Sender: TObject);
var
p1, p2, p3, p4, p5: TPointF;
MyPolygon: TPolygon;
begin
// sets the points that define the polygon
p1.Create(100, 100);
p2.Create(200, 100);
p3.Create(200, 200);
p4.Create(100, 200);
p5.Create(100, 100);
// creates the polygon
SetLength(MyPolygon, 5);
MyPolygon[0] := p1;
MyPolygon[1] := p2;
MyPolygon[2] := p3;
MyPolygon[3] := p4;
MyPolygon[4] := p5;
Image.Bitmap.Canvas.BeginScene;
// draws the polygon on the canvas
Image.Bitmap.Canvas.DrawPolygon(MyPolygon, 50);
Image.Bitmap.Canvas.EndScene;
// updates the bitmap
// Image.Bitmap.BitmapChanged;
end;
http://roman.yankovsky.me/?p=1018
if Canvas.BeginScene then
try
Canvas.Stroke.Thickness := 1.5;
Canvas.Stroke.Kind := TBrushKind.bkSolid;
Canvas.Fill.Color := TAlphaColorRec.Black;
Canvas.Fill.Kind := TBrushKind.bkSolid;
for I := 1 to 9 do
begin
Canvas.DrawLine(PointF(50 + I * 25 - (Canvas.Stroke.Thickness / 2), 0),
PointF(50 + I * 25 - (Canvas.Stroke.Thickness / 2), ClientHeight), 1);
end;
finally
Canvas.EndScene;
end;
This is easy to fix once you understand the better paradigm of Firemonkey. Firemonkey uses real coordinates, not integer coordinates. You unwittingly told it to draw lines that were centered the boundaries between pixels, so each of your lines were half in one set of pixels and half in another set of pixels.
Specifically, what happened is that your integer coordinates were interpreted as exact center points on a continuous number line. For example, say the point is 7. A line of width 1 centered on the point at 7.0 will extend from 6.5 to 7.5 on the number line. But because the pixels extend from 6.0 to 6.99 and from 7.0 to 7.99 on the number line, each pixel is half black and half white. Automatic antialiasing caused them to be drawn 50% black, which is where the two-pixel wide gray comes from.
When using FMX (now called FMX) you have to switch your thinking from integer coordinates to real coordinates, which is far more sophisticated and powerful.
The easiest solution is to move your integer-based math by 0.5 to the right and 0.5 down. Then a one-pixel wide line at 7.5 will extend from 7.0 to 7.999, which is what you were expecting. To do this, just add 0.5 to all your pixel coordinates, both horizontal and vertical, as you issue drawing commands.
The nice thing is, lines that are 0.8 pixels wide or 1.5 pixels wide will automatically appear thinner or thicker, respectively. Diagonal lines and other curves will appear correct without jagged edges. You can scale complex drawings and they will look perfect at any zoom level. (The math for the half-pixel shift stays the same for all zoom levels. The 0.5 is added after scaling immediately before drawing the line.)
The above is true for all devices: screens, bitmaps, and printers, etc. So the same code to draw on screen can be used to draw to everything else. When drawing text, you can use fractional point sizes for the fonts, so they scale with everything else.

Scrolling TImage horizontally or vertically

I am writing 2-3 Trees application in Lazarus for my school project.
Everything's done, now playing with GUI (I get the same number of points for good GUI as I do for a good etc. Insert function, which is weird but nvm).
When I have like 10+ nodes in the tree, my 300*200 image size just isn't large enough.
I would like to have an TImage component which would be like 300*200 on the TForm, but it would be like 10000 * 10000 really and you could scroll in it.
Is it even possible?
Thanks
EDIT TO MAKE THE QUESTION CLEARER
A 2-3 Tree is a data structure. When drawn on a paper to see how it works, it looks like this http://www.cosc.canterbury.ac.nz/research/RG/alg/tree23.gif
As a real noobie in lazarus/delphi (have to do it in lazarus) a use this code to draw it (even if I doubt u need it to answer my question):
procedure TStrom.Paint(Image: TImage);
var
C: TCanvas;
procedure Paint1(V: TNode; Width, X, Y: integer);
begin
if V.L <> nil then //left child
begin
C.MoveTo(X, Y);
C.LineTo(X - Width div 3, Y + 50);
Paint1(V.L, Width div 3, X - Width div 3, Y + 50);
end;
if V.S <> nil then //middle child
begin
C.MoveTo(X, Y);
C.LineTo(X + Width div 3, Y + 50);
Paint1(V.S, Width div 3, X + Width div 3, Y + 50);
end;
if V.P <> nil then //right child
begin
C.MoveTo(X, Y);
C.LineTo(X + Width div 3 + Width div 3, Y + 50);
Paint1(V.P, Width div 3, X + Width div 3 + Sirka div 3, Y + 50);
end;
if V.isLeaf then
begin
C.Ellipse(X - 15, Y - 15, X + 15, Y + 15);
C.TextOut(X - 3, Y - 8, IntToStr(V.Info1));
end
else
begin
C.Rectangle(X - 15, Y - 15, X + 15, Y + 15);
C.TextOut(X - 7, Y - 8, IntToStr(V.Info1));
C.Rectangle(X + 15, Y - 15, X + 50, Y + 15);
if V.Info2 <> 0 then
C.TextOut(X + 27, Y - 8, IntToStr(V.Info2));
end;
The draw function works well, but some (most) of the nodes at the height of 3+ are drawn on other nodes, so it looks bad. The node is sitting on another node and is not 20 pixels next to it.
I thought I'd make the image where the tree is painted real big, but it would be in a small "panel". Like this: the TImage would really be 1000*1000, but in the form you could see only a little part of it. In this part there'd be horizontal and vertical scrollbars, so you could scroll through the image and see what's painted in the sections. (Like when you scroll through a web browser to see the bottom of the page :) )
We are not permitted to use any other code, just built in lazarus components. (nor are we permitted to create new components -> have no idea why)
While I'm still curious about how this could be done, it's no longer necessary for my application ( installed a second monitor to see if it'd help and it wouldn't, so I guess I'd dig through my paint method a bit :-) )
Your edit makes it more clear that you want to draw a schematic like the last tree in the example you link to.
From your code I understand that you are drawing all nodes, childs and leaves, onto a single canvas resulting in one large image/bitmap. Now, how to display only part of that large image with scroll bars next to it?
The obvious choice would be to place a TPaintBox on a TScrollBox. I do not know the default suite of components in Lazarus, but I expect both of them to be present. Give the paint box a size equal to the bounds of your visual tree and you're set: scroll bars will be shown automatically.
The paint box has an OnPaint event in which you do your paint work by drawing to PaintBox.Canvas. To optimize this, you could limit your drawing to PaintBox.Canvas.ClipRect (the part of the paint box that is visible within the scroll box), but I suspect that to be difficult, since you already have difficulty with computing the right distance between adjacent nodes. (About that: I expect the maximum width and height of the total tree to be a function of the total depth, but I could easily be mistaken.)
If Lazarus does not have a TPaintBox, then use a TImage which also has a Canvas property. Downside is that TImage "stores" all drawing operations in one big internal bitmap, which could raise memory or resource issues when your tree expands too much.
Could all images be combined to one large image? If so, then maybe this component answers your question.
It is a descendant of TGraphicControl, capable of animated zooming. Zoom in on a part of the graphic by dragging a selection rectangle, zoom out the whole graphic by double clicking it. Shoot if you need help to update it to be able to perform pan operations.

TeeChart VCL chart scale issue

I have an issue using Delphi 2007 & TChart 7.0.10.0 or 7.0.11.0 or the latest evaluation 9.0.5.0 on the TChart scaling.
The problem arises as soon I enlarge the window after a certain width and KEEP the Form height!
This is the drawing using a smaller form size.
now if I enlarge to 1200 weight I get this ugly scaling:
If I export in the designer without the aspect ratio set and with 1200 weight you will se this:
How to get ride of this?
Hp
I see you've set top and bottom margins to Chart1 in your project (8 and 20 percent respectively). I guess this has the intention of giving more space (in height) for Chart2 when you resize the form making it bigger.
Chart1's Top and Height properties should be set according to fill this blank space in the Form's OnResize event.
Try this:
procedure TGSSkillgroupStatisticForm.FormResize(Sender: TObject);
begin
Chart1.Draw;
Chart2.Top:=Chart1.ChartRect.Bottom + 25;
Chart2.Height:=Chart1.Height-Chart1.ChartRect.Bottom-40;
end;
Steema Support Central
Keep in mind that I only scale in the x-axis. Your 3-D bar / construct will after a certain width, overlap the scaling numbers! Your given answer do not fix this issue at all. To see the real problem in a better way, I added on the form creation:
Chart2.BottomAxis.Maximum := 20;
Series2.AddBar(12, 'Hallo', clred);
Here the result:

Confusion with CreatePolygonRgn

I am working with delphi. I have an array of points which are continues as shown in image.
Then I give this array to CreatePolygonRgn and create the region say rgn1.
rgn1 := CreatePolygonRgn(tmpary1[0],Count,WINDING);
Then I fill the region and show it on my TImage control as shown in image. The problem is from the left side, the points are also covered in region but from right side the points of array are not covered. This can be seen in image that from left side green border is not shown but from right side border is visible. Am I mistaking somewhere??? If my question is not clear to you then please ask.
Thank You.
Edit:
for cnt := 0 to Count - 1 do begin
p1 := imgmain.Picture.Bitmap.ScanLine[tmpary[cnt].Y];
p1[tmpary[cnt].X].rgbtBlue := 0;
p1[tmpary[cnt].X].rgbtGreen := 255;
p1[tmpary[cnt].X].rgbtRed := 0;
end;
rgn1 := CreatePolygonRgn(tmpary1[0],tmpseq1.Count,WINDING);
imgmain.Picture.Bitmap.Canvas.Brush.Color := clRed;
FillRgn(imgmain.Picture.Bitmap.Canvas.Handle,rgn1,imgmain.Picture.Bitmap.Canvas.Brush.Handle);
It may just be the way it works. FillRect, for example, includes the left and top borders, but excludes the right and bottom borders of the rectangle.
I think the same probably applies to FillRgn.
Edit: Confirmed here, too.
At last I found the feasible solution to my problem and also the solution of this problem as both question are related to each other.
I was filling the region and then tried to get boundary of that region. I was getting some points of original array as boundary and some points were actual boundary points. I wanted all points of actual boundary.
So, now I fill the region with red color then fill the pixels of array with red color and then I run floodfill algorithm. It will give all points I needed.

Resources