Recent

Author Topic: How to draw some rectangles into a TPicture with undo-feature?  (Read 1032 times)

Hartmut

  • Hero Member
  • *****
  • Posts: 855
How to draw some rectangles into a TPicture with undo-feature?
« on: November 04, 2024, 10:33:47 am »
I'm still a beginner to Graphics. My program loads a grahic file (JPG, PNG, BMP, etc.) into a TPicture via Picture1.LoadFromFile(filename), adds some rectangles and saves the result back via Picture1.SaveToFile(filename).

I found 4 different ways to add a rectangle to a TPicture, which all seem to work:
Code: Pascal  [Select][+][-]
  1. Picture1.Bitmap.Canvas.Rectangle(x1,y1,x2,y2);
  2. Picture1.Pixmap.Canvas.Rectangle(x1,y1,x2,y2);
  3. Picture1.Jpeg.Canvas.Rectangle(x1,y1,x2,y2);
  4. Picture1.PNG.Canvas.Rectangle(x1,y1,x2,y2);

Why has a TPicture 4 different Canvas?

When I print the pointer values of the 4 Canvas then I get 4 times the same value. How can this be? I thought, that this are 4 different properties...

Which of this 4 Canvas should I use to add my rectangles? I want to avoid unneccessary conversions, quality loss and time consumption. Must I detect the Graphic type from the filename and use e.g. Picture1.Jpeg.Canvas for JPG's and Picture1.PNG.Canvas for PNG's and so on?

And for the undo-feature: in which part of the TPicture are the picture data stored? I mean, which part of the TPicture must I copy and save somewhere, before I add a rectangle, to be able, to do the undo?

Thanks in advance.

jcmontherock

  • Sr. Member
  • ****
  • Posts: 272
Re: How to draw some rectangles into a TPicture with undo-feature?
« Reply #1 on: November 04, 2024, 11:24:54 am »
What I'm using:
Code: Pascal  [Select][+][-]
  1. procedure TForm1.AddBorder;
  2. var
  3.   pWidth: Integer;
  4. begin
  5.   Shape1.Pen.Width := 4;
  6.   pWidth := Shape1.Pen.Width;
  7.   Shape1.Pen.Color := clRed;
  8.   Shape1.Shape := stRectangle;
  9.   Shape1.Pen.Style := psSolid;
  10.   Shape1.SetBounds(Image1.Left - pWidth, Image1.Top - pWidth, Image1.Width  + (2 * pWidth),
  11.                    Image1.Height + (2 * pWidth));
  12. end;
  13.  
« Last Edit: November 04, 2024, 11:35:13 am by jcmontherock »
Windows 11 UTF8-64 - Lazarus 4RC1-64 - FPC 3.2.2

Hartmut

  • Hero Member
  • *****
  • Posts: 855
Re: How to draw some rectangles into a TPicture with undo-feature?
« Reply #2 on: November 04, 2024, 01:17:54 pm »
Hmmm, I don't understand your reply. Maybe you answered accidentally to the wrong post? I want to draw some rectangles somewhere in a TPicture and am asking for the right Canvas to use for that. I don't want to draw a border around a TImage...

wp

  • Hero Member
  • *****
  • Posts: 12474
Re: How to draw some rectangles into a TPicture with undo-feature?
« Reply #3 on: November 04, 2024, 01:53:17 pm »
When you load, as an example, a jpeg file, the pixels is unpacked and stored in some location in some well-defined arrangement. The same happens when you load a bmp or png file, the process always ends with a well-defined pixels matrix, the difference is only in the reading step.

The pixel matrix in memory can accessed in several ways: in a generic way as Image.Picture.Graphic, like a TBitmap as Image.Picture.Bitmap, like a Jpeg as Image.Picture.Jpeg etc. All of them refer to the same memory location of the pixel matrix.

I don't know how it is done internally, but as a consquence of this, all these image types in TImage have the same canvas.

Hartmut

  • Hero Member
  • *****
  • Posts: 855
Re: How to draw some rectangles into a TPicture with undo-feature?
« Reply #4 on: November 04, 2024, 05:19:06 pm »
Thanks wp for that explanaitions. Now I understand, why all the 4 Canvas pointers return the same value.

But does this also mean, that it does not matter (in speed and quality), which of the following 4 statements I use to draw a rectangle into my TPicture?
Code: Pascal  [Select][+][-]
  1. Picture1.Bitmap.Canvas.Rectangle(x1,y1,x2,y2);
  2. Picture1.Pixmap.Canvas.Rectangle(x1,y1,x2,y2);
  3. Picture1.Jpeg.Canvas.Rectangle(x1,y1,x2,y2);
  4. Picture1.PNG.Canvas.Rectangle(x1,y1,x2,y2);

Warfley

  • Hero Member
  • *****
  • Posts: 1763
Re: How to draw some rectangles into a TPicture with undo-feature?
« Reply #5 on: November 04, 2024, 08:11:32 pm »
And for the undo-feature: in which part of the TPicture are the picture data stored? I mean, which part of the TPicture must I copy and save somewhere, before I add a rectangle, to be able, to do the undo?

