How do I create a wrapped / scrollable area that is not slow?

Imagine a canvas that is a slab filed both horizontally and vertically using a single bitmap, for example:

enter image description here

The above is achieved with:

procedure TForm1.PaintBox1Paint(Sender: TObject);
var
  X, Y: Integer;
begin
  Y := 0;
  while Y < PaintBox1.Height do
  begin
    X := 0;
    while X < PaintBox1.Width do
    begin
      PaintBox1.Canvas.Draw(X, Y, Image1.Picture.Bitmap);
      Inc(X, Image1.Picture.Bitmap.Width);
    end;
    Inc(Y, Image1.Picture.Bitmap.Height);
  end;

  PaintBox1.Canvas.Brush.Style := bsClear;
  PaintBox1.Canvas.Rectangle(PaintBox1.ClientRect);
end;

      

Now what I would like to do is expand on the above to allow horizontal and vertical offset to simulate the effect that tiled images have been wrapped / scrolled when using the scrollbar, etc.

The idea is that when you change the horizontal offset, for example, the tiled images move to the left, while on the right you should see that the tiled images look as if they were wrapped on one side of the drawing box for the other.

I was able to create a test project that works the way I want and it looks like this:

enter image description here

I wrote this test project in Lazarus, but it can be easily adapted for testing in Delphi.

Below is the lfm:

