Googleマップのようにタイル状の画像を表示&スクロールするには?

解決


瑠璃  2012-12-26 18:59:26  No: 43558

環境:  Delphi2010 / Windows7

Googleマップは、確か、タイル状にカットされた地図画像が表示されていて、
スクロールすると、次に必要な画像が読み込まれて表示される・・・
そういう仕組みだったと思います。

いま、タイル状の地図画像が縦横100x100個あり、
これを合体して1枚の画像にして表示させたところ、
読込にとても時間が掛かってしまいました。

そこで、Googleマップと同じように、タイル状の地図画像のままで、
表示とスクロールを実現したいのですが、
どういう仕組みにすれば良いのか設計(?)の部分でつまづいてます。

▼現在の状況
  1枚に合成した地図画像では・・・
  1.ScrollBox上にTImageを貼り付けることでスクロールが可能になること。
  2.TImageのマウスイベント(Down/Move/Up)を利用することで、
      マウススクロールさせられること。
  は分かりました。

▼分からないこと
  1.複数の画像を表示させる方法
  2.表示させた複数の画像をスクロールさせる方法
  3.次の画像が必要と判断させて、次を表示させる方法

どういう仕組みが利用できるのか、全く想像がつかない状況です。
アドバイス頂けると助かります。
よろしくお願いいたします。


KHE00221  2012-12-26 19:10:44  No: 43559

RPG マップ表示


Nov  2012-12-28 01:10:26  No: 43560

>1.複数の画像を表示させる方法
空の画像を用意し、必要に応じて画像を指定位置に書き込みます。

>2.表示させた複数の画像をスクロールさせる方法
スクロールボックスにするなら、スクロール時にイベントを発生させるようにします。

>3.次の画像が必要と判断させて、次を表示させる方法
表示範囲(rc1)の画像が書き込み済みかチェック(cells[ix, iy])します。

試してみましたが、全体の画像サイズが大きすぎると、システムリソース不足になる可能性があります。
その場合は、Image1のサイズを限定して、表示範囲を中心として追従するような工夫が必要になるかも。

uses
  Windows, Messages, Classes, Graphics,
  Controls, Forms, ExtCtrls, jpeg, Math;

type
  TScrollBox = class(Forms.TScrollBox)
  protected
    procedure WMHScroll(var Msg: TWMHScroll); message WM_HSCROLL;
    procedure WMVScroll(var Msg: TWMVScroll); message WM_VSCROLL;
  private
    FOnScroll: TNotifyEvent;
  public
    property OnScroll: TNotifyEvent read FOnScroll write FOnScroll;
  end;
  TForm1 = class(TForm)
    ScrollBox1: TScrollBox;
    Image1: TImage;
    procedure FormCreate(Sender: TObject);
    procedure FormResize(Sender: TObject);
  private
    { Private 宣言 }
    const
      // 単一画像サイズ(幅/高さ)
      cw = 1024;
      ch = 768;
      // タイル画像の数(縦/横)
      wcount = 10;
      hcount = 10;
      // この画像サイズで100x100にするとシステムリソース不足になるので...
    var
      // タイル画像のロード済みフラグ
      cells: array[0..wcount-1] of array[0..hcount-1] of Boolean;
    procedure ScrollBox1Scroll(Sender: TObject);
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TScrollBox.WMHScroll(var Msg: TWMHScroll);
begin
  inherited;
  if Assigned(FOnScroll) then FOnScroll(Self);
end;

procedure TScrollBox.WMVScroll(var Msg: TWMVScroll);
begin
  inherited;
  if Assigned(FOnScroll) then FOnScroll(Self);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  ScrollBox1.OnScroll := ScrollBox1Scroll;
  with Image1 do begin
    Left := 0;
    Top := 0;
    Width := cw*wcount;
    Height := ch*hcount;
  end;
  Image1.Picture.Bitmap.SetSize(Image1.Width, Image1.Height);
  ZeroMemory(@cells, SizeOf(cells));
  // 必要に応じて設定
  ScrollBox1.HorzScrollBar.Tracking := True;
  ScrollBox1.VertScrollBar.Tracking := True;
  // 初期位置の設定
  ScrollBox1.VertScrollBar.Position := 0;     // コンポーネント生成時は空撃ちが必要みたい...
  ScrollBox1.VertScrollBar.Position := Image1.Height div 2;
  ScrollBox1.HorzScrollBar.Position := Image1.Width div 2;
  ScrollBox1Scroll(nil)