Thanks in advance.
I assume you want to create some form of graphic editor, then I would recommend you a few things.
1. Don't work on the picture directly, but work on an intermediate representation, e.g. a TBitmap in your memory, which you then copy into the TPicture to show. The reason for that is, depending on what you load into your TPicture, there can be a 32 bit PNG with alpha channel in there, or a 1 bit BMP pixelmap. If you work directly on the TPicture you need to handle every single format TPicture can display.
Instead do the following. Load the picture into some raw format that is easy to work with, e.g. BMP, TGA, whatever. Then do all your computations on that intermediate format and lastly, when exporting convert it back to the desired format.

2. Do not apply all things to the bitmap directly. This makes things like your undo specifically hard. Instead work with discrete objects, like you see in GIMP or Photoshop (layers!). This also makes undo very efficient. For example you add a rectangle. Shure it's easy to just draw it on the canvas, but let's say you want do do an undo, so you make a snapshot of the picture before and after the rectangle was drawn. If the rect is 10x10 pixels, but your picture is 1000x1000, you just have 99% of your snapshot is wasted memory, as nothing has changed.
If instead you create a new layer for the rectangle, the undo is just removing that layer rather than storing the whole picture twice, you only ever store what's happening to the rectangle layer. Even more efficiently, the layer does not need to be rasterized in the first place, you can store the rectangle layer as an abstract form, similar to how vector graphics work. Then changes to the rectangle also will not need full copies of the pixels of that rectangle, but only a snapshot of it's parameters (x, y, width, hight, color, etc.).

For undo then just put any change as reversible object into a undo class hierachy and maintain a list (or tree) of those objects to go back and forth.

Because I was a bit bored, I attached an example project

Hartmut

  • Hero Member
  • *****
  • Posts: 855
Re: How to draw some rectangles into a TPicture with undo-feature?
« Reply #6 on: November 05, 2024, 06:44:33 pm »
Thank you very much Warfley for your long post and your demo. I tried it and I'm impressed, what you accomplished with only 288 lines. But your code is for me too difficult to understand really, because I'm a beginner to Graphics and my knowledge about Layers, Interfaces, Generics, Collections, TList's and some more is zero. And I'm not very experienced with Classes.

I have thought a lot about your suggestion, for the undo-feature not to store always the complete TPicture, but only to store a 'Layer' with informations about e.g. 1 rectangle to draw. I agree, that this has some benefits, especially for memory consumption.

You assumed right, that my "real program" does some more than only drawing some rectangles. Additionally it can crop the picture and I need the undo-feature therefore too. My program already uses a TBitmap, which contains always the TPicture contents after scaling them with a selectable scaling factor. Then this scaled TBitmap is drawed on a TPaintBox. I will try to use this TBitmap to crop the picture using a list of 'Layers' with the appropriate informations what to crop. If I can manage this, undo should be not difficult.

I will need some time for this. If I'll face problems, I will ask again.

Warfley

  • Hero Member
  • *****
  • Posts: 1763
Re: How to draw some rectangles into a TPicture with undo-feature?
« Reply #7 on: November 06, 2024, 01:05:00 am »
I just wanted to put some comments into the code, but then got the idea for some slight improvements, long story short, I got a bit nerdsniped with the editor and added a bunch of new functionalities.

Now the editor has two types of Layers, Bitmaps you can load from pictures, and Rectangles. You can place the layers anywhere on the Paintbox, can select the layers, move them around resize them. Also now you can delete layers, reorder layers and even duplicate layers.
And each of these actions is undo and redoable.

I've attached the new version of the code, together with a whole bunch of comments trying to explain everything that I did.

A few side notes: The canvas is easy to use but it's very limited (when loading pictures, the scaling algorithm is horrible), also on my GTK Linux it is really slow (on windows it works quite well tho). That said, the code can be very easily be ported to better systems like BGRABitmap, which provides it's own, more powerful canvas class.
Also the coupling between the Undo items and the form is still quite tight, as they directly access the form. It would probably be cleaner to have some form of owner reference in the layers, that the undo actions can apply them through this.
Lastly, this editor currently records the whole undo history. For this simple example this is no problem, but if you want to make it into a real editor, that is used in real scenarios, this may run into problems. Modern editors usually have a limit on undo trails, of like 100 or so steps, before the last ones get deleted. This is actually why I built the layers as COM interfaces, because they are reference counted. You can easily have them referenced by the undo tree, but if the undo tree get's to long and you prune some items which have been not used for a long time, they will get removed, and there is no need to manually keep track of what is still in use and what not.

jianwt

  • Full Member
  • ***
  • Posts: 126
Re: How to draw some rectangles into a TPicture with undo-feature?
« Reply #8 on: November 06, 2024, 06:08:01 am »
@Warfley

Very powerful. Thank you very much.

Thaddy

  • Hero Member
  • *****
  • Posts: 16199
  • Censorship about opinions does not belong here.
Re: How to draw some rectangles into a TPicture with undo-feature?
« Reply #9 on: November 06, 2024, 08:42:51 am »
@Warfley

Forgot about TRecall?  ::) Greatly simplifies it and TCanvas derives from TPersistent.
(reminder to self: write an ff'ing example, TRecall is underused, less known)
« Last Edit: November 06, 2024, 09:51:15 am by Thaddy »
If I smell bad code it usually is bad code and that includes my own code.

 

TinyPortal © 2005-2018