object Form1: TForm1
  Left = 0
  Height = 625
  Top = 0
  Width = 782
  Caption = 'Form1'
  ClientHeight = 625
  ClientWidth = 782
  OnCreate = FormCreate
  Position = poScreenCenter
  Visible = False
  object Label1: TLabel
    Left = 25
    Height = 15
    Top = 16
    Width = 40
    Caption = 'Regular'
    ParentColor = False
  end
  object Label2: TLabel
    Left = 361
    Height = 15
    Top = 16
    Width = 32
    Caption = 'Offset'
    ParentColor = False
  end
  object PaintBox1: TPaintBox
    Left = 24
    Height = 320
    Top = 40
    Width = 320
    OnPaint = PaintBox1Paint
  end
  object PaintBox2: TPaintBox
    Left = 361
    Height = 320
    Top = 40
    Width = 320
    OnPaint = PaintBox2Paint
  end
  object Image1: TImage
    Left = 25
    Height = 48
    Top = 544
    Width = 48
    AutoSize = True
    Picture.Data = {
      1754506F727461626C654E6574776F726B47726170686963830A000089504E47
      0D0A1A0A0000000D49484452000000300000003008060000005702F98700000A
      4A494441546881CD997F8C5C5515C73FF7BE376F66F6C77477DBEDF677A9A4EC
      A2056D34223110C0468A091122CA822151227FD45F31C400898A1AF803635249
      C4F40F883424842A46A28480A6828AD820581A1A4B7F125AFBBBDBFD313BBB33
      F3DEBBF7F8C77D333B3F763B3BBB267A9297B9F7BD7BDF3BDF7BCEF99E73EF28
      6B85FF3779FA6F87552E1B749C199B5AD1BF24BB6A595766C5D989D286209559
      7DEDE0C0536BBABCFD95B17E3B2FDEF1EA81A67B294FD3DB95E1CCD8147DDD19
      B2299F53A385BA766F57864CCAE3E4C5C9A4ED7366AC90EEEDCAF4766782DEF1
      E9F2DABECEF4FA251DE9354664DD47D72D5DDFD3915E7DFDE0409FA7BDDE13E3
      9167BC6E56F7A4F8F99F8E8EFEE4F3830B03B0EDA60FCF7BECCE378EA4D3BE74
      A53CBDD2F7D4EAEE6CB06AC3F225EB7B3A830D3D1DE9B557ADED5D9ECB0403D9
      C0EBCBFA9E4EA73CB452D5F916383C1272AE10D19DF1185A11E06B087CDD5DFB
      9D455B402BB57A656FE7A6E9305ED7D795599FCB066B7DAD567D7AE3C09A8EC0
      1FE84CFB5D595FA73C4FE36BDDF21BB1B11CBA18727E322697F5D9D89F21E529
      26C398D800A06AC72FCA023B5E3D70E5351B07760F2ECFADCA066DBDAA49CAB1
      E5E048999129434FD667E34016AD15B115E248304E798CAD9FB7600B182BEC3B
      7C7ACBBDD75DB12A9D5A9CF28747CABC3F1AD2DF9D62437F06AD15A1018C54D7
      5B002B601703A0D102F71E3A65622BA4012B8ECD2A7EDCD89F4BF265C39EE3D3
      5CB7B19B5814A111748D923A21C9C8088803B160004FECFE57B56DAC45A49982
      6DC3BD5A20B5CF1C30E1743E647434C4A228C782566044A8C5AD05E20454BC18
      0B7C73CB47EAFA5F3D7C7ADE736B95B722942343680CE3D365AC80566E7545EA
      A3546B88C4B1921230A67E81DA02B0FD9577AB6D6305D31851F304321DC64C96
      62AC588C1504B7B2612C68A5D015040AB475734293805C8C05EEDF7A755DFF9E
      C7E76F818A9423C3443122328257C3AAD60AC68255159723F955587131E06B17
      D70B06F0D88BEFD47DB0D1DF5B8915A16CDCAAD78940642116E7264ACD28AA11
      626BB1C9F3C64FB605E0A15B37D7F5EFDEFE52132BB40651C32452B927C406A2
      58507AC68534CEF7232358014F2F320F3CF2C2DBD5B6B182B582D026824651EE
      8AAD10092833E3429E5208E2005820E5D86FC1007E70FB27EAFA77FEF445C0AD
      A01569C9F9738958B7C2462C4A5442A52E1E8C08C6385A350667920503F8F59B
      D5B61167815A69E6F9D65289E3589C825AD5D854C0183BE3F7162C8BA0D147BE
      744D5DFF8EC77E37E7D8762C22022282A904A9024F25F4190B2A798FD2B2381A
      7DE8B93DD5B6313661A2B9C737B2945899357B4342A346B02AA14E208A6DF20E
      97A151AAC9EAEDD1E85DD7D6F56F7BF4B7ED4CAF4A158476E5851117A871A29C
      46B008C68ACBCA0AC4B81831F122007C67E75FAB6DC742ED67E27A20954692D9
      AD73178B101B41251CA7948B95624928858B00F0F857AEAFEBDFFAE3E7EB154A
      5650E9F6D8C8E27283490A1E93B88ED60A57F281AF61AC685D55BA5000DF78EA
      B56A3B36B6EAFF56A4AA7C2D905A309264EE590F11923A3F322E464CB20F5022
      E86AB6538C4C5A6431007EF1B51BEBFAB73CFCAB96736416852BC1ED55EA4EE5
      4A89C8D494E82E763149069E2C19268BA66143D92680FB76ECAEB68DB5583393
      C0CC0232B2B5E2283451BE1C591433855C8585B5C085494B1C0BB2181A7D72DB
      96BAFECDDF7B6E469959E8713E794029B058F2C53809DC99FB5A293C0DF992E5
      623E464410594429B1FE96EF73ED673E45269D228A0DC658EB8AB339B87D9664
      168B5437305AE1AC6884C8B8CD8A02D06ED545095614A74663577E57227AA100
      8EBFFC685DFFEB4FBE96F55B304EFD4EACFE5939364C873146847CD1602D7404
      9A5264B1CA31CF48DE30316550B878688CA9B600745D7E3B9B6EDECAE0D0FAA0
      A73B7BC34D9B2FDB96CB386B342B3EE3CBB5A20144988A0C2282D230D09FE6D8
      C922FDFD198C5862EB363BE3D3C2D9F188DAB85ED4A6BE70EC05001E78F6EF3B
      1EB8EDE3F72EEB4C530C63A7989A51BC1644ED3370543915C6D5673D198FCBFB
      04A533BCF77E910FADCD622C144A8673E33171ECE2412A001A10B43E2AAB91DC
      E09D74ACDD4AAE33BD6959679AA97254A7EC5C759115088D5BF562546F2DAD14
      EB7B35FDD910C4920934A39311A74762A2A46C90A45C1769DE925DD202B9A1E1
      CADAE9E4523A9551B1B1552D5A9DFF44D6129A9903806CCAC35A4B313255C0B1
      71C561A11873763CE2C2B841254B2BB8F32057A54AEB723A515A250A7BC9AF9F
      B415A044A4C972B540625B29D06C93C9D3BE46A91446C4056BA57E568A722894
      CA33AB5ED1B576D12F99077243C3C12C4AD7025180B2B61E8015B752715290B5
      FACFC1D7CA156D8DE576E2268DF3DD30690D00C8004183D2F500C422222AB696
      E9C49FE7FB274945E1B9868B50BF00CD8717D816892C063A80748DF28D16C088
      780B51FC929204A8A599EBA5F29CA6186E02504C940D12103E33F15005803496
      540B50B83A36711D35D39E6BBE4053355AE7CBF983BB042800A3409800C92457
      BA72999A18A89C48D45EF31191E6EDA85857ACD958DC65672E1171ABE8296F4E
      003520A680F3403E1993AEBD444455EAFB76E552B59324C02AE7123A2927B482
      52D916CF8D44E70BA5DC91DA39B3E681FCC15D921B1A9E06C68125407715AC63
      8AB612E07C4245092804AD04CF8352D9469313F18589317B267F313E519AB4A3
      D1B41D11237F6C09209114D00FF430C34C00182B2D62A0B5C2E07242E06BBA02
      0FA5C3383F194FC8D9E8C2FE7D8523A5097B269AB679890981349A69A578E6FC
      1F361F9D2F00838B8718E8023C944A08A11E403BE7A356045F2B966483E89DE3
      178FEE3D7AF6CD7FEE3FFAC67B47CC581C5EF9C5E51BFDA9890FC273CA531E0A
      8D56392C23589E3EB77BF389C6F7CD09207F70579C1B1A3E0D9481CB811538AB
      48CAF702CFF7E6AD7C39B64C14C3786CAAFCBE15D9F3EEF191BF3CB5F3E57746
      3E387E6AFAC29982298E84C1B22D9B7AAFB8120C018AC0E51B9D41CC49A4BC53
      791D67677BF7256BA1FCC15D06389F1B1A0E7114BB1E48BDBDEFD89E939FFBD8
      CA8EB4DF618CAD9EE7D42A3C3E5D666C3A3C3A5628BD558CCC6B22F20F84431B
      57F694BEFDD94DDCBFF56ABA37DE91D17ED0E9752F4DEB742A4070078BC662F1
      324878CCE45F797A6CDF8FC6E7D251CD3719E5868633C05A609D9898CE156BB2
      7D6B56AD7BE287F7FCEC86C115C1DE13A38C4E95FF9D9F2EBF558CCC9F813780
      C33837444408523E5D81CF7DDFDA4EE1F471941F68208B943C2FF7C9C1DCDA2D
      DF5D7A592E3CF1D6545EA474C0E45FDA69CBFBA6505D2031F943CD0769F30690
      80F070F1A0252A174C794AAEBAF50B776FFBF28D03FD4B3A5EBF38593C70597F
      777EAEF97D9D6936ADEA6D7EA7943C2F77CDD5DDABB73C3875569F4315F69AC2
      CBCF8EEF7FACDC4AA776FFE035C004804AA5F15369F63DBFF3991D08BF7C7898
      8B93458E8F14005795766752F47406F474A4E9C906746553FC7EDF09EEB9EB41
      C41A929AD9803248398F8895F0D4EBA6F4C26FB0E32637345CF7F1FCC15D4D0A
      B565814BC98542893313457AB201B96C8A9E6CD0D6FCDCE0B0EF77AE5A3ABA77
      FBB976E6FDD700FCAFE43F765B2B40A722D3C30000000049454E44AE426082
    }
    Visible = False
  end
  object Label3: TLabel
    Left = 645
    Height = 15
    Top = 376
    Width = 36
    Alignment = taRightJustify
    AutoSize = False
    Caption = '0'
    ParentColor = False
  end
  object ScrollBar1: TScrollBar
    Left = 361
    Height = 17
    Top = 374
    Width = 273
    Max = 5000
    PageSize = 0
    TabOrder = 0
    OnChange = ScrollBar1Change
  end
  object ScrollBar2: TScrollBar
    Left = 696
    Height = 320
    Top = 40
    Width = 17
    Kind = sbVertical
    Max = 5000
    PageSize = 0
    TabOrder = 1
    OnChange = ScrollBar2Change
  end
  object Label4: TLabel
    Left = 720
    Height = 15
    Top = 40
    Width = 34
    Caption = 'Label4'
    ParentColor = False
  end