end;

procedure TForm1.FormResize(Sender: TObject);
begin
  ScrollBox1Scroll(nil)
end;

procedure TForm1.ScrollBox1Scroll(Sender: TObject);
var
  x, y, ix, iy, ix0, iy0, ix1, iy1: Integer;
  jpg: TJPEGImage;
  rc0, rc1, rc2: TRect;
begin
  x := ScrollBox1.HorzScrollBar.ScrollPos;
  y := ScrollBox1.VertScrollBar.ScrollPos;
  // 見えている範囲
  rc1 := Rect(x, y, x+ScrollBox1.Width, y+ScrollBox1.Width);
  // 範囲の要素の先頭のインデックス
  ix0 := x div cw;
  iy0 := y div ch;
  // 範囲の要素の末尾のインデックス
  ix1 := Ceil((x+ScrollBox1.Width)/cw);
  iy1 := Ceil((y+ScrollBox1.Height)/ch);
  if ix1>=wcount then ix1 := wcount-1;
  if iy1>=hcount then iy1 := hcount-1;
  // 範囲内で空のタイルを描画
  for ix := ix0 to ix1 do begin
    rc2.Left := ix*cw;
    rc2.Right := rc2.Left+cw;
    for iy := iy0 to iy1 do begin
      rc2.Top := iy*ch;
      rc2.Bottom := rc2.Top+ch;
      if IntersectRect(rc0, rc1, rc2) and not cells[ix, iy] then begin
        jpg := TJPEGImage.Create;
        // この位置に表示したい画像ファイルを指定
        jpg.LoadFromFile('Penguins.jpg');
        Image1.Picture.Bitmap.Canvas.Draw(cw*ix, ch*iy, jpg);
        jpg.Free;
        cells[ix, iy] := True;
      end;
    end;
  end;
end;


Nov  2012-12-28 01:16:22  No: 43561

このサンプルでは、同じ画像'Penguins.jpg'を並べているだけですので、
座標インデックスに応じた画像をロードしてください。

また、画像読み込み時にカクカクするのが気になる場合は、別スレッドで
画像をロードするなどの工夫が必要かも。


瑠璃  2012-12-28 03:45:10  No: 43562

ありがとうございます。

>KHE00221さん
ようやく漠然としたイメージができてきました。

>Novさん
私には「複数のImageを配置する」という方法しか想像できなかったので、
「全画像サイズのImage1を初めから準備する」という考えには助けられました。
また、具体的なコードまで出して頂き大変助かりました。

ありがとうございました。


瑠璃  2012-12-28 20:24:40  No: 43563

その後、実際に分割画像を読み込ませてみて、
うまく表示されて感激していたのですが…、
それをXp(32bit)で動かしてみたところ、
十分な記憶域がありませんとなってしまいました。

そこで
>Image1のサイズを限定して、表示範囲を中心として追従
というのに取り組もうと、仕組みを考えているのですが、
どうもイメージできずにいます。

・スクロールバーとの兼ね合いをどうすれば良いのか。
・そもそも全体図より小さいImageをどうスクロールさせれば良いのか。

こういうのが閃く人と閃かない人。
完全に後者な私はプログラムセンスの無さに凹みます。
もっとプログラマーの集まるサイト等にアンテナ張って、
勉強しなければいけませんね。

脱線しました、すみません。
またアドバイス頂けると助かります。
よろしくお願いいたします。


Nov  2012-12-28 21:24:55  No: 43564

試してませんので、もっと効率のよい方法が有ると思いますが...
(つまり、結局試さないと自信がない、という程度の思いつき=ひらめきです。こういうとき、定石をもっと知っていればと思いますが)

