Anders Melander 1782 Posted March 18, 2023 7 hours ago, cltom said: Sorry, the TImage32Access and Stretchtransfer come from which unit? The code was copied from TCustomBitmapLayer in GR32_Layers, so you can find it there: type TImage32Access = class(TCustomImage32); Stretchtransfer is declared in GR32_Resamplers. I'm considering making TIndirectBitmapLayer the base class for TCustomBitmapLayer. 1 Share this post Link to post
cltom 1 Posted March 21, 2023 On 3/18/2023 at 3:06 PM, Anders Melander said: The code was copied from TCustomBitmapLayer in GR32_Layers, so you can find it there: type TImage32Access = class(TCustomImage32); Stretchtransfer is declared in GR32_Resamplers. I'm considering making TIndirectBitmapLayer the base class for TCustomBitmapLayer. Again, a huge thank you! As an update: I got this to run with the TIndirectBitmapLayer class which - both to my shame but also to my joy - works well with very little code. Also, I had some inspirational from the ImgView_Layers-Demo. In general: I have now a main class which handles the Objectlist for all elements (which is then used to save the drawing, etc.) and another class that does the drawing. Consequently, some conceptual questions: - When using the TLayerCollection, the whole movement/scaling of objects is handled (great!). What is a good method of getting information on the movement of the layer back to my main class where I need to keep track of the objects and their coordinates? I could access the TLayerCollection and the position of the layers. I can only rely though on an ID in either structure to get the same object. Alternatively I could follow track the selected object and the mouse move in the main class. - also, when trying to restrict the movement of layers, how can I access the coordinates of a layer while moving it to mimic a SnapToGrid function? - coming back to the rubberband-question: how to implement this? Should I create a TRubberband layer via the MouseDown/MouseMove events? Share this post Link to post
Anders Melander 1782 Posted March 21, 2023 42 minutes ago, cltom said: - When using the TLayerCollection, the whole movement/scaling of objects is handled (great!). What is a good method of getting information on the movement of the layer back to my main class where I need to keep track of the objects and their coordinates? I could access the TLayerCollection and the position of the layers. I can only rely though on an ID in either structure to get the same object. Alternatively I could follow track the selected object and the mouse move in the main class. I would just store a reference to your object and the object owner in the layer and then notify the owner when the layer is moved. Something like this: type TObjectLayer = class; TObjectLayerNotification = ( olnDestroy, // Subscribers should remove reference to layer olnPosition // Layer has moved ); IObjectLayerNotification = interface ['{C5715B62-6D20-4BEE-841A-A898AA67D6F7}'] procedure ObjectLayerNotification(ALayer: TObjectLayer; ANotification: TObjectLayerNotification); end; TObjectLayer = class(TIndirectBitmapLayer) private FSubscribers: TList<IObjectLayerNotification>; FObjectID: TSomeType; protected procedure Notify(ANotification: IObjectLayerNotification); procedure DoSetLocation(const NewLocation: TFloatRect); override; public destructor Destroy; override; procedure Subscribe(const ASubscriber: IObjectLayerNotification); procedure Unsubscribe(const ASubscriber: IObjectLayerNotification); property ObjectID: TSomeType read FObjectID write FObjectID; end; destructor TObjectLayer.Destroy; begin Notify(olnDestroy); FSubscribers.Free; inherited; end; procedure TObjectLayer.DoSetLocation(const NewLocation: TFloatRect); begin inherited DoSetLocation(NewLocation); Notify(olnPosition); end; procedure TObjectLayer.Notify(ANotification: IObjectLayerNotification); begin if (FSubscribers = nil) then exit; for var Subscriber in FSubscribers.ToArray do // ToArray for stability Subscriber.ObjectLayerNotification(Self, ANotification); end; procedure TObjectLayer.Subscribe(const ASubscriber: IObjectLayerNotification); begin if (FSubscribers = nil) then FSubscribers := TList<IObjectLayerNotification>.Create; FSubscribers.Add(ASubscriber); end; procedure TObjectLayer.Unsubscribe(const ASubscriber: IObjectLayerNotification); begin if (FSubscribers <> nil) then FSubscribers.Remove(ASubscriber); end; The object ID is stored in the ObjectID property (change the type to whatever type you use an an ID). The owner must implement the IObjectLayerNotification interface and call Subscribe on the layer to get notifications. This is a pretty standard observer pattern. 1 hour ago, cltom said: - also, when trying to restrict the movement of layers, how can I access the coordinates of a layer while moving it to mimic a SnapToGrid function? If you are using the TRubberbandLayer then there's a OnConstrain event where you can examine and modify the move/resize. If you are doing move/resize with some other method then I'll need some information about that. 1 hour ago, cltom said: - coming back to the rubberband-question: how to implement this? Should I create a TRubberband layer via the MouseDown/MouseMove events? I usually implement rubber-band selection via the TImgView32 mouse events (i.e. I'm not using a layer). So I manage the selection-in-progress state (usually just the mouse-down position) and any current selection on the form. In the mouse-up handler, I create a rectangle polygon from the mouse-down pos and the mouse-up pos and then either replace the current selection with the new one or merge the two (union), depending on the keyboard shift state. The selection is stored as a polygon. You can also use a polypolygon depending on your needs. The selection is drawn by a custom layer (visible only when there actually is a selection). The layer has a copy of the polygon and draws a marching ants (btw, try googling "marching ants") animated line using a stipple pattern and a timer. Here's the Paint method of the layer: procedure TSelectionLayer.Paint(Buffer: TBitmap32); begin try // Update local copy of selection polygon UpdateCache; if (BitmapEditor.HasSelection) then begin Buffer.SetStipple(SelectionStipple); Buffer.StippleCounter := FSelectionStippleCounter; Buffer.StippleStep := 1; PolylineXSP(Buffer, FCachedSelection, not SelectionInProgress); end; except // Prevent AV flood due to repaint Visible := False; raise; end; end; and the setup and control of the stipple pattern: constructor TSelectionLayer.Create(ABitmapEditor: TBitmapEditor); begin inherited Create(ABitmapEditor); FCacheValid := False; FTimer := TTimer.Create(nil); FTimer.Interval := 50; FTimer.OnTimer := OnTimer; FTimer.Enabled := False; CreateStipple(FSelectionStipple, $F0F0F0F0, clBlack32, clWhite32); end; procedure TSelectionLayer.OnTimer(Sender: TObject); begin // TODO : Remove dependency on Forms unit if (not Application.Active) then exit; FSelectionStippleCounter := FSelectionStippleCounter+1.5; if (FSelectionStippleCounter >= Length(SelectionStipple)) then FSelectionStippleCounter := FSelectionStippleCounter - Length(SelectionStipple); Update(FBitmapRect); end; 1 Share this post Link to post
Anders Melander 1782 Posted March 22, 2023 Btw, you might want to check out the Image32Background and PanAndZoom examples in the image32_background branch. They demonstrate how to do the following without any custom code (they are now built-in optional features in TImage32/TImgView32): and panning & animated (using cubic tweening) exponential zoom with pivot point: 1 Share this post Link to post
cltom 1 Posted March 22, 2023 18 hours ago, Anders Melander said: I would just store a reference to your object and the object owner in the layer and then notify the owner when the layer is moved. Something like this: The object ID is stored in the ObjectID property (change the type to whatever type you use an an ID). The owner must implement the IObjectLayerNotification interface and call Subscribe on the layer to get notifications. This is a pretty standard observer pattern. If you are using the TRubberbandLayer then there's a OnConstrain event where you can examine and modify the move/resize. If you are doing move/resize with some other method then I'll need some information about that. I usually implement rubber-band selection via the TImgView32 mouse events (i.e. I'm not using a layer). So I manage the selection-in-progress state (usually just the mouse-down position) and any current selection on the form. In the mouse-up handler, I create a rectangle polygon from the mouse-down pos and the mouse-up pos and then either replace the current selection with the new one or merge the two (union), depending on the keyboard shift state. The selection is stored as a polygon. You can also use a polypolygon depending on your needs. The selection is drawn by a custom layer (visible only when there actually is a selection). The layer has a copy of the polygon and draws a marching ants (btw, try googling "marching ants") animated line using a stipple pattern and a timer. Here's the Paint method of the layer: and the setup and control of the stipple pattern: A massive thank you for your support and work! I will dive into it and try to get it implemented! As a quick reaction, again: thank you!! Share this post Link to post
Anders Melander 1782 Posted March 22, 2023 On 3/17/2023 at 6:04 PM, Anders Melander said: TIndirectBitmapLayer = class abstract(TPositionedLayer) This should of course not have been an abstract class. I have just committed an implementation of the class to the main branch: https://github.com/graphics32/graphics32/commit/aec3713e187d1300b111f0315380a38b50033fef Share this post Link to post
cltom 1 Posted March 24, 2023 On 3/23/2023 at 12:24 AM, Anders Melander said: This should of course not have been an abstract class. I have just committed an implementation of the class to the main branch: https://github.com/graphics32/graphics32/commit/aec3713e187d1300b111f0315380a38b50033fef Would this ObjectLayer class also be the right place to add a TAffineTransformation for rotating or flipping an element? Should then the transformation take place in the Paint event? Share this post Link to post
Anders Melander 1782 Posted March 24, 2023 2 hours ago, cltom said: Would this ObjectLayer class also be the right place to add a TAffineTransformation for rotating or flipping an element? Should then the transformation take place in the Paint event? Yes, and yes. See TCustomAffineLayer in Examples\Layers\RotLayer\GR32_RotLayer.pas Share this post Link to post
cltom 1 Posted April 2, 2023 Despite the help even in the form of source code that I could just paste, I am struggling with some fundamentals: On 3/21/2023 at 6:42 PM, Anders Melander said: If you are using the TRubberbandLayer then there's a OnConstrain event where you can examine and modify the move/resize. How to do that? Do I just write a new OnConstrain event that I assign to the TRubberband-Layer? On 3/21/2023 at 6:42 PM, Anders Melander said: type TObjectLayer = class; TObjectLayerNotification = ( olnDestroy, // Subscribers should remove reference to layer olnPosition // Layer has moved ); .... Here, I guess I am lacking the fundamentals on how this should work. I can't get this to run. On 3/21/2023 at 6:42 PM, Anders Melander said: property ObjectID: TSomeType read FObjectID write FObjectID; on this one, field definition not allowed after methods or properties, no matter where I put it. On 3/21/2023 at 6:42 PM, Anders Melander said: Notify(olnDestroy); Here it says incompatible types IObjectLayerNotification and TObjectLayernotification. On 3/21/2023 at 6:42 PM, Anders Melander said: procedure TSelectionLayer.Paint(Buffer: TBitmap32); begin try // Update local copy of selection polygon UpdateCache; if (BitmapEditor.HasSelection) then begin Buffer.SetStipple(SelectionStipple); Buffer.StippleCounter := FSelectionStippleCounter; Buffer.StippleStep := 1; PolylineXSP(Buffer, FCachedSelection, not SelectionInProgress); end; except // Prevent AV flood due to repaint Visible := False; raise; end; end; should this be a separate class of its own? TSelectionLayer? And another one: in order to save all objects including the background, do I need to flatten all layers? Essentially I guess I have to make a copy of the bitmap and all layers flatten the copies and continue to work with the original one? I was thinking about sharing the project as I think this generic functionality could be a useful template, at this point I it is still rather a fragment though. Share this post Link to post
Anders Melander 1782 Posted April 2, 2023 1 hour ago, cltom said: Do I just write a new OnConstrain event that I assign to the TRubberband-Layer? Yes. You write an event handler and assign it to the event: procedure TMyForm.LayerConstrainHandler(Sender: TObject; const OldLocation: TFloatRect; var NewLocation: TFloatRect; DragState: TRBDragState; Shift: TShiftState); begin // Snap to nearest 10 pixels if [Shift] is down. // Note that the roQuantized layer option provides similar functionality. if (ssShift in Shift) then begin NewLocation.Left := Trunc((NewLocation.Left + 5) / 10) * 10; NewLocation.Top := Trunc((NewLocation.Top + 5) / 10) * 10; NewLocation.Right := Trunc((NewLocation.Right + 5) / 10) * 10; NewLocation.Bottom := Trunc((NewLocation.Bottom + 5) / 10) * 10; end; end; ... var Layer := TRubberbandLayer.Create(MyImage.Layers); Layer.OnConstrain := LayerConstrainHandler; Layer.Options := Layer.Options + [roConstrained]; ... 1 hour ago, cltom said: Here, I guess I am lacking the fundamentals on how this should work. I can't get this to run. The code I posted isn't runnable as-is. It's just an example from the top of my head. 1 hour ago, cltom said: on this one, field definition not allowed after methods or properties, no matter where I put it. Replace TSomeType with whatever type you use to identify your object. 1 hour ago, cltom said: Here it says incompatible types IObjectLayerNotification and TObjectLayernotification. Typo on my part. The Notify method should take a TObjectLayernotification as a parameter. 1 hour ago, cltom said: should this be a separate class of its own? TSelectionLayer? It's just an example of how to draw a marching ants selection in a layer but I suggest you postpone that until you have a better grasp of the fundamentals and have gotten the other stuff working. I just remembered that it is it's actually possible to animate TRubberBandLayer via the FrameStippleCounter property. For example, if you modify the ImgView_Layers example and place a 100 mS TTimer on the mainform with this code: procedure TMainForm.Timer1Timer(Sender: TObject); const StippleSize = 4; // The size of the default stipple pattern begin if (RBLayer = nil) or (not RBLayer.Visible) then exit; var NewStippleCounter := RBLayer.FrameStippleCounter+0.5; // Handle overflow if (NewStippleCounter >= StippleSize) then NewStippleCounter := NewStippleCounter - StippleSize; RBLayer.FrameStippleCounter := NewStippleCounter; RBLayer.Changed; end; 2 hours ago, cltom said: And another one: in order to save all objects including the background, do I need to flatten all layers? Essentially I guess I have to make a copy of the bitmap and all layers flatten the copies and continue to work with the original one? That really depends on your use case. If you just need a bitmap with the final image, then you can use the PaintTo method to draw the flattened image on another bitmap. If you want to save your image as objects then you're on your own; You'll have to write code to save the image state, including layers, to some custom file format. 2 hours ago, cltom said: I was thinking about sharing the project as I think this generic functionality could be a useful template, at this point I it is still rather a fragment though. Sure. Create a github (or whatever you prefer) repository for it. Share this post Link to post
cltom 1 Posted April 19, 2023 Hej Anders, thank you again for the dedicated help! As I had some to do some clean-up in another project, this one received a little less attention. But some progress is there: - the save I could just use from the Layers-example 😉 - the OnConstraint works (I was using OldLocation instead of new - along the lines of NewLocation := OldLocation div x etc. So I have quite a bit of work left to do but the project is alive! 😄 1 Share this post Link to post
cltom 1 Posted January 19 Hej, since some other projects took over, there is quite some delay now. However, I still should make some progress on this, so I put it on github as promised: tomonsight/PMDraw: Symbolic Drawing App (github.com) Needless to say this is not a ready application and it surely reflects all sorts of incompetencies ... Anyway, some issues that I am facing: - the constraint to the grid is "jumpy" - I cannot select text - the functionality to select multiple objects is missing But still, the idea is probably visible of what I am trying to do. Thanks for attention/help, maybe you can spot some easy fixes. Share this post Link to post