Delphi Graphics32 combining normal layers with dra

2019-09-01 08:34发布

问题:

By drawing layer I mean a layer where the user can manually draw lines, circles or other shapes. And by normal layers I mean the layers described in the graphics32 layers example (the layers that can be moved or resized at runtime using mouse events) So I am having difficulties combining these 2 types of layers. In my test project, for now, I will assume I only have one drawing layer and multiple PNG layers. So in my project I set properties for the ImgView32 in the OnFormCreate like:

procedure TForm1.FormCreate(Sender: TObject);
begin
  AWidth:= 800;
  AHeight:= 600;
  FillColor:=clWhite;

  with ImgView do
  begin
    Selection := nil;
    RBLayer := nil;
    Layers.Clear;
    Scale := 1;
    Scaled:=true;
    Bitmap.SetSize(AWidth, AHeight);
    Bitmap.DrawMode := dmTransparent;
    Bitmap.Clear(FillColor);
  end;
end;

After this, onClick of a button, I add a number of layers (containing transparent PNG images). So it's like this

procedure TForm1.Button1Click(Sender: TObject);
begin
  AddPNGLayer(1);
  AddPNGLayer(2);
  AddDrawingLayer;
  AddPNGLayer(3);
end;

(I wont elaborate here the adding of PNG layers in order to keep the question short. I will only say that it uses a different onMouseDown event (layerMouseDown) than the one used in the drawingLayer) and the AddDrawingLayer is as follows:

procedure TForm1.AddDrawingLayer;
var
  P:TPoint;
  jumaH, JumaW, W, H: Single;
begin
  imwidth := ImgView.Bitmap.Width;
  imheight := ImgView.Bitmap.Height;

  xofx := (ImgView.ClientWidth - 17 - imwidth) div 2; // substracting the width of the scrollbar
  yofy := (ImgView.ClientHeight - 17 - imheight) div 2; // same here with height

  bm32 := TBitmap32.Create;
  bm32.DrawMode := dmTransparent;
  bm32.SetSize(ImgView.Bitmap.Width,ImgView.Bitmap.Height);
  bm32.Canvas.Pen.Width := 3;
  bm32.Canvas.Pen.Color := clBlack32;//pencolor;

  BB := TBitmapLayer.Create(ImgView.Layers);
  try
    BB.Bitmap.DrawMode := dmTransparent;
    BB.Bitmap.SetSize(imwidth,imheight);
    BB.Bitmap.Canvas.Pen.Width := 3;
    BB.Bitmap.Canvas.Pen.Color := pencolor;
    BB.Location := GR32.FloatRect(0, 0, imwidth, imheight);
    BB.Scaled := true;
    BB.Tag:=3;
////    Selection:=BB;  // if I use this then I cant draw because the entire layer is selected and the mouseDown event works as a mover/resizer
//    BB.OnMouseDown := DrLayerMouseDown;
//    BB.OnMouseUp := DrLayerMouseUp;
//    BB.OnMouseMove := DrLayerMouseMove;
//    BB.OnPaint := DrLayerOnPaint;
    RBLayer:=nil;
    EdLayerIndex.Text:=IntToStr(BB.Index);
  finally
    BB.Free;
  end;
  FDrawingLine := false;
//    swapBuffers32; // needed when mouse events are active
end;

EdLayerIndex is a EditBox where I display the created/selected Layer index (for debugging)

  • As you can see above, if I keep Selection:=BB and RBLayer:=nil then the drawingLayer is only movable and resizable, so it's not a good solution since I want to use my Mouse events in this particular layer to draw.
  • If I comment only the RBLayer:=nil while keeping Selection:=BB then the drawingLayer is not movable anymore, but I cannot select other layers that are under the drawingLayer. I can only access the top layer (the last added PNG layer)

  • If I comment the Selection:=BB then I cannot select other layers with my mouse. So in my case I declared 2 png layers before my drawingLayer and one after it. On runtime I can only select the last layer (the one 'above' the drawingLayer) So this is not a solution either.

How can I do it that when I click on the drawing layer (or select it otherwise, like in a listbox or something), the drawingLayer wont be movable, but my drawing Mouse Events will kick in? And all this while I can go away from the drawingLayer whenever I want and select other layers to move around and play with. So basically I need a particular layer to NOT act like the other layers.

What I want to achieve is having a classic Photoshop-like or paint.net like behavior using graphics32. And it is very confusing how these layer properties actually work.

So far I figured out how to draw (lines, circles, rectangles) on a transparent layer dynamically (using mouse events). So I can have a drawing layer. The drawing happens in my DrLayerMouseDown, DrLayerMouseUp, DrLayerMouseMove, DrLayerPaint events. But I cannot seem to understand how to combine such a drawing layer with regular movable/resizable layers.

