画像の描画

imageタグ

画像を描画させるimageタグについて見ていこうと思います。前回触れたとおり、ifタグなど一部のタグを除き、タグの処理は、タグハンドラ群を介して行われます。つまりここを見ればどうやって処理されているかがわかります。

elmには、タグを解析した結果が%["tagname" => image, "layer" => "base", "page" => "fore", "storage" => "maki", "visible" => "true"]のような形式で渡されます。値はすべて文字列です。画像の読み込みにかかった時間を計測したりしていますが、直接画像を読み込んで描画しているのは5180行目です。

// kag3/templete/system/MainWindow.tjs
5175:   image : function(elm)
5176:   {
5177:     // 画像読み込み
5178:     updateBeforeCh = 1;
5179:     var start = System.getTickCount();
5180:     getLayerFromElm(elm).loadImages(elm);
5181:     dm(elm.storage + " の読み込みに " + (System.getTickCount() - start) + "ms かかりました");
5182:     return 0;
5183:   } incontextof this,

getLayerFromElm(elm)は、layerとpageの値からレイヤオブジェクトを返すメソッドです。

// kag3/templete/system/MainWindow.tjs
3095:   function getLayerFromElm(elm, prefix = '')
3096:   {
3097:     // elm に指定されている page と layer 属性から、該当する
3098:     // オブジェクトを返す
3099:     // prefix には、属性名の前につけるプレフィクスを指定する
3100:     var base;
3101:     if(elm[prefix + 'page'] == 'back') base = back; else base = fore;
3102:     var layer = elm[prefix + 'layer'];
3103:     if(layer == 'base') return base.base; // 背景
3104:     if(layer[0] == 'm')
3105:     {
3106:       // message? ( ? = 数値 )
3107:       // ここではあまり厳密にエラーチェックはしない
3108:       if(layer == 'message') return base.messages[currentNum];
3109:       return base.messages[+layer.substr(7)];
3110:     }
3111:     return base.layers[+layer];
3112:   }

そして、KAGWindow内では以下のような変数に各レイヤオブジェクトが格納されています。よって、%["tagname" => image, "layer" => "base", "page" => "fore", "storage" => "maki", "visible" => "true"]がelmに与えられたとき、背景レイヤの表ページである、fore.baseが返されます。ちなみに、KAGで使われる表ページと裏ページですが、TJSのレイヤには表ページも裏ページもありません。KAGシステム内では、主にトランジションを楽に行うためにそれぞれのレイヤに表ページ用と裏ページ用の2枚のレイヤを用意してまとめて扱っています。

fore.base 背景レイヤ(表ページ)
fore.layers[0] 前景レイヤ0(表ページ)
fore.layers[1] 前景レイヤ1(表ページ)
fore.layers[2] 前景レイヤ2(表ページ)
fore.messages[0] メッセージレイヤ0(表ページ)
fore.messages[1] メッセージレイヤ1(表ページ)
back.base 背景レイヤ(裏ページ)
back.layers[0] 前景レイヤ0(裏ページ)
back.layers[1] 前景レイヤ1(裏ページ)
back.layers[2] 前景レイヤ2(裏ページ)
back.messages[0] メッセージレイヤ0(裏ページ)
back.messages[1] メッセージレイヤ1(裏ページ)

レイヤの種類

吉里吉里が提供するLayerクラスがありますが、KAGシステムで使われているレイヤは、Layerクラスを継承しさまざまな機能を追加したレイヤです。また、背景レイヤ・前景レイヤ・メッセージレイヤはそれぞれ、BaseLayer, CharacterLayer, MessageLayerの各クラスが定義され、細かな部分が異なるレイヤとなっています。

まず、KAGで使われているすべてのレイヤの元となるKAGLayerが定義され、そこから更に継承され、さまざまなレイヤが定義されています。今回は画像の読み込みということで、BaseLayer, CharacterLayer側のツリーを見ていきます。

