ご提案ありがとうございます。
たとえば、 256 色画像から Texture オブエジェクトをつくった場合、
初期状態はたしかに 256 色なのですが、
別の Texture オブジェクトをそれにアルファブレンディングした場合、
新しい色が誕生してしまい、パレットを保持できなくなってしまいます。
そのため、 Texture は最初からパレットを持たずに 32bit カラーのピクセル列という
ことにしてしまっています。
ここらへん何か折り合いつける案はございませんでしょうか。
--
Hajime Hoshi <hajim...@gmail.com>
>> たとえば、 256 色画像から Texture オブエジェクトをつくった場合、
>> 初期状態はたしかに 256 色なのですが、
>> 別の Texture オブジェクトをそれにアルファブレンディングした場合、
>> 新しい色が誕生してしまい、パレットを保持できなくなってしまいます。
>> そのため、 Texture は最初からパレットを持たずに 32bit カラーのピクセル列という
>> ことにしてしまっています。
>
> つまり Texture の実装にインデックスカラーの概念はない、ということですか。
そーです。
全部 32bit ピクセル列に変換しています。
>> ここらへん何か折り合いつける案はございませんでしょうか。
>
> 例えば、PNGからテクスチャを作るときにパレットを指定して色違いのテクスチャが作れるだけでも有用かなと思いました。ただPNG自体、インデックス
> カラーが有効なのは8bitまでなんでしたっけ。
>
> 32bitモードと8bitモードの Texture サブクラスなんて考えも浮かびましたが、やりすぎでしょうか。Numeric 下の
> Float と Integer みたいな感覚で。
うーむ、「32bit にできて 8bit にできること」やまたはその逆は必ずあって、
それでクラスを分けるのは分かるのですが、
どっちのテクスチャか区別しなければならなくなってしまうのは
使いづらくなってしまうと思います。
また、 Integer と Float の場合は、それぞれ immutable なオブジェクトで
オブジェクトそのもののクラスが変わることはないですが、
Texture は mutable なのでそうはいかないです。
たとえば 8bit Texture オブジェクトを描画して 32bit に変化してしまった場合、
新しいオブジェクトを生み出すことになります。
すると、クラスを分ける場合、 render_texture メソッドの仕様が
「self の状態を変える」のではなく「新しく 32bit Texture オブジェクトを生み出す」
という仕様にならざるを得なくなり、それは使いやすいのかどうか疑問です。
--
Hajime Hoshi <hajim...@gmail.com>
> また、 Integer と Float の場合は、それぞれ immutable なオブジェクトで
> オブジェクトそのもののクラスが変わることはないですが、
Ruby は immutable かどうかに関係なく、そもそもオブジェクトのクラスを
変えることはできないので、このつながりは変でした。
--
Hajime Hoshi <hajim...@gmail.com>
> ではこんなのはどうでしょう?
>
> 8bit画像を表現するクラス EightbitTexture クラスを新設し、このクラスのインスタンスはテクスチャ描画の転送元としてのみ参照す
> ることが可能とする。つまり、 Texture#render_texture(EightbitTexture,x,y,options) とするこ
> とは可能だが EightbitTexture#render_texture メソッドは実装しない、ということです。
>
> このようにすると、
>
> EightbitTexture のビット数は変化しないのでパレットを保持できる
> Texture クラスは今までどおりの使い方ができる
> 転送先・転送元ともにビット数が変化しないので新しいオブジェクトを生成する必要はない(本当か?)
>
> こんなメリットが得られるんじゃないかと思いました。
> もともと最終出力である Game.screen はテクスチャなので、他のテクスチャをブレンドできなくとも特には問題ないはずです。
さっきだいご君と話していたのですが、結論から言うとそれでいけそうだと思います。
描画先にならない 8bit 画像クラスを作るということですね。
実装もそんなに難しくなさそうです。
だいごくんの提案で、 EightbitTexture 同士は (お互い違うパレットを持っていようとも)
相互書き込み可能にしてしまおうという案もありましたが、
便利なのかどうか怪しいし、実装が面倒になるので保留にします。
> 転送先・転送元ともにビット数が変化しないので新しいオブジェクトを生成する必要はない(本当か?)
ってのは、 EightbitTexture 内部に普通の Texture 持たせて、
それを使いまわせるだろうということであってますか?
そうだとしたらその通りだと思います。
あと、 EightbitTexture って名前よりよい名前がありそうなので、それが思いついたらかなあ。
P.S.
そもそも Texture って名前がよくないという指摘を受けました。確かに。
Bitmap とかそういう名前にしておけばよかったんですが、今更変えようがない。
--
Hajime Hoshi <hajim...@gmail.com>
> はじめまして。
> RubyでですがStarRubyのサポートライブラリを作りたいなどと思っているISAと言います。
> よろしくお願いします。
おお、それはありがたいです!
ありがとうございます!
> >EightbitTexture
> クラスの本質はビット数ではなくインデックスカラーであるという点と、
> インデックスカラーは8bit以外のものも存在するため、
> 「8bit画像」というクラス名は適切でないと思われます。
>
> ・PalettedTexture
> ・IndexedColorTexture
> あたりが無難ではないでしょうか。
> 後者なら既存の「Texture」は「TrueColorTexture」のことだと考えられますし。
>
> 既存のクラスの意味が変化するのが問題ならば、ネームスペースを使って
> ・Texture::IndexedColor
> などとするのもアリかも知れません。
8bit であることは本質でないのは同意です。
リードオンリーであることを断らずにに○○Texture とすると、
Texture と同じような操作ができると期待されてしまうので
よろしくないんじゃないかと思います。
特徴として
* パレットを持つこと (インデックスカラーであること)
* リードオンリーであること
があるので、
ImmutablePatelleTexture
ReadOnlyPaletteTexture
IndexedColors
うーん長い。まだこれだという案はないですね・・。
--
Hajime Hoshi <hajim...@gmail.com>
>> おお、それはありがたいです!
>> ありがとうございます!
>
> まだ実用には至っていませんが、
> スプライトやシーン管理フレームワークなどを作ろうと思っています。
>
> 「すべてがテクスチャである」という特徴が、
> こういったライブラリを作成するのには非常に便利でとても楽しんてやれます。
なるほど。
結構アニメーションさせるためのクラスは需要があるのですが、
一般解を出すのが難しいので様子見状態です。
>> リードオンリーであることを断らずにに○○Texture とすると、
>> Texture と同じような操作ができると期待されてしまうので
>> よろしくないんじゃないかと思います。
>> 特徴として
>> * パレットを持つこと (インデックスカラーであること)
>> * リードオンリーであること
>> があるので、
>
> 既存のゲーム機のネーミングにならってCharactorなんてのはどうでしょう。
> 単体では画像として使用できず、CharactorとPaletteを組み合わせて
> Textureを作成し、初めて画像として使用できるイメージです。
>
> 慣習としてリードオンリーであることも暗示していますし。
> (そもそもTexture扱いではなくなるのでそんな心配もない?)
> リードオンリーだと明示しておかなければ、
> 将来的にキャラクタをドット単位で操作させるということもできるかと思います。
Character かな。
キャラクターというとゲーム中に登場する人物のようなイメージがあるのですが
違いますでしょうか?
既存のゲーム機の慣習というのがよくわからないもので、
広く使われているならばそれはいいかもしれませんね。
--
Hajime Hoshi <hajim...@gmail.com>
>> キャラクターというとゲーム中に登場する人物のようなイメージがあるのですが
>> 違いますでしょうか?
> FC、SFC等の2Dゲーム機では良く使われているネーミングだと思います。
> 確かにゲーム内のキャラクター(人物登場)と混同してしまいそうな気はしますが、
> StarRubyネームスペースの中で命名する分にはあまり問題にはならないんじゃないでしょうか?
>
>
> http://hp.vector.co.jp/authors/VA042397/nes/ppu.html
> http://akkera102.sakura.ne.jp/gbadev/index.php?Step.4%20%A5%BF%A5%A4%A5%EB%A5%E2%A1%BC%A5%C9%281%29
>
> キャラクタが1枚のタイルだとして、それを複数枚組み合わせることでスプライトや背景を表示します。
> その際に、キャラクタごとにパレットを適用させることで色のついた画像になります。
>
> StarRuby標準では組み合わせのことは考えず、単純に1枚のキャラクタから1枚の画像が作成できるだけで良いんじゃないかと思います。
> そういえば、この方法ならTextureに手を加えなくて良いのでライブラリで対応することもできますね。ちょっと処理が重そうですが。
なるほどう。
たしかに StarRuby モジュール内で Character クラスを作れば問題はなさそうですね。
ただ、なるべくトップレベルの名前空間内でもユニークな名前にするのに越したことはないです。
たとえば、 StarRuby::Array というクラスを定義すると、いくら名前空間が分かれていると
いっても、使いづらいものがあります。
> 以下に擬似コードを示します。
> hoge = Character.load("hoge_char")
> hoge_color_1p = Palette.load("hoge_color_1p") # てきとー
> hoge_color_2p = Palette.load("hoge_color_2p")
>
> hoge_1p = hoge.draw(hoge_color_1p) # ここでTextureが作成される
> hoge_2p = hoge.draw(hoge_color_1p)
>
> Game.screen.render_texture(hoge_1p, 0, 0)
> Game.screen.render_texture(hoge_2p, 64, 0)
毎回 Texture オブジェクトを生成するのは現実的ではないと思います。
パレットアニメーションをさせたい場合、パレットが変わる度に Texture オブジェクトを
生成することになってしまうからです。
そもそもインデックスカラーのテクスチャが欲しいという要望には、
パレットアニメーションをやりたいということがあったはずで、
パレットが頻繁に変わることを想定せねばなりません。
Texture オブジェクト生成はせず、描画元の Texture オブジェクトが描画の対象として
Character (仮) オブジェクトを認識するようにするのがよいと思います。
使う側にとっても変換を意識せずに済みますし。
--
Hajime Hoshi <hajim...@gmail.com>
ご意見ありがとうございます。
>> class IndexedColors
>> インデックスカラーの画像を表すクラス。
>> 内部的にはインデックス列、幅、高さ、カラーキー (インデックス) を保持する。
>> パレットは保持しない (あえて分けた方がわかりやすいかなあと思いました)。
>
> パレットをインスタンス内に保持するか否かはどっちもどっちかなあ、といった感想です。
> パレットとインデクス列がくっついているほうが都合がいいとプログラマが思えば、そのように管理する仕組みをプログラマ自身が作れるでしょうし、そうい
> う意味では離れていたほうがいいのかもしれないと、個人的には思います。
ほとんど変化のない「ピクセル列」の情報と、
頻繁に変わりえる「パレット」の情報は分けたほうが良いかと思いました。
(前の議論で IndexedColors (EightbitTexture) は Immutable であるべきという
僕の主張がありましたが、内部 Texture を持たなければ別にその必要はないかなと思いました。)
>> IndexedColors.load_with_palette(file)
>> 8bit PNG 画像の file を読み込み、
>> [IndexedColors オブジェクト, palette] という配列を返す。
>> palette は、パレットを表す Color の単なる配列である。
>>
>> Texture#render_indexed_colors(indices, palette)
>> self にインデックスカラーの画像を描画する。
>> 左上からピクセル列をそのまま流し込む形で描画することを想定しているので、
>> 縦横の大きさが合わないと変になる。
>>
>> この案のいいところは以下の通りです:
>>
>> * 大規模なクラス追加がない
>> * Texture クラスの既存のメソッドに影響を及ぼすことはない
>> * 描画の際に新しい Texture オブジェクトを毎回生成する必要はなく、
>> 中間バッファとしての Texture を使いまわせば効率的
>
> IndexedColors オブジェクトとTextureの大きさが一致していないといけない、というのがちょっと引っかかります。様々な大きさの
> IndexedColors オブジェクトを扱いたい場合、同じように様々な大きさの中間バッファを使わねばなりません。その場合、実行効率・プログラ
> ミング効率に影響を与えないとも限らないと思います。
>
> 気になったのはそこだけです。
まず既存の Texture に IndexedColors (名前は仮ですが) を描画させる方法として
1. Texture の既存の描画メソッドが IndexedColors を受け付ける
1.1. IndexedColors のピクセル列をそのまま認識できるよう改造する
1.2. IndexedColors が内部 Texture を持ち、それを使う ([starruby:130])
2. Texture が IndexColors を描画できる専用メソッドを作る ([starruby:161])
という案があります。
1.1. は提案すらされていませんが、改造コストが高すぎるのでだめです。
ということで今のところ 1.2. か 2. かのどちらかになります。
1.2. の場合、パレットがちょっとでも変わるたびに内部テクスチャの変化が行われるため、
(内部テクスチャが一個の場合は) 速度面で不利になると思われます。
2. の場合、パレットが変わる分 Texture オブジェクトを用意してもよいし、
メモリの効率のために 1 つの Texture を使いまわすこともできます。
つまり、速度面が最悪のケースでも 1.2. と同様です。
プログラミング効率は落ちるというか、
内部テクスチャに比べて余計な手間が確実に増えますね。
と書きながら思いついたのですが、
1.2. の案で内部テクスチャを複数個持つこともできます。
* パレットはせいぜい n 種類であろうと想定して、内部テクスチャを n 個もつ
(一色でも変わると違うパレットとみなさなければならないので、実用的でないかも?)
* パレットに関係なく n 個の内部 Texture を持ち、なんらかの方法 (FIFO など) で管理する
もう少し検討してみます。
--
Hajime Hoshi <hajim...@gmail.com>
> 1. Texture の既存の描画メソッドが IndexedColors を受け付ける
> 1.1. IndexedColors のピクセル列をそのまま認識できるよう改造する
> 1.2. IndexedColors が内部 Texture を持ち、それを使う ([starruby:130])
> 2. Texture が IndexColors を描画できる専用メソッドを作る ([starruby:161])
>
> (中略)
>
> 1.2. の場合、パレットがちょっとでも変わるたびに内部テクスチャの変化が行われるため、
> (内部テクスチャが一個の場合は) 速度面で不利になると思われます。
> 2. の場合、パレットが変わる分 Texture オブジェクトを用意してもよいし、
> メモリの効率のために 1 つの Texture を使いまわすこともできます。
> つまり、速度面が最悪のケースでも 1.2. と同様です。
と述べましたが、 1.2. で速度が気になるならばその分 IndexedColors を複製すればよい
だけの話であることに気づきました。
1.2. と 2. は「内部テクスチャ」が内部か表に出ているかの違いだけになります。
すると、 Star Ruby 利用者にとって使いやすい API であることを最重要視して
比較するのがよさそうに思いました。
ところで、 1.2. を発展させて、新しいクラス追加が一切ない簡単な案を思いつきました。
* Texture.load(path)
基本的に変化なし。
8bit 画像をロードしたときは、内部にインデックス列とパレットも保持する。
パレットとピクセル列 (Texture オブジェクトが元々持っているもの) とは
自動的に同期を取る。
パレットを変化させると、ピクセル列もそれに伴い変化する。
逆に、ピクセル列を直接変更すると、その瞬間にパレットおよびインデックス列は消滅する
(今までの普通の 32bit テクスチャになる)。
* Texture#palette
パレット (Color の frozen な配列) を取得する。
frozen なので、要素を変化させることはできない。
例)
texture.palette[i] = Color.new(0, 0, 0) # TypeError になる
なぜ frozen なのかというと、パレットと実際のテクスチャのピクセルとの同期をとる必要があり、
パレットが変化したらピクセル列の取得時にはピクセル列が変化してなくてはならず、
その同期を取るのが大変だからである。
(パレットを配列ではなく専用のクラスにし、ギリギリまでピクセル列の変化を遅延するなどすれば
Texture#palette を frozen にせずともピクセル列と同期をとることは可能です。
ちなみに、ピクセル列変化を遅延しないで、パレット一色変わるたびに
ピクセル列を変えてしまうと、速度的に使い物にならなくなると思います。
ただ、実装にかかるコストの割にはそんなにうれしい事はないかなあと思い、今回は見送りました。
パレットを変化させる場合は、僕の感覚では大抵がらっと変わるものだと思うからです。
実際そうじゃないかもしれませんが、そのときはそのときで考えます。)
パレットおよびインデックス列がない場合は nil を返す。
* Texture#palette=(palette)
パレット (Color の配列) を設定する。
設定した瞬間に、ピクセル列もそれにあわせて変化する。
引数 (palette) は複製され (dup) 凍結される (freeze)。
そのため、代入後に plaette 引数の値を変化させても、テクスチャには影響はない。
例)
texture.palette = palette # texture に設定されるパレットと palette は別物になる
palette[0] = Color.new(0, 0, 0) # texture に何の影響もない
Texture#palette が nil の場合に Texture#palette= を呼ぶとエラーになる。
議論にかなり時間を食ってしまいました。
とりあえずこれで実装しようと思います。
--
Hajime Hoshi <hajim...@gmail.com>