The rest of the code (like setSelection, RBResizing and layerMouseDown) is mostly taken from the layers example of the graphics32 library.

EDIT

In order to test your idea with layerOptions, I did the following:

1.Started a new test project with an ImgView on it, and a button

2.On create i used the same code as before

3.OnButtonClick I added ONE layer using a modified AddDrawingLayer like this:

...
    BB.Scaled := true;
    Selection:=BB;
    Selection.LayerOptions:=Selection.LayerOptions and (not LOB_MOUSE_EVENTS); // I also tried it with BB instead of Selection
    BB.OnMouseDown := DrLayerMouseDown;
    BB.OnMouseUp := DrLayerMouseUp;
    BB.OnMouseMove := DrLayerMouseMove;
    BB.OnPaint := DrLayerOnPaint;
...

expecting it to become insensitive to Mouse Events. But the layer is still movable instead of being insensitive to mouse. So it's like I did not do anything

So I do not think it helps me using this option unless I am doing it wrong So onCreate of the layer, this option does not seem to stick. But if I disable mouse events for all layers, one-by-one like in the next EDIT, then the drawing layer gets disabled (mouse events)

EDIT

Also I tried another test project, same idea: same onCreate, and onButtonClick I add 3 layers (using the Layers example of the library) containing an image each (no drawing layer this time, to keep it simple). Then I added a new button where if you click it, the next code is executed:

  for i := 0 to ImgView.Layers.Count-1 do
    (ImgView.Layers.Items[i] as TPositionedLayer).LayerOptions:= (ImgView.Layers.Items[i] as TPositionedLayer).LayerOptions and (not LOB_MOUSE_EVENTS);

My purpose was to make all layers insensitive to mouse events. I succeeded, after clicking the new button, the layers could not be selected anymore, however when I wanted to re-enable mouse events for the layers (adding a third button with the next code onClick):

  for i := 0 to ImgView.Layers.Count-1 do
    (ImgView.Layers.Items[i] as TPositionedLayer).LayerOptions:= (ImgView.Layers.Items[i] as TPositionedLayer).LayerOptions and (LOB_MOUSE_EVENTS);

No error was shown, but when I tried to select a layer in order to move it ... all the images of the layers disappeared from the view... leaving me with a white background empty ImgView.

What am I doing wrong? In order to do what you suggested with LayerOptions, I need to be able to disable mouse events for all layers, and enable mouse events for a specific layer, and then when editing is done, I need to be able to re-enable mouse events for all layers, but I am doing it wrong I guess.

回答1:

Following items affect mouse events

  • Layers.MouseEvents (boolean). Layers is the TLayerCollection of TCustomImage32 that manages the layers. If MouseEvents is False, mouse events are not propagated to the layers.

  • Layers.MouseListener (TCustomLayer). The layer that 'captures' mouse events between left button MouseDown and MouseUp. 'Captures' in quoutes because it is not capturing the mouse as understood in Windows context.

  • Layer Option Bits. Each layer has a 32 bit LayerOptions property. The interesting bit is LOB_MOUSE_EVENTS (bit 29) which specifies whether the layer reacts to mouse events. A layer may also specify LOB_NO_CAPTURE bit (bit 27) which prevents mouse events, even if LOB_MOUSE_EVENTS is set.

  • Layer index. Layers are checked (in order topmost down to lowest) for LOB_MOUSE_EVENTS option bit. When a layer is found with this bit, the X and Y coordinates are checked in the layers HitTest function. If the X and Y coordinates are within the layers location HitTest succeeds. The result of built-in HitTest can be overridden in your own OnHitTest event. Finally, if the layers options does not contain the LOB_NO_CAPTURE bit, the layers MouseDown event is called.

Based on previous I suggest that when the user enters an 'edit' mode, you disable all other layers, except the drawing layer, by setting their LayerOptions to not include the LOB_MOUSE_EVENTS bit

Layer.LayerOptions := Layer.LayerOptions and (not LOB_MOUSE_EVENTS);

Further info about using layers is available here

Edit

To manage the LOB_MOUSE_EVENTS create for example something like following

procedure TForm7.LayerMouseDisEnable(Enable: boolean);
var
  i: integer;
  Lo: cardinal;
begin
  for i := 0 to ImgView.Layers.Count-1 do
  begin
    Lo := ImgView.Layers.Items[i].LayerOptions;
    if Enable then
      ImgView.Layers.Items[i].LayerOptions := Lo or LOB_MOUSE_EVENTS
    else
      ImgView.Layers.Items[i].LayerOptions := Lo and (not LOB_MOUSE_EVENTS);
  end;
end;

Call this (with False) to disable mouse events in the layers just before you create the drawing layer. The drawing layer will have mouse events enabled as newly created layers have both LOB_VISIBLE and LOB_MOUSE_EVENTS set. Call again (with True) when you stop drawing to enable the mouse event.