// kag3/templete/system/KAGLayer.tjs
0004: /*
0005:  レイヤ関連のクラス階層
0006: 
0007:  Layer ( 吉里吉里ネイティヴクラス )
0008:   |
0009:   +-- KAGLayer ( このファイル )
0010:        |
0011:        +-- AnimationLayer ( AnimationLayer.tjs )
0012:        |    |
0013:        |    +-- ClickGlyphLayer ( AnimationLayer.tjs )
0014:        |    |
0015:        |    +-- GraphicLayer ( GraphicLayer.tjs )
0016:        |         |
0017:        |         +-- BaseLayer ( GraphicLayer.tjs )
0018:        |         |
0019:        |         +-- CharacterLayer ( GraphicLayer.tjs )
0020:        |
0021:        +-- MessageLayer ( MessageLayer.tjs )
0022:        |
0023:        +-- ButtonLayer ( ButtonLayer.tjs )
0024:        |    |
0025:        |    +-- LinkButtonLayer ( MessageLayer.tjs )
0026:        |    |
0027:        |    +-- LButtonLayer ( HistoryLayer.tjs )
0028:        |
0029:        +-- EditLayer ( EditLayer.tjs )
0030:        |    |
0031:        |    +-- LinkEditLayer ( MessageLayer.tjs )
0032:        |
0033:        +-- CheckBoxLayer ( CheckBoxLayer.tjs )
0034:             |
0035:             +-- LinkCheckBoxLayer ( MessageLayer.tjs )
0036: */

loadImages

画像を読み込んで描画するメソッドであるloadImagesメソッドを見ていきましょう。実際に画像を読み込むという処理を行っているのはLayer.loadImagesです。これはC++で記述されています。このメソッドは読み込む画像ファイルのストレージと、カラーキーを指定するといったシンプルなものです。(kr2doc)Layer.loadImagesを参照してください。

これを継承したのがKAGLayer.loadImagesです。カラーキーを文字列の"adapt"や"0xff0000"のような値として引数に与えられても是正するような処理(adjustColorKey)が入っています。

// kag3/templete/system/KAGLayer.tjs
0125:   function loadImages(storage, key)
0126:   {
0127:     // loadImages オーバーライド
0128:     key = adjustColorKey(key);
0129:     return super.loadImages(storage, key);
0130:   }
0131: 
0132:   function adjustColorKey(key)
0133:   {
0134:     // 文字列で与えられたカラーキーの変換
0135:     if(key === void)
0136:       key = clNone;
0137:     else if(typeof key == "String")
0138:     {
0139:       if(key == "adapt")
0140:         key = clAdapt; // adaptive color key
0141:       else
0142:       {
0143:         if(key.length >= 7)
0144:           key = +key;
0145:         else
0146:           key = +key + 0x3000000; // 0x3000000 = パレットインデックスによる指定
0147:       }
0148:     }
0149:     return key;
0150:   }

そして、KAGLayer.loadImagesを継承したのがAnimationLayer.loadImagesです。imageタグに与えることができるさまざまな属性について大体カバーしています。また、多くの情報を与えるため、引数が辞書配列のelmに変化しています。特に細かく追っていきませんが、引数elmが空(void)であればレイヤをクリアしています。上下左右反転(フリップ)や切り取り(クリッピング)を行ったり、ガンマ値などの色補正(550行目)しています。ほかにもいろいろ。アニメーションについては、またそのうち取り上げるかもしれません。

