Recent

Author Topic: BGRABitmap - layers  (Read 25981 times)

Dibo

  • Hero Member
  • *****
  • Posts: 1046
BGRABitmap - layers
« on: November 26, 2011, 07:25:39 pm »
Hi,

Have TBGRABitmap some kind of layers functionality? For example: I am adding new image by BGRA.PutImage(). Can I somehow change order or visibility of "layers" added by this method?

Regards.
« Last Edit: November 26, 2011, 07:52:16 pm by Dibo »

lainz

  • Guest
Re: BGRABitmap - layers
« Reply #1 on: November 26, 2011, 10:55:17 pm »
I think that it doesn't have. But you can add Layers with using multiple bgrabitmap in memory.

One for the background, the biggest bitmap where to put all the layers, the others for each visual object you have. For example:

bgra1_ transparent background 640x480 << this is the final bitmap
bgra2_ a picture with an sky and a floor << resample to 640x480 to fit
bgra3_ a drawing of a tree 300x400 at pos 170,40 << put in the position you want

manage with something like:

Code: [Select]
  TBGRALayerPosition = record
    x, y: integer;
    Source: TBGRACustomBitmap;
    mode: TDrawMode;
    AOpacity: byte;
  end;

  TBGRALayerPositions = array of TBGRALayerPosition; 

this is an example with a bgravirtualscreen and two rectangles. Every time you update the virtualscreen the order of the layers will switch (is a public boolean variable in the form), the black square in bottom and the red in top, the black in top and the red in bottom.

Code: [Select]
procedure TForm1.BGRAVirtualScreen1Redraw(Sender: TObject; Bitmap: TBGRABitmap);
var
  layers: TBGRALayerPositions;
  bgra_2, bgra_3: TBGRABitmap;
  o,p,i: integer;
begin
  SetLength(layers,2);

  bgra_2 := TBGRABitmap.Create(100,100, BGRABlack);
  bgra_3 := TBGRABitmap.Create(100,100, BGRA(255,0,0,255));


  if switch then
  begin
    o := 1;
    p := 0;
    switch := not switch;
  end
  else
  begin
    o := 0;
    p := 1;
    switch := not switch;
  end;

  with layers[o]do
  begin
    x := 10;
    y := 10;
    mode := dmSet;
    AOpacity := 255;
    Source := bgra_2;
  end;

  with layers[p]do
  begin
    x := 50;
    y := 50;
    mode := dmSet;
    AOpacity := 255;
    Source := bgra_3;
  end;

  for i:=0 to High(layers) do
    Bitmap.PutImage(layers[i].x, layers[i].y, layers[i].Source, layers[i].mode, layers[i].AOpacity);

  bgra_2.Free;
  bgra_3.Free;
end;

you only need to change the order of the addition in the array and that's all.

Of course it needs to be well coded with usefull procedures, object oriented...
« Last Edit: November 26, 2011, 10:58:08 pm by lainz »

circular

  • Hero Member
  • *****
  • Posts: 3078
    • Personal webpage
Re: BGRABitmap - layers
« Reply #2 on: November 27, 2011, 12:59:40 pm »
It is also possible to use BlendImage to use special blending operations. I'm working on a class according to the proposition you've made Lainz.
Conscience is the debugger of the mind

circular

  • Hero Member
  • *****
  • Posts: 3078
    • Personal webpage
Re: BGRABitmap - layers
« Reply #3 on: November 27, 2011, 05:07:17 pm »
Here is a new version (5.3) of BGRABitmap with layer support (in BGRALayers) :

http://sourceforge.net/projects/lazpaint/files/src/

How to use :
Code: [Select]
  layers := TBGRALayeredBitmap.Create(640,480);
  layers.AddLayer(someBmp,128);
  layers.AddLayerFromFile('filename1');
  layers.LayerOpacity[layers.AddLayerFromFile('filename1')] := 128;
  ...
  layers.Draw(bmp,0,0);
  ...
  layers.free;
Conscience is the debugger of the mind

lainz

  • Guest
Re: BGRABitmap - layers
« Reply #4 on: November 27, 2011, 07:41:16 pm »
Amazing you're really fast.

circular

  • Hero Member
  • *****
  • Posts: 3078
    • Personal webpage