最初に縦横スクロールバーのレンジを必要なサイズにセットしておきます。

>Image1のサイズを限定して
単一画像が表示される最大数を、スクリーンサイズ等より計算し、
縦横とも、その数よりも縦横+2枚分程度多めに確保します。

>表示範囲を中心として追従
Image1のポジションを、表示範囲のインデックス-1(ix-1, iy-1)の
座標に移動し、Image1内の画像のシフトと追加読み込みを実施し、
また、このとき、範囲から外れた読み込み済みフラグをクリアします。

以上、言葉だけでは分かりにくいと思いますが、スクロールボックスの
表示範囲に掛かっている部分+αだけ、
Image1が存在しているイメージです。


瑠璃  2012-12-28 22:28:35  No: 43565

>Novさん

なるほど、そういう方法でしたか。
言葉だけでイメージが沸き助かります。
ありがとうございました。

私の頭ではスクロールボックスを用いる方法ではアイデアが浮かんでこず、
諦めていたところ、スクロールバー単体(TScrollBar)が目にとまり、
これを用いて挑戦してみています。

・TImage×1、TScrollBar×2(縦/横)を配置
・ScrollBarのそれぞれの.Maxはタイル画像の数(縦/横)
・ScrollBarのポジションを元にタイル画像を読み込み。

Canvas.Drawする際のX,Y座標の計算が間違っているようで、
完成はしていませんが(汗)、なんとか出来そうな感じがしています。

これができたら、Novさんに教えて頂いた方法にも挑戦してみます。

この度はとても助かりました。
ありがとうございました。


瑠璃  2012-12-28 22:30:05  No: 43566

すみません、誤字の訂正です。
×言葉だけで  ○言葉だけでも


KHE00221  2012-12-28 23:10:11  No: 43567

>・TImage×1、TScrollBar×2(縦/横)を配置
>・ScrollBarのそれぞれの.Maxはタイル画像の数(縦/横)

スムーズスクロールさせないで切り替えだけなら

procedure TForm1.FormCreate(Sender: TObject);
begin
    ScrollBar1.Kind := sbVertical;     //縦
    ScrollBar2.Kind := sbHorizontal;   //横

    ScrollBar1.OnScroll := ScrollBar1Scroll;
    ScrollBar1.OnScroll := ScrollBar1Scroll;
end;

procedure TForm1.ScrollBar1Scroll(Sender: TObject; ScrollCode: TScrollCode;
  var ScrollPos: Integer);
var
    FileName: String;
begin
    //縦_横.BMP
    FileName := IntToStr(ScrollBar1.Position) + '_' + IntToStr(ScrollBar2.Position) + '.BMP';
    Image1.Picture.LoadFromFile(FileName);
    Exit;
end;

だけだぞ


KHE00221  2012-12-28 23:12:03  No: 43568

ScrollBar1.OnScroll := ScrollBar1Scroll;
    ScrollBar2.OnScroll := ScrollBar1Scroll;


瑠璃  2012-12-28 23:58:37  No: 43569

>KHE00221さん

ありがとうございます。
私の書いた方法だとスムーズスクロール「しない」ことに気付きました。
スクロールボックスで再挑戦してみます。


KHE00221  URL  2012-12-29 00:59:32  No: 43570

Imageに表示させている画像をスクロールさせるのであって
Imageをスクロールさせるわけではないので
ScrollBoxにする必要はない


Nov  2012-12-30 01:13:21  No: 43571

この流れとは、まったく関係ありませんが、自分のソースにバグ発見。
気づいているかもしれませんが、念のため。

>// 見えている範囲
>rc1 := Rect(x, y, x+ScrollBox1.Width, y+ScrollBox1.Width);
正しく(?)は、rc1 := Rect(x, y, x+ScrollBox1.Width, y+ScrollBox1.Height);
でした。Clientかな、とか、スクロールバーの幅とか、細部を気にするときりがありませんが...

ちなみに、
>ScrollBoxにする必要はない
ですが、ScrollBoxでも実現可能なことは確認しましたので、どちらの方法でも、頑張れば可能と思います。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加