// kag3/templete/system/AnimationLayer.tjs
0462:   function loadImages(elm)
0463:   {
0464:     // loadImages オーバーライド
0465:     // elm は読み込み情報
0466:     if(elm === void)
0467:     {
0468:       freeImage();
0469:       return;
0470:     }
0471: 
0472:     Anim_loadParams = %[];
0473:     (Dictionary.assign incontextof Anim_loadParams)(elm);
0474:       // パラメータを待避
0475: 
0476:     // アニメーション情報をクリア
0477:     clearAnim();
0478: 
0479:     // 追加画像読み込みの情報をクリア
0480:     Anim_partialImageInfo = void;
0481: 
0482:     // 画像を読み込む
0483:     var taginfo = super.loadImages(elm.storage, elm.key);
0484: 
0485:     // 画像のタグ情報をデフォルト値として採用
0486:     if(taginfo)
0487:     {
0488:       (Dictionary.assign incontextof taginfo)(elm, false);
0489:       elm = taginfo;
0490:     }
0491: 
0492:     // フリップ
0493:     var ud, lr;
0494:     if(elm.flipud !== void && +elm.flipud)
0495:     {
0496:       // 上下反転
0497:       flipUD();
0498:       ud = true;
0499:     }
0500:     else
0501:     {
0502:       ud = false;
0503:     }
0504: 
0505:     if(elm.fliplr !== void && +elm.fliplr)
0506:     {
0507:       // 左右反転
0508:       flipLR();
0509:       lr = true;
0510:     }
0511:     else
0512:     {
0513:       lr = false;
0514:     }
0515: 
0516:     // クリッピング
0517:     if(elm.clipleft !== void)
0518:     {
0519:       // クリッピングが指定されている
0520:       width = +elm.clipwidth;
0521:       height = +elm.clipheight;
0522:       var cl = elm.clipleft;
0523:       if(lr) cl = imageWidth - cl - width;
0524:       var ct = elm.cliptop;
0525:       if(ud) ct = imageHeight - ct - height;
0526:       imageLeft = -cl;
0527:       imageTop = -ct;
0528:     }
0529:     else
0530:     {
0531:       setSizeToImageSize();
0532:     }
0533: 
0534:     // レイヤモード
0535:     {
0536:       var mode = ltAlpha;
0537: 
0538:       if(elm.mode !== void)
0539:       {
0540:         var layertypeinfo = imageTagLayerType[elm.mode];
0541:         if(layertypeinfo !== void)
0542:           mode = layertypeinfo.type;
0543:       }
0544: 
0545:       type = mode;
0546:     }
0547: 
0548:     // 色補正
0549:     face = dfAuto;
0550:     applyColorCorrection(this, elm);
0551: 
0552:     // 可視不可視、位置、不透明度、インデックス
0553:     if ( elm !== void && elm.pos !== void ) {
0554:       // ポジションに従って位置を決定
0555:       left= window.scPositionX[elm.pos] - width \ 2;
0556:       top = window.scHeight - height;
0557:     }
0558:     else
0559:     {
0560:       if(elm.left !== void) left = +elm.left;
0561:       if(elm.top !== void) top = +elm.top;
0562:     }
0563:     if(elm.visible !== void) visible = +elm.visible;
0564:     if(elm.opacity !== void) opacity = +elm.opacity;
0565:     absolute = +elm.index if elm.index !== void;
0566: 
0567:     // アニメーション情報があれば、読み込む
0568:     Anim_storageName =
0569:       Storages.getPlacedPath(
0570:         Storages.chopStorageExt(elm.storage) + ".asd");
0571:     if(Anim_storageName != '')
0572:     {
0573:       // アニメーション情報があった!
0574:       // アニメーション情報をデフォルトのコンダクタに読み込む
0575:       loadAnimInfo(0, ''); // ついでにアニメーション開始(もし開始できれば)
0576:     }
0577:   }

AnimationLayer.loadImagesに加え領域アクション(クリッカブルマップ)にも対応したのがGraphicLayer.loadImagesです。引数は引き続き辞書配列elmです。領域アクション以外については特に差異はありません。

そして、更にGraphicLayerを継承したのがBaseLayerとCharacterLayerです。BaseLayer.loadImagesでは、visibleなど一部の状態を変化させられないようにしていますが、ほぼGraphicLayer.loadImagesと同じものです。そしてCharacterLayerではloadImagesメソッドを定義していませんので、そっくりそのままGraphicLayer.loadImagesの定義が用いられます。

