環境: Delphi2010 / Windows7
Googleマップは、確か、タイル状にカットされた地図画像が表示されていて、
スクロールすると、次に必要な画像が読み込まれて表示される・・・
そういう仕組みだったと思います。
いま、タイル状の地図画像が縦横100x100個あり、
これを合体して1枚の画像にして表示させたところ、
読込にとても時間が掛かってしまいました。
そこで、Googleマップと同じように、タイル状の地図画像のままで、
表示とスクロールを実現したいのですが、
どういう仕組みにすれば良いのか設計(?)の部分でつまづいてます。
▼現在の状況
1枚に合成した地図画像では・・・
1.ScrollBox上にTImageを貼り付けることでスクロールが可能になること。
2.TImageのマウスイベント(Down/Move/Up)を利用することで、
マウススクロールさせられること。
は分かりました。
▼分からないこと
1.複数の画像を表示させる方法
2.表示させた複数の画像をスクロールさせる方法
3.次の画像が必要と判断させて、次を表示させる方法
どういう仕組みが利用できるのか、全く想像がつかない状況です。
アドバイス頂けると助かります。
よろしくお願いいたします。
RPG マップ表示
>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;
このサンプルでは、同じ画像'Penguins.jpg'を並べているだけですので、
座標インデックスに応じた画像をロードしてください。
また、画像読み込み時にカクカクするのが気になる場合は、別スレッドで
画像をロードするなどの工夫が必要かも。
ありがとうございます。
>KHE00221さん
ようやく漠然としたイメージができてきました。
>Novさん
私には「複数のImageを配置する」という方法しか想像できなかったので、
「全画像サイズのImage1を初めから準備する」という考えには助けられました。
また、具体的なコードまで出して頂き大変助かりました。
ありがとうございました。
その後、実際に分割画像を読み込ませてみて、
うまく表示されて感激していたのですが…、
それをXp(32bit)で動かしてみたところ、
十分な記憶域がありませんとなってしまいました。
そこで
>Image1のサイズを限定して、表示範囲を中心として追従
というのに取り組もうと、仕組みを考えているのですが、
どうもイメージできずにいます。
・スクロールバーとの兼ね合いをどうすれば良いのか。
・そもそも全体図より小さいImageをどうスクロールさせれば良いのか。
こういうのが閃く人と閃かない人。
完全に後者な私はプログラムセンスの無さに凹みます。
もっとプログラマーの集まるサイト等にアンテナ張って、
勉強しなければいけませんね。
脱線しました、すみません。
またアドバイス頂けると助かります。
よろしくお願いいたします。
試してませんので、もっと効率のよい方法が有ると思いますが...
(つまり、結局試さないと自信がない、という程度の思いつき=ひらめきです。こういうとき、定石をもっと知っていればと思いますが)
最初に縦横スクロールバーのレンジを必要なサイズにセットしておきます。
>Image1のサイズを限定して
単一画像が表示される最大数を、スクリーンサイズ等より計算し、
縦横とも、その数よりも縦横+2枚分程度多めに確保します。
>表示範囲を中心として追従
Image1のポジションを、表示範囲のインデックス-1(ix-1, iy-1)の
座標に移動し、Image1内の画像のシフトと追加読み込みを実施し、
また、このとき、範囲から外れた読み込み済みフラグをクリアします。
以上、言葉だけでは分かりにくいと思いますが、スクロールボックスの
表示範囲に掛かっている部分+αだけ、
Image1が存在しているイメージです。
>Novさん
なるほど、そういう方法でしたか。
言葉だけでイメージが沸き助かります。
ありがとうございました。
私の頭ではスクロールボックスを用いる方法ではアイデアが浮かんでこず、
諦めていたところ、スクロールバー単体(TScrollBar)が目にとまり、
これを用いて挑戦してみています。
・TImage×1、TScrollBar×2(縦/横)を配置
・ScrollBarのそれぞれの.Maxはタイル画像の数(縦/横)
・ScrollBarのポジションを元にタイル画像を読み込み。
Canvas.Drawする際のX,Y座標の計算が間違っているようで、
完成はしていませんが(汗)、なんとか出来そうな感じがしています。
これができたら、Novさんに教えて頂いた方法にも挑戦してみます。
この度はとても助かりました。
ありがとうございました。
すみません、誤字の訂正です。
×言葉だけで ○言葉だけでも
>・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;
だけだぞ
ScrollBar1.OnScroll := ScrollBar1Scroll;
ScrollBar2.OnScroll := ScrollBar1Scroll;
>KHE00221さん
ありがとうございます。
私の書いた方法だとスムーズスクロール「しない」ことに気付きました。
スクロールボックスで再挑戦してみます。
Imageに表示させている画像をスクロールさせるのであって
Imageをスクロールさせるわけではないので
ScrollBoxにする必要はない
この流れとは、まったく関係ありませんが、自分のソースにバグ発見。
気づいているかもしれませんが、念のため。
>// 見えている範囲
>rc1 := Rect(x, y, x+ScrollBox1.Width, y+ScrollBox1.Width);
正しく(?)は、rc1 := Rect(x, y, x+ScrollBox1.Width, y+ScrollBox1.Height);
でした。Clientかな、とか、スクロールバーの幅とか、細部を気にするときりがありませんが...
ちなみに、
>ScrollBoxにする必要はない
ですが、ScrollBoxでも実現可能なことは確認しましたので、どちらの方法でも、頑張れば可能と思います。
ツイート | ![]() |