Re: BGRABitmap - layers
« Reply #5 on: November 27, 2011, 08:45:13 pm »
Well, sometimes, i've got the inspiration and support, then I can go pretty fast, and I just write programs like I am talking. Sometimes my fingers struggle to follow.  :D
Conscience is the debugger of the mind

j0x

  • Full Member
  • ***
  • Posts: 126
Re: BGRABitmap - layers
« Reply #6 on: November 27, 2011, 11:31:53 pm »
i hope this layer functionality will be added to LazPaint too so that i can forget about Paint .NET already  :)

Rails

  • Guest
Re: BGRABitmap - layers
« Reply #7 on: March 24, 2012, 09:03:08 pm »
I have written a short program to test the concept of having a drawing cursor rubber banding a drawing element (simple lines in this test case) on one layer while the drawn elements are on another. It seems to work fine.  It is much faster than storing the lines' coordinates in an array and redrawing everything each time the mouse moves a pixel.

I fill the cursor layer with white to erase the old line each time the mouse is moved because I couldn't find a simple way to just clear the layer. Perhaps I am missing something?  Yes, probably so!   ::)

I have minimal experience with graphics, and OOP for that matter, and this is my first attempt at using layers.

Please tell me what you think.

Thanks.



Code: [Select]
unit uMain;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ComCtrls,
  BGRABitMap, BGRABitMapTypes;

type

  { TForm1 }

  TForm1 = class(TForm)
    StatusBar1: TStatusBar;
    procedure FormCreate(Sender: TObject);
    procedure FormMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
    procedure FormMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure FormPaint(Sender: TObject);
  private
    image, mainLayer, cursorLayer: TBGRABitmap;
    procedure PaintImage;
    { private declarations }

  public
    { public declarations }
  end;

var
  Form1                   : TForm1;
  x1, y1, x2, y2, count   : Integer;


implementation

{$R *.lfm}

{ TForm1 }

procedure TForm1.FormCreate(Sender: TObject);

begin
  Form1.Width  := 800;
  Form1.Height := 600;
  image        := TBGRABitmap.Create(800,600,BGRAWhite);  //create a 800x600 image
  cursorLayer  := TBGRABitmap.Create(image.Width,image.Height);
  mainLayer    := TBGRABitmap.Create(image.Width,image.Height);

end;

procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if  Button = mbLeft then
    begin
      x1 := x;  // Store the line's starting point
      y1 := y;
    end;
end;

procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
begin

  if ssLeft in Shift then   // Mouse is moving with left button down
    begin
      cursorLayer.Fill(BGRAWhite);  // Erase the line since the mouse has moved
      x2 := x;                      // Get the new coordinates for the cursor end
      y2 := y;

      // Draw the line with the new coordinates
      cursorLayer.DrawLineAntiAlias(x1,y1,x2,y2,BGRABlack,1.5);
      // Put it on the image
      image.PutImage(0,0,cursorLayer,dmDrawWithTransparency);
      // Now put rest of the drawing back on the image
      image.PutImage(0,0,mainLayer,dmDrawWithTransparency);
      PaintImage;
    end;

end;

procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  // Draw the completed line on the main layer
  mainLayer.DrawLineAntiAlias(x1,y1,x2,y2,BGRABlack,1.5);
  // Might as well count the lines
  inc(count);
  StatusBar1.Panels[0].Text :=IntToStr(count) + ' lines';
end;

procedure TForm1.FormPaint(Sender: TObject);
begin
  PaintImage;
end;

procedure TForm1.PaintImage;
begin
  image.Draw(Canvas,0,0,True);
end;

end.

« Last Edit: March 24, 2012, 11:07:41 pm by Rails »

circular

  • Hero Member
  • *****
  • Posts: 3078
    • Personal webpage
Re: BGRABitmap - layers
« Reply #8 on: March 24, 2012, 11:05:05 pm »
So, in fact you draw the layer with the current line under the main image ? Doing this way, it may happen that you don't see that you are drawing something. You can instead draw the layer with the current line above it. To do so, use FillTransparent instead, so that only the line is drawn over.
Conscience is the debugger of the mind

Rails

  • Guest
Re: BGRABitmap - layers
« Reply #9 on: March 24, 2012, 11:33:56 pm »
I tried cursorLayer.FillTransparent and also cursorLayer.Fill(BGRAPixelTransparent).