// kag3/templete/system/GraphicLayer.tjs
0021: class GraphicLayer extends AnimationLayer
0022: {
0061:   function loadImages(elm)
0062:   {
0063:     // elm に記述されている内容に従ってこのレイヤに画像を読み込む
0064:     clearProvinceActions();
0065:     loadedProvinceImage = "";
0066:     super.loadImages(elm);
0067:     if(elm !== void)
0068:     {
0069:       if(elm.mapimage !== void) super.loadProvinceImage(elm.mapimage); // 領域画像を読む
0070:       if(elm.mapaction !== void)
0071:       {
0072:         internalLoadProvinceActions(elm.mapaction); // 領域アクションを読む
0073:       }
0074:       else
0075:       {
0076:         var name;
0077:         var storage = Storages.getPlacedPath(
0078:           name = (Storages.chopStorageExt(elm.storage) + ".ma")); // 拡張子が .ma のファイル
0079:         if(storage != '')
0080:           loadProvinceActions(name);
0081:       }
0082:     }
0083:   }
 
0329: class BaseLayer extends GraphicLayer
0330: {
0354:   function loadImages(elm)
0355:   {
0356:     // loadImages オーバーライド
0357:     if(elm !== void)
0358:     {
0359:       elm.mode = "opaque"; // レイヤモードを変えられると困るので
0360:       delete elm.index; // インデックスを変えられると困るので
0361: 
0362:       if(elm.visible !== void)
0363:       {
0364:         // visible の状態を変えられると困るので
0365:         if(isPrimary) elm.visible = true; else elm.visible = false;
0366:       }
0367:     }
0368:     super.loadImages(elm);
0369:   }

とまぁ、主な部分はAnimationLayerでほとんど定義し終わってますね。このようにして、目的のタグの動作をTJSとして確認することができます。レイヤは何回も継承しており、ややこしいですけどね。

また、evalタグなどからKAGシステムのレイヤにアクセスして画像を読み込むときの引数は(タグハンドラでも同じですが)辞書配列なので注意してください。吉里吉里/KAGのリファレンスではネイティブクラスのLayer.loadImagesの記事を見て、ストレージとカラーキーの引数を与えてしまうとエラーが起こります。このように、汎化したクラスのメソッドと特化したクラスのメソッドの引数が異なることもあるかもしれません。(吉里吉里/KAGではloadImages以外では特に目立ったのはなかったと思いますが。)

laycount

もう1つレイヤ関連のタグを追ってみたいと思います。前景レイヤとメッセージレイヤの数を増減させるlaycountタグです。[laycount layers="10" messages="4"]のようにして使われ、これが解析されると%["tagname" => "laycount", "layers" => "10", "messages" => "4"]としてタグハンドラに渡されます。

そのタグハンドラはMainWindow.tjsの5332行目からがlaycountタグのハンドラです。if elm.layers !== voidというのは、タグにlayers属性があるか(省略されたか)を判別し、省略されたら(layersなら前景レイヤに関しては)何も行いません。また、+elm.layersは文字列の"10"という値を整数の10に変換する演算を行っています。

// kag3/templete/system/MainWindow.tjs
5332:   laycount : function(elm)
5333:   {
5334:     updateBeforeCh = 1;
5335:     allocateCharacterLayers(+elm.layers) if elm.layers !== void;
5336:     allocateMessageLayers(+elm.messages) if elm.messages !== void;
5337:     return 0;
5338:   } incontextof this,

numの数だけの前景レイヤを割り当てるメソッドです。現在の前景レイヤの数より少なければ無効化(invalidate)して減らしていき、多ければ新たに前景レイヤを作っています。メッセージレイヤ(allocateMessageLayer)も同様の処理を行っています。

// kag3/templete/system/MainWindow.tjs
2826:   function allocateCharacterLayers(num)
2827:   {
2828:     // 前景レイヤ数を num に設定する
2829:     if(fore.layers.count > num)
2830:     {
2831:       // レイヤが減る
2832:       for(var i = num; i<fore.layers.count; i++)
2833:       {
2834:         invalidate fore.layers[i];
2835:         invalidate back.layers[i];
2836:       }
2837:       fore.layers.count = num;
2838:       back.layers.count = num;
2839:     }
2840:     else if(fore.layers.count < num)
2841:     {
2842:       // レイヤが増える
2843:       for(var i = fore.layers.count; i<num; i++)
2844:       {
2845:         fore.layers[i] = new CharacterLayer(this, fore.base, "表前景レイヤ" + i, i);
2846:         back.layers[i] = new CharacterLayer(this, back.base, "裏前景レイヤ" + i, i);
2847:         fore.layers[i].setCompLayer(back.layers[i]);
2848:         back.layers[i].setCompLayer(fore.layers[i]);
2849:       }
2850:       reorderLayers();
2851:     }
2852:     numCharacterLayers = num;
2853:   }

よく、吉里吉里/KAGにおけるよくある質問に「(void) から Object へ型を変換できません。Object 型が要求される文脈で Object 型以外の値が渡されるとこのエラーが発生します」というエラーが発生することがあります。これはlaycountタグでレイヤの数が正しく設定されてなくて起こるケースがほとんどです。

たとえば、[laycount layers="3"]で0から2までの前景レイヤが設定されていたとします。前景レイヤ(の表ページ)はkag.fore.layersに配列として格納されています。kag.fore.layers[0]は0番目の前景レイヤを格納していますが、kag.fore.layers[5]には格納されていません。逆を言えば、空を意味するvoidが入っているとみなします。

ここで、[image layer="5" storage="maki"]のようなタグを実行すると、getLayerFromElmメソッドによってkag.fore.layers[5]が返されます。しかしこれはvoidです。

そしてloadImagesメソッドを呼ぼうとしたとき、問題は起こります。ドット記号でつなげて「このオブジェクトのあのメソッド」のようなことは、左側がオブジェクトでなければいけません。今回はオブジェクトでないのでなんらかのオブジェクト変換しようと試みますが、voidからオブジェクトへは変換できません。結果、ああいうエラーメッセージがでるのです。

 
inside/loadimages.txt · 最終更新: 2008/01/28 10:23 by tohka