end

      

and source

unit Unit1;

{$mode delphi}

interface

uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
  ExtCtrls;

{ TForm1 }

type
  TForm1 = class(TForm)
    Image1: TImage;
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    PaintBox1: TPaintBox;
    PaintBox2: TPaintBox;
    ScrollBar1: TScrollBar;
    ScrollBar2: TScrollBar;
    procedure FormCreate(Sender: TObject);
    procedure PaintBox1Paint(Sender: TObject);
    procedure PaintBox2Paint(Sender: TObject);
    procedure ScrollBar1Change(Sender: TObject);
    procedure ScrollBar2Change(Sender: TObject);
  private
    FOffsetX: Integer;
    FOffsetY: Integer;
  public
    { public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.lfm}

{ TForm1 }

procedure TForm1.FormCreate(Sender: TObject);
begin
  FOffsetX := 0;
  FOffsetY := 0;
end;

procedure TForm1.PaintBox1Paint(Sender: TObject);
var
  X, Y: Integer;
begin
  Y := 0;
  while Y < PaintBox1.Height do
  begin
    X := 0;
    while X < PaintBox1.Width do
    begin
      PaintBox1.Canvas.Draw(X, Y, Image1.Picture.Bitmap);
      Inc(X, Image1.Picture.Bitmap.Width);
    end;
    Inc(Y, Image1.Picture.Bitmap.Height);
  end;

  PaintBox1.Canvas.Brush.Style := bsClear;
  PaintBox1.Canvas.Rectangle(PaintBox1.ClientRect);