Both built with no problems, but neither one erased the old cursor lines, which I don't understand. I also tried reversing the order of

     image.PutImage(0,0,cursorLayer,dmDrawWithTransparency);
      // Now put rest of the drawing back on the image
      image.PutImage(0,0,mainLayer,dmDrawWithTransparency);   

which is what I assume you meant. That also made no difference when used with FillTransparent, but reversing them does cause problems with Fill(BGRAWhite).

I have tried drawing over 500 lines with no apparent issues due to the order.

Your thoughts?
« Last Edit: March 24, 2012, 11:47:31 pm by Rails »

Rails

  • Guest
Re: BGRABitmap - layers
« Reply #10 on: March 25, 2012, 01:22:14 am »
Okay, I think I have it now. I needed to wipe the image itself in the OnMouseMove event, which I wasn't doing. FillTransparent now works on the cursorLayer, and I now have the cursor's rubber banding on top of the previous lines, instead of below them. I changed the rubber band to red to confirm it.

The changed code is below. Thanks for your assistance. BGRABitMap is a great thing.   8)



Code: [Select]
unit uMain;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ComCtrls,
  BGRABitMap, BGRABitMapTypes;

type

  { TForm1 }

  TForm1 = class(TForm)
    StatusBar1: TStatusBar;
    procedure FormCreate(Sender: TObject);
    procedure FormMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
    procedure FormMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure FormPaint(Sender: TObject);
  private
    image, mainLayer, cursorLayer: TBGRABitmap;
    procedure PaintImage;
    { private declarations }

  public
    { public declarations }
  end;

var
  Form1                   : TForm1;
  x1, y1, x2, y2, count   : Integer;


implementation

{$R *.lfm}

{ TForm1 }

procedure TForm1.FormCreate(Sender: TObject);

begin
  Form1.Width  := 800;
  Form1.Height := 600;
  image        := TBGRABitmap.Create(800,600,BGRAWhite);  //create a 800x600 image
  cursorLayer  := TBGRABitmap.Create(image.Width,image.Height);
  mainLayer    := TBGRABitmap.Create(image.Width,image.Height);

end;

procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if  Button = mbLeft then
    begin
      x1 := x;  // Store the line's starting point
      y1 := y;
    end;
end;

procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
begin

  if ssLeft in Shift then   // Mouse is moving with left button down
    begin
      // Erase the line since the mouse has moved
      cursorLayer.FillTransparent;
      image.Fill(BGRAWhite);
      image.PutImage(0,0,mainLayer,dmDrawWithTransparency);
      // Get the new coordinates for the cursor end of the line
      x2 := x;
      y2 := y;
      // Draw the line with the new coordinates
      cursorLayer.DrawLineAntiAlias(x1,y1,x2,y2, BGRA(255,0,0),1.5);
      // Put it on the image
      image.PutImage(0,0,cursorLayer,dmDrawWithTransparency);
      PaintImage;
    end;

end;

procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  // Draw the completeed line on the main layer
  mainLayer.DrawLineAntiAlias(x1,y1,x2,y2,BGRABlack,1.5);
  // Might as well count the lines
  inc(count);
  StatusBar1.Panels[0].Text :=IntToStr(count) + ' lines';
end;

procedure TForm1.FormPaint(Sender: TObject);
begin
  PaintImage;
end;

procedure TForm1.PaintImage;
begin
  image.Draw(Canvas,0,0,True);
end;

end.

circular

  • Hero Member
  • *****
  • Posts: 3078
    • Personal webpage
Re: BGRABitmap - layers
« Reply #11 on: March 26, 2012, 01:26:44 am »
Cool. Thanks.

Now your code works on Windows, but may not work on other platforms, for example on MacOS, because of the way form Canvas is available. It is guaranted to be available only in an OnPaint event. So you should not call PaintImage directly, but instead call Invalidate or Repaint, which will call OnPaint. But then you get a flickering, so you need to prevent background to be cleared.

You can use the TBGRAVirtualScreen component of BGRAControls so you just need to redefine the OnRedraw event and call RedrawBitmap.
Conscience is the debugger of the mind

Rails

  • Guest