end;

// needs improvement
procedure TForm1.PaintBox2Paint(Sender: TObject);
var
  X, Y: Integer;
begin
  Y := -FOffsetY;
  while Y < PaintBox2.Height do
  begin
    X := -FOffsetX;
    while X < PaintBox2.Width do
    begin
      PaintBox2.Canvas.Draw(X, Y, Image1.Picture.Bitmap);
      Inc(X, Image1.Picture.Bitmap.Width);
    end;
    Inc(Y, Image1.Picture.Bitmap.Height);
  end;

  PaintBox2.Canvas.Brush.Style := bsClear;
  PaintBox2.Canvas.Rectangle(PaintBox2.ClientRect);
end;

procedure TForm1.ScrollBar1Change(Sender: TObject);
begin
  FOffsetX := ScrollBar1.Position;
  Label3.Caption := IntToStr(FOffsetX);
  PaintBox2.Invalidate;
end;

procedure TForm1.ScrollBar2Change(Sender: TObject);
begin
  FOffsetY := ScrollBar2.Position;
  Label4.Caption := IntToStr(FOffsetY);
  PaintBox2.Invalidate;
end;

end.

      

My question is how can I improve my existing code and is there a better way to do this, maybe I made it harder?

At small offsets, the paint stains quite quickly, but with large offsets like 5000 (which I'm currently testing) the scrolling becomes quite slow, and I'm wondering if I'm missing something obvious?

+3


source to share


1 answer


It is not necessary to paint all the bitmaps that are not visible. For any given bitmap width / height (let's call it "tile"), you only need to start / stop drawing no more than 1 tile width or height outside the edge of your canvas.

i.e. calculate the first visible tile and start painting with that.

enter image description here

The illustration above tries to show what I mean ... only the green tiles are in the visible area (shaded blue), so even if the offset includes those shown in red, there is no point to draw them.

Windows (OS) will pin them anyway, but you are wasting time on those invisible tiles anyway, so your large offsets result in poor performance ... you waste time counting the "invisible" "space before you get to the tiles that you really needs to be drawn.



Computing the "origin" at which to start drawing is simple arithmetic and does not require loop iterations. Instead of initializing Y and X to your absolute offsets, set them to the first multiple of the tile height / width from that offset using a simple mod :

Y := -FOffsetY mod Image1.Height;

X := -FOffseXY mod Image1.Width;

      

Or similar

Observing Jerry wrt using a bitmap off-screen and then blitting into your control will also optimize the actual picture.

+8


source







All Articles