Re: BGRABitmap - layers
« Reply #12 on: March 26, 2012, 04:04:24 am »
Well, it works fine in Linux too.  :)

So you should not call PaintImage directly, but instead call Invalidate or Repaint, which will call OnPaint. But then you get a flickering, so you need to prevent background to be cleared.

But, like all good ideas, I stole the present method -- from one of your tutorials.  :D  However, I will keep this in mind. I am now confident that layers will work for my app, which I have begun to work on once again. At this point the test app is retired.

I do have another question though. Aside from greater memory usage and somewhat more processing time required, is there a practical limit to the number of layers? Right now, I am anticipating a total of five.
« Last Edit: March 26, 2012, 04:08:57 am by Rails »

circular

  • Hero Member
  • *****
  • Posts: 3078
    • Personal webpage
Re: BGRABitmap - layers
« Reply #13 on: March 26, 2012, 02:41:35 pm »
But, like all good ideas, I stole the present method -- from one of your tutorials.  :D
Oh no, I did not propose that ?  :o

Well, yeah, it is simpler that other methods. Ok, if it works on Linux, then it is essentially on MacOS that it won't.

Quote
I do have another question though. Aside from greater memory usage and somewhat more processing time required, is there a practical limit to the number of layers? Right now, I am anticipating a total of five.
You're right, the main issues are memory and processing time. The program can also get complex, for example if the number of layers can change. In this case, you can use BGRALayers. Your test program would become :
Code: [Select]
unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ComCtrls,
  BGRABitMap, BGRABitMapTypes, BGRALayers;

type

  { TForm1 }

  TForm1 = class(TForm)
    StatusBar1: TStatusBar;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
    procedure FormMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure FormPaint(Sender: TObject);
  private
    layers: TBGRALayeredBitmap;
    backgroundLayer, mainLayer, cursorLayer: TBGRABitmap;
    procedure PaintImage;
    { private declarations }

  public
    { public declarations }
  end;

var
  Form1                   : TForm1;
  x1, y1, x2, y2, count   : Integer;


implementation

{$R *.lfm}

{ TForm1 }

procedure TForm1.FormCreate(Sender: TObject);
begin
  Form1.Width  := 800;
  Form1.Height := 600;
  layers       := TBGRALayeredBitmap.Create(800,600);

  //create background layer
  backgroundLayer    := TBGRABitmap.Create(layers.Width,layers.Height,BGRAWhite);
  layers.AddOwnedLayer(backgroundLayer);

  //create main layer
  mainLayer    := TBGRABitmap.Create(layers.Width,layers.Height);
  layers.AddOwnedLayer(mainLayer);

  //create cursor layer
  cursorLayer  := TBGRABitmap.Create(layers.Width,layers.Height);
  layers.AddOwnedLayer(cursorLayer,192);

end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  //free layers (don't need to free each layer individually because they are owned)
  layers.Free;
end;

procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if  Button = mbLeft then
    begin
      x1 := x;  // Store the line's starting point
      y1 := y;
    end;
end;

procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
begin

  if ssLeft in Shift then   // Mouse is moving with left button down
    begin
      // Erase the line since the mouse has moved
      cursorLayer.FillTransparent;
      // Get the new coordinates for the cursor end of the line
      x2 := x;
      y2 := y;
      // Draw the line with the new coordinates
      cursorLayer.DrawLineAntiAlias(x1,y1,x2,y2, BGRA(255,0,0),1.5);
      PaintImage;
    end;

end;

procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  // Draw the completeed line on the main layer
  mainLayer.DrawLineAntiAlias(x1,y1,x2,y2,BGRABlack,1.5);
  cursorLayer.FillTransparent;
  PaintImage;
  // Might as well count the lines
  inc(count);
  StatusBar1.SimpleText :=IntToStr(count) + ' lines';
end;

procedure TForm1.FormPaint(Sender: TObject);
begin
  PaintImage;
end;

procedure TForm1.PaintImage;
begin
  layers.Draw(Canvas,0,0);
end;

end.
Conscience is the debugger of the mind

Gintas

  • Jr. Member
  • **
  • Posts: 71
    • Developer's Diary
Re: BGRABitmap - layers
« Reply #14 on: April 27, 2012, 10:57:13 pm »
If talking about layers I wonder how to easily move them up and down (change the Z axis order)?