mieki256's diary



2017/04/01() [n年前の日記]

#1 [dxruby] HLSLでフォンシェーディングをやろうとしてハマり中

NVIDIA FX Composer を試用したら Phongシェーディング(フォンシェーディング)のシェーダがあったので、コレをコピペしたら簡単にできるんじゃないか、と安易に思って DXRuby で試したらこれが全然上手くいかず。とんでもない描画になる…。何がおかしいのや…。

2017/04/02() [n年前の日記]

#1 [dxruby] フォンシェーディングっぽくなってきた気がするけどまだなんだか怪しい

DXRuby の CutomRender で3D描画の実験中。

鏡面反射をそれらしく見せてくれるという、フォン(Phong)シェーディングとやらができないものかと試しているのだけど…。なんとなくそれっぽくなってきた気がする。けど、なんだか、まだちょっと怪しい気もする。これで本当に正しく処理されているのだろうか…。何か間違えてないか…。



ソースは以下。

_crt_test06_phongshading.rb , hlsl_lambert.fx , hlsl_phong.fx

動作には _tinydaeparser.rb_tinywavefrontobj.rb_サンプルデータ が必要。

というか、ソースもデータもまとめておいたほうがいいのか…。 _customrender_sample.zip として置いときます。

関係ありそうなところをメモ。 :

hlsl_phong.fx の中の、Phongシェーディングと関係ありそうなところを抜き出してメモ。
// vertex shader with texture
VS_OUT VS(VS_IN v) {
  ...
  output.vNormal = mul(v.vNormal, (float3x3)mWorld).xyz;
  output.vWPos = mul(v.vPosition, mWorld);
  output.vEyePos = mView[3].xyz;
  ...
}

// pixel shader with texture
PS_OUT PS(VS_OUT p) {
  ...
  float3 light = normalize(vLightDir.xyz);
  float3 normal = normalize(p.vNormal);
  ...
  // specular
  float3 view = normalize(p.vEyePos - p.vWPos);
  float3 r = normalize(light - 2.0f * normal * dot(light, normal)).xyz;
  float spe = pow(max(dot(r, view), 0.0f), vMatShininess);

  // float3 halfway = normalize(light - view);
  // float spe = pow(max(dot(normal, halfway), 0.0f), vMatShininess);

  float3 specular = vMatSpecular * spe;
  ...
}
これで合ってるのですかね…? 分からん…。

気づいた点をメモ。 :

DXRuby開発版に同梱されていた公式サンプル、spheretest.rb の中の光源用変数は、位置座標じゃなくてライトの方向を示してたのですな…。それも、光源から頂点へのベクトルじゃなくて、頂点から光源へのベクトル、のように思えてきたり。

フォンシェーディングの計算をする際は、頂点から視点へのベクトルが必要なのだけど…。サンプル内では mView[3].xyz で似たような値が取り出せそうな気がする、けど、それで合っているのかどうか…。

_その39 知っていると便利?ワールド変換行列から情報を抜き出そう

_☆PROJECT ASURA☆ [OpenGL] 『ライティング(3) Lambert』 によると、ハーフランバートなるものがあるらしいので試したり。

参考ページ。 :

#2 [emacs] emacsでHLSLを編集

Rubyスクリプトソースを書く時は emacs (NTEmacs) を使っているのだけど、HLSLもemacs上で書きたいなと。

_HLSL Mode download | SourceForge.net で、emacs 用の hlsl-mode が公開されている。NTEmacs 上で導入してみたり。作業が楽になった。

~/.emacs 等に以下を記述。これで .fx ファイルを開くと hlsl-mode になる。
;; HLSL mode
(autoload 'hlsl-mode "hlsl-mode" nil t)
(add-to-list 'auto-mode-alist '("\\.fx\\'" . hlsl-mode))
(add-to-list 'auto-mode-alist '("\\.hlsl\\'" . hlsl-mode))

2017/04/03(月) [n年前の日記]

#1 [cg_tools][blender] 宇宙船を自動生成するアレコレを試したり

宇宙船の3DCGモデルを自動生成するアレコレを試したり。

blenderで自動生成 :

_a1studmuffin/SpaceshipGenerator というアドオンを使うと、blenderで宇宙船を自動生成できるようで。

導入の仕方は、 _Releases - a1studmuffin/SpaceshipGenerator から、add_mesh_SpaceshipGenerator.zip をダウンロード。blender を起動して、ファイル → ユーザー設定 → アドオン → ファイルからインストール、で、先ほどDLしたzipファイルを選択して、「ファイルからインストール」ボタンをクリック。アドオンリストに表示されるのでチェックを入れて有効化。

使い方は、Shift + A → メッシュ → Spaceship。もしくはスペースキーを押して「spaceship」と打ち込めば項目がリストアップされるのでソレを選択。

3Dビュー画面に形状が追加されたら、左下のプロパティ?で生成時の設定を弄れる。とりあえず、シード(おそらく疑似乱数のシード)にテキトーな数値を入れていけば、生成される形がランダムで変わる模様。

デフォルト設定ではテクスチャも貼ってある状態なので、そのままレンダリングできるっぽい。

Webサービスで自動生成。 :

_Ship - shapeWright.com を使うと、ブラウザ上で宇宙船っぽい3Dモデルを自動生成できる。 _宇宙船っぽい3Dモデルをランダムで生成してくれる『shapeWright』 | IDEA*IDEA によると、少なくとも2011年以前から存在していたらしい…。

上のほうの「Your name here」と表示されてる入力欄にテキトーな文字列を打ち込んで(「apple」とか「pen」とか文字列なら何でもOK)、「BUILD IT」ボタンをクリックすると、生成されるモデルの形状が変わる。おそらく文字列を疑似乱数のシードにしている予感。

生成された形状は、右下の「Downloads as VRML」をクリックすることで、 .wrl形式のファイルでダウンロードできる。blender でインポートできることも確認した。

ただ、このサービス、ライセンスが不明なんだよな…。 _shapeWright.com には「free」としか書いてないけど…。あらかじめそれっぽく作られたモデルをいくつか組み合わせて生成してるようだけど、そのパーツモデルにも著作権があるだろうし…。ダウンロードしたモデルを何かしらに使う場合、ライセンス面でどんな制限がついてまわるのやら…。

#2 [tv] 「NHKスペシャル メルトダウン6 原子炉冷却12日間の深層〜見過ごされた危機」を視聴

録画したまま見てなかった、「 _NHKスペシャル メルトダウン6 原子炉冷却12日間の深層〜見過ごされた危機 」を今頃になって視聴。なかなか興味深い話がチラホラ。

覚えてる範囲で内容だの感想だのをメモ。

イソコンの話。 :

イソコン(isolation condenser、 _非常用復水器 )なる、電源喪失に陥っても冷却できる装置が1号機にはちゃんとついてたけれど。

そのイソコン、原発の建設時にテストで動かしただけで、その後40年間、一度も動かしてなかったものだから…。正常動作していたらどういう状態になるか知ってる人が誰も居なくて、動いてないことに誰も気づかず、冷却できていないことすら気付けなかった、てな話が紹介されてたり。

_福島第一原発の非常用冷却装置、長期に渡って稼働試験がされていない状態だった | スラド ハードウェア

本来、ちゃんと動いたら、建屋の壁についてる二つの穴、通称「豚の鼻」から、まるで雲のように凄い勢いでモクモクと水蒸気が出るそうで。40年前に動作テストを見ていた人はそう証言してた。

が、件の事故ではちょろちょろと水蒸気が出てただけ。しかし正常動作してる様子を見たことのある人が現場に誰一人居ないものだから、「水蒸気が一応出てるね」→「正常動作してるんだろう。してるはず」と思い込んだそうで。

ちなみにアメリカではイソコンの実動作試験が義務付けられてる、と番組内で紹介されてた。アメリカの原発関係者は、「普段から動作テストしておかないと、いざという時にちゃんと動いてるか分からんだろ。何言ってんのお前」的な、実に当たり前なことを言っていた…。

日本の場合、万が一イソコン内で配管が壊れてたら放射性物質が外部に出てしまうので周辺地域へのリスクを考えてこれまで動作試験しなかった、との東京電力の弁で。地域住民に配慮して、あえて動作試験しなかったのだ、俺達悪くない、みたいな物言い。

配管が壊れてたらいざという時に手が打てないのだからそこまで含めてチェックして動作試験する体制を整えておかないといかんのではと素人考えでは思うのだけど。まあ、「日本の原発は絶対に事故を起こさない」と念仏のように唱えてた人達にそんなこと言ってみてもどうせニヤニヤ笑われてスルーされるだけ、ではあったのだろうなとも。

なんでも本社を通さないと話が進まない話。 :

1号機は早い段階で中の水が空になってメルトダウンしてたのだけど、実は水量計が正しい値を示してなくて、半分ぐらいは水が入って冷却できてるものと12日間もずっと思い込んでいたそうで。

しかし、柏崎刈羽原発の所長さん達は、「ガンガン注水してるのに水量計がピクリとも変化しないってどう考えてもおかしいやろ」「これ、水量計壊れてるやろ」「中は水が空になってる可能性を考えんと」と助言してくれていたようで。

でも、なんでもかんでも東電本社(本店)を通さないと話が進まないものだから、東電本社+福島第一原発所長の「実は半分どころじゃなく満杯になってるけど、水量計がソレを示してくれないだけではないのかなあ」てな、今になってみると明らかな _正常性バイアス でかき消されて助言が活かされなかったという…。

要は、縦の繋がりが強過ぎて、横の繋がりで現状を把握や推測することが難しい状態に置かれてたっぽいよねコレ、てなことが番組内で指摘されてた。

たしか欧州のどこかの国では、原発を運用する会社は本社を原発の横に置くこと、みたいな義務化がされているらしいのだけど…。重大事故が発生した時に、各対策について決定権を持つ本社が遠くにあったら、正確な現状把握も、迅速な対策決定も、まともにできるわけないやろ、みたいな理由だったはずだけど。せめて、日本もそうしておいたほうがいいんじゃないかなあ、と思えたり。いやまあ、本社を都市圏から移せないというなら逆に原発を都市圏に持っていくのでも構いませんけど。もっとも、その選択をするよりは、本社を原発の横に移転するほうがマシじゃないかと…。

と思ったけど、遠く離れた柏崎刈羽原発の人達が客観的に推測できてるのに、現場の人達が正常性バイアスに支配されてるという状況を考えると…。いや、決定権を持つ本社が事故現場の近くにあるだけでも、分離していた現場と本社を一体化できるのだからマシになりそうな…。どうなんだ。

リーダーにかかる負荷の話。 :

12日間の、関係者のTV電話を使った発言内容をAIで分析してみたら、福島第一原発の所長さんの疲労度等がグラフの形でハッキリわかる状態になったそうで。同じ単語が何度も出てくるとか、発言時間をカウントすると休息が一切取れてないとか、把握かつ指示を出さなければいけないジャンルがあまりにも多岐にわたるとか。

最後は所長の意識がもうろとして対策室から外に運ばれていく瞬間が来るのだけど…。次回もこういうシステムで対応してたらまた同じ展開になるよね、みたいな話に。

てなわけで。 :

リーダーにかかる負荷とかどうやって軽減したらいいのか素人考えでもさっぱり思いつかないけど。とりあえず、再稼働の認可が下りた原発ってイソコンの類はあるのかな、一応実動作試験したのかな、てなあたりが気になりました。もし、動作試験してなかったら、フラグ臭いなと…。 *1

*1: まあ、「東日本と西日本の原発はそもそも仕組みが違うから」とか言ってうやむやになってそうだけど。所詮は他所の国で起きた事故だから俺達関係ねえ、みたいなノリだろうなあ…。日本って、明治時代になるまで、各地域はそれこそ「国」と呼ばれてたわけで、距離感と当事者意識って未だにそんなレベルだろうと思ったりもするので、件の事故の経験はどうせろくに活かされないのだ、防ぐ努力をしておけばちゃんと防げたものも結局防がないあたりが日本人、などと予想してたりもしますが。とは言えこのあたり、神様の振ったサイコロの目が悪かったらこうなる、という話だし、貴方の国(=県)ではイイ目が永遠に出続けるといいですねえ、どうせ皆さんイイ目が出続けると信じ込んでそうしてるんでしょ、てなところもあるわけで、絶対に事故が起きるとも言えないし、絶対に事故が起きないとも言えないしで、難しい面があるよなと思ったり思わなかったり。

2017/04/04(火) [n年前の日記]

#1 [ruby][dxruby] ドット絵を自動生成するスクリプトをRubyで移植してみたり

_zfedoran/pixel-sprite-generator という、ドット絵っぽい何かを自動生成してくれるJavaScriptがあるのだけど、なんだか面白そうなので Ruby で移植して DXRuby から使ってみたり。

_mieki256/tinypixelspritegenerator

こんな感じのドット絵、っぽい何かが作れるようです。

spaceship_24x24.png

生成時にガイド相当としてマスクパターンを渡すので、全自動じゃないあたりがちとアレですけど、それでも同じマスクパターンからこれだけ違うモノを生成してくれるので、何か使い道が…あるのかな。どうかな。どうなんだろう。

使い方。 :

_tinypixelspritegenerator.rb をダウンロード。Rawと書かれたボタンを右クリックして「リンク先を保存」等で落とせるかと。

DXRuby がインストールされてる環境なら、ruby tinypixelspritegenerator.rb でひとまず動作確認はできるかと。

何かから呼び出して使いたい時は…。
require 'dxruby'
require_relative 'tinypixelspritegenerator'

# 配列を DXRuby の Image に変換
# @param a [Array<Array>] 3次元配列
def array_to_image(a)
  h = a.size
  w = a[0].size
  img = Image.new(w, h, [0, 0, 0, 0])
  a.each_with_index do |row, y|
    row.each_with_index do |rgb, x|
      r, g, b, a = rgb
      img[x, y] = [a, r, g, b]
    end
  end
  return img
end

# マスクパターン
mask_spaceship = [
  # 12x12
  # "0" or " " = 透明部分
  # "1" or "." = ランダムで、ボディ部分か透明部分になる
  # "2" or "+" = ランダムで、境界線かボディ部分になる
  # "3" or "*" = 常に境界線になる
  '000000',
  '000011',
  '000013',
  '000113',
  '000113',
  '001113',
  '011122',
  '011122',
  '011122',
  '011113',
  '000111',
  '000000',
]

# 文字列で指定されたマスクパターンを配列のマスクパターンに変換
mask = TinyPixelSpriteGenerator.convert_mask(mask_spaceship)

# ドット絵生成
p = TinyPixelSpriteGenerator.new(
  mask,                    # マスクパターンの配列
  mirror_x: true,          # true で左右ミラーを有効
  mirror_y: false,         # true で上下ミラーを有効
  colored: true,           # 色付きにするか白黒にするか
  edgebrightness: 0.3,     # 境界線の明度。値が小さければ黒に近付く
  colorvariations: 0.2,    # 色付け時のバリエーション幅
  brightnessnoise: 0.3,    # 色付け時の明度差
  saturation: 0.5,         # 色付け時の彩度幅
  seed: rand(65536)        # ランダムシード。ここが同じなら同じドット絵ができる
)

# 得られたドット絵のパターン配列を DXRuby の Image に変換
img = array_to_image(p.pixel_data)

Window.loop do
  break if Input.keyPush?(K_ESCAPE)
  Window.draw(0, 0, img)
end
こんな感じですかね…。

二次元配列に [R, G, B, A] を入れて返すだけなので、配列から画像用クラスに変換する処理さえ書けば、DXRuby以外、例えば gosu 等でも使える、のではないかと。

アルゴリズム。 :

_Pixel Spaceships (Internet Archive) で元々のアルゴリズムが紹介されてますが、簡単に説明すると、ざっくりとしたマスクパターンを渡して、ボディ部分をランダムに On/Off した後、境界線を作って縁取る、てな処理のようで。

しかしコレ、元々の画像を見ると、ボディとコクピット部分で色が分かれてるような…。自分の書いた版は、というかオリジナルのJavaScript版も、そこまで反映されてないな…。

色付けする時に、元々のマスクパターンを参照して、「ココはコクピット部分だから色を変えよう」みたいな処理をすればいいのかな。たぶん。まあ、そこらへんは宿題ってことで。

#2 [prog] TortoiseGitでエラー

TortoiseGit 2.4.0.0 を使おうとしたら libgit2 とやらがエラーを出して。

ググったら、バグだったらしい。

_AWS CodeCommit+TortoiseGitで”libgit2 returned invalid path”エラー | Mashpote - マシュポテ
_Doing Add on repository root fails with libgit2 returned invalid path" (#2911) - Issues - TortoiseGit / TortoiseGit - GitLab

#3 [tv][movie] 「アナと雪の女王」を視聴

TV放送されたソレを録画したまま放置してたのだけど、ようやく視聴。

なるほど、ディズニーっぽい…ってディズニーアニメだから当たり前、だろうか。いやいや、手描き時代のスタッフはもうほとんど居ないだろうから、 次の世代がディズニーらしさを再現・復元しようと努力した末にこうなったはずで。 *1 できて当前、ではなくて、道具もスタッフも何もかも違うだろうによくぞここまでそれらしく仕上げてみせたものだなあ、恐ろしいなあ、てな印象を。

見ていてなんとなく思ったけれど、これこそが、ディズニーがずっと望んでいたけど手描き時代は実現できなかった、真に理想のディズニーアニメ映像、なのだろうなと。手描き時代の基本2コマ撮りではなく、24コマ/秒で動かしてる上に、画面に登場する全ての形状を正確に完全にコントロールしているわけで。究極のアニメ映像。本物のフルアニメーション。

そしておそらく日本のアニメも、「作画崩壊」と叩かれることばかり気にしてお綺麗なお人形さんばかり描くことを意識して作っていたら、最終的にはこういう映像に到達せざるを得ないだろう、などとますます思えてきたり。 *2

みんなで歌おうのソレ。 :

さておき、本編が終わった後の「みんなで歌おう」の映像を目にして、なんだか絶望的な気分になりつつ映像を早送り。日本人って心底救いようがねえな…。一体誰だよ、この同調圧力映像をツッコもうと言い出したヤツは…。どうしてこういうことしちゃいけないのか、TVの仕事をしていながら全然分かってないんだな…。 *3

いや待て。もしかしてコレは幼児向け番組として企画・放送されていたのだろうか…。映像も幼児がやたらと出ていたし…。だとしたらアリなのか…。「おかあさんといっしょ」の最後のあたりで幼児の群れがわらわらと動いてるコーナーみたいなもんで…。

などとだんだん気になってきてググったら。

_『アナ雪』の「みんなで歌おう」企画が賛否両論 不向きな日本人は「クール」じゃないのか - AOLニュース
_「アナと雪の女王の主題歌みんなで歌おう♪」シング・アロングは、なぜ成功したか。(碓井真史) - 個人 - Yahoo!ニュース
_成功と失敗の違いは?:GWの映画館「アナと雪の女王の主題歌みんなで歌おう♪」シング・アロング(碓井真史) - 個人 - Yahoo!ニュース

うげ。劇場公開時からやってたのか…。知らなかった…。TV局の方をアレしてしまってゴメンナサイ。とばっちりですね。映画版からしてそもそもアレだったのね…。

ひょっとして、 _シン・ゴジラ発声可能上映 みたいなものか…。するとこの「アナ雪」も、TVの前の視聴者は、「歌うぞ…歌うぞ…歌うぞおおおお…」と手に汗握ってその時を待ち続けてあの映像が流れた途端に「ありょりょおおお!」と絶唱し始めたってことなんでしょうか。ですか。だったらいいんじゃないでしょうか…。そういう祭りがあるとは知らなかったもので文句言っちゃってゴメンナサイ。その手の祭りで「君が担いでるソレ、チ○コの形してるけどいいの?」と心配してるようなもんだよな…。KYにもほどがある。

それはともかく。

_『ありのままで』の歌詞はトンデモ訳?→日本語版に合わせたプロの仕事! - Togetterまとめ

自分、あの歌を耳にすると、「訳はこれで良いのだろうか…」「本当はもっと意味が重なっていたはず…」「しかし英語と日本語では時間あたりに詰め込める情報量が違うし…」「このキャラクターにしてみれば思ってもいないことを歌わされてるようなものだよな…」「とは言え翻訳と言う作業が入ってくる娯楽コンテンツには必ず付きまとう問題…」「かといって英語のままでは例えば幼児は理解できないのでは…」などと正解がない問題について悩み始めちゃうので仮に「歌え」と強制されても頭の中は色々とグルグル回ってしまって今ちょっと歌うどころじゃないです勘弁してください。

*1: たしかディズニーの手描きアニメーターさん達は一時期全員解雇されてたような…。PIXAR買収後にラセター監督達がディズニーに乗り込んで手描きのソレを再評価させたことで戻った人も居るかもしれんけど、それでも実作業をしてるのはやっぱり若い世代のCGアニメーターさん達だろうと想像するわけで。
*2: 個人的には、手描きアニメの強みを自覚してソレを活かしたアニメも作ってほしいのだけど…。いやまあ、活かしてる作品もちゃんとあるのでアレなんだけど、3DCGで作るべきではないかと思う作品を見かけてしまうこともそれなりに…。
*3: TV番組等を見せた際に視聴者がどういう気分になるか、どういう表情をするか、そこから先は全部は視聴者側の仕事というか担当なわけで、それを映像提示側が先に前に出てやっちゃったらダメだろうと。例えば、子供が自発的に宿題やろうとして教科書開いたタイミングで「早く宿題やりなさい!」と母ちゃんが怒鳴ったら子供はどんな気持ちになりますか。漫才コンビがコントでネタを一つ披露するたびに客が笑うより早く自分達で笑ってたらどうなりますか。そういう状況を想像したら「あっ。コレやっちゃダメだ」と容易に分かるだろと。なのに、昨今の日本のTV局は民放に限らずNHKも含めて延々とその類の愚行を繰り返して…。それでいて「視聴者離れが」とか愚痴ってるし。正直、こういう言葉は吐きたくないけど…馬鹿じゃないのか。

#4 [zatta][neta] 「みんなで歌う」ことに対して何か妙な気分になる自分

自分、基本的に、皆で揃って歌うことに対してどうも抵抗があるのだけど。なんでだろう、どうしてだろうと考えた時、どうも小中学校の体験が関係してそうな気もしてきたり。

小中学校って、君が代斉唱等、自分の意志とは関係なくとにかく揃って歌うことを強制されるじゃないですか。竹刀を持った先生が目の前をウロウロして、「おらあ! オマエラ声が出てねえぞ!」と竹刀で地面をバシッと叩いたりとかやってましたけど。まあ、戦中の軍隊ノリが学校と言う閉鎖空間の中でずっと続いてたというか、上からの命令に絶対服従する兵士を効率よく育てるためには必要な訓練時間なのだ、みたいなところがあるんでしょうけど。

もちろん自分は当時子供だったし頭も回るほうじゃないから「なんで歌わなきゃいけないんだろう」と疑問を持ちながらも反抗する気力もなければそれらしい理論武装を構築できるわけでもなくひたすら怒られないように口パクパクさせて歌ってるフリだけはしてましたけど。どうもあの頃に、「皆で歌うという行為」に対して、「気分が滅入ること・なんだか気持ち悪いこと」てなイメージが付加されてしまった、ような気もしてきたり。

そういや、今現在も「愛国心を育てるために」とか言ってその手のアレコレを学校内で強制されてたはずですが。本来愛国心ってコミュニティに属する人達の心の中で自発的に湧き上がってくるのが理想的な状況だろうと思うわけなんですけど、それって何かしらをグリグリと強制することで各人の中にムクムクと育つものなのかねえ、などと思うわけですよ。特に「歌う」という行為はかなり自発的な行為だよなと。そこを強制したら、嫌な思い出しか残らないのとちゃうかと。

何せこの歳になっても、子供の頃に味わったいやーな感じの体験・気分を引き摺って、未だに「皆で歌うとかちょっと気持ち悪いなあ」なんてうっかり思ってしまうのだから、アレはちと逆効果だったのではないかしらん、などと思ったり思わなかったり。 *1

かといって、子供達が皆で自然と歌いたくなるような状況を作り出せるかと考えたらコレもちょっと難しいわけで。「君が代斉唱したあの瞬間、楽しかったなあ…」なんて思える状況を作れたら理想的ですけど、そんなの無理ですわな。教えてる先生達自身にもそういう体験がまず無いだろうし。

まあ、そこに一体感があるから自然と歌うのか、それとも歌うことで一体感があるように錯覚させたいのか ―― 歌は結果として出現するのか、それとも歌を道具として扱うのか、みたいな話なのでしょうかね。わからんですが。

オチはないです。思考メモです。

先生達でバンド組むとか。 :

校長先生がギターをギュイーン。教頭先生がドラムをズダダダ。音楽の先生がキーボードをピコピコピコ。生徒達は「クッ…体が…自然と動いちまう…」「このグルーブに乗るしかねえ!」みたいな感じで「君が代はー!」「イエーイ!」「校歌斉唱ー!」「イエーイ!」「チクショウ、こんなイカレタ国に生まれて、俺達ってもしかして幸せなのかも…!」

ねーよ。

でも、そういう漫画があってもいいような気もしてきたり。ていうかあるんじゃないか、もしかして。

*1: しかし、小中学校のソレも、外国の人達から「なんで日本人は国家を歌わないんだ?」と妙な疑問を持たれないようにあくまで儀礼の一つとして身に着けておきませう、みたいなことを当時言われてたらちょっとは印象違ったかもと思ったりもするのですが。必要性さえ理解できれば、そこからは自発的行為に変わっていく可能性も期待できるわけで。

#5 [anime][neta] 「戦姫絶唱シンフォギア」ってアメリカで実写ドラマ化しないものか

つい先日まで、BS-11で「戦姫絶唱シンフォギア」というアニメの1期が放送されていたので一応視聴してたのですが。なるほどコレは続編が作られるわけだなと納得を。最終回1話前のラストは、たしかに燃える。ような気がしたり。

ところで。

「アナと雪の女王」を見ながら、「どうしてこの人達、何かというとすぐに歌い始めるのかなあ。そういう病気なんだろうか」などと素朴で幼い疑問を持ちながらも、圧倒的な歌と映像のシンクロ及びコンボを目にして呆気に取られているうちに、ふと「シンフォギア」のことを思い出して。

もしかして、「シンフォギア」をアメリカで流したらウケたりしないかなと。見せ方についてはディズニーアニメと変わらん気がするわけで。ディズニーアニメは歌で城が建つけど、シンフォギアは歌でミサイルが飛ぶ…その程度の些細な違いしかないよなあ、と。

まあ、アメリカ内で、アニメとして流したのでは見る層が限られるから…。やっぱり実写ドラマ化だよなと。おそらくあちらにも自社で抱える若い娘さん達をどうにか売り出したいと考えるレコード会社の人達が絶対居るだろうから、企画内容・作品設定を聞いて「それはイイ!」「ウチの商品を前面に出せる!」と喜ぶはず…。

とは言えおそらく最初のうちは視聴者から「なんで歌いながら戦うんだよコイツラ頭おかしいんじゃないの?」てな冷たい扱いを受けるだろうけど、おそらく途中で「アレ? コレ面白いんじゃね?」「意外と燃えるんじゃね?」と気づいてしまう目利きが少しずつ増えてきて、最後のあたりはもう「みんなで歌おう!」状態に。

てな妄想をしてしまったのでメモ。

いや、ホント、「シンフォギア」ってディズニーアニメとそんなに違わないと思うんだけど…似たようなもんだよなあ…。「レリゴーレリゴー」と歌いながらモンスターをスパーンスパーンと破裂させていくいかにもアメリカンな体型の三次元美少女戦士達。全然アリでしょう。ナシですか。ですか。

歌ってないと死んじゃう病。 :

ディズニーキャラ達が病院のベットでゴホゴホと咳き込んで、付き添いのキャラが「早く! 早く歌うのよ! じゃないと死んじゃう!」と。ベットの上のキャラが「もう嫌! ゴホゴホ…もう歌いたくない! 私達、一体いつまで無意味に歌い続けなければいけないの!?」と。

そういうパロディが既にありそう…。サウスパークやシンプソンズでやってたりしないか…。

2017/04/05(水) [n年前の日記]

#1 [dxruby][ruby] ドット絵を自動生成するスクリプトを弄ってたり

昨日アップロードした _tinypixelspritegenerator を弄ってたり。わざわざマスクパターンを渡すあたりがどうも気になるので、中に何種類かマスクパターンを持っておいて、パターンの種類を文字列を渡して指定するだけでもひとまず出力できるようにしてみようと。

種類を指定して出力するソレはできたので、ついでにサイズを渡すとそのサイズに拡大して生成する処理も入れてみようと思いついて。マスクパターンを拡大すれば、それっぽくなってくれないか…。

ダメだった。境界線部分が予想以上に目立ってしまって、かなり酷い結果に。考えてみれば、1ドットの境界線が2〜3ドットになってそこに居座るのだから、目立つのは当たり前か…。

マスクパターンを拡大する処理は諦めた。大きい画像を生成したい時は、面倒臭いけどマスクパターンを渡す方向で。その代わり、出来上がったピクセルデータをニアレストネイバーで拡大できる処理を追加してみたり。ドット絵らしさが強調されるから、これはこれでアリ、かもしれない。

_画素の補間(Nearest neighbor,Bilinear,Bicubic) 画像処理ソリューション

2017/04/06(木) [n年前の日記]

#1 [dxruby][ruby] ドット絵を自動生成するスクリプトを更新

_tinypixelspritegenerator を修正してアップロードしておきました。

all_type.png

使い方は…。
require_relative 'tinypixelspritegenerator'
psg = TinyPixelSpriteGenerator.new("spaceship", seed: 43291)
p psg.pixel_data
こんな感じで。最初にアップロードした版より少しは簡単に使えるようになったはず。

もっとそれっぽくしたいとか、あるいはもうちょっと大きい画像が欲しいときは、自前でマスクパターンを用意して渡してやってください。

根本的な問題に気づいてしまった。 :

今頃になって気づいてしまったけど…。例えば、何かしらの本体スクリプトとは別に、このファイルも添付して配布、という形になると…。ソレって最初から画像ファイルを一つ同梱・添付しても状況として変わらないのでは、ていうかむしろ画像ファイルを添付したほうが確実なのでは、という気もしてきたり。

いやまあ、このファイル一つで、ランダムシードさえ変えれば膨大な数のドット絵モドキを出せるメリットはあるけれど。しかし、それぞれがちゃんと使えそうな見た目になってくれるのかという問題も…。

gem にして配布すれば使える場面も出てくるのだろうか。しかし、そもそも Ruby でこういうソレを使いたい場面がどれだけあるんだという疑問も…。いや待て。インストールが楽になれば、その分何かしら違う使い道が思いつく展開もあり得るのだろうか。

まあ、いいか。個人的にはこういうのって面白いし。

#2 [prog] 「ここはテキトーな数を書け」的な定番の書き方ってあるのだろうか

README.md を書いているうちに、ちと疑問が湧いて。

「ココはランダムシード(疑似乱数の種)を渡せばいいですよ」と伝えたいのだけど、サンプルソース上ではどう書けばいいのだろうと。例えば、0 だの 32 だの 256 だの 65536 だの、キリのいい数値を書いてしまうと、「この数値、おそらく何か意味があるのだろう」と思われそうだなと。違うんだ。なんでもいいんだ。疑似乱数の種にするだけなんだから。でも、どうやってソレを伝えればいいのか。

プログラマーの間では、 _hoge, fuga, piyo, foo, bar 等、「ここはなんでもいいんだよ」と伝えるソレがあるけれど。「○○○○○という数値が出てきたら、どんな数値をそこに入れても良いのじゃ」的な暗黙のルールってあるのだろうか。どうなんだろ。

もしかして、数値じゃなくて「random_seed」とか変数名でも書いとけばいいのかな?

一応数値版もあるらしい。 :

_メタ構文変数 - Wikipedia を眺めてたら、気になる記述が。

サンプルプログラムに使われる無意味な整数リテラルにも同様の概念が存在し、「23」や「42」などが用いられる。「42」は生命、宇宙、そして万物についての究極の疑問の答えに由来する。

メタ構文変数 - Wikipedia より


知らなかった…。一応数値にも、そういうのがあるのか…。

しかしソレ、短すぎてなんだか意味がありそうな気もしてくる値だけど…。いや、でも、1byte内で表せる数値じゃないと汎用性が、てなところもあるのだろうか。

そもそも知られてないらしい。 :

_若者のhoge離れ | スラド デベロッパー

書くほうも、読むほうも、双方が「これは意味が無い」と知ってないと意味が無いのだな…。ややこしい。

#3 [cg_tools] 宇宙船っぽい2Dドット絵を自動生成するツールを試用

横から見て宇宙船っぽく見えるドット絵っぽい画像を自動生成してくれるツールに遭遇したので試用してみたり。Windows上で動作するツールらしい。

_Pixel Ship Generator by thydungeonsean

「Download Now」のボタンをクリックすると、「無料で使えるけど寄付してくれればサポートもできるんだけどなあ」てな感じのダイアログが開く。一応、「No thunks 〜」のリンクをクリックすればダウンロードページに行けるようで。試用してみて「これは応援したい」と思ったら寄付(?)する感じでもいいのかな。

PixelShipGenerator v1.10.zip をDLして解凍。PixelShipGenフォルダ内の PixelShipGen.exe を実行すれば起動する。

起動画面はこんな感じ。

pixelshipgen_ss.png

左上の「GENERATE」をクリックすると、次々に新しい画像が生成される。「この形がカッコイイな」と思ったら、ソレをクリックすると選択できる。上のほうの「SAVE」をクリックすると、選択してるそれぞれにフロッピーディスクのアイコンがついて、exportsフォルダの中に ship30.png とか ship87.png というファイル名で画像が保存される。

化粧してみる。 :

このままではシンプル過ぎてアレだろうけど、ざっと見た感じでは四角と円のみで構成されてるように見えるので、それぞれを安易にグラデ塗りしてみるだけでも多少違った印象になりそうな。

ship223_edit_ss.gif

ship223_edit.png

ship498_edit_ss.gif

うむ。これはこれで。

逆に考えると…。どういうアルゴリズムで生成してるか分からんけど、四角と丸を単にグラデーションで塗り潰しながら出力するように機能を追加するだけでも雰囲気が変わりそうな。枠をつけたり等、お化粧のバリエーションは他にも色々あるけれど、ひとまずグラデだけでも効果が期待できる予感。

pattern_00.png

もっとも、レトロな2Dドット絵を目指してこういう仕様に留めている可能性も。それに、何かしらの下絵にする時は、このほうが都合がいい気もする。

他にも見かけたソレをメモ。 :

_GameDev Resource Pick: Random Sprite Generators - EandEGames 経由で、ドット絵っぽい何かを生成するソレを他にも見かけたのでメモ。

_Sprite Generator
_Sprite Generator 1.2 - procedurally generated pixel art
_procedural graphics | Ludum Dare

アニメーションまでしている…。どういうアルゴリズムになってるんだろう…。

#4 [ruby] 正規乱数とやらが作れそうかテスト

_乱数にコクを出すアレ が気になり始めたので Ruby + DXRuby を使ってテスト。

_「ちゃんとした正規乱数が欲しいなら Box-Muller を使うべき」 _「sin,cosとか平方根とか今時はたいして重くないし」 という主張も見かけたので、以下を参考にして Box-Muller もテスト。

_Box-Muller法による正規分布列生成 - Qiita

こんな感じのソースでどうだろう。合ってるのだろうか。

_boxmuller.rb
# Box-Muller法による正規乱数が作れそうかどうかをテスト

require 'dxruby'

# 乱数の結果を DXRuby の Image に描き込む
def get_image(w, h, data)
  # 最大カウント数を取得
  m = data.max

  # Imageに描き込み
  img = Image.new(w, h)
  w.times do |x|
    cnt = (data[x] * h.to_f / m).to_i
    y = h - 1
    cnt.times do
      img[x, y] = C_WHITE
      y -= 1
    end
  end
  return img
end

# Box-Muller法で乱数を取得
def get_box_mular(mu, sigma)
  x = rand
  y = rand
  r = Math.sqrt(-2.0 * Math.log(x)) * Math.cos(2.0 * Math::PI * y)
  return r * sigma + mu
end

w, h = 1280, 720
hh = h / 3
tc = 200000

# 一様分布になってるか確認
n = Array.new(w, 0)
tc.times do
  r = (rand * w).to_i
  n[r] += 1
end
nml_img = get_image(w, hh, n)

# Box-Muller法
n = Array.new(w, 0)
tc.times do
  rbm = get_box_mular(0.0, 1.0)
  r = (rbm * (w / 8) + (w / 2)).to_i
  n[r] += 1 if r >= 0 and r < w
end
bmr_img = get_image(w, hh, n)

# 乱数にコクを出すアレ
n = Array.new(w, 0)
tc.times do
  r = 0
  cnt = 4
  cnt.times { r += rand }
  r = ((r / cnt) * w).to_i
  n[r] += 1
end
kok_img = get_image(w, hh, n)

# 結果表示
Window.resize(w, h)
Window.loop do
  break if Input.keyPush?(K_ESCAPE)
  Window.draw(0, 0, nml_img)
  Window.draw(0, hh, bmr_img)
  Window.draw(0, hh + hh, kok_img)
end

結果はこんな感じに。

boxmuller_ss.png

上から、只の rand (= 一様分布)、Box-Muller法、乱数にコクを出すアレ。

ゲーム等で使う分には乱数にコクを出すソレでもそこそこ十分っぽい雰囲気。Box-Mullerはどこからどこまでの範囲を取るのかもちと分からんかったし…。

その後、 _「一様乱数の平均値を正規乱数として代用する」という話をゆるふわ統計的に検証する - k11i.biz で、 _Ziggurat法 なるものもあると知った。コレも試せば良かったかしらん。

#5 [anime] EVA旧劇場版「Air/まごころを、君に」を視聴

NHK-BSで放送されてたソレをようやく視聴。一応、昔見た記憶があるような。わざわざDVDを買った…ような気がする。もっともこの作品、色んな版があるそうで、自分が買ったのはどの版か分からんのだけど。

改めて見てみると…やっぱり凄いなと…。初見時も思ったけど、内容はともかく映像が凄いからもうこれで十分じゃないかなあ、という気分に。無理して新劇場版を作らんでも…これでええやん…。

それはともかく。NHKがモザイクをかける際の基準が分からん…。「え? そこでモザイク?」「そこよりもあっちのシーンのほうがヤバくない…?」と何度か困惑。せっかくここまで攻めたんだから「作品の時代性を尊重して」とかテキトーなテロップ出してそのまま流しちゃえば良かったのに。 *1
*1: 今となっては、こんなアレなソレに熱狂しちゃってた若者達が当時居たんだよ、てな状況自体が貴重な記録になっていくだろうと思うわけで、そのためには「うげえ。こんなもんを当時見てたのかよ。作ったヤツラも見てたヤツラも気持ち悪い」と今時の若い視聴者がゲンナリする映像をそのまま流したほうが、それこそ当時の「社会現象としてのEVA」を的確に伝えてくれるのでは、などと思ったり思わなかったり。とは言え今回「妙なところで保身に走るNHKという組織」「所詮はNHK」の記録になってたような気もしてきてこれはこれでアリかも、などと思えてきたりもして。いやまあ、「伝えることには妥協しないNHK」「さすがNHK」を目撃したかった気もしますが…。コレを放送しただけでも「一体どうしたNHK」「気はたしかなのかNHK」ってところもあるわけで…。

2017/04/07(金) [n年前の日記]

#1 [dxruby][ruby] ドット絵モドキを自動生成するソレを自分でも考えて試したり

ドット絵というか PixelArt というかソレっぽいものを自動生成するアレコレを眺めているうちに、もしかして乱数でテキトーにドットを打つだけでもそれっぽくなるんじゃないかと安易に思えてきたので一応試してみたり。Ruby + DXRuby で実験。

乱数でテキトーにドットを打つだけの版。 :

まずは本当に、乱数でテキトーにドットを打ってみた。

_tinypixelartgen_take1.rb
# ドット絵自動生成のテスト
#
# License : CC0 / Public Domain

class TinyPixelArtGen

  attr_accessor :pixels

  def initialize(w, h, mirror_x: true, mirror_y: false, seed: 0, color: 8)
    srand(seed)
    @color = color
    @w, @h = w, h
    @mirror_x, @mirror_y = mirror_x, mirror_y
    @canvas = Array.new(@h).map { Array.new(@w, 0) }
    @pixels = Array.new(@h).map { Array.new(@w).map { Array.new(4, 0) } }

    ww, hh = (@w / 2.0).round, (@h / 2.0).round
    wwf, hhf = @w % 2, @h % 2

    # generate dot pattern
    @h.times do |y|
      @w.times do |x|
        @canvas[y][x] = rand(@color)
      end
    end

    # generate palette
    @pal = Array.new(@color)
    @pal.size.times do |i|
      if i == 0
        r, g, b, a = 0, 0, 0, 0
      elsif i == 1
        r, g, b, a = 0, 0, 0, 255
      elsif i == @pal.size - 1
        r, g, b, a = 255, 255, 255, 255
      else
        r, g, b = rand(256), rand(256),  rand(256)
        a = 255
      end
      @pal[i] = [r, g, b, a]
    end

    # make pixel data
    @h.times do |y|
      @w.times do |x|
        sx, sy = x, y
        sx = (x >= ww and @mirror_x)? (ww - 1 - wwf - (x - ww)) : x
        sy = (y >= hh and @mirror_y)? (hh - 1 - hhf - (y - hh)) : y
        r, g, b, a = @pal[@canvas[sy][sx]]
        @pixels[y][x] = [r, g, b, a]
      end
    end
  end
end

if $0 == __FILE__
  # ----------------------------------------
  require 'dxruby'

  def conv_pixels_to_image(pixels)
    w, h = pixels[0].size, pixels.size
    img = Image.new(w, h, [0, 0, 0, 0])
    h.times do |y|
      w.times do |x|
        r, g, b, a = pixels[y][x]
        img[x, y] = [a, r, g, b]
      end
    end
    return img
  end

  def generate_images(n, seed)
    sz = 16
    imgs = []
    n.times do |i|
      pat = TinyPixelArtGen.new(
        sz, sz,
        mirror_x: (rand > 0.5)? true : false,
        mirror_y: (rand > 0.5)? true : false,
        seed: seed + i,
        color: 8
      )
      imgs.push(conv_pixels_to_image(pat.pixels))
    end
    return imgs
  end

  Window.resize(640, 480)
  Window.bgcolor = [32, 128, 196]
  Window.minFilter = TEXF_POINT
  Window.magFilter = TEXF_POINT
  cnt = 96
  seed = 0
  imgs = generate_images(cnt, seed)
  seed += cnt

  Window.loop do
    break if Input.keyPush?(K_ESCAPE)

    if Input.keyPush?(K_SPACE)
      imgs = generate_images(cnt, seed)
      seed += cnt
    end

    scale = 3
    border = 4
    x, y = 0, 0
    imgs.each do |img|
      Window.drawScale(x, y, img, scale, scale, 0, 0)
      x += img.width * scale + border
      if (x + img.width * scale) >= Window.width
        x = 0
        y += img.height * scale + border
      end
    end
  end
end

結果は以下。

take1_ss.png

うむ。完全に甘かった。全く話にならない。ダメだこりゃ。

輪郭を与えてみた。 :

sinカーブを使って、ざっくりとした形っぽいものを与えてみた。かつ、最後にエッジ(境界線)を追加してみた。

_tinypixelartgen_take2.rb
# ドット絵自動生成のテスト
# sinカーブで型を与えて生成、かつ、境界線を付加してみる
#
# License : CC0 / Public Domain

class TinyPixelArtGen

  attr_accessor :pixels

  def initialize(w, h, mirror_x: true, mirror_y: false, seed: 0, color: 8)
    srand(seed)
    @color = color
    @w, @h = w, h
    @mirror_x, @mirror_y = mirror_x, mirror_y
    @canvas = Array.new(@h).map { Array.new(@w, 0) }
    @pixels = Array.new(@h).map { Array.new(@w).map { Array.new(4, 0) } }

    ww, hh = (@w / 2.0).round, (@h / 2.0).round
    wwf, hhf = @w % 2, @h % 2

    # generate dot pattern
    ang = rand(360)
    ang_d = rand(270.0 / @h) + (45.0 / @h)
    (1..(@h-2)).each do |y|
      rad = (ang + (ang_d * y)) * Math::PI / 180.0
      rw = (((w * 4 / 10.0 - 1) * Math.sin(rad)).to_i + (w * 5 / 10.0)) * 2
      rx = (w - rw) / 2
      @w.times do |x|
        xx = rand(rw) + rx
        @canvas[y][xx] = rand(@color)
      end
    end

    # generate edge
    @h.times do |y|
      @w.times do |x|
        next if @canvas[y][x] <= 1
        @canvas[y - 1][x] = 1 if (y - 1 >= 0 and @canvas[y - 1][x] == 0)
        @canvas[y + 1][x] = 1 if (y + 1 < @h and @canvas[y + 1][x] == 0)
        @canvas[y][x - 1] = 1 if (x - 1 >= 0 and @canvas[y][x - 1] == 0)
        @canvas[y][x + 1] = 1 if (x + 1 < @w and @canvas[y][x + 1] == 0)
      end
    end

    # generate palette
    @pal = Array.new(@color)
    rr, gg, bb = rand(128), rand(128), rand(128)
    @pal.size.times do |i|
      if i == 0
        r, g, b, a = 0, 0, 0, 0
      elsif i == 1
        r, g, b, a = 0, 0, 0, 255
      elsif i == (@pal.size - 1)
        c = 255 - rand(48)
        r, g, b, a = c, c, c, 255
      else
        r = rr + rand(128)
        g = gg + rand(128)
        b = bb + rand(128)
        a = 255
      end
      @pal[i] = [r, g, b, a]
    end

    # make pixel data
    @h.times do |y|
      @w.times do |x|
        sx, sy = x, y
        sx = (x >= ww and @mirror_x)? (ww - 1 - wwf - (x - ww)) : x
        sy = (y >= hh and @mirror_y)? (hh - 1 - hhf - (y - hh)) : y
        r, g, b, a = @pal[@canvas[sy][sx]]
        @pixels[y][x] = [r, g, b, a]
      end
    end
  end
end

if $0 == __FILE__
  # ----------------------------------------
  require 'dxruby'

  def conv_pixels_to_image(pixels)
    w, h = pixels[0].size, pixels.size
    img = Image.new(w, h, [0, 0, 0, 0])
    h.times do |y|
      w.times do |x|
        r, g, b, a = pixels[y][x]
        img[x, y] = [a, r, g, b]
      end
    end
    return img
  end

  def generate_images(n, seed)
    sz = 16
    imgs = []
    n.times do |i|
      pat = TinyPixelArtGen.new(
        sz, sz,
        mirror_x: (rand > 0.5)? true : false,
        mirror_y: (rand > 0.5)? true : false,
        seed: seed + i,
        color: 8
      )
      imgs.push(conv_pixels_to_image(pat.pixels))
    end
    return imgs
  end

  Window.resize(640, 480)
  Window.bgcolor = [32, 128, 196]
  Window.minFilter = TEXF_POINT
  Window.magFilter = TEXF_POINT
  cnt = 96
  seed = 0
  imgs = generate_images(cnt, seed)
  seed += cnt

  Window.loop do
    break if Input.keyPush?(K_ESCAPE)

    if Input.keyPush?(K_SPACE)
      imgs = generate_images(cnt, seed)
      seed += cnt
    end

    scale = 3
    x, y = 0, 0
    imgs.each do |img|
      Window.drawScale(x, y, img, scale, scale, 0, 0)
      x += img.width * scale + 4
      if (x + img.width * scale) >= Window.width
        x = 0
        y += img.height * scale + 4
      end
    end
  end
end

結果は以下。

take2_ss.png

むっ。ちょっとはマシになってきた、そんな気がしないでもない。

が、これは 16x16ドットで生成して3倍に拡大表示してるからそれっぽく見えている、てなところもあって。例えば、32x32ドットで生成してみると…。

take2_ss2.png

「なんじゃこりゃああ!(ジーパン刑事風に)」度が増してしまってダメダメ状態に近づいていくなと。

小さく作って拡大するのはどうだろう。 :

世の中には、昔の荒いドット絵を如何にそれらしく綺麗に拡大するか、てなアルゴリズムがいくつか存在していて。

_Scale2x
_Scale2x Algorithm
_HiEnd3D - hq3x (Internet Archive)
_Pixel Scalers
_Pixel art scaling algorithms - Wikipedia

もしかすると、小さいサイズで自動生成しておいて、そこからこれらのアルゴリズムで拡大、みたいなことをすれば多少はそれらしくなる、かもしれないと夢想してみたりもして。となると、まずはこれらのアルゴリズムを試しに書いて実験してみて、かな…。

最初から意味がありそうな断片を配置するのはどうだろう。 :

例えば四角を、それもグラデ塗りした四角を、ランダムにテキトーに場所にいくつか配置していって、ソレを横方向 or 縦方向で反転描画するだけでもそれっぽくならないだろうか。そのためには、四角をグラデ塗りする処理を書いてみて…かな…。明度、いや、輝度がちゃんと変わっていかないと立体感のあるグラデにならないだろうから、色相、彩度、輝度からRGB値を求める処理(HSL → RGB変換)も書いてみないと…。

#2 [anime] 「AKIBA'S TRIP」アニメ版最終回を視聴

面白かった…。カットが変わるとパロディ、またカットが変わると別のパロディ、みたいな。よくまあここまでツッコめるもんだなと…。秋葉原を舞台にした作品らしいというか…。

2017/04/08() [n年前の日記]

#1 [dxruby][ruby] Scale2x や Scale3x をRubyで書いてみたり

Ruby + DXRuby を使って、ドット絵専用の整数倍拡大アルゴリズム、Scale2x や Scale3x の処理を書いて試してみたり。

以下のページに書かれてる内容をそのままコピペ。

_Scale2x Algorithm

画像端の部分を読み取る際に、元画像をはみ出しながら読み取ろうとしてしまうので、ちょっと一手間入れる必要が。本来、画像端とソレ以外でループを分けて速度面の最適化をすべきだろうけど、動くかどうかをひとまず試したいだけなので遅くなるのは分かってるけど安易な書き方を。

ソースはこんな感じに。

_scalenx.rb
# pixel scalking , Scale2x, Scale3x

module ScaleNx

  # pixel scaling, Scale2x
  # @param src [Array<Array>] pixel data array
  # @return [Array<Array>] 2x pixel data array
  def scale2x(src)
    width, height = src[0].size, src.size
    nwidth, nheight = width * 2, height * 2
    dst = Array.new(nheight).map { Array.new(nwidth).map { Array.new(4, 0) } }

    height.times do |y|
      width.times do |x|
        e = src[y][x]
        d = (x - 1 >= 0)? src[y][x - 1] : e
        f = (x + 1 < width)? src[y][x + 1] : e

        if y - 1 >= 0
          b = src[y - 1][x]
          a = (x - 1 >= 0)? src[y - 1][x - 1] : b
          c = (x + 1 < width)? src[y - 1][x + 1] : b
        else
          a = d
          b = e
          c = f
        end

        if y + 1 < height
          h = src[y + 1][x]
          g = (x - 1 >= 0)? src[y + 1][x - 1] : h
          i = (x + 1 < width)? src[y + 1][x + 1] : h
        else
          g = d
          h = e
          i = f
        end

        if b != h && d != f
          e0 = (d == b)? d : e
          e1 = (b == f)? f : e
          e2 = (d == h)? d : e
          e3 = (h == f)? f : e
        else
          e0 = e
          e1 = e
          e2 = e
          e3 = e
        end

        dx = x * 2
        dy = y * 2
        dst[dy][dx] = e0
        dst[dy][dx + 1] = e1
        dst[dy + 1][dx] = e2
        dst[dy + 1][dx + 1] = e3
      end
    end
    return dst
  end

  # pixel scaling, Scale3x
  # @param src [Array<Array>] pixel data array
  # @return [Array<Array>] 3x pixel data array
  def scale3x(src)
    width, height = src[0].size, src.size
    nwidth, nheight = width * 3, height * 3
    dst = Array.new(nheight).map { Array.new(nwidth).map { Array.new(4, 0) } }

    height.times do |y|
      width.times do |x|
        e = src[y][x]
        d = (x - 1 >= 0)? src[y][x - 1] : e
        f = (x + 1 < width)? src[y][x + 1] : e

        if y - 1 >= 0
          b = src[y - 1][x]
          a = (x - 1 >= 0)? src[y - 1][x - 1] : b
          c = (x + 1 < width)? src[y - 1][x + 1] : b
        else
          a = d
          b = e
          c = f
        end

        if y + 1 < height
          h = src[y + 1][x]
          g = (x - 1 >= 0)? src[y + 1][x - 1] : h
          i = (x + 1 < width)? src[y + 1][x + 1] : h
        else
          g = d
          h = e
          i = f
        end

        if b != h and d != f
          e0 = (d == b)? d : e
          e1 = ((d == b and e != c) or (b == f and e != a))? b : e
          e2 = (b == f)? f : e
          e3 = ((d == b and e != g) or (d == h and e != a))? d : e
          e4 = e
          e5 = ((b == f and e != i) or (h == f and e != c))? f : e
          e6 = (d == h)? d : e
          e7 = ((d == h and e != i) or (h == f and e != g))? h : e
          e8 = (h == f)? f : e
        else
          e0 = e
          e1 = e
          e2 = e
          e3 = e
          e4 = e
          e5 = e
          e6 = e
          e7 = e
          e8 = e
        end

        dx = x * 3
        dy = y * 3
        dst[dy][dx] = e0
        dst[dy][dx + 1] = e1
        dst[dy][dx + 2] = e2
        dst[dy + 1][dx] = e3
        dst[dy + 1][dx + 1] = e4
        dst[dy + 1][dx + 2] = e5
        dst[dy + 2][dx] = e6
        dst[dy + 2][dx + 1] = e7
        dst[dy + 2][dx + 2] = e8
      end
    end
    return dst
  end
end

if $0 == __FILE__
  # ----------------------------------------
  require 'dxruby'
  include ScaleNx

  # convert DXRuby Image to array
  def conv_image2array(img)
    w, h = img.width, img.height
    data = []
    h.times do |y|
      dt = []
      w.times do |x|
        a, r, g, b = img[x, y]
        dt.push([r, g, b, a])
      end
      data.push(dt)
    end
    return data
  end

  # convert array to DXRuby Image
  def conv_array2image(src)
    w, h = src[0].size, src.size
    img = Image.new(w, h, [0, 0, 0, 0])
    h.times do |y|
      src[y].each_with_index do |v, x|
        r, g, b, a = v
        img[x, y] = [a, r, g, b]
      end
    end
    return img
  end

  srcimg = Image.load("hq3x_original.png")
  src = conv_image2array(srcimg)

  dst = scale2x(src)
  dstimg2x = conv_array2image(dst)

  dst = scale3x(src)
  dstimg3x = conv_array2image(dst)

  Window.resize(540, 360)
  Window.minFilter = TEXF_POINT
  Window.magFilter = TEXF_POINT
  Window.scale = 2

  Window.loop do
    break if Input.keyPush?(K_ESCAPE)
    x, y = 0, 0
    Window.draw(0, 0, srcimg)
    x += srcimg.width + 8
    Window.draw(x, y, dstimg2x)
    y += dstimg2x.height + 8
    Window.draw(x, y, dstimg3x)
  end
end

テスト用画像は、 _Hq3x - Oss4art で紹介されてる画像を使わせてもらったり。消えるとアレなので転載させてください…。

_hq3x_original.png

ruby scalenx.rb で実行すると、こんな結果に。

scalenx_ss.png

一応実装できた、ような気がする。

コレ、ライセンスはどうなるのかな…。 _scale2x-4.0.tar.gz をDLして中身を見てみたら、COPYING に GPL と書いてあったので、コレも GPL になるのだろうか。たぶん。自分が書いて公開・配布するアレコレは、できれば CC0 / Public Domain にしたいけど、コレを使ってしまうとソレができないなと…。これもいわゆるGPL汚染、だろうか…。

書いた後で気が付いたけど、 _Scale2x のページで、「Scale2x の結果と EPX の結果は全く同じになるんやで」と説明が。EPX を書いてみるべきだったか…。8点ではなくて4点を見るだけで済むみたいだし…。

hqxも試してみたいけど。 :

hq2x、hq3x、hq4x も試してみたいけど、ソースをググってみたらなかなか複雑な感じで…。

一応、関連ページをメモ。

_Pixel art scaling algorithms - Wikipedia
_hqx - Wikipedia
_hq2x - HiEnd3D (Internet Archive)
_hq3x - HiEnd3D (Internet Archive)
_hq4x - HiEnd3D (Internet Archive)
_Usage - Arcnor/hqx-java Wiki
_Arcnor/hqx-java

ちなみに、hqx も GPL らしい。

処理をまとめたツールがあるらしい。 :

_2dimagefilter - Google Code Archive
_2dimagefilter by Hawkynt
_2dimagefilter - ImageResizer.wiki - Google Code Archive

GUI もしくは CUI で各アルゴリズムを試せる模様。ImageResizer-r133.exe をDLして実行してみたけど、Scale3x や hq3x が動くことを確認できた。

2017/04/09() [n年前の日記]

#1 [dxruby][ruby] ドット絵モドキを自動生成するRubyスクリプトを少し手直し

ドット絵モドキを自動生成するソレを Ruby + DXRuby で試しているところ。

_先日の実験 では、乱数でテキトーにドットを打っていくという安易なやり方でも、 てなことをすればちょっとそれっぽいのが生成されると分かったわけだけど。更に、ドット絵専用の拡大アルゴリズムである Scale2x、Scale3x を適用してみたらどうなるか実験してみたり。

結果はこんな感じに。前回は3倍表示してたけど、コレは原寸表示。(16x16 を Scale3x で3倍に拡大して生成。

tinypixelartgen_take3_ss.png

意外とイケそうな…でもないかな…どうなんだ…ちと判断が難しい微妙な結果に。

ちなみに、8倍に拡大するとこんな感じに。

tinypixelartgen_take3_ss2.png

これはこれで味わい深い…のかな…どうなんだ…。

ソースについて。 :

ソースは以下。tinypixelartgen_take3.rb と scalenx.rb の2つが必要。

_tinypixelartgen_take3.rb
_scalenx.rb

Ruby + DXRuby がインストールされてる環境で、ruby tinypixelartgen_take3.rb を実行すると生成される。スペースキーを押せば次々に生成し直し。

ライセンス面での問題。 :

惜しむらくは、Scale2x、Scale3x がGPLなので、Scale2x、Scale3x を利用してるこのドット絵自動生成スクリプトも GPL になるあたりが厳しいなと。いわゆるGPL汚染。

このスクリプトを何かに組み込んで利用したいと思っても、おそらくはそのアプリ全体までGPLで配布しないといけないのだろうと…。MIT License や CC0 / Public Domain に比べると、GPLは不便と言うか不自由だなと。

ん? 待てよ? 自分は _Scale2xのアルゴリズム解説ページ を参考にしてソースを書いたけど、 _Scale2x の exe のソース は一切見ないでソースを書いたわけで…。それでもこのスクリプトはGPLになるんだろうか?

いや、でも…。処理の肝心要の部分を解説ページ中から丸々コピペしてるのだから、コレはやっぱり GPL になるのかしらん。どうなんだろう。

まあ、ニアレストネイバー(Nearest neighbor)で拡大したほうがドットが強調されてドット絵らしさが増すのでは、という気もするので、いざとなれば Scale2x を使ってるあたりをゴッソリ削除してもいいんじゃないか、そうなれば自由に使えるやろ、と思ったりもしますが。

課題というか別の疑問。 :

「5x5ドットぐらいなら横方向3ドット分を作ってミラーするだけでもそれっぽくなるよ」てな方法があるのだけど。

_Space Invader Generator | The 8Bit Pimps Pixel Mine

たしか、GitHubのアイコンも同様の手法で生成されてたと思ったけど。

_Identicons!

それらの各1ドットに、事前に用意した8x8ドットのパターンをランダムに置いていったらどうなるかなと疑問が。例えば以下のようなパターンを使って…。

_mieki256's diary - DXRubyでキャラグラエディタを作ってみた

どうなるんでしょうね。

#2 [ruby] 配列の初期化って時間がかかるのだな

昨日書いた Scale2x の処理を行う Ruby スクリプトを、もうちょっと最適化できないものかと弄っていたのだけど。

ループ内をアレコレ弄って「あまり速くならねえな」とガッカリした直後、ふと、配列の初期化はしなくてもいいんじゃないかと気が付いて。配列の確保だけするようにしてみたら処理時間が二桁減って愕然。 *1

配列の初期化って結構処理時間を食うのだな…。いやまあ、メモリを確保して中身を書き換えていくわけだから当然だろうけど。にしても、まさか条件分岐が山ほど入ってるループ処理部分より配列の初期化で時間を食ってたとは…。

測ってみた。 :

こんな感じで。環境は Windows10 x64 + Ruby 2.2.6-p396 mingw32版。
# Array.new のベンチマーク

require 'benchmark'

w, h = 512, 256

Benchmark.bm(7) do |x|

  # 最初にやってたソレ
  x.report("[][][]") do
    a = Array.new(h).map { Array.new(w).map { Array.new(4, 0) } }
  end

  # 書き方を変えてみた
  x.report("[][][]B") do
    a = Array.new(h) { Array.new(w) { Array.new(4, 0) } }
  end

  # ループを馬鹿みたいに回してみた
  x.report("loop") do
    a = []
    h.times do
      b = []
      w.times do
        b << [0, 0, 0, 0]
      end
      a << b
    end
  end

  # 初期化を省いた
  x.report("[][]") do
    a = Array.new(h).map { Array.new(w) }
  end

  # 一次元配列にしてみた
  x.report("[]") do
    a = Array.new(w * h * 4, 0)
  end

end

結果。
              user     system      total        real
[][][]    0.047000   0.000000   0.047000 (  0.049730)
[][][]B   0.047000   0.000000   0.047000 (  0.041508)
loop      0.031000   0.000000   0.031000 (  0.032914)
[][]      0.000000   0.000000   0.000000 (  0.000188)
[]        0.000000   0.000000   0.000000 (  0.000549)

配列の中身をゼロクリアするとやっぱり遅くなるようで…。多次元配列じゃなく一次元配列にしてゼロクリアしたら速くならないか、と思ったけど甘かった。でもないか。いや、しかし、多次元配列の確保より、一次元配列でゼロクリアするほうが遅いんだよな…。

ちなみに、Ruby 2.2 ではなく Ruby 2.3 でも試してみたけど、さほど違いは無く。ていうかむしろ微妙に遅くなったり。まあ、誤差かもしれないけど。

*1: もっとも、渡された配列の中身をそのまま使う形になった ―― ポインタだけを書き換えてる形になったようなものだから、元の配列の内容を弄られるとおかしなことになるけれど、それは元々そうなってたから今更だし。

#3 [ruby][dxruby] RGBとHLS(HSL)を変換するRubyスクリプトを書いた

グラデ塗りを試す際に必要になりそうだったので、RGBと _HLS(HSL) を変換するRubyスクリプトを書いた。

HLSてのは…。、Hue(色相)、Lightness(輝度)、Saturation(彩度)で色を表す例のヤツ、という説明でいいのだろうか。お絵かきソフトでえてして用意されてたりするアレ。とはちょっと違うか。アレは _HSV か…。

とりあえず、変換処理のソースは以下。

_tinycolorsys.rb

_RGBとHSLの相互変換ツールと変換計算式 - PEKO STEP の解説を参考にして書きました。ありがたや。

使用例。

test_colorsys_ss.png

DXRuby を使って、マウスのx/y座標やカーソルキーの上下でHLSの値を変えて、背景色を変更してる。

require_relative 'tinycolorsys' を呼んで、include TinyColorSys しておくと、hls_to_rgb() と rgb_to_hls() が使えるようになる。

_test_colorsys.rb
# tinycolorsys.rb の動作テスト

require 'dxruby'
require_relative 'tinycolorsys'

include TinyColorSys

fnt = Font.new(28)
v2 = 0.5
set_hls = true

Window.loop do
  break if Input.keyPush?(K_ESCAPE)

  set_hls = !set_hls if Input.mousePush?(M_LBUTTON)

  v0 = Input.mousePosX.to_f / (Window.width - 1)
  v1 = Input.mousePosY.to_f / (Window.height - 1)
  v2 += (Input.keyDown?(K_UP))? (1.0 / 255.0) : 0
  v2 -= (Input.keyDown?(K_DOWN))? (1.0 / 255.0) : 0

  v0 = [[v0, 1.0].min, 0.0].max
  v1 = [[v1, 1.0].min, 0.0].max
  v2 = [[v2, 1.0].min, 0.0].max

  if set_hls
    h = v0
    l = v1
    s = v2
    r, g, b = hls_to_rgb(h, l, s)
    h = (h * 360).to_i
    l = (l * 100).to_i
    s = (s * 100).to_i
  else
    r = (v0 * 255).to_i
    g = (v1 * 255).to_i
    b = (v2 * 255).to_i
    h, l, s = rgb_to_hls(r, g, b)
    h = (360 * h).to_i
    l = (100 * l).to_i
    s = (100 * s).to_i
  end

  Window.bgcolor = [r, g, b]
  col = (l < 50)? C_WHITE : C_BLACK
  msg = (set_hls)? "use HLS" : "use RGB"
  Window.drawFont(4, 4, msg, fnt, :color => col)
  Window.drawFont(4, 40, "RGB=#{r}, #{g}, #{b}", fnt, :color => col)
  Window.drawFont(4, 80, "HLS=#{h}, #{l}, #{s}", fnt, :color => col)
  Window.drawFont(4, 120, "(mouse x/y , Up/Down key)", fnt, :color => col)
end

既にgemがあった。 :

上記のスクリプトを書いた後で、ふと気になってググってみたら、既にそういう gem があった…。自分で書く必要は無かった…。

_color | RubyGems.org | your community gem host
_halostatue/color: Color tools for Ruby.

gem install color でインストールできる。

以下は、前述のソレを color を使って書き直した例。
# gem color の動作テスト

require 'dxruby'
require 'color'

fnt = Font.new(28)
v2 = 0.5
set_hls = true

Window.loop do
  break if Input.keyPush?(K_ESCAPE)

  set_hls = !set_hls if Input.mousePush?(M_LBUTTON)

  v0 = Input.mousePosX.to_f / (Window.width - 1)
  v1 = Input.mousePosY.to_f / (Window.height - 1)
  v2 += (Input.keyDown?(K_UP))? (1.0 / 255.0) : 0
  v2 -= (Input.keyDown?(K_DOWN))? (1.0 / 255.0) : 0

  v0 = [[v0, 1.0].min, 0.0].max
  v1 = [[v1, 1.0].min, 0.0].max
  v2 = [[v2, 1.0].min, 0.0].max

  if set_hls
    h = v0 * 360.0
    l = v1 * 100.0
    s = v2 * 100.0
    rgb = Color::HSL.new(h, s, l).to_rgb
    r = rgb.red
    g = rgb.green
    b = rgb.blue
  else
    r = (v0 * 255).to_i
    g = (v1 * 255).to_i
    b = (v2 * 255).to_i
    hsl = Color::RGB.new(r, g, b).to_hsl
    h = hsl.hue
    l = hsl.luminosity
    s = hsl.saturation
  end
  r = r.to_i
  g = g.to_i
  b = b.to_i
  h = h.to_i
  l = l.to_i
  s = s.to_i

  Window.bgcolor = [r, g, b]
  col = (l < 50)? C_WHITE : C_BLACK
  msg = (set_hls)? "use HLS" : "use RGB"
  Window.drawFont(4, 4, msg, fnt, :color => col)
  Window.drawFont(4, 40, "RGB=#{r}, #{g}, #{b}", fnt, :color => col)
  Window.drawFont(4, 80, "HLS=#{h}, #{l}, #{s}", fnt, :color => col)
  Window.drawFont(4, 120, "(mouse x/y , Up/Down key)", fnt, :color => col)
end

  • Color::HSL.new(hue, saturation, luminosity) で渡す値は、hue が 0〜360、saturation と luminosity が 0〜100。
  • Color::RGB.new(red, green, blue) で渡す値は、0〜255。
  • HSL.to_rgb で RGB になるし、RGB.to_hsl で HSL になる。
  • RGB.red、RGB.green、RGB.blue で取り出した値は、0〜255。
  • HSL.hue、HSL.luminosity、HSL.saturation で取り出し値は、0〜360、0〜100、0〜100。
  • HSL も RGB も、0.0〜1.0を与えて生成するメソッドがあるらしい。hsl = Color:HSL.from_fraction(0.5, 1.0, 0.5) みたいな書き方をすればいいのかな? たぶん。ちと自信無し。

ホント、最初からコレを使えばよかった…。

#4 [gimp] G'MICプラグインがQtベースになるっぽい

G'MICプラグインという、500種類以上のフィルタが使える画像フィルタ集があるのですが。 *1 えてしてGIMPと組わせて使うことが多い、のかな。Kritaにも同梱されてたりしますが。

_G'MIC - GREYC's Magic for Image Computing: A Full-Featured Open-Source Framework for Image Processing
_「G'MIC (GREYC's Magic Image Converter)」

最新版に更新してみようと公式サイトを開いて 2.0.0pre版をDLしようとしたら、gimp_gtk版とgimp_qt版の2つがあることに気が付いて。

_G'MIC - Google+ を眺めると…。どうやら 2.0.0版からはQtベースになるっぽい雰囲気。Qt、なんだか流行ってるなあ…。人気あるなあ…。
*1: 今現在、手元の環境で確認してみたら、526種類のフィルタが有効になってた…。

2017/04/10(月) [n年前の日記]

#1 [gimp] G'MICプラグインでアレコレ試したり

_GIMP 2.8.20 Portable + _G'MIC 2.0.0pre Qt版 で、使えそうなフィルタがあるか調べ始めてしまったり。環境は Windows10x 64。

ちなみに、目的のフィルタ名が分かってれば、フィルター → G'MIC で開いたウインドウの上のほうに「Search」と表示されてる入力欄があるので、そこにフィルタ名の一部を打ち込めば即座に候補がリストアップされる。G'MICプラグインは何百種類もフィルタを持ってるので、さすがにこういう検索機能を使わないと…。ていうかGIMPの側にもこういう検索機能が欲しい…。

邪魔な物体を消してしまう Repair/Inpaint(multi-scale) :

写真画像内の、比較的巨大な物体もそれっぽく消してしまう、Inpaint(multi-scale)というフィルタがなんだかイイ感じだなと。以下の動画で紹介されてた。

_Introducing a new (fast) inpainting method in the G'MIC plug-in for GIMP - YouTube

使い方は、消したい物体を特定のマスク色(例えば赤(#ff0000)など)で塗り潰してから、フィルター → G'MIC → Repair → Inpaint(multi-scale)。

一度使用すると、GIMPのショートカットキー、Ctrl + F に、前回利用したフィルタが割り当てられるので…。鉛筆ツールでちょこっと赤く塗りつぶして Ctrl + F、また鉛筆ツールでちょこっと赤く塗りつぶして Ctrl + F、てな操作で、次々に邪魔な部分を消していくことができる。

線画に自動で色を塗ってくれる Black & white/Colorize lineart [auto-fill] :

白黒の線画に、自動でテキトーな色を仮で塗ってくれる、Colorize lineart [auto-fill] というフィルタがなんだか凄いなと。以下の動画で紹介されてる。ちなみに動画の時点では Auto-fill lineart という名称だった模様。

_New G'MIC filter 'Auto-fill lineart' to help colorizing comics - YouTube

使い方は、白黒の線画レイヤーを用意して、フィルター → G'MIC → Black & white → Colorize lineart [auto-fill]。実行すると、線画レイヤーと色塗りレイヤーが作られる。

色塗りレイヤーは二値化されているので、好きな色を選んでバケツツールでクリックして塗っていけば、あっという間に下塗り完成。

ちなみに、動画を見れば分かるけど、線画が途切れていてもそれなりにイイ感じに塗ってくれる。素晴らしい。

Colorizeフィルタには、他にも操作方法がちょっと違うフィルタが色々あるようで…。

例えば、Colorize lineart [smart coloring] を使えば、あらかじめ塗りたい色を一番上の透明レイヤーに描き込んでおくことで、自動で色塗りする際にそれらの色を使って塗ってくれる。(Input/Outputの Input layers を All にしておく必要有。)

_Overview of new G'MIC filter 'Colorize lineart [smart paint]' for colorizing comics - YouTube

以下はGIMPじゃなくて Krita の例だけど、Colorize [Interactive] を使えば、「このあたりはこの色で」的にマウスでポチポチとクリックしていくだけで塗り分けできたり。

_Krita 2.9 tutorial -part1/2- Gmic colorize[interactive] - YouTube

アンチジャギに使えそうな Repair/Smooth [antialias] :

GIMPのフィルタの中には、フィルター → 強調 → アンチエイリアス、てのがあるけれど。そのフィルタは調整パラメータが一切無いし、アルファチャンネルを持ってるレイヤーで使うとアルファチャンネルが消滅してしまうわけで。

対して、G'MIC の Smooth [antialias] は、効果の調整もできるし、アルファチャンネルもちゃんと残るあたりがイイ感じだなと。コレを二値化の線画にかけると多少はジャギを消せそうな予感。

_New filter Repair / Smooth [antialias] available in the G'MIC plug-in for +GI...

インタラクティブにマスクを作れる Colors/Color mask [interactive] :

Colors/Color mask [interactive] を使うと、画像の上をマウスでなぞるだけでリアルタイムにマスクの結果が表示される。

_New filter in G'MIC for interactive color masking - YouTube
_New filter in G'MIC for interactive color masking (with spatial masking) - YouTube

使い方は、フィルター → G'MIC → Colors → Color mask [interactive]。OKを押すとプレビューウインドウ?が開くので、この色をマスクしたいと思ったあたりを左ボタンドラッグ。右ボタンはマスク解除、だろうか。これでいいなと思ったら Enterキー、なのかな。たぶん。

もっとも、GIMP の場合、前景抽出選択、という選択機能もあったりするので…。

_【GIMP】パスより簡単!前景抽出選択で切り抜く方法 - YouTube

#2 [gimp] GIMPで宇宙空間を生成できるプラグインを試したり

Gimp Astronomy Plugins というプラグインを使うと、宇宙空間と言うか星空を簡単に生成してくれるらしいので、試しに導入してみたり。

_Gimp Plug-Ins - Programme - Georgs Homepage
_gimp-plugin-astronomy | GIMP Plugin Registry

昔は「星空作成プラグイン」として紹介されてたらしいけど…。

_星空作成プラグイン (Internet Arichive)

どうやら、 _GIMP2.6を使うよ! - Togetterまとめ によると、一時期はウイルス対策ソフトが反応したようで、おそらくそのせいで紹介されなくなったのかなと…。

一応今回、GIMP 2.8 にも対応したという gimp-plugin-astronomy-0.8.zip をDLして、 _オンラインで怪しいファイルのウィルスチェックをしてくれるWebサービスまとめ - ぼくんちのTV 別館 で紹介されてるウイルスチェックサイトに渡してみたけど、現行版は問題無いと判断された。

さておき。使い方は、何かしら新規画像を作成して、フィルター → 宇宙(もしくはAstronomy) → Artificial Stars。左下の「Smaple Distributions」で何かしらを選んで「Apply」ボタンを押すと、右側のパラメータにプリセット値が設定される。その状態で「Star Distribution」の下のボタンを押せば、星空が生成される。

こんな感じの画像が生成できるようで。

artificial_stars_test01.jpg

Artificial Galaxy や、フィルター → 照明と投影 → レンズフレア、超新星、等を、レイヤーのモードを「加算」にして追加してみたり。

artificial_stars_test02.jpg

結構イイ感じ。ボタンを数回クリックするだけで作れるのはありがたい。

#3 [gimp] GIMP上でScale2xアルゴリズムを使ってドット絵を拡大

GIMP上で、Scale2x/Eagle2x等を利用してドット絵を拡大できるプラグインがあるようで。Pythonで書かれてるっぽい。

_Pixelart Scaler | GIMP Plugin Registry
_Pixelart Scaler (ScaleNx and EagleNx) for GIMP | OpenGameArt.org

導入してみたところ、たしかに Scale2xっぽい感じで拡大された。フィルター → 強調 → Scale Pixelart... で利用できる。

ImageMagickもScale2xを持ってたらしい。 :

_Resizing or Scaling -- IM v6 Examples によると、ImageMagick も Scale2x が利用できるようで。-magnify オプションをつけると、Scale2xアルゴリズムを利用して2倍に拡大するらしい。たくさん指定すると、4倍、8倍、16倍、になっていくのかな? たぶん。

_Pixel Art Scaling Algorithm - ImageMagick によると、どうやら 2013年頃に実装されたっぽいなと。元々、-magnify オプションは、200%に拡大するオプションのエイリアス、みたいなものだったらしいけど、「ImageMagick にもドット絵専用拡大アルゴリズムを入れてみたらどうスか?」てな提案が出た際に、「どうせこのオプション、ろくに使ってないだろうからコイツに割り当てるぜ」という流れになったらしい。

となると、hq3x等も実装されてないかなーと期待してしまうのだけど。

_hq2x, hq3x, hq4x scaling algos into imagemagic? - ImageMagick

「hqx は GPLだからダメ。ImageMagickのポリシーに反する」「アルゴリズムが別途説明されててソース見ないで実装できるなら話は別だが hqx は GPL のソースしかねえじゃん。無理」てなやり取りが2009年頃にあったようで。

より広い自由を求めるプログラマー達にとっても「GPL汚染」という問題は存在するのだなと再認識。

にしても、Scale2x のアルゴリズム解説ページを参考にしながらも、しかしソースは見ないで実装して、ImageMagick に機能を取り込めたということは…。自分が先日書いたソレも GPL にしないで済むのかな。だったら助かるのだけど、実際はどうなんだろう。

2017/04/11(火) [n年前の日記]

#1 [dxruby][ruby] ランダムにグラデーション塗りの矩形を描いたらドット絵っぽくなるかどうか実験

ランダムにグラデーション塗りの矩形を描いていったらドット絵っぽくならないかなと安易に思いついてしまったので、Ruby + DXRuby + color で実験してみたり。環境は Windows10 x64。

アルゴリズムとしては、ランダムな位置、サイズ、色相、彩度、輝度で、横もしくは縦方向のグラデーション塗り矩形(四角)を何度か描いて、最後にx、もしくはyでミラーするだけ。

こんな感じの結果に。

fillpixelrect_ss.png

予想より、きらびやかな結果が出力されて少々喜んだものの、使えるかと言うと…またしてもビミョーな…。何かのパーツとして使えそうな画像が生成された感じもするけど、単体で使えるかと言うと…。うーん。ビミョー。

ソース。 :

ソースは以下。

_fillpixelrect.rb

実行には、color と dxruby が必要。Ruby がインストールされている Windows環境なら、以下でインストールできるかと。
gem install dxruby
gem install color

ruby fillpixelrect.rb で実行。スペースキーを押すと次々にドット絵モドキを作り直す。

課題というか案というか。 :

矩形を塗り潰す際に、矩形の輪郭を、黒、あるいは輝度を落とした色で描画してやれば、もうちょっとドット絵っぽくなる…かな…どうかな…。おそらくは輪郭の有無がドット絵らしさに関係してくるのではないかと推測してみたり。

矩形だけではなく、丸や三角をグラデーション塗りで描いてやれば、もうちょっと雰囲気が違ってくる…かな…どうかな…。今現在は矩形しか塗ってないので、ゴツゴツ感しか無いよなと。

そもそも、シルエットがどうも今一つだなと…。 _ランダムにドット単位で打っていくソレ も、事前にシルエットを決めてやるか否かで結果が随分違っていた感じがしたけど、このやり方もそういうところがあるのかもしれない。

#2 [anime] 「アリスと蔵六」1話を視聴

録画したままだった1話を視聴。漫画が原作のアニメらしい。

スタッフ構成を見てちょっとビックリ。業界内で高評価を受けながらも何故か圧倒的に参加作品が少なくて _仙人と称されてる らしい、あの _高山文彦 監督がシリーズ構成・脚本を担当、とな…。それだけでもどうやらこれはフツーの企画じゃなさそうだなと…。 *1

_桜美かつし 監督の、以前の作品で階段を使った仕掛け云々の話を知り。するともしかしてこの回の、神社の階段のシーンはそういう意味が提示されているのかなと勝手な想像を。少女より高い段に居る老人 → 妥協案を提示しながら少女と同じ段に降りてくる → 少女と目線が合う高さまで降りてきて色々言い聞かせる、てなあたりが、なんだかそれっぽい。と思ったものの、もしかすると原作にあったシーンをそのまま描いてる可能性もあるからちょっと分からないなと。

少女が老人の仕事を目にするシーンが、個人的には少々残念だった気もしたり。白黒の漫画と違ってアニメは色を制御できるのだから、直前まで無彩色でまとめておけば、少女が受けたソレを視聴者にも感じさせることができただろうに、と…。いやまあ、寒色系の暗い道から電気のついた部屋に入って、その際に壁が暖色系に見えることで、なんだか暖かさを感じる場所に少女が足を踏み入れたことが伝わるはず、てなことを考えてたのかもしれないけど。しかしそうなると次のカットとの色彩的なコントラストが弱まって少女の受けた印象がそのままでは強調されず、仕方なく画面を白く飛ばし気味に、かつ、漫画的記号を追加、という若干わざとらしい強調方法になったということかなと…。 *2 なので、見せ方の意図はなんとなく分かる気もするけど、これはちと惜しい、みたいな。ぼんやりした何かを伝えることを優先するのか、ハッとする何かを伝えることを優先するのか…。しかし今後のアレコレを考えると前者を優先するのも全然アリだろうか。ハッとしたからと言って、少女の今後に意味があるかどうかはビミョーだし。それに、寒々とした場所でああいうのを作ってるかと考えるとそれも非現実的でツッコミが入りそうでもあるし。

カーアクションのシーンで、車が止まってるはずなのに、車内から外の風景が流れて見えてるカットは気になった。スケジュール、厳しそうだな…。にしても、背景が止まってるほうが作業的には楽だろうに、むしろ動かしちゃって、しかし間違ってるというのも不思議な話。いやまあ、TVアニメの制作現場はかなりの部分を並列処理でこなしてるということだろうけど。昨今のCPUの仕組みを ―― 使うか使わないか分からないけど事前に分岐先まで並列で計算しておいてどちらかの結果を捨ててしまうソレを連想してしまったり。

何にせよ、今後の展開や描写が楽しみなアニメだなと。少女と老人という組み合わせだけで、なんかもう…。いや、考えてみたらソレってアルプスの少女ハイジなのか。平成版SFハイジ、かもしれず。
*1: いや、もしかすると別人かもしれんけど。自分は時々、いや、頻繁に、人様の名前を間違えるポンコツなので酷い勘違いをしてる可能性も…。
*2: BGMも使ってるけど、音楽は場の雰囲気を支配する力があるのでコレはむしろ活用しないと、てな個人的印象が。

2017/04/12(水) [n年前の日記]

#1 [ruby] RubyInstaller2 を使って Ruby 2.4 を Windows上でインストールしてみたり

現在、 _RubyInstaller for Windows でインストールできるのは、Ruby 2.3 まで、らしい。

_RubyInstaller2でWindows環境にRuby 2.4 + Rails 5.0.2をインストールする - Qiita
_Windows でも Ruby 2.4.0 が使えるんです。そう、RubyInstaller2 ならね。 - Qiita

Ruby 2.4 は RubyInstaller2 とやらを使うとインストールできるのだとか。試しにインストールしてみたり。環境は Windows10 x64。

MSYS2のインストール。 :

Rubyのライブラリをビルドする際、RubyInstaller なら DevKit とやらをインストールすれば済んだけど。RubyInstaller2 の場合は MSYS2 とやらをインストールしないといけないらしくて。なので、まずは MSYS2 をインストール。

ちなみに自分の環境は、環境変数 HOME をあらかじめ指定してある。環境変数 HOME が指定されてない場合、C:\Users\アカウント名\ あたりがホームフォルダとして使われる、という話をどこかで見かけた、とメモ。

_MSYS2 homepage から、msys2-i686-20161025.exe と msys2-x86_64-20161025.exe をDL。
  • msys2-i686-20161025.exe が 32bit版。
  • msys2-x86_64-20161025.exe が 64bit版。
今回は32bit版、msys2-i686-20161025.exe をインストールしてみた。

一応念のため書いとくけど、実行する前に .exe を右クリックして Windows Defender でスキャンして、問題無いと言ってきたら、右クリック → プロパティ → ブロックを解除。コレをしてから実行しないと、えてして「他のPCで作られたexeなんて今時怖くて実行できねえよ」とWindowsから怒られる。

デフォルトではインストール場所が C:\msys32 になってるけど、HDD容量が厳しいので今回は D:\msys32\ にインストールすることにした。が、コレは罠。デフォルトのままにしておけるなら、そのほうがいい…。

インストールが終わると、最後に「MSYS2 32bit を実行中」というボタンがあるのでクリック。ちなみに「実行中」は「実行する」の誤訳という説有り。

ターミナル(おそらくmintty)が起動。pacman -Syu と打ってパッケージを更新。最初の1回目は処理が完了すると無反応になる、というか「一旦exitしてくれよ」的な英文メッセージが表示されるので、ウインドウを閉じる。

スタートメニュー → MSYS2 32bit → MSYS2 MSYS を起動。再度、pacman -Syu と打つ。更にパッケージが更新される。処理が終わったら、Ctrl + D でターミナルを終了。

ちなみに、スタートメニュー内のショートカット先は以下のようになっていた。
  • MSYS2 MSYS : MSYS2インストールフォルダ\msys2_shell.cmd -msys
  • MSYS2 MinGW 32-bit : MSYS2インストールフォルダ\msys2_shell.cmd -mingw32

以下、参考ページ。

_2. MSYS2 - とりあえず雑記帳
_MSYS2でWindows上にコマンドライン環境を作る - Qiita

RubyInstaller2のインストール。 :

_Releases - oneclick/rubyinstaller2 から、rubyinstaller-2.4.0-8-x86.exe と rubyinstaller-2.4.0-8-x64.exe をDL。今回は32bit版の rubyinstaller-2.4.0-8-x86.exe をインストールした。

実行すると、RubyInstaller と同様に、ウイザード形式でインストールができる。

インストールが終わると、MSYS2のインストールだか設定画面だかが出てくるのだけど…。ここで、MSYS2のインストールフォルダをデフォルトから変えていたことが裏目に出た。

「MSYS2はインストール済みだから3番だな」と選んだところ、MSYS2がインストールされていないものと判断され、MSYS2のインストーラをDLしたあげく実行までしてきた。どうやらMSYS2のインストール場所を決め打ちで処理してるらしい。ひどいや。

仕方ないのでインストール済みの MSYS2 の場所から、C:\msys32 にシンボリックリンクを貼った。その状態で3番を選んだら先に進んでくれた。

が、しかし、やたらめったらパッケージがインストールされていく。しかも、Python2 や Perl までインストールしてる…。嘘だろ…マジかよ…。処理終了後、MSYS2 (mintty) を起動して、perl -v や python2 --version と打ったら、やっぱりインストールされてた。

つまり、「Windows上で Ruby 2.4 を使うためにはPerlやPythonも必要になりますぜ!」という状況らしい。だったら最初からPython使ったほうがいいのでは…? Rubyって一体何のために存在しているの? Rubyって何なの? *1 *2

それはともかく、何かビルドが必要になるライブラリをインストールしてみる。とりあえず nokogiri をインストール。 _oneclick/rubyinstaller2 の README.md を参考に、以下を入力。
ridk exec pacman -S mingw-w64-i686-libxslt
gem install nokogiri --platform ruby -- --use-system-libraries
自分は32bit版をインストールしたから、x86_64 を i686 に置き換えて実行。

ビルドされたっぽい。irb を実行して、require 'nokogiri' とやってみたら true と表示された。たしかに、RubyInstaller と同じ感覚で使えそうな雰囲気。

ちなみに、MSYS2 のフォルダ容量は 1.74GB になっていた。RubyInstaller 時代の DevKit2 は 370MB程度で済んでたのに…。なかなか厳しい。

Ruby 2.4 は 2.3 より5倍のHDD容量を消費するのだから…。2.3 → 2.4 で、例えば処理速度が5倍ぐらい速くなったりしないと割に合いませんなあ。なんちゃって。

余談。 :

自分の環境では、シンボリックリンクについては以下のユーティリティをインストールして作成してる…と思ったけど、違ったかな…。たしかそのはず…。

_Link Shell Extension - k本的に無料ソフト・フリーソフト

*1: もちろん、Ruby本体だけを使うなら、ビルドに使うMSYS2までインストールせずに済むわけだけど、それではライブラリがビルドできない(時がある)ので、道具としてRubyを使うのは厳しい状態になるわけで。おそらく、ライブラリのビルド時に、Perl や Python2 が使われているのかな…? でも、ソレ、Ruby で処理できないの…? 何のために Ruby をインストールしてあるの…?
*2: もっとも、各ライブラリが mingw版のバイナリも含んだ状態で配布されていれば、各ユーザがビルド用にMSYS2をインストールする必要は無くなりそう…ではあるのだけど、今まで mingw版の Ruby2.4 がそもそも無かった・導入が難しかったわけで…。今後このあたり、改善されていくのかなあ…?

#2 [tv][zatta] テロップが個人的に好き

思考メモ。

たまたまTVをつけたらNHKの朝の連続テレビ小説が映っていて。登場人物の前に「社会科の○○先生」「体育の○○先生」等のテロップが表示されて、なんだか感心してしまったり。と言うのも…。

娯楽映像作品においては、登場人物は知っているけど視聴者が知らない情報ってのがフツーにゴロゴロしていて。登場人物達の会話についてリアルっぽさを追求していくと、視聴者が知らない情報が何の説明もなくポンポン出てきて視聴者が置いてけぼりになる。ので、えてして脚本上で説明台詞と揶揄されるソレを盛り込んだりするわけだけど。その説明台詞のせいで、「ナウシカは独り言が多いなあ」とか「富野アニメはどうして突然フルネームで呼び始めるんだろう」とかそういうツッコミが…。 *1

かといって、「そんなツッコミするんやったら説明台詞を極力省いてやる! どうだ!? これで満足か!?」などとやってしまうと、近年の宮崎アニメのように観客置いてけぼり感が増してしまって…。アレ、ブランドになってるし大作家様だから許されてるけど、もし、PIXARのラセター監督がボスだったら、「コレ、お客さんに伝わるかな…? どう思う?」と笑顔でやんわりと諭されつつリテイク続出だよなあ…。

その点、テロップがポンと出てくるソレは、登場人物がリアルっぽい会話をしながらも、しかし視聴者の知らない情報について同時に補完することができる。ので、個人的には結構好きだったり。

ていうかテロップを出そうと判断して実行してる時点で、登場人物にとって自然な会話と、視聴者にとって分かりやすいやり取りは別なのだとちゃんと知った上で作ってることが伝わってくるし、映像作品制作における問題点を認識しつつも何かしらの手管を駆使してソレをクリアしていこうとする前向きさも感じて、スタッフの姿勢そのものに好感が持てるというか。この人達、まだ投げ出してないな、みたいな。

まあ、「テロップ出すなんて安易過ぎる」と馬鹿にしたり無意味に思い込んだりしてるクリエイター様の姿もチラホラ見かけるけど、そういう方々は違った方法で解決すればいいだけの話で。映像作品って総合芸術だから解決策は一つじゃないし。各人好きなように工夫していただければ…。お客さんが話の流れを理解できて、かつ、作品に乗ってくれるなら、なんでもOK。どれも正解。だよなと。

連続テレビ小説ならではの問題がありそう。 :

テロップを出したことを褒めるようなソレを書いた直後にアレだけど。連続テレビ小説でテロップを出すのはどうなんだろう、とも思えてきたり。

アレって、朝の忙しい時間にTVをつけて音声だけを聞きながら出勤の準備その他をしてる視聴者がほとんど、てな話を昔どこかで聞いた記憶があるような…。それが本当なら、映像を見なくても話の流れが分かる作りが望ましい、てなことになるよなと…。テロップは、画面から目を逸らしていたら情報を見逃すので、そういう意味ではちょっと良くないのかもしれない。

これがもし映画であれば、観客は映画館でスクリーンに釘付けになってるはずだから、画面で情報を伝えても全く問題はない。けれど、TVで流すコンテンツはそこらへん違ってきそうだなと。たしか平成ライダーシリーズの偉い人も、そのあたりについて言及してた記憶も…。視聴者が画面をちゃんと見ていることを現代では期待できない、とかなんとか…。

でもまあ、連続テレビ小説も、出勤する必要のないお年寄り相手に見せるつもりで作るのであれば、テロップ使っても全然問題無いのかも。視聴者側の状況が昔とは随分違っている可能性だってあるよなと。

それに、音声だけ聞いていても分かるけど画面を見たらなんだかスカスカな作品と、画面を見たらちゃんと情報が詰まってる作品では、最終的には後者のほうが評価されそうな気もするし…。ていうか、「画面から目が離せなくなる、そんなドラマを作ってやるぜ…!」ぐらいの意気込みで作ってほしい、ような気もしてきたり。

そもそもラジオドラマではダメなんだろうか。 :

連続テレビ小説に関して、もし本当に音声だけ聞いてる視聴者が多数であれば、それはもうラジオドラマにしたほうがいいのでは…。場合によってはそういう企画もアリ、なんだろうか…。NHKはラジオもやってるし…。

平成ウルトラマンシリーズや、平成ライダーシリーズのラジオドラマとかどうだろう。って何で話がそっちの方向に。いや、さっき、平成ライダー云々とか書いちゃったから…思考がそっちに…。

ガンダムのラジオドラマもいいな。富野脚本のラジオドラマとか…想像すると違う方向でハラハラドキドキ。サウンドオンリーだからこのくらい大丈夫だろう的に歯止めが効かなくなってエンジン全開な富野脚本が炸裂。銀魂よりも各方面に謝罪して回らなきゃいけない展開に。作品の存在自体が放送事故と後世で語られるほどの伝説のラジオドラマに。みたいな。などと妄想しながらググってみたら、ガイア・ギアはラジオドラマ版があったようで。でも、富野脚本ではないのですな。

などと書きつつも、自分、ラジオって全く聞かないのでアレなんだけど。ラジオ番組になってしまうと扱われ方がまた違ってくる印象も。

*1: いや、富野アニメのフルネーム呼びは、説明台詞とはちょっと種類が違う気も…。

2017/04/13(木) [n年前の日記]

#1 [pc] HDD内を整理中

Windows10機のHDD内に、WindowsXP時代のファイルがごっそり残してあることに気がついて。昔、XPからWindows7に移行した際に、設定ファイル等を探し出して反映させるのに都合が良かったからバックアップも兼ねて丸々コピーしていたけど…。さすがに今から設定ファイル等を発掘することはほとんどないだろうと外付けHDDに移動作業を。

#2 [anime] 「正解するカド」1話を視聴

これはまた…なんだか凄いアニメが始まった…。ていうか、よくこんな企画が通ったなあ、とすら。

メインキャラクター達はおそらく3DCGで描いてるのだろうけど、このくらいの見た目にできるならもう全然OKだなと感心を。手描きのモブキャラと一緒に立っていてもほとんど違和感が無いように見えた。素晴らしい。

モブキャラのほとんどが手描きだったけど、数カットしか出てこないキャラに関してはモデリングにかかる時間を考えると手描きでやってもらったほうが早い、ということなのだろうなと。となると、今後の課題はモデリング作業にかかる期間短縮、ということになるのだろうか…。そこがクリアできたら全体的に3DCGでもイケる可能性が出てくる、みたいな。PIXARあたりはパラメータを変えるだけでモブキャラをどんどん生成できるプログラムを作って対処してるらしいけど。 *1 将来的には日本もそういう技術が必要になってきそう。

_村田和也総監督 は、 _翠星のガルガンティア で異文化交流を面白く描いていたので、こういう設定の作品にはピッタリかも、などと勝手な想像を。

関連情報をググってたらスタッフインタビュー記事に遭遇。

_謎に満ちたアニメCGプロジェクト『正解するカド』(総監督:村田和也)に迫る - mystery 01:3Dフラクタル - CGWORLD.jp

Unityも活用してるという話が出ていてなんだかビックリ。そんなところでも使われているのか…。
*1: 「Mr.インクレディブル」のメイキング映像で、モブキャラジェネレータが紹介されてた記憶が。

#3 [tv] サイエンスZERO 「ゲームの最新技術」回を視聴

録画してたソレを視聴。ゲームをテーマにした回は、サイエンスZEROでは初、らしい。

_サイエンスZERO「ゲームの最新技術 異世界が現実に!?」 - NHK

基本的にはVR技術について紹介する内容だったけど、微弱な電流を与えて筋肉を操作したり三半規管を誤認識させたりするあたりがなんだか凄いなと。ていうか、怖いなと。

何が怖いって…。コンピュータが電流を流すソレは、現実世界では味わうはずがない刺激や、もしかすると人間の肉体というハードウェアが想定してなかった動きまで作れてしまうわけで、そのあたり大丈夫かねえ、みたいな。

例えばだけど、高橋名人の一秒間16連射をVRで再現できるコンテンツがあったとして、その電気刺激を与えられてる最中にプログラムがバグって無限ループに入って1時間も16連射させられたら、これは右腕がボロボロにならないか、みたいな。いやまあ、「なあに。そのくらいならかえって筋肉がつく」とか言われそうだけど。

あるいは、三半規管に秒間30〜60回等の高速な電気刺激を与え続けたらどうなるんだろう、とか…。ヘッドフォンでテクノを聞きまくるだけでも耳が悪くなったり、ポケモンアニメを見てるだけで子供達がバタバタと倒れていったりするぐらいに人間の体は脆弱なのに、三半規管に直接電気的刺激を与えるとか…使い方によっては三半規管が馬鹿になったりしないか、等々…。

まあ、SAOの _茅場晶彦 みたいなことができそうで、それはそれで 面白そう なんだか恐ろしいなと。

もっとも、電流を流して治療云々とか、電気を流してダイエット云々 *1 は、今現在も行われてることだから、安全対策をしっかりやれば…。と思いつつも、故障やバグはどこでもありえることだし。安いからと中国製のVRデバイス使ったら重大事故が、とか…。

まあ、見ていて色々と妄想してしまう回でした。そういった技術が早く普及しないかなあ。
*1: 通販番組でよく流れてるスレンダートーンとか…。

2017/04/14(金) [n年前の日記]

#1 [pc] HDD内をまだ整理中

ネットからDLしたアプリのセットアップファイル関係を外付けHDDに移動していたけど、古いバージョンをどの程度削除するかで悩んでしまって結構時間がかかってしまって。最新版で不具合があった際は古いバージョンに戻す、てな状況を考えてわざわざ残していたりするのだけど…。

本来であれば配布元で旧バージョンもDL可能にしておくのが望ましいのだろうなあ。しかし、最新版のほうが不具合が少ない、という状況こそが望ましいと考えて旧バージョンをDL不可にしてしまう配布元がほとんどで。となると、各ユーザが各版を一々手元で残していくしかなく。まあ、自分が作ったアレコレも配布の仕方がグチャグチャなのでその手の言及ができる資格は無いのだけど。

2017/04/15() [n年前の日記]

#1 [ruby] RGBAで並んでる配列をARGBに並び替えたい

先日書いたスクリプトでは、Ruby の配列から DXRuby の Image にする際に Image#[x, y] = [a, r, g, b] を使って1ドットずつチマチマと描き込んで変換していたのだけど。ふと、 _Image.create_from_array を使えばよかったんじゃないかと思い出したわけで。コレを使えば配列を渡して Image を作成できる。使わない手は無いよなと。

しかし、元画像データに相当する配列は、1ドット分が RGBA で並んでいて。対して、Image.create_from_array に渡す配列は ARGB の並びじゃないといけない。つまり、配列の並びを変更しないといけないわけだけど、どういう書き方をしたら多少でも処理が速くなるのかなと。

ということで。

問題:

Ruby を使って、
[y][x][r, g, b, a] の多次元配列を、
[a, r, g, b, a, r, g, b ...] の一次元配列に変換せよ。

ただし個数が多いので、処理時間はできるだけ短いほうが望ましい。

てなわけで、いくつか思いついた書き方を並べて、ベンチマークを取ってみたり。

_array_bench2.rb
# [y][x][r, g, b, a] の配列からDXRubyのImageに変換する処理をいくつか試す

require 'dxruby'
require 'benchmark'

# 元画像データの生成
def make_src_array(w, h)
  src = Array.new(h) { Array.new(w) }
  h.times do |y|
    w.times do |x|
      r = 255 * x / (w - 1)
      g = 255 * y / (h - 1)
      b = 255 * (w - 1 - x) / (w - 1)
      a = 255
      src[y][x] = [r, g, b, a]
    end
  end
  return src
end

# Imageに1ドットずつRGBを書き込んでいくやり方
def convert_to_image(ary)
  w, h = ary[0].size, ary.size
  img = Image.new(w, h, [0, 0, 0, 0])
  h.times do |y|
    w.times do |x|
      r, g, b, a = ary[y][x]
      img[x, y] = a, r, g, b
    end
  end
  return img
end

# Imageに1ドットずつRGBを書き込んでいくやり方その2
def convert_to_image2(ary)
  w, h = ary[0].size, ary.size
  img = Image.new(w, h, [0, 0, 0, 0])
  ary.each_with_index do |line, y|
    line.each_with_index do |rgba, x|
      # img[x, y] = rgba[3], rgba[0], rgba[1], rgba[2]
      r, g, b, a = rgba
      img[x, y] = a, r, g, b
    end
  end
  return img
end

# 配列を平坦化してRGBAをARGBに並び替え、Image.createFromArrayを使う
def convert_to_image3(ary)
  w, h = ary[0].size, ary.size
  d = ary.flatten
  i = 0
  while i < d.size
    # d[i], d[i+1], d[i+2], d[i+3] = d[i+3], d[i], d[i+1], d[i+2]
    # d[i, 4] = d.values_at(i + 3, i, i + 1, i + 2)
    d[i, 4] = d[i+3], d[i], d[i+1], d[i+2]
    i += 4
  end
  return Image.createFromArray(w, h, d)
end

# ループを回してARGB並びの配列を作成後、Image.createFromArrayを使う
def convert_to_image4(ary)
  w, h = ary[0].size, ary.size
  d = []
  ary.each do |line|
    line.each do |r, g, b, a|
      # d.push(a) ; d.push(r) ; d.push(g) ; d.push(b)
      d << a << r << g << b
    end
  end
  return Image.createFromArray(w, h, d)
end

# ----------------------------------------

src = make_src_array(1600, 140)  # 元画像データ作成
imgs = []

# 配列をDXRubyのImageに変換
Benchmark.bm(12) do |x|
  x.report("Image[]  ") { imgs.push(convert_to_image(src)) }
  x.report("Image[]2 ") { imgs.push(convert_to_image2(src)) }
  x.report("flatten  ") { imgs.push(convert_to_image3(src)) }
  x.report("loop     ") { imgs.push(convert_to_image4(src)) }
end

Window.resize(800, 600)
Window.loop do
  break if Input.keyPush?(K_ESCAPE)

  x, y = 0, 0
  imgs.each do |img|
    Window.draw(x, y, img)
    y += img.height + 4
  end
end

結果はこんな感じに。Ruby 2.2.6 p396 を使用。
> ruby array_bench2.rb

                   user     system      total        real
Image[]        0.125000   0.000000   0.125000 (  0.120086)
Image[]2       0.125000   0.000000   0.125000 (  0.127031)
flatten        0.156000   0.000000   0.156000 (  0.160704)
loop           0.047000   0.000000   0.047000 (  0.044219)

どうやら、素直にループを回して、新しい並びの配列を新規作成して渡すやり方が比較的マシっぽい。

ただ、自分はRubyに関して初心者なので、Rubyの達人ならもっと速い書き方を知ってそう。

2017/04/16() [n年前の日記]

#1 [ruby][dxruby] Ruby+cairo(rcairo)を勉強し直し

矩形グラデ塗り+境界線描画を自分で書くのが面倒になってきたので、既存の描画ライブラリを使いたいなと。そこで、cairo(rcairo)はどうだろうと。

_Rubyist Magazine - cairo: 2 次元画像描画ライブラリ

一応、以前に少し触った記憶もあるのだけど。

_mieki256's diary - cairoの出力結果をファイルを作らずにDXRubyに渡せた
_mieki256's diary - DXRuby と cairo と rsvg2 で svgを描画して表示してみたり
_mieki256's diary - DXRubyとcairoでswfのシェイプを描画してみる実験

使い方をすっかり忘れてるので再度勉強し直し。

環境は Windows10 x64 + Ruby 2.2.6 p396。

rcairoのインストール。 :

Ruby 2.x を使っているなら、おそらく以下でインストールできる、ような気がする。
gem install cairo --platform=x86-mingw32

もしかすると以下も必要になるのだろうか。そのへんちょっとよく分からず。
gem install glib2
gem install gdk_pixbuf2
gem install rsvg2

使い方の基本。 :

require 'cairo'
surface = Cairo::ImageSurface.new(Cairo::FORMAT_ARGB32, 640, 480)  # フォーマット, 幅, 高さ
context = Cairo::Context.new(surface)
context.set_source_rgba(1.0, 0.0, 0.0, 1.0)   # R,G,B,A。0.0から1.0で指定
context.rectangle(0, 0, 16, 8)                # x, y, 幅, 高さ
context.fill                                  # 塗り潰し
サーフェイスを作って、コンテキストを作って、そのコンテキストを利用してアレコレ描画、という流れになるらしい。

DXRubyのImageに変換して結果を表示。 :

cairo の描画結果を DXRuby の Image に変換して、その場で表示して結果を確認したい。

cairo には png出力する機能があるし、DXRuby には png を読み込む機能があるので…。pngファイルを一旦HDDに出力して、ソレを読み込み直せばなんとかなる。けど。一々画像ファイルをHDDに書き出してまた読み込むのもなんだかアレで。

StringIOを使うと、文字列に、ファイルと似たインターフェイスを持たせることができるらしい。つまり、メモリ上にファイル(の中身)が存在してるかのような状態にできるので、その仕組みを利用する。

_cairo_helloworld_dxruby.rb
# cairo(rcairo)の描画テスト

require 'cairo'
require 'stringio'
require 'dxruby'

w, h = 640, 480

# サーフェイス作成
surface = Cairo::ImageSurface.new(Cairo::FORMAT_ARGB32, w, h)

# コンテキスト作成。コレを使って描画していく
context = Cairo::Context.new(surface)

# 背景相当を描画。サーフェイス全体を透明色で塗り潰し
context.set_source_rgba(0, 0, 0, 0)  # r, g, b, a
context.rectangle(0, 0, w, h)        # 矩形を指定
context.fill                         # 塗り潰し

# 赤丸を描画
context.set_source_rgb(1, 0, 0)  # r, g, b
radius = h / 3
context.arc(w / 2, h / 2, radius, 0, 2 * Math::PI)  # 円を指定
context.fill

# StringIOを使ってpng出力
# StringIOを使うと、文字列にファイルと同様のインターフェイスを持たせられる
# つまり、メモリ上にファイルを作成しているのと似た状態になる
temp = StringIO.new("", 'w+')
surface.write_to_png(temp)
temp.rewind                    # アクセス位置を最初に戻す

# DXRubyのImageとして読み込む
img = Image.loadFromFileInMemory(temp.read)
temp.close

# DXRubyで表示
Window.bgcolor = [64, 64, 64]
Window.loop do
  break if Input.keyPush?(K_ESCAPE)
  Window.draw(0, 0, img)
end

cairo_helloworld_dxruby_ss.png

表示できた。

もちろん、一旦pngに変換して出力 → pngを読み込み、てな回りくどいことをしてるので、60FPSでリアルタイムに描画内容を変更して表示、みたいなことは速度的にできないだろうけど。一応、cairo で描画して DXRuby で表示するだけならできなくもない、ということで。

Ruby/Tkで使って結果を表示。 :

Ruby/Tk を使って cairo の描画結果を表示確認したい。

いやまあ、Ruby/Tk自体がキャンバスにアレコレ描画する機能を持ってるらしいので、Ruby/Tk を使うならわざわざ cairo で描かなくてもいいんじゃないのって気もしてくるけど、せっかくだから…。

これも、StringIO を使ってpng出力後、TkPhotoImage にデータを渡すやり方で表示できなくもない模様。ただし、Tk で png を読み込むためには、Tk拡張(この場合 tkimg?) が必要になるので注意。

_cairo_helloworld_tk.rb
# cairo(rcairo) + Ruby/Tk の描画テスト

require 'cairo'
require 'tk'
require "tkextlib/tkimg/png"
require 'stringio'

w, h = 640, 480

# ----------------------------------------
# cairoによる描画

# サーフェイス作成
surface = Cairo::ImageSurface.new(Cairo::FORMAT_ARGB32, w, h)

# コンテキスト作成。コレを使って描画していく
context = Cairo::Context.new(surface)

# 背景相当を描画。サーフェイス全体を塗り潰し
context.set_source_rgba(0, 0, 0, 0)  # r, g, b, a
context.rectangle(0, 0, w, h)        # 矩形を指定
context.fill                         # 塗り潰し

# 赤丸を描画
context.set_source_rgb(1, 0, 0)  # r, g, b
radius = h / 3
context.arc(w / 2, h / 2, radius, 0, 2 * Math::PI)  # 円を指定
context.fill

# ----------------------------------------
# Tk関係の設定

# Tkでキャンバス作成
canvas = TkCanvas.new(:width => w, :height => h).pack

# フルカラー画像を扱えるクラスを生成
img = TkPhotoImage.new(width: w, height: h)

# cairoの描画結果をStringIOを使ってpng出力後、
# TkPhotoImage の data に渡す
StringIO.open { |io|
  surface.write_to_png(io)
  img.data = Tk.BinaryString(io.string)
}

x, y = w / 2, h / 2  # 画像の中心が描画位置の基準になるらしい
TkcImage.new(canvas, x, y, :image => img)

# Tkのウインドウを表示
Tk.mainloop

cairo_helloworld_tk_ss.png

一応表示できた。DXRubyのソレと比べると妙に時間がかかるけど…。

ちなみに、RMagick (Ruby から ImageMagick を扱えるライブラリ) を使って変換する事例も見かけたのだけど、Windows上では RMagick をインストールするのが大変、というか時期によってはハマるので、RMagick の利用は極力避けたいところ。

_RMagick を Windows にインストールする方法 - 君の瞳はまるでルビー - Ruby 関連まとめサイト

もっとも、Windows上では、Ruby/Tk の Tk拡張インストール作業自体が結構面倒臭いわけで…。「毒を食らわば皿まで」的に RMagick を使ってしまうのもアリだろうか…。

_Ruby/TkでTk拡張を使っているRubyスクリプトをexe化 - ござるのブログ
_mieki256's diary - Ruby/Tkで拡張Tkを使えるようにするために少し試したり

そもそも Ruby で GUI は鬼門…。Ruby本体のバージョンが上がるたびに放棄されていったGUIライブラリが死屍累々。なので、Ruby/Tk を使うという選択自体がどうなんだろうと思ったり思わなかったり。

ちなみに Python の場合は、tkinter + pycairo + PIL(Pillow) を使って、PILの ImageTk で cairo のサーフェイスを Tk で扱える状態に変換できるらしい。

_python - Cairo with tkinter? - Stack Overflow

データを取り出したい。 :

cairo の描画結果を、DXRuby なり Ruby/Tk なりで一応表示できたけど。一旦 png にしてまた読んで、てのがアレだなと…。どうせ内部ではビットマップデータを持っているに違いないのだから、ソレを取り出して変換して、とかやれたらいいよなと。

cairo のサーフェイスからデータを取り出すのは、ImageSurface#data でできるらしい。
irb(main):001:0> require 'cairo'
=> true
irb(main):002:0> surface = Cairo::ImageSurface.new(8, 8)
=> #<Cairo::ImageSurface:0x34d88a8>
irb(main):003:0> context = Cairo::Context.new(surface)
=> #<Cairo::Context:0x331fec0>
irb(main):004:0> context.set_source_rgb(0.75, 0.5, 0.25)
=> #<Cairo::Context:0x331fec0>
irb(main):005:0> context.rectangle(0, 0, 1, 1)
=> #<Cairo::Context:0x331fec0>
irb(main):006:0> context.fill
=> #<Cairo::Context:0x331fec0>
irb(main):007:0> surface.data
=> "@\x80\xBF\xFF\x00\x00\x00\x00\x00\x00\x00\x00 (中略) \x00\x00\x00\x00"
irb(main):008:0>
Windows10 x64上では、BGRA の順で並んでるように見える。が、このあたりはOSやCPUによって違う可能性もありそうな。エンディアンが関係してそうな予感…。

_バイトオーダ - ビッグエンディアン/リトルエディアン

Cairoのドキュメントを眺めてたら気になる記述が。

_Cairo: A Vector Graphics Library: Image Surfaces
CAIRO_FORMAT_ARGB32

each pixel is a 32-bit quantity, with alpha in the upper 8 bits, then red, then green, then blue.
The 32-bit quantities are stored native-endian.
Pre-multiplied alpha is used.
(That is, 50% transparent red is 0x80800000, not 0x80ff0000.) (Since 1.0)

Cairo: A Vector Graphics Library: Image Surfaces より

32bitの上位から A,R,G,B の順で並んでいて、「native-endian」で記録されてる、と書いてあるのかな。予想は当たってた。

つまり、今回はリトルエンディアンのCPUで動かしてるから、ARGB とは逆の並び、BGRA に見えたのだろう。

一旦png出力せずにDXRubyのImageに変換。 :

上記を踏まえて、cairo のサーフェイスを DXRuby の Image に変換してみる。

_packテンプレート文字列 を眺めたところ、unpack("L*") を使えば、エンディアンに依存した32bit符号無し整数、の配列が得られそうな雰囲気。後は、32bitをARGBに分解していけば…。

_cairo_helloworld_dxruby2.rb
# cairo(rcairo) + DXRuby の描画テストその2
# サーフェイスのデータを変換してDXRubyのImageにしてみる例

require 'cairo'
require 'dxruby'

w, h = 640, 480

surface = Cairo::ImageSurface.new(Cairo::FORMAT_ARGB32, w, h)
ct = Cairo::Context.new(surface)

ct.set_source_color(Cairo::Color::RED)
radius = h / 3
ct.arc(w / 2, h / 2, radius, 0, 2 * Math::PI)
ct.fill

# DXRubyのImageに変換。StringIOを使わない版
src = surface.data.unpack("L*")  # 32bit符号無し整数の配列を得る
dst = []
(w * h).times do |i|
  argb = src[i]
  a = (argb >> 24) & 0x0ff
  r = (argb >> 16) & 0x0ff
  g = (argb >> 8) & 0x0ff
  b = argb & 0x0ff
  dst << a << r << g << b
end

# 配列からDXRubyのImageを作成
img = Image.createFromArray(w, h, dst)

# DXRubyで表示
Window.bgcolor = [64, 64, 64]
Window.loop do
  break if Input.keyPush?(K_ESCAPE)
  Window.draw(0, 0, img)
end

cairo_helloworld_dxruby2_ss.png

表示された。合っているっぽい。

さて、png出力するソレと、Rubyでループを回して変換するソレ。どちらが速度的にマシなのだろう。ベンチマークを取ってみたり。おそらく、複雑な画像を描いた場合はpng圧縮/展開で更に時間がかかるだろうから、多少は複雑さを増した描画内容に変更してみたりもして。

_cairo_helloworld_dxruby3.rb
ruby cairo_helloworld_dxruby3.rb 
                      user     system      total        real
cairo draw        0.031000   0.000000   0.031000 (  0.021152)
use StringIO      0.047000   0.000000   0.047000 (  0.048209)
not StringIO      0.235000   0.000000   0.235000 (  0.242087)

一旦png出力して読み込み直すほうが、全然速い…。ループを回して変換するソレは約5倍遅いのか…。

各ライブラリが持ってる機能はおそらくCで書かれてるから、処理に無駄があるように見えても、Rubyでゴチョゴチョやるよりは各ライブラリの機能を素直に使ったほうがまだ速い、ということかなと。しかし、まさかpng圧縮/展開してるソレに負けるとは…。今更ではあるけど、やっぱりRubyって遅いんだなあ…。

それと、予想通り、VGA程度の画像サイズですら 1/60秒 = 0.0167秒以内では処理が全然終わらないことも分かった。cairoのサーフェイスからDXRubyのImageに変換するだけでも0.5秒かかってるから…。例えばゲーム等に使ったとして、毎フレーム描画内容を変更したら、1〜2FPSになってしまう。話にならない。

そのあたりを考えると、FLASHって凄かったのだなと。ベクターデータをアンチエイリアスをかけながら描画しても、高フレームレートで動いてた印象が…。

参考ページ。 :


2017/04/17(月) [n年前の日記]

#1 [ruby] BGRAをARGBに変換するソレ

昨日書いたRubyスクリプトをもっと高速化できないものかとググってたら以下の記事を見かけて。

_Rubyでのビッグエンディアンとリトルエンディアンの相互変換 - エンジニアの低脳っぷりを晒す戦記

あー。その手があったか…。なるほどなあ…。

つまり、以下の処理は…。
def convert_image2(w, h, surface)
  dst = []
  src = surface.data.unpack("L*")
  src.each do |d|
    dst << ((d >> 24) & 0x0ff)  # a
    dst << ((d >> 16) & 0x0ff)  # r
    dst << ((d >>  8) & 0x0ff)  # g
    dst << (d & 0x0ff)          # b
  end
  return Image.createFromArray(w, h, dst)
end

こう書けるのだな…。
def convert_image3(w, h, surface)
  dst = surface.data.unpack("L*").pack("N*").unpack("C*")
  return Image.createFromArray(w, h, dst)
end

ベンチマークを取ってみたら、2倍ほど速くなった。
ruby cairo_helloworld_dxruby3.rb 
                      user     system      total        real
cairo draw        0.031000   0.000000   0.031000 (  0.020485)
use StringIO      0.047000   0.000000   0.047000 (  0.046708)
not StringIO      0.203000   0.016000   0.219000 (  0.224593)
unpack+pack       0.110000   0.000000   0.110000 (  0.112492)
それでも、一旦png出力するほうが速いという結果に。無念。

このやり方、何をやってるのかちょっと分かりづらい、ような気もする。せめてコメントをつけておかないと3週間後の自分が頭の上に「???」を浮かべてそう。

2017/04/18(火) [n年前の日記]

#1 [ruby][dxruby] Ruby + cairo(rcairo)でドット絵モドキを自動生成

Windows10 x64 + Ruby 2.2.6 p396 mingw32版 + cairo(rcairo) + color で、ドット絵モドキを自動生成できるかどうかテスト。

結果はこんな感じに。

tinypixelartgrad_ss.png

_前回の実験結果 と比べると、境界線を追加しただけでも結構それっぽくなってきた、ような気もするけど、やっぱりちょっとビミョーな感じが…どうなんだコレ。

ちなみに、スペースキーを押すと次々にドット絵モドキを自動生成します。

ソースは以下。

_mieki256/tinypixelartgrad.rb

表示確認には DXRuby を使用。生成部分は cairo と color しか使ってないので、Cairo::ImageSurface を変換できれば他の何かしらでも使える、のかな。どうかな。

cairo、color 等は以下でインストールできる、のではないかなと。
gem install cairo --platform=x86-mingw32
gem install color
gem install dxruby

アルゴリズムは…。ランダムに、グラデ塗り+境界線で、矩形もしくは丸をたくさん描いて、横方向や縦方向でミラー(反転コピー)してるだけ。やっぱりグラデ塗りをするだけでも、なんだか色々と誤魔化せるような気が。

ライセンスについて。 :

今回自分が書いたスクリプトソースは CC0 / Public Domain ってことで。ランダムに矩形や丸を描いてるだけだし…。

ただ、バイナリにまとめてツールとして配布云々となると、cairo(rcairo) が GPL なので、ツールも GPL になっちゃう気がするわけで。いわゆるGPL汚染。そこはちょっと注意を…。 *1 もちろん、cairo を別のライブラリで置き換えたり、描画処理を自前で書けばGPLに汚されなくて済みそうですが。GPL、面倒臭いッスね。

改良案。 :

もしかすると台形も描画するようにしたらもうちょっとそれっぽく…。いや、どうかな…。

ザクのパイプっぽい模様とか、網目っぽい模様とかを、矩形の一種として描画したらもうちょっと…。いや、どうだろう…。

範囲が広い矩形の上には明度を変えた小さな矩形を何度か描いてやれば、STAR WARSに登場するメカのテクスチャっぽくなってくれたり…するのかな…どうなんだ。

pixelart_idea01.png

*1: cairo自体はLGPLも選べるみたいだけど、rcairo が GPL っぽいようで。 _rcairo/rcairo - github には GPL ってファイルがあるし…。

#2 [ruby][ubuntu] gosuでcairo(rcairo)のサーフェイスを表示できないか実験

上記のドット絵モドキ生成スクリプトは、結果の表示確認用として DXRuby を使っているけど。コレを、Ruby + gosu で表示できないものかと。

少しググって調べてみたけど、どうも gosu は、ファイル名を渡して Gosu::Image を作る以外の方法が無い上に、ドット単位でチマチマと描画していくメソッドすら無いみたいで、コレはちょっと難しいなと。いやまあ、cairo から、一つずつHDDにpngファイルを書きだして、それを読み込み直して、とかやればできなくもないけど…。ソレってダサいというか、絶対に遅いよなと…。

一応、RMagick の何かを渡して Gosu::Image を作れる、という記述も見かけたけど。Windows上で RMagick をインストールしようとすると、一緒にインストールする ImageMagick のバージョンによってはハマる罠があるので、できれば RMagick は使わずに済ませたいわけで。

でもまあ、*NIX や Mac なら RMagick のインストールも特にハマらないのかもしれないか…。せめて Ubuntu Linux上で試してみるか…。

Ubuntu Linux で RMagickのインストール。 :

一応、Ubuntu Linux で RMagick をインストールする方法もググったり。

_Ubuntu 14.04 で gem install rmagick - kunitooの日記
_Ubuntu12.04でimage-magickとrmagickを入れる - Qiita
_ruby - Installing rmagick on Ubuntu - Stack Overflow
_Ubuntu14.04にRmagickをインストールしようとしたらエラーが出たので、やったことメモ
_rmagick/rmagick: Ruby bindings for ImageMagick

Ubuntuもインストール時にエラーが出てハマる時があるのか…。何だよソレ…面倒臭いな…。

これがもし Python だったら、PIL(Pillow) を入れればある程度は画像処理ができてしまうのだけど。Ruby には PIL 相当が無いから ImageMagick関連に頼るしか無くてなんだか不便だなと思えてきたり。

などとブツブツ言いつつも、一応 github のソレには、「libmagickwand-dev もインストールしろ」と注意書きがあるので、ソレを信じて VMware Player + Ubuntu 16.04 LTS上で試してみたり。
$ sudo aptitude install libmagickwand-dev

以下の新規パッケージがインストールされます:
  bzip2-doc{a} gir1.2-rsvg-2.0{a} libbz2-dev{a} libcdt5{a} libcgraph6{a} libdjvulibre-dev{a} 
  libexif-dev{a} libgraphviz-dev{a} libgvc6{a} libgvc6-plugins-gtk{a} libgvpr2{a} 
  libilmbase-dev{a} libjasper-dev{a} libjbig-dev{a} libjpeg-dev{a} libjpeg-turbo8-dev{a} 
  libjpeg8-dev{a} liblcms2-dev{a} liblqr-1-0-dev{a} liblzma-dev{a} 
  libmagickcore-6-arch-config{a} libmagickcore-6-headers{a} libmagickcore-6.q16-dev{a} 
  libmagickwand-6-headers{a} libmagickwand-6.q16-dev{a} libmagickwand-dev libopenexr-dev{a} 
  libpathplan4{a} librsvg2-dev{a} libtiff5-dev{a} libtiffxx5{a} libwmf-dev{a} libxdot4{a} 
0 個のパッケージを更新、 33 個を新たにインストール、 0 個を削除予定、0 個が更新されていない。
12.3 M バイトのアーカイブを取得する必要があります。 展開後に 37.8 M バイトのディスク領域が新たに消費されます。
先に進みますか? [Y/n/?]
(以下略)

$ sudo gem install rmagick

Fetching: rmagick-2.16.0.gem (100%)
Building native extensions.  This could take a while...
Successfully installed rmagick-2.16.0
Parsing documentation for rmagick-2.16.0
Installing ri documentation for rmagick-2.16.0
Done installing documentation for rmagick after 6 seconds
1 gem installed

$ irb

irb(main):001:0> require 'rmagick'
=> true
irb(main):002:0> exit
大量にパッケージがインストールされたけど、すんなり入ってくれた、ような気がする。助かった。

でも、本当にインストールできたのかな。

RMagickの動作確認。 :

ちゃんとインストールできたのか試してみる。
$ irb

irb(main):001:0> require 'rmagick'
=> true

irb(main):002:0> img = Magick::ImageList.new("0001.jpg")
=> [0001.jpg JPEG 3888x2592 3888x2592+0+0 DirectClass 8-bit 3307kb]
scene=0

irb(main):003:0> img.flop.write("dst.jpg")
=> 0001.jpg=>dst.jpg JPEG 3888x2592 3888x2592+0+0 DirectClass 8-bit 3299kb
動いてるっぽい。左右反転した jpg が保存された。

cairoのサーフェイスをRMagickのImageに変換。 :

さて。cairo(rcairo)のサーフェイスを RMagick の Image に変換するには、何をどうすればいいのやら。

以下でやり方が紹介されてた。ありがたや。

_3階建ての2階角部屋 2nd : ruby/tk + cairoで高速化を目指す 3

おそらく以下の2行がソレなのだろう…。
magick = Magick::Image.new(width, height)
...
magick.import_pixels(0,0, cairo.width, cairo.height, 'BGRA', cairo.data)

ただし、この書き方は、リトルエンディアンのCPUにしか対応してない書き方、のような気がする。ビッグエンディアンのCPUで cairo を使うと、BGRAじゃなくてARGBの並びで返ってきそうな予感。

gosuで表示確認ができた。 :

ということで、Ubuntu 16.04 LTS上で、Ruby 2.3.1 p112 + RMagick 2.16.0 + gosu 0.11.3.1 を使って、前述のドット絵モドキ生成スクリプトの動作確認ができた。以下は証拠画像。

gosu_draw_ss.png

ソースは以下。もちろん、 _tinypixelartgrad.rb も必要。

_gosu_draw.rb

さておき。gosu はウインドウの背景色を指定できないようで、各画像の輪郭が分かりづらいなと…。背景画像を用意して背景色指定の代替に、と思ったら単色豆腐画像もすぐには作れないようだし。それと、キーボードを押した瞬間を取得できないのもなんだかアレで…。

その点 DXRuby は、ウインドウの背景色は1行で指定できるし、即座に単色豆腐画像を作れるし、キーボードを押した瞬間も取得できるしで、細かい部分で色々と気が利いてるので助かります。ありがたや。

#3 [pc] 何故にsvgビューアが存在しないのだろう

ズームとパンができてWindows上で動作するフリーのsvgビューアが無いものかとググっていたのだけど、これが全然見つからず。

単にsvgを表示するだけでズームもパンも何もできないソフトならチラホラ見かけたのだけど…。ラスターデータと違って拡大しても画質が荒れませんよ、てのがベクターデータの売りなのに、何故にどのソフトもズーム機能がついてないのだろう。不思議。

ていうか、皆一体何を使ってsvgの表示確認をしているんだ…。いやまあ、*NIX系ならデスクトップ環境にsvg表示機能が入ってたりもするからこのあたりWindowsだけの問題かもしれないのだけど。

bmp、jpg、png等、ラスター画像対応のビューアは山ほど存在するのに、svgは…。作るのが難しいのだろうか。それともWebブラウザで表示して済ませてる場面が多いのだろうか。にしても、この少なさは…。いやまあ、つまりはそれだけ、svg画像自体が作られていないし使われていないということかもしれんけど。

Batikの使い方をメモ。 :

一応、 _Apache Batik の batik-squiggle というsvgブラウザが存在していて、ソレを使えばズームもパンもできるのだけど、ちょっと操作方法が一般的なビューアと違っているのでそのあたりをメモ。

入手は、 _Download Apache&trade; Batik 経由で _Index of /pub/apache/xmlgraphics/batik/binaries あたりから。batik-bin-1.9.zip をDLして解凍。

Javaで作られたソフトなので、Javaのインストールが必要。Javaがインストールされてないなら、 _java.com からJavaをインストール。

batik-squiggle の起動は、以下でいいのかな…。たぶん。
javaw -jar Batikインストールフォルダ\batik-squiggle-1.9.jar
javaw -jar Batikインストールフォルダ\batik-squiggle-1.9.jar hoge.svg
batik-bin-*.*.zip を解凍すると、中に samples というフォルダがあって、その中に色々な svgファイルが入ってるから、試しに表示してみるのもいいかもしれず。まあ、表示/描画に失敗するsvgファイルも入ってるのがなんだか気になるけど。

操作方法は…。
  • Shift + 左ボタンドラッグ : PAN。(スクロール)
  • Shift + 右ボタンドラッグ : ズーム
  • Ctrl + 左ボタンドラッグ : 選択範囲をズーム
  • Ctrl + 右ボタンドラッグ : 回転
  • Ctrl + T : ズーム率を戻す
  • Ctrl + I : ズームイン (拡大表示)
  • Ctrl + O : ズームアウト (縮小表示)
  • Ctrl + Y : サムネイル表示。拡大表示時はサムネイル内の枠をドラッグすることで表示場所を変更できる。
マウスホイールや中ボタンドラッグが使えないあたりが惜しい。

ここまでメモしてから気が付いたけど、 _Batik - SVG Browser に、バージョンは古いけどドキュメントの日本語訳があることに気が付いた。該当ページにリンクを貼っておくだけで済んだのだな…。

2017/04/19(水) [n年前の日記]

#1 [gimp][python] GIMPのPython-fuでcairo(pycairo)を使えるか実験

GIMP という、フリーで使える高機能な画像編集ソフトがあるのだけど。この GIMP は、TinyScheme (Script-fu)、もしくは Python (Python-Fu、GIMP-Python) を使ってアレコレ自動化できるスクリプト機能を持っていて。

Python が使えるなら、もしかして Python から cairo (pycairo) を使って色々な描画ができないかなー、と思いついて。ちと試してみようかなと。

cairoを使うメリット。 :

「わざわざ cairo を使って描かなくても、GIMPが持ってる描画機能を呼び出して描けばよくね?」と言われそうな気もするけれど。

しかし、例えば…。
  • スクリプトからGIMPのブラシ描画を使うには、事前に、使いたいブラシファイルを用意しなきゃいけない。
  • GIMPのグラデーション塗りを使うには、事前に、グラデーションパターンを用意しなきゃいけない。
  • スクリプトで前景色や背景色を変更したら、スクリプト終了時にその色が選択された状態になってしまう。
等々、ちと面倒なところがあるわけで。

その点、cairo を使えたら…。
  • ブラシファイルを用意しなくても線が引ける。
  • グラデーションのパターンを用意せずともスクリプト内でいきなり色指定してグラデ塗りができる。
  • GIMP側の前景色や背景色を変更しなくても好きな色で描画できる。
等々のメリットがありそうだなと。cairoを使うことで、プログラムで描画することに関しては自由度が上がるはず。たぶんそのはず。だといいな。

そもそもcairoは入っているのか。 :

そもそも GIMP に cairo(pycairo) は同梱されているのだろうか。Windows10 x64 + GIMP 2.8.20 Portable で確認してみたり。

GIMPを起動して、フィルター → Python-Fu → コンソール。

import cairo と打ち込んでみる。エラーは出なかった。ということは、どうやら pycairo も一緒にインストールされてるっぽいなと。

まあ、ほんのちょっと昔、GIMP 2.6 を使ってた頃は、Python-Fu を使うにあたって Python の他にも _pygtk-all-in-one という、PyGTK、PyCairo、PyGObject が全部入ってるブツをインストールする必要があったので…。

GIMP 2.8 からは、別途 Python や pygtk-all-in-one をインストールしなくても済むようになった、ということは、GIMP 2.8 には Python のコア部分や PyGtk、PyCairo、PyGObject が最初から同梱されてるはず、と予想してたけど、どうやら当たってたようで。

いや待て。本当にそうだろうか…。実はOS側・システム側にインストールされている Python 2.7 + pycairo を使ってたりしないか…。でもまあ、それでも、手元の環境では cairo を import できたのだから、同様の環境にすることは可能なはず、ではあるよな…。

まずは画像やレイヤーを新規作成。 :

Pythonコンソールからチマチマ打って、動作を一つずつ確認していくことにする。

まずは、新規画像の作成と、新規レイヤーの作成を試す。
>>> from gimpfu import *
>>> img = gimp.Image(512, 512, RGB)
>>> layer = gimp.Layer(img, "layer01", 512, 512, RGBA_IMAGE, 100, NORMAL_MODE)
>>> layer.fill(TRANSPARENT_FILL)
>>> img.add_layer(layer, 0) 
>>> gimp.Display(img)
<display>
>>>
GIMPのウインドウ上に画像が表示された。

やってることは…。
  • from gimpfu import * は、Python-Fu を使う時のおまじない。
  • img = gimp.Image(512, 512, RGB) で、512x512、RGBモードで新規画像を作成。
  • layer = gimp.Layer(img, "layer01", 512, 512, RGBA_IMAGE, 100, NORMAL_MODE) で、レイヤーを作成。
  • layer.fill(TRANSPARENT_FILL) で、レイヤー全体を透明色で塗り潰す。
  • img.add_layer(layer, 0) で、画像にレイヤーを追加する。
  • gimp.Display(img) で、画像をGIMPのウインドウに表示。
大体こんな感じ。

cairoで描画してGIMPに渡す。 :

cairoで描画した内容を、GIMPの画像、というかレイヤーに渡してみたい。

そんな処理をしてる事例は少ない、というよりほとんど全く存在していないのだけど、諦めきれずにググっていたら以下のスクリプトに遭遇。Python-Fuスクリプトでありながら cairoを使っている。参考にさせてもらった。ありがたや。

_harmony-gimp-plugin/harmony.py at master - yihuang/harmony-gimp-plugin

cairo のサーフェイスを作成して、サーフェイスからコンテキストを作って、そのコンテキストを使って赤い四角を描画してみる。
>>> import cairo
>>> surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 512, 512)
>>> ctx = cairo.Context(surface)
>>> ctx.set_source_rgb(1.0, 0.0, 0.0)
>>> ctx.rectangle(128, 128, 256, 256)
>>> ctx.fill()

そして、cairo のサーフェイス内容を取り出して、GIMP のレイヤーに描き込む。
>>> src = surface.get_data()
>>> rgn = layer.get_pixel_rgn(0, 0, 512, 512, True, True)
>>> rgn[0:512, 0:512] = str(src)

レイヤーに描き込んだだけでは表示されないので、表示されるように反映させる。
>>> layer.flush()
>>> layer.merge_shadow()
>>> layer.update(0, 0, 512, 512)
>>> pdb.gimp_displays_flush()

一応、GIMP の画像ウインドウ上に、cairoによる描画内容が出てきた。

しかし。赤い四角を描き込んだはずなのに、青い四角が表示されてしまった。何故。

おそらく、cairo のサーフェイスから取り出したデータは BGRAの順になっているのに、GIMP側では RGBAの順でデータを持つから、ではあるまいか…。となると、BGRA の並びのデータを、RGBA の並びのデータに変換しないといけない。

BGRAをRGBAに変換。 :

Ruby を使ってその手の変換処理をした際には unpack() や pack() を使ったけれど。もしかして Python にも unpack() があったりしないかな。

ググってみたら、Python の場合、
struct.unpack(フォーマット文字列, 文字列)
を使うと、unsigned long 等を指定しつつ、文字列から配列に変換できる模様。

_pythonのstructモジュールを触ってみる | KentaKomai Blog
_Pythonでバイナリデータの取り扱い - 猫型エンジニアのブログ

しかし、Ruby のように、unpack("L*") みたいなイイ感じの指定はできないようで。これは4バイトずつ取り出して変換するしかないのかな。

ググってみたら、スライスとやらを使うことで、文字列の中から一部を取り出すことができるっぽい。コレを使ってみよう。

_スライスを使った部分文字列の取得 - 文字列 - Python入門

てなわけで。まずは struct を import してから…。
>>> import struct

こんな感じで、4バイト = 1ドット分を取り出せる、かな。
>>> src = surface.get_data()
>>> i0 = 0
>>> i1 = i0 + 4
>>> bgra = src[i0 : i1]
>>> bgra
'\x00\x00\xff\xff'
>>> bgra = struct.unpack("@L", bgra)
>>> bgra
(4294901760L,)
取り出せたっぽい。

ちなみに、わざわざ struct.unpack("@L", bgra) を使うのは、エンディアンに関係なく ARGB が並んだ4バイトを取り出すため。

cairo 自身は ARGB の並びでデータを保持しているつもりで動いてるけど、リトルエンディアンのCPU(Intel製CPU等)で cairo を動かすと、メモリ上では逆の並びの BGRA で記録される。なので、「そうか! cairo は BGRA の並びでデータを持ってるのだな!」と決めつけて、1バイト単位で取り出して並び替える処理を書いてしまうと、万が一ビッグエンディアンのCPU上でスクリプトを動かした際、おそらくデータは BGRA じゃなくて ARGB で並んでるだろうから、色やアルファチャンネル値がおかしなことになってしまう。はず。たぶん。

でも、struct.unpack("@L", ) を経由させれば…。「"@L"」の指定は、「その環境のネイティブなエンディアンで32bitを取り出せ」という指定なので、ARGB で並んだ 32bit = 4バイトが得られて問題は起きないはず。

さて、ARGB の4バイトが得られたので、RGBA に並べ替える。
>>> a = (bgra[0] >> 24) & 0x0ff
>>> r = (bgra[0] >> 16) & 0x0ff
>>> g = (bgra[0] >> 8) & 0x0ff
>>> b = bgra[0] & 0x0ff
>>> struct.pack('4B', r, g, b, a)
'\xff\x00\x00\xff'
>>>
並び替えができた。元々、BGRA = 0x0000fff だったものが、RGBA = 0xff0000ff になってるし。

これらを踏まえて、BGRA もしくは ARGB で並んでる cairo のサーフェイス内容を RGBA の並びに変換して返す関数、にしてみる。
import struct
def get_rgba_str(bgra_buf):
    rgba_buf = ""
    l = len(bgra_buf)
    for i in range(l / 4):
        i0 = i * 4
        i1 = i0 + 4
        bgra = struct.unpack('@L', src[i0 : i1])[0]
        a = (bgra >> 24) & 0x0ff
        r = (bgra >> 16) & 0x0ff
        g = (bgra >> 8) & 0x0ff
        b = bgra & 0x0ff
        rgba = struct.pack('4B', r, g, b, a)
        rgba_buf += rgba
    return rgba_buf
コレをコピーして、GIMP の pythonコンソールに貼り付ければ、get_rgba_str() という関数が定義されて、以後使えるようになる。

再度cairoで描画して動作確認。 :

レイヤーを追加して。
layer = gimp.Layer(img, "layer03", 512, 512, RGBA_IMAGE, 100, NORMAL_MODE)
layer.fill(TRANSPARENT_FILL)
img.add_layer(layer, 0)

cairoで赤い四角を描いて。
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 512, 512)
ctx = cairo.Context(surface)
ctx.set_source_rgb(1, 0, 0)
ctx.rectangle(64, 64, 256, 256)
ctx.fill()

GIMP側のレイヤーに反映させる。先ほど定義した get_rgba_str() を使う。
src = surface.get_data()
dst = get_rgba_str(src)
rgn = layer.get_pixel_rgn(0, 0, 512, 512, True, True)
rgn[0:512, 0:512] = str(dst)
layer.flush()
layer.merge_shadow()
layer.update(0, 0, 512, 512)
pdb.gimp_displays_flush()

赤い四角が表示された。(小さくガッツポーズ)

この調子なら、Python-Fu + cairo (pycairo) を使って、GIMP上で何かを描画するスクリプトを書けそう。後は Python-Fuスクリプトの書き方を調べれば…。

2017/04/20追記。 :

環境変数 PATH を修正してシステム側の Python 2.7 を見つけられない状態にしてから、GIMP の Pythonコンソールで import cairo と打ってみた。この状態でもエラーは出なかった。

つまり、少なくとも GIMP Portable には cairo (pycairo) が同梱されている模様。

VMware Player + Ubuntu Linux 6.04 LTS 上でも、GIMP 2.8.16 をインストールして確認してみた。こちらも import cairo でエラーは出なかった。cairo は使える模様。

ただ、Ubuntu上では、get_rgba_str() がエラーを出すようで。struct.unpack() が、
error: unpack requires a string argument of length 8
と文句を言ってくる…。何故。

2017/04/20(木) [n年前の日記]

#1 [python] 環境変数PYTHON_ROOTって何だろう

Windows10 x64 の環境変数を眺めていたら、PYTHON_ROOT という環境変数があって。しかも、Python 2.4 のインストール場所を指定してあった。何だコレ? 何に使ってるんだ?

消してみたけど、不具合があるようにも見えず。何だろうなコレは…。

#2 [gimp][python][ubuntu] Ubuntu Linux 16.04LTS上でGIMPのPython-fuに関して試行錯誤中

VMware Player + Ubuntu Linux 16.04 LTS上で GIMP 2.8.16 を動かして、Python-Fuコンソール上でアレコレ実験中。

昨日、Windows10 x64上 + GIMP 2.8.20 Portable で、Python-fu の実験をした際には上手くいったのだけど、同じことを Ubuntu上で行うと、struct.unpack('@L', ) が以下のエラーを出すようで。
error: unpack requires a string argument of length 8

与えてる文字列は、こんな感じだけど…。
>>> ctx.set_source_rgb(1, 0, 0)
>>> ctx.rectangle(0, 0, 64, 64)
>>> ctx.fill()
>>> src = surface.get_data()
>>> src[0 : 4]
'\x00\x00\xff\xff'
別におかしくはないよなあ…。

コレを struct.unpack() に渡すと…。
>>> struct.unpack('@L', src[0:4])
Traceback (most recent call last):
  File "<input>", line 1, in <module>
error: unpack requires a string argument of length 8
なんでや。

書式文字列を変えてみた。 :

書式文字列を、「@L」から「=L」に変えてみたら、エラーが出ないことに気づいた。
>>> struct.unpack('=L', src[0:4])
(4294901760,)

_7.3. struct - 文字列データをパックされたバイナリデータとして解釈する - Python 2.7.13 ドキュメント
  • 「@」は、バイトオーダ、サイズ、アライメント、全部がネイティブ。
  • 「=」は、バイトオーダはネイティブ、サイズは standard、アライメントは none。
WindowsとUbuntuでは、このあたりのサイズだのアライメントだのが違ってる、ということなんだろうか。

Python-Fuコンソール上で打ったアレコレをまとめて列挙。 :

from gimpfu import *
import struct
import cairo
def get_rgba_str(src):
    rgba_buf = ""
    l = len(src)
    for i in range(l / 4):
        i0 = i * 4
        i1 = i0 + 4
        bgra = struct.unpack('=L', src[i0: i1])[0]
        a = (bgra >> 24) & 0x0ff
        r = (bgra >> 16) & 0x0ff
        g = (bgra >> 8) & 0x0ff
        b = bgra & 0x0ff
        rgba = struct.pack('4B', r, g, b, a)
        rgba_buf += rgba
    return rgba_buf
img = gimp.Image(256, 256, RGB)
gimp.Display(img)
w = 64
h = 64
layer = gimp.Layer(img, "layer03", w, h, RGBA_IMAGE, 100, NORMAL_MODE)
layer.fill(TRANSPARENT_FILL)
img.add_layer(layer, 0)
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
ctx = cairo.Context(surface)
ctx.set_source_rgb(1, 0, 0)
ctx.rectangle(8, 8, 32, 32)
ctx.fill_preserve()
ctx.set_source_rgb(0, 0, 0)
ctx.stroke()
src = surface.get_data()
dst = get_rgba_str(src)
rgn = layer.get_pixel_rgn(0, 0, w, h, True, True)
rgn[0:w, 0:h] = str(dst)
layer.flush()
layer.merge_shadow()
layer.update(0, 0, w, h)
pdb.gimp_displays_flush()
コレをGIMPのPython-Fuコンソールに貼り付ければ、cairoで描画した内容をGIMPのレイヤーに反映させられるかどうかが分かる。みたいな。

#3 [pc] Atomエディタが起動せず

久々にAtomエディタを起動しようとしたら、JavaScriptがエラー云々と表示されて起動せず。

以下のやり取りを参考に、DOS窓から atom --safe と打ったら起動してくれて、1.15から1.16にアップデートされた。

_Atom (テキストエディタ) - 【atom】Windows版Atomでプロセスのみ起動し、本体が起動しない現象について(21992)|teratail

その後は起動するようになったけど、何だったんだろう…。まあいいか。動いてるし。

2017/04/21(金) [n年前の日記]

#1 [gimp][python] Python-Fuのregister関数でハマった

GIMP の Python-Fu について実験していたのだけど。Python-FuスクリプトをGIMPのメニューに登録する際に使う register関数の指定の仕方でハマったり。

巷のサンプルを参考にして打ったつもりが、実行すると「3つの引数が要求されてるのに1つしかきてねえぞ」と延々怒られて。

原因は…。Python-Fuのregister関数には2種類の書き方があって、どちらの書き方にするかでパラメータの記述の仕方も変わってくる、ということを忘れていたせいだった。

以下は、古い書き方。
from gimpfu import *

def my_example_plugin_main(timg, tdrawable, msg):
    """main func."""
    gimp.message("Hello, World: " + msg)

register(
    "python_fu_FUNCTION_NAME",
    "Python-Fu example",
    "help",
    "Your name",
    "Your name",
    "2017/04/20",
    "<Image>/Filters/Languages/Python-Fu/Python-Fu example...",
    "RGB*",      # Alternately use RGB, RGB*, GRAY*, INDEXED etc.
    # params (type, name, description, default [, extra])
    [
        (PF_STRING, "msg", "Text string", 'Hello, world!')
    ],
    # return vals
    [],
    my_example_plugin_main  # function name
)

main()

以下は、新しい書き方。
from gimpfu import *

def my_example_plugin_main(timg, tdrawable, msg):
    """main func."""
    gimp.message("Hello, World: " + msg)

register(
    "python_fu_FUNCTION_NAME",
    "Python-Fu example",
    "help",
    "Your name",
    "Your name",
    "2017/04/20",
    "Python-Fu example...",
    "RGB*",      # Alternately use RGB, RGB*, GRAY*, INDEXED etc.
    # params (type, name, description, default [, extra])
    [
        (PF_IMAGE, "timg", "Input image", None),
        (PF_DRAWABLE, "tdrawable", "Input drawable", None),
        (PF_STRING, "msg", "Text string", 'Hello, world!')
    ],
    # return vals
    [],
    my_example_plugin_main,  # function name
    menu="<Image>/Filters/Languages/Python-Fu"
)

main()

古い書き方は、7つ目の引数で、メニューの登録位置を記述する。パラメータとして、画像(PF_IMAGE)、レイヤー(PF_DRAWABLE) を書かなくても、メイン処理をする関数が呼ばれた際に、自動でその2つが最初の引数として渡される。

新しい書き方は、一番最後に、メニューの登録位置を記述する。パラメータとして画像とレイヤーも含めておかないと、メイン処理をする関数に、画像、レイヤーが渡されない。

ていうかコレ、以前もハマって、メモしてた…。

_mieki256's diary - 画像を斜めにするGIMP-Pythonスクリプトを書いた
_mieki256's diary - GIMPのPython-fuについてメモ

古い書き方と新しい書き方がある、ということはかろうじて覚えてたけど、パラメータの記述も変わるということをすっかり忘れていた…。

ちなみに前回は以下の記事を読んで解決策が分かった模様。ありがたや。

_【Gimp】Pythonによるプラグイン開発 - プログラマーのメモ書き

#2 [gimp][python] GIMP + Python-Fuでcairoを使って描画するスクリプトの雛形を作成

GIMP + Python-Fu で、cairo(pycairo)を使ってアレコレ描画するスクリプト、の雛形を作成できたので、とりあえずアップロード。あくまで雛形なので、描画内容については全く意味がないソレだけど。

_mieki256/cairo_draw_example.py

gimp_pythonfu_cairo_ss.png

フィルター → Python-Fu → Cairo draw example を選べば、ランダムな位置と色で四角を描きまくる。

以下の環境で動作することを確認済み。
導入の仕方は、 _GIMPにプラグインをインストールする方法 | GIMP2の使い方 という記事が参考になるかと。ちなみに Linux の場合は、~/.gimp-2.8/plug-ins/ の中に該当ファイルをコピーすればOK。だと思う。たぶん。

さて、後は肝心の描画処理を書いていくだけ…。

懸念事項。 :

なんとなく思ったけど、まさか、次期バージョンの GIMP 2.10 では、pycairo が同梱されない状態になったり…しないよな…。

#3 [python] pysdl2をほんの少しだけ試用

Python関係のページをググって眺めてるうちに pysdl2 の単語が目に入って。

アレって今時はどうなってるんだろうと気になり始めたので、せめて導入だけでも試してみようかと。いや、以前も一度試したんですが、面倒臭かった記憶しか…。少しは変わってるのかな…。

ちなみに動作環境は Windows10 x64 + Python 2.7.13。

pysdl2についてざっくり説明。 :

pysdl2 ってのは…。Python で2Dゲームを作れる pygame というライブラリの後継になりそうだったけど今一つなれてないビミョーな扱いを受けてるライブラリで。

昔は「Pythonで2Dゲーム作るならpygame」てな空気があったのだけど。pygame は SDL を使っていて、その SDL は標準状態だとソフトウェアレンダリングだから描画が遅くて。 *1 何せ、全画面書き換えをすると30FPSすら出ないから _「書き換えが必要なところだけ覚えておいてそこだけ書き換えれば速くなるんだぜ! Dirty rectって言うんだぜ!」 てな感じのソレってPC-8801だのFM-7だのWindows3.1時代の話ですかと首を傾げるようなノウハウが公式サイトで語られ続けるほどに今となってはトホホなレベルのライブラリで。

後継ライブラリとして一時期は _pygame2てのが作られていたらしい のだけど。実装しなきゃいけない部分が多岐にわたって作者さんが途中で力尽きて、もうこういう pygame っぽい機能豊富でゴージャスなライブラリは作ってられませんわ開発停止しますわー、となったようで。

その代わり、SDL2の薄いラッパーを目指すぜ、シンプルにするぜ、最低限のことしかしないぜ、やりたいことがあったら自分で頑張って実装してくれよな、と方針が変わって pysdl2 が出てきたのだけど。

薄いラッパーだから、pygame と比べるとユーザ側でやることが増えて使い勝手が悪くなった上に、例えば Windows上で動かす際にはSDL2関係のDLLが必要なのだけど一部のバイナリが配布されてなくて「使いたい人はDLLを自分でビルドしてね」と平然と言われる始末。pygame に比べたら導入すら面倒臭いので、ほとんど誰もあえてわざわざコレを使おうとはしないし誰も使わないからユーザレベルのドキュメントもさっぱり増えないという状況に…。

てな感じの説明で合ってるんですかね? もし間違ってたら誰かツッコミ入れといてください。

pysdl2をインストール。 :

pysdl2自体のインストールは、DOS窓で以下を打つだけ。
pip install pysdl2
すんなりインストールできた。素晴らしい。

が、コレだけでは何もできない。SDL2関連のDLLが別途必要。ここからが…うう…。

SDL2関連のDLLを入手。 :

_Installing PySDL2 - PySDL2 0.9.5 documentation に必要なSDL2関連DLLが列挙されてる。以下から入手できる模様。

_Simple DirectMedia Layer - SDL version 2.0.5 (stable) から、SDL2-2.0.5-win32-*.zip をDL。32bit版と64bit版があるので使いたいほうを。

_SDL_image 2.0 から、SDL2_image-2.0.1-win32-*.zip をDL。

_SDL_mixer 2.0 から、SDL2_mixer-2.0.1-win32-*.zip をDL。

_SDL_ttf 2.0 から、SDL2_ttf-2.0.14-win32-*.zip をDL。

ちとウンザリしてきた…。

しかも、「 _SDL_gfx / SDL2_gfx - ferzkopp.net から SDL2_gfx が入手できるよ」と紹介されてるけど、この SDL2_gfx ってソースしか配布されてなくて。DLL、入ってない。英語圏の掲示板を眺めると、「SDL2_gfx.dll が見つからないんだけど、どこで入手できるの?」という質問に「自分でビルドしろ」と返される光景が何度も出現。pygame使って「ゲームプログラミング、たーのしー!」と思ったであろう初心者達を全力で噛み殺しに来てる…。さすがニシキヘビ…。

そもそもこの SDL2_gfx って、一体何ができるのか、そこからして分からない。オプションと言いつつも、pysdl2 の動作にそこそこ必要になるDLLなの? 別にそれほど必要じゃないの? どっちなの? 正体が分からない。

にしても、いくらなんでも誰もビルドしてないとかあり得ないのでは、とググってたら、 _Windows Binaries - nim-lang/sdl2 Wiki で公開してくれてる方を発見。現行のSDL2は2.0.5だけど、この版は SDL2 2.0.3 なのでちょっぴり古い、けど、手元の環境で試したところ SDL2_gfx.dll 自体は動くようで。ありがたや。

しかしコレ、DLLを入手していくだけでもウンザリするので…。32bit版のDLLを全部まとめて(バックアップも兼ねつつ)こっそりと _ココ に置いときますので面倒臭いなと思った人はどうぞ。「ちょ…おま…ソレ問題あるんじゃねえの?」と不安になった人は各ページを開いてコツコツと一つずつ入手していけば全く同じファイル群が入手できますからどうぞ正規の手順で入手してウンザリして「どこかにまとめて置いとけよ!」と憤っていただければと。自分は憤りました。 *2

DLLを呼び出せるように設定。 :

とりあえず、これで紹介されてるDLL群はあらかた入手できた。zipを解凍して、中に入ってたファイル群をどこか一つのフォルダにまとめて入れておく。重複してるファイルがあったら…タイムスタンプが新しいほうを入れとけばいいんじゃないかな…たぶん…自信無いけど…。

これだけでは、pysdl2 が、各DLLの居場所を知らないままだから呼び出せないので…。OSの環境変数に
PYSDL2_DLL_PATH
を追加して、それらDLLが入ってるフォルダを指定しておく。

環境を汚したくない場合は、DOS窓で
set PYSDL2_DLL_PATH=DLLが入ってるフォルダ
とでも打てば、それ以降は反映されるはず。DOS窓閉じれば設定消えるので次回も設定し直しですが。

あるいは、Pythonスクリプト側に書いていいなら、スクリプトの最初のほうで環境変数を追加すれば済むようで。 _公式ドキュメント でも、「 _os.environ を使えば環境変数を追加した状態にできるよ」と書いてある。

ここまでやれば、ようやく pysdl2 が使える状態に。

サンプルを動かす。 :

pysdl2 がインストールされていれば、Python2.7インストールフォルダ\Lib\site-packages\sdl2\examples\ の中にいくつかサンプルファイルが入ってるので、実行して動作確認ができる。

pong.py と gui.py は、ちょっと注意。例えば、
python pong.py
で動かすと、
> python pong.py
Using software rendering
と表示されて、「ここまで苦労したのに結局ソフトウェアレンダリングかよおお!? 一体何なんだよ、このクソライブラリいい!」と絶望的な気分になるのだけど。

ソースを眺めてみたら、オプションで切り替えられるようで。
> python pong.py -hardware
Using hardware acceleration
ちょっと焦りましたヨ。

たぶん、デフォルトでハードウェアアクセラレーションを有効にしておくと、環境によっては動作しないとかOSが落ちる等の事例があって、故にデフォルトは安全側に振ってあるのですかねえ…。SDLもそうだったし…。たしか昔のIntel製内蔵GPUがヤバかった…ような…。pygame に限らず、SDLを使ってた Ruby/SDL もコレが理由でパフォーマンスが出なかった記憶が…。

ところで、SDL2_gfx って何に使うのか分からなかった件ですが。

gfxdrawing.py を動かしてみたら、どうやら、線や四角や丸を半透明で描画できるDLLのようで。…正直イラネ。こんなもんのために散々ネットサーフィンさせられたのか。ふざけんな。嘘。言い過ぎました。状況によっては便利なDLLなんだろうけど、コレ、スクリーンショットがもうちょっとあれば正体が分かったんでしょうけど、「Graphics drawing primitives and other support functions wrapped up in an add-on」とスクリーンショット2枚だけでは、ちょっと…。もっと凄いブツが描画できる、pysdl2にはほぼ必須のDLLなのかと想像してしまった…。

まあ、現状、ドキュメントの類がほとんど無いから正体が分からないアレコレがあっても仕方ないですな…。

とりあえず、サンプルは動きました、ということで。しかし…相変わらず導入が面倒臭い…。これじゃ流行らないわ…。

余談。 :

_PythonGames - Python Wiki で、Pythonで使えるゲームライブラリが色々紹介されてるのでメモ。

個人的には pyglet に期待してたのですが、アレはOpenGLを使うので、AMD/ATI製GPU上では不具合が出たり *3 、Intel Atom の載ってるPCではスプライトすら出なかったりで。 *4 今はどうなんだろう…。

ググってたら、pygame_sdl2 てのもあるみたいですな。

_renpy/pygame_sdl2: Reimplementation of portions of the pygame API using SDL2.
_Pygame_SDL2 Documentation - pygame_sdl2 latest documentation

しかし、Nightly builds のダウンロード先が404という…。「もうすぐ公式リリースが _PyPI に登録できると思うよ」と書いてあるけど、まだ登録されてないっぽい…。

pygame_sdl2 とは別に、pygame-sdl2 てのもあるようで。これはpysdl2に被せた形っぽい。

_ShadowApex/pygame-sdl2: Drop-in SDL2 implementation of Pygame.

ただ、 _Pygame SDL2 : pygame のやり取りの中で、「pygame_sdl2 が出てきたから、もう pygame-sdl2 は作りません」と pygame-sdl2 の作者さんが書いてますな…。

*1: 「pygameの描画が遅いんですけど…」と質問すると「OpenGLと組み合わせて使え」と言われるのが若干定番な印象。
*2: ていうかホントはコレって pygame 使ってた頃のノリを考えたら pip install pysdl2 をしただけでひとまず全部入る状態が妥当じゃないのかよ何だこの状況てな感じで実は結構ムカムカしてるんですけど。SDL2_gfxのあたりでキレそうになりましたよ。や、ソースを公開してくれてるだけでも大変ありがたいのですが…。しかしpysdl2の使い方を勉強するだけでもこれからユーザに一苦労させるのは間違いないのにソレ以前の導入作業からしてコレでは話にならんだろと。こんなんでpygameの後継ライブラリになるとホントに思ってんのか、これじゃ普及するわけねえだろ何考えてんだ、みたいな。いやまあ、pysdl2 と DLL を分離することでSDL2が更新されるたびに pysdl2 も更新しなきゃみたいな状態を回避できるから負担が減ると踏んでこうしてるのかなと想像するのでそれもそれで分かるのですけど。とは言え開発負荷を下げた結果誰も使えないし使わないライブラリになりましたではそもそも何でコレ作ったのよとなるわけで。さりとて実現不可能な予定を立ててみてもそれもそれでアレですし。実際一回 pygame2 という形でポシャってますから。まあ、色々と難しい…。これではダメだが事情は分かる、事情は分かるんだがこれではダメだろう、みたいな。
*3: _mieki256's diary - pygletがダメなのかと思ったらどうやらRadeonが糞だったらしい話
_mieki256's diary - pyglet + Radeon で描画できない件について
*4: _mieki256's diary - タブレットPCで自作サンプルを動かしてもらった

2017/04/22() [n年前の日記]

#1 [python] pygame_sdl2について調べてるけど

_pygame_sdl2 について調べてるけど、さっぱり分からんなと…。Windows上にインストールする方法すら分からない…。ちなみに手元の環境は Windows10 x64 + Python 2.7.13 32bit版。

_http://nightly.renpy.org/pygame_sdl2/ に「Nightly builds が置いてあるよ」と書かれてるけど、404で。

_Index of /pygame_sdl2/current/ (Internet Archive) には2016/07/09版のリストがあったけど、.whlファイルそのものは残ってない模様。残念。

_Lemma Soft Forums - View topic - Can't install pygame_SDL2 によると、「Ren'PyのSDKを落とせば中に pygame_sdl2 も入ってる」と書いてあるような…。

_Ren'Py 6.99.12 からSDKのzipをDLして中を覗いてみたけど、たしかに SDL2.dll だの pygame_sdl2 なるフォルダが入ってる、が、前述のやり取りでは「ソレを取り出したけどやっぱり動かないんですけど」みたいなことが書かれてるようでもあり。

ビルドとやらをするしかないのかな…。ハードル高いな…。こんなんじゃ「たーのしー!」とか全然言えないよな…。

ビルド云々について調べる。 :

_renpy/pygame_sdl2_windeps: Windows dependencies for pygame_sdl2. に、pygame_sdl2 をWindows上でビルドする際に必要になるファイル群があるよ、てな話も見かけた。ていうか pygame_sdl2 の README.md に書いてあるな…。一覧を眺めると、 _pygame_sdl2_windeps/lib/x86 のあたりに SDL2.dll や SDL2_image.dll が入ってるようで。

ビルドするには、使ってるPythonのバージョンと合ってる Visual Studio が必要、とも書いてある…。

自分が使ってるのは Python 2.7 だけど…。 _WindowsCompilers - Python Wiki を眺めた感じでは、
  • Python 2.6 〜 3.2 までは Visual C++ 9.0。
  • Python 3.3 と 3.4 は Visual C++ 10.0。
  • Python 3.5 と 3.6 は Visual C++ 14.0。
と書いてあるから…。Python 2.7用の pygame_sdl2 をビルドするには Visual C++ 9.0 が必要、ってことになるのかな。「Visual Studio 2008 をインストールする必要は無い」って書いてあるけど…Express版は使えないのだろうか。 _Microsoft Visual C++ - Wikipedia を眺めると、一応 Visual C++ 2008 Express に入ってるみたいだけど…。

むむ? よく見たら、 _Microsoft Visual C++ Compiler for Python 2.7 てのが用意されてるのか。Python用のアレコレをビルドするための環境がMicrosoftのサイトから正式に入手できるとは…。嘘だろ。マジかよ。Microsoft、やるなあ。 _Windows での Python 2.7, 3.4, 3.5 の拡張モジュールビルド環境 - Qiita によると去年から用意してくれたらしい。Microsoft、凄いぞ。素晴らしい。いや、ちょっとコレ、 _ヤンキー効果 が入ってる気もするけど…。でも、スゴイ。Microsoft、グッジョブ。

とりあえず Visual C++ Compiler for Python 2.7 を Windows10上でインストール。スタートメニューに「Microsoft Visual C++ Compiler Package for Python 2.7」というフォルダが出来て、ショートカットが登録された。「Visual C++ 2008 32-bit Command Prompt」を実行。DOS窓が開いた。
>where cl
C:\Users\ユーザアカウント名\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\bin\cl.exe
このへんにインストールされたのか…。「インストールしたユーザしか使えないよ」って感じかな…。 *1

cl /?
と打つと、色々出てくる。

とりあえず、この Visual C++ のコンパイラを使いたい時は、「Visual C++ 2008 *-bit Command Prompt」を実行してDOS窓開いてそこで作業すべし、ということらしい。

_Microsoft Visual C++ Compiler for Python 2.7はひとのためならず | site-hhs を参考に、 _lru-dict とやらをビルドすることで動作確認。lru-dict-1.1.6.tar.gz をDLして解凍して中に入って python setup.py build と打ったら、buildというフォルダが作られて中に色々なファイルが作られた。どうやらちゃんと使えそうな予感。

Nightly buildsがあるっぽい。 :

ここまで調べといてなんだけど。色々見ていたら、 _Unavailable nightly builds - Issue #66 - renpy/pygame_sdl2 で、「Nightly buildsは _http://nightly.renpy.org/current/ の一番上にあるよ」てな話が…。しかし、DLして中を覗いたけどバイナリが入ってるようには見えなかった。何だろう、コレは…。

githubからcloneしてビルドに挑戦してみる。 :

pygame_sdl2 がビルドできそうなのか、ダメ元でやってみる。再度書いておくけど、環境は Windows10 x64 + Python 2.7.13 32bit版。

「Visual C++ 2008 32-bit Command Prompt」を実行して、DOS窓を開いて…。

そのままだと C:\WINDOWS\system32\ というWindowsのシステム的に大変恐ろしい場所に居るので、cd でどこか問題無さげな場所に移動して。…Cドライブのルートにtempフォルダでも作ってそこに行こうかな。
mkdir C:\temp
cd C:\temp

適当なフォルダを作って、中に入って。
mkdir pygame_sdl2
cd pygame_sdl2

git clone で github からソース群をDL。git を使うので、git がインストールされてる必要有。もっとも、githubのページからzipをDLして解凍するのもアリかもしれんけど。
git clone https://github.com/renpy/pygame_sdl2.git

pygame_sdl2 というフォルダが作られた。中に入って、ファイルを確認。
cd pygame_sdl2
dir
色々なファイルがDLされた模様。Python でビルドやインストールに使われるらしい setup.py というファイルも見える。

ビルド時に必要になるという、pygame_sdl2_windeps も git clone でDL。
git clone https://github.com/renpy/pygame_sdl2_windeps
dir と打って確認すると、pygame_sdl2_windeps というフォルダが作られてる。中にはビルドに必要な色んなファイルが入ってるらしい。

いきなりインストールするのは怖いので、まずはビルドだけ試してみる。
python setup.py build

エラーが出た。cython がねえよ、って怒られてるような?

_Why don't have yet a release in PyPI? - Issue #39 - renpy/pygame_sdl2 によると、「Cythonをインストールしろや」って書かれてるな…。Cythonって何ですか?

_深入りしないCython入門 - Qiita
Pythonは処理速度は決して早くない、むしろ遅い部類である。 そこで、C/C++に変換することにより高速化しようというのがCythonである。

深入りしないCython入門 - Qiita より

そういうものらしい。

_WindowsでもCythonを使えるようにする。 - Qiita でインストール方法が紹介されてた。pip install でイケるらしい。
pip install cython
たぶんこれでインストールされたんじゃないかな。

もう一度ビルドにチャレンジ。
python setup.py build
警告がガンガン出てるけど、なんだかずらずらと表示しながら処理が進んでるっぽい…。ていうか、Windows上でやってるのに、
sh: sdl2-config: command not found
とか出てるけど、大丈夫なの? 自分の環境は mingw をインストールしているから sh.exe も入ってるけどさ…。

とりあえず、処理が終わったっぽい。build というフォルダが作成されて、中に色々なファイルが入ってた。SDL2.dll等のファイルも見える。

一応ビルドが通ったように見えなくもないし…。怖いけど…インストールしてみるか…。
python setup.py install
Python2.7インストールフォルダ\Lib\site-packages\ の中に、pygame_sdl2-2.1.0-py2.7-win32.egg というフォルダが作られて、中に色々入った、ようではある。

ここまでできたということは…。wheelパッケージとやらも作れるのだろうか?

_Python: Wheel でパッケージを配布する | CUBE SUGAR STORAGE によると、
python setup.py bdist_wheel
で作れるっぽいけど…。でも、site-packages\ の中は 〜.egg というフォルダが作られているんだよな…。

試してみたら、dist というフォルダの中に、 _pygame_sdl2-2.1.0-cp27-cp27m-win32.whl というファイルができていた。コレを使ってインストールできるのかしら。一応置いときます。手元の環境では pip install 〜.whl でインストールできたけど、他の環境でインストールできるかどうかは不明。

サンプルは無いのかな。 :

動作確認してみたいのだけど、どこかにサンプルは無いのかな?

_renpytom/rapt-pygame-example: Example of using RAPT to package Pygame(_sdl2) games. で、Android版のサンプルは公開されてるようだけど…。

_rapt-pygame-example/main.py を眺めると、最初のあたりで、
import pygame_sdl2
pygame_sdl2.import_as_pygame()

import pygame
みたいな感じで書いとけば、pygame_sdl2 が使われるっぽい。

_ウィンドウを表示する - 人工知能に関する断創録 を参考に、ウインドウを表示するだけのスクリプトを書いてみたり。

_helloworld_pygame_sdl2.py
# pygame_sdl2 test. window display only

import pygame_sdl2
pygame_sdl2.import_as_pygame()

import pygame
from pygame.locals import *
import os

SCREEN_SIZE = (640, 480)

def main():
    pygame.init()
    screen = pygame.display.set_mode( SCREEN_SIZE )
    pygame.display.set_caption('Hello pygame_sdl2')

    while True:
        screen.fill((0, 0, 255))  # screen fill Blue
        pygame.display.update()
        
        for event in pygame.event.get():  # check event
            if event.type == QUIT:
                return
            if (event.type == KEYDOWN and
                event.key  == K_ESCAPE):  # push Escape key ?
                return

if __name__ == '__main__':
    main()

helloworld_pygame_sdl2_ss.png

一応動いたことは動いた、ような気がする。しかしコレだけでは見た目が pygame と変わらん…。何をどうしたら pygame_sdl2 で動いてるってことが分かるのやら。

画像描画を試してみたり。 :

画像ファイルを読んで描画する処理を試してみたり。

_draw_image.py
_ufo.png
_bg.png
# draw image by pygame_sdl2

try:
    import pygame_sdl2
    pygame_sdl2.import_as_pygame()
except:
    print "pygame_sdl2 not available"

import pygame
import pygame.image
# from pygame.locals import *

SCREEN_SIZE = (640, 480)

def main():
    pygame.init()
    screen = pygame.display.set_mode( SCREEN_SIZE, pygame.HWSURFACE | pygame.DOUBLEBUF )
    pygame.display.set_caption('draw image by pygame_sdl2')

    sysfont = pygame.font.SysFont(None, 64)
    textsurface = sysfont.render("Hello world", True, (255, 255, 255))
    
    bg = pygame.image.load('bg.png').convert()
    image = pygame.image.load('ufo.png').convert_alpha()

    x = 10
    y = 10
    dx = 2
    dy = 1
    bgx = 0
    clock = pygame.time.Clock()
    cnt = 0

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                return
            if (event.type == pygame.KEYDOWN and event.key  == pygame.K_ESCAPE):
                return

        if x + dx < 0 or x + dx + image.get_width() >= SCREEN_SIZE[0]:
            dx *= -1
        if y + dy < 0 or y + dy + image.get_height() >= SCREEN_SIZE[1]:
            dy *= -1
        x += dx
        y += dy
        bgx -= 3
        if bgx < -640:
            bgx = 0
        
        # draw bg image
        screen.blit(bg, (bgx,0))
        screen.blit(bg, (bgx + SCREEN_SIZE[0],0))
        
        screen.blit(image, (x, y))        # draw image
        screen.blit(textsurface, (0, 0))  # draw text image
        
        # pygame.display.update()
        pygame.display.flip()
        
        clock.tick(60)  # 60fps
        
        cnt += 1
        if cnt % 180 == 0:
            print str(clock.get_fps()) + " FPS"

if __name__ == '__main__':
    main()

draw_image_ss.png

一応描画されたが、妙なメッセージが表示される。
> python draw_image.py
libpng warning: iCCP: known incorrect sRGB profile

libpng のバグ(?)らしい。

_Libpng errors - ArchWiki
_Cocos Studioで生成した.csbをcocos2d-xに読み込んだ際に"libpng warning: iCCP: known incorrect sRGB profile" - Qiita
_dvipdfmxで「warning: iCCP: known incorrect sRGB profile」というエラーが出る場合の対処方法 - Qiita

ImageMagickの convert を使って画像ファイル内から余計な情報を削除せよ、とのこと。
convert -strip input_file output_file

しかし、ImageMagick 7.0.3-2 の magick.exe を使ったら上記のオプションが反応しなかった。ImageMagick 6.9.1-10 の convert なら処理してくれたのだけど…。

書き方がいかんかったらしい。以下の書き方をしたら処理してくれた。
magick input_file -strip output_file

そもそもSDL2って何だろう。 :

SDL1.2 に比べて SDL2 は何が違っているのだろう。

_SDL2の使い方 - mirichiの日記
_SDL2で出来る事メモごく一部 - nyaocatのがんばるブログ
_SDL 2.0と1.2に関するメモ
_SDL 1.2から2.0への移行
_SDL2の紹介

色々変わったらしいが…とにかく描画が速くなった、と思っておけばいいのかな。

pygameよりpygame_sdl2のほうが遅いらしい。 :

_Pygame_SDL2 runs slower when large quantity of sprites are drawn - Issue #36 - renpy/pygame_sdl2 で気になる話が。pygameよりpygame_sdl2のほうが遅いらしい…。

ここまで試したのに、なんじゃそりゃって感じ…。

でもまあ、おかげで Microsoft Visual C++ Compiler for Python 2.7 の存在を知ることができたのは幸いというか。

*1: 一応、ダウンロードページの下のほうに、「全ユーザで使える状態にしたいならオプションを指定してインストールすればできるよ」と書いてある。

2017/04/23() [n年前の日記]

#1 [python] pygame_sdl2についてまだ調べてたり

pygame_sdl2についてまだ調べてたり。環境は Windows10 x64 + Python 2.7.13 32bit版。

ちなみに pygame_sdl2ってのは、Python用の2Dゲームライブラリ pygame を、SDL ではなくて SDL2 を使って動かせるようにしたライブラリ、という説明で合ってるのかな。

実はPyPIにアップロードされてた。 :

_昨日 は、Windows用バイナリの入手先すら見つけられなくて、githubから _pygame_sdl2のソース を落としてきてビルドして、とかやってたのだけど。

その後ググってたら、 _pygame_sdl2 2.1.0 : Python Package Index で .whl(wheel形式のパッケージ)公開ページを発見。公式の _PyPI(Python Package Index) では公開されてないけど、 _テスト版を公開してるPyPI にはアップロードされてた模様。わざわざビルドしなくても、コレを落としてインストールすれば、ひとまず試せたのかもしれない…。

でも、アップロード日が2016/01/24と結構古い。githubからソースをDLしてビルドするほうが最新の…。ってわけでもないか。githubの各ソースもアップロード日がそこそこ古いし、ビルドすると 2.1.0 という同じバージョン番号になるし。どっちが新しいのか、あるいは両方同じなのか、ちと分かりませんな。

ちなみに、一応インストールの仕方を書いとくけど。 _pygame_sdl2 2.1.0 : Python Package Index の下のほうから、自分の環境に合った .whl をDLして、
pip install 〜.whl
でインストールできますよ、とメモ。

自分でビルドした版もインストールできた。 :

昨日作った _pygame_sdl2-2.1.0-cp27-cp27m-win32.whl を、VMware Player + Windows10評価版上で、Python 2.7 32bit版をインストールしてから pip install 〜 をしたら一応インストールできたし動いてくれた。

ただ、VMware Player上では、30FPSちょっとしか出ない…。

pip uninstall 〜 でアンインストールしてから、TestPyPI で公開されてる .whl を入れ直して確認してみたけど、そちらでも30FPSちょっとしか出ない…。

VMware Player上だから遅くなるのか、それともわざわざ遅くなるようなスクリプトの書き方をしちゃっているのか。何にせよ、これじゃ話にならない。

確実にpygame_sdl2を使ってることが分かるようにしたい。 :

pygame_sdl2 は、「pygameって便利だよね」「pygameのAPIはそのままに、SDLを使ってるところをSDL2に置き換えた版が欲しいよね」という考えで作られたライブラリなので、スクリプトの最初に2行ほど追加するだけで、後は pygameとほぼ同じ書き方で使えるようになっていて。
import pygame_sdl2
pygame_sdl2.import_as_pygame()

import pygame
from pygame.locals import *

SCR_SIZE = (640, 480)

pygame.init()
pygame.display.set_mode(SCR_SIZE)
pygame.display.set_caption("SDL2 render test")
...

これはこれでおそらく便利だろうけど。逆に、「コレって本当に pygame_sdl2 を使ってるのかな?」「実は pygame が呼び出されてたりしないのかな?」という不安もあり。

そういう時は、pygame.hoge と書かれてる部分を pygame_sdl2.hoge で置き換えて書けばいいようで。
import pygame_sdl2
from pygame_sdl2.locals import *

SCR_SIZE = (640, 480)

pygame_sdl2.init()
pygame_sdl2.display.set_mode(SCR_SIZE)
pygame_sdl2.display.set_caption("SDL2 render test")
...

ただ、pygame にはあるけど pygame_sdl2 では実装されてないAPIがあるらしいので…。もし、実行して「そんなメソッドはないよ」とエラーが出たら、「ああ、コレは実装されてないのか」と諦めながら別の方法を探す、みたいなことになるだろうなと。

pygame_sdl2.renderが強力っぽい気配を感じる。 :

pygameはソフトウェアレンダリングを前提にした作りなので、画面に何か描画する際も、メインメモリ上に画像に相当するバッファを持って、領域を書き換えて、書き換えて、最後に画面表示用の領域に転送して、てな作りになってるはずで。例えば screen.blit() のように blit という単語が出てくるあたりからして、おそらくそうであろうと。

なので、pygame_sdl2 を使った場合も、pygame と同じノリで描画指定をしていくと、パフォーマンスが出ないどころか、逆に pygame より余計なことをしている分、かえって遅くなる可能性もありそうだなと。もちろん、pygame_sdl2 の作者さんは、遅くならないようにそのあたり気づいた範囲で工夫しているはずではあるけれど…。

で。そのあたり、スクリプトソースを書く側でも、速度が出るよう意識しつつ書きたい場合は、ひょっとすると pygame_sdl2.render を使うといい、のかもしれない。いや、ちょっと自信無いけど。たぶんそうじゃないのかなと。

pygame_sdl2.render を使う場合は、例えば画像ファイルを読み込む際も、pygame.image.load() ではなく load_texture() を使うようだし。名前からして、テクスチャとかレンダリングとか、つまりはGPUを意識した機能っぽい…よなと。外してるかもしれんけど。

ただ、pygame_sdl2.render について、使い方を説明したドキュメントが全く存在していなくて…。使用例として見つかったのは、以下のソースぐらいで。

_pygame_sdl2/test_render.py
  • r = pygame_sdl2.render.Renderer(vsync=False) で、画面全体を描画するための何かを確保して。
  • bg = r.load_texture('paper.jpg') で、テクスチャ画像を読み込んで。
  • r.clear((0,0,0)) で、全体を消去。
  • bg.render() で、描画。
  • r.render_present() で、描画内容を画面に反映。
このくらいしか予想できなかった。

しかし、このやり方だと、画像が画面全体に引き延ばされた形で描画されてしまう…。例えば、96x96の画像が、640x480の画面一杯に拡大表示されてしまったり。

_render_test.py
_ufo.png
_bg.png

render_test_ss.png

位置やサイズの指定はどうすればいいのやら。

おそらく pygame_sdl2 の作者さんは、「pygame をそのまま pygame_sdl2 に置き換えて高速に動くのが理想」みたいなノリで作ってるはずだから、pygame には存在していない pygame_sdl2.render については積極的に解説されない可能性もあるわけで。「pygameに無い機能をガンガン使われてしまうようでは pygame_sdl2 の存在理由が怪しくなる」と思っていてもおかしくないよなと…。

実は筋が良くないのかもしれない。 :

pygame の中身をSDL2に置き換えれば更にグッドなライブラリに、という発想は一見するとヨサゲに思えるけれど。SDL2の何を使ってその処理が行われるのか、てなあたりが見えにくくなるというデメリットもあるなと。結果、SDL2には合ってない、パフォーマンスが出ない書き方をしているのでは、という不安が拭えない。

もちろん、pygame時代に書かれた膨大な既存スクリプトを最低限の手直しでひとまず一応は動かせるように、という目的が最優先で、パフォーマンスが出る出ないは優先順位として低いのだから、これはこれで全然アリなライブラリ、ではあるのだけど。しかし欲を出して、せっかくSDL2を使ってるのだからソレに見合ったパフォーマンスを…と思った場合は、ちょっと筋が悪いライブラリに思えるかもしれないなと。

まあ、そういう時は、pygame_sdl2 じゃなくて pysdl2 を選ぶべきかもしれず。

とは言えこのへん、「そうまでしてPythonでゲーム書かなくてもいいじゃん。どうせ使い物にならねえだろ」「ゲーム作りたいならまずは Unity でも触るべき」と身も蓋も無いことを言われそうでもあり…。 *1

このあたり、「ゲームを作りたい」のか、それとも「ゲーム制作を通じてせめて少しは楽しくプログラミングを学びたい」のか、てな違いもありそうな。

*1: 自分も、(Windows環境なら) Ruby + DXRuby を推すし…。Pythonの2Dゲーム制作ライブラリは、どれもこれもちょっとアレなところがあるように感じているので、別に無理して Python 使わなくてもいいんじゃないか、みたいなソレが…。

#2 [windows] Windows10 Creators Update版の仮想PCイメージを試用

_Windows 10 Creators Update/Visual Studio 2017が入った開発向け評価用仮想マシンが公開 - 窓の杜 という記事を読んで興味が湧いたので試用してみたり。

とりあえず、vmware版をDLしてみたり。zipを解凍すると、以下の3つのファイルが入ってた。
WinDev1704Eval.mf
WinDev1704Eval.ovf
WinDev1704Eval-disk1.vmdk

VMware Player から開いてみたら、最初は元ファイルをインポートして、丸々コピーしたファイル群を新規に作成するようで。

仮想HDDのサイズは127GB。初期の実サイズは25GBだけど、自分の環境ではちと厳しい…。でも、Visual Studio 2017 等も入ってるから、仕方ないよな…。

起動してみたら、英語版のWindows10だった。スタートボタンをクリックして、ギアアイコンをクリックして設定画面を開いて、Time & language のあたりを選んで設定していけば日本語表示になるのかな。アレコレクリックして「Japan」だの「日本語」だのを選んだり、Language pack とやらをDLしてみたら日本語表示になってくれた。DLに結構時間がかかるけど…。

パスワードは設定されてないっぽい。開発環境一式が入ってる試用版という扱いだから、かな…。

余談。 :

何はともあれ、こういうソレが用意されてるのはいいことだなと。*NIXやMacを使ってるアプリ制作者が、「俺、Windows持ってないから対応できねえ」的な言い訳をしにくくなるし、などと思ったり思わなかったり。

考えてみれば…。Linux系は仮想PC上でインストールすれば、あるいは Windows10 なら Bash on Ubuntu on Windows があるからお試しは容易だし。Windowsも、コレを使えばお試しができるわけだけど。

Macだけだよな…お試し版が無いのは…。「Macもサポートしろ」とか言われても困るよな…。 *1

とか書いてるうちにふと気になって、各OSのシェアをググってみたら…。

_Windows 7/10/XPが増加 - 12月OSシェア | マイナビニュース

うーん。

*1: と思ったけどBSD系を使えばある程度どうにかなるのだろうか? でも、超絶魔改造して、もはや別物になってるのがMacだったりしないの? そうでもないの?

#3 [anime] 「エロマンガ先生」2話を視聴

「エロマンガ先生」というラノベ原作のアニメ版を視聴。しかし、なんという作品タイトル…。

それはさておき、本当に妹専用アニメーターがスタッフリストに並んでいる…。スゲエ…。や、昔のディズニーがやってたことだから制作体制としては全然これっぽっちも何一つおかしくはないのだけど。日本もいよいよそんな作り方にチャレンジする時代になってきたのか、しかもTVアニメで…などと思うと、これはこれでなんだか目頭が熱く…。ようやく半世紀前のディズニーの背中が見えてきた、のであろうか…。よりにもよって「エロマンガ先生」というタイトルで…。とっても日本っぽいなあ…。

2017/04/24(月) [n年前の日記]

#1 [python] pysdl2について調べていたり

pygame_sdl2 についてアレコレ調べていたけど、どうも筋が悪い気がしてきたので、pysdl2について調べ始めたり。

pysdl2てのは…。Pythonから、SDL2というマルチメディア向けのライブラリを呼び出して使えるライブラリ。要するにゲームが作れるライブラリ。

pysdl2の概要を一応メモ。 :

公式ドキュメントによると、pysdl2は2つのパッケージで構成されているそうで。
  • sdl2。SDL2のAPI(機能?)と1:1で対応してる。
  • sdl2.ext。sdl2だけでは各機能が低レベル過ぎて記述が面倒臭くなるので、もう少し簡単に書けるようにsdl2を拡張する。

更に、「全部 Python で実装してある」とも書いてあった。Cで書いてあるわけじゃないから、例えば sdl2.ext を極力使ったほうが処理時間が短くなる、というわけでもないらしい。

チュートリアルを勉強。 :

とりあえず、公式ドキュメントのチュートリアルから ―― hello world から始めたり。まずはウインドウを表示する。できれば画像も表示する。

_Hello World - PySDL2 0.9.5 documentation

公式ドキュメントのソレは sdl2.ext を利用した版になっている。日本語コメントをガシガシ追加しつつ写経。

もし、このスクリプトを動かしたい人が居たら…。動作には画像が必要なので、 _hello.png をDLして、res というフォルダを作ってその中に入れといてもらえれば、と。

_helloworld_pysdl2_ext.py
u"""
PySDL2 (pysdl2.ext) のテスト.

ウインドウを表示して画像(スプライト)を表示

動作確認環境:
Windows10 x64 + Python 2.7.13 32bit + PySDL2 0.9.5
"""

import sdl2.ext

# sdl2.ext.Resource() で、リソース(画像等)の格納場所を指定できる
# res という名前のフォルダを作って、中に画像(hello.png)を入れた
RESOURCES = sdl2.ext.Resources(__file__, "res")

# PySDL2 の初期化
sdl2.ext.init()

# ウインドウを作成。タイトル文字列とウインドウサイズを指定
window = sdl2.ext.Window("Hello World", size=(640, 480))

# 作成したウインドウを表示
window.show()

# スプライトを作成。hello.png を読み込んで渡す
factory = sdl2.ext.SpriteFactory(sdl2.ext.SOFTWARE)
sprite = factory.from_image(RESOURCES.get_path("hello.png"))

# スプライトの表示位置を指定
sprite.position = 24, 24

# スプライトを描画
spriterenderer = factory.create_sprite_render_system(window)
spriterenderer.render(sprite)

# イベントループ。閉じるボタンが押されるまで待つ(ループする)
processor = sdl2.ext.TestEventProcessor()
processor.run(window)

# 終了処理。今まで確保したアレコレを破棄する
sdl2.ext.quit()

以下で実行。
python helloworld_pysdl2_ext.py
helloworld_pysdl2_ext_ss.png

ウインドウが表示されて、画像も表示された。また、閉じるボタンをクリックすれば、ウインドウを閉じることができた。

ところで、ソースを眺めると…。

「sdl2.ext.SpriteFactory()って何だ?」
「factory.create_sprite_render_system()って何?」
「sdl2.ext.TestEventProcessor()って何なの? 何をしてくれるの?」

という気分になりませんかね。自分はなりました。

sdl2だけで書いてみる。 :

sdl2.extを使わずに、sdl2だけで書いてみたり。pysdl2をインストールしたフォルダ内の、examples\sdl2hello.py が参考になるかと。

ちなみに、公式のサンプルはbmpファイルを読み込んでいたので sdl2 だけで処理ができていたのだけど。欲を出して(?)、うっかり png を読み込もうとしたら…。pngの読み込みには、SDL2_image.dll が必要になるのですな…。

_helloworld_pysdl2.py
u"""
PySDL2 のテスト.

ウインドウを表示して画像(スプライト)を表示
sdl2.ext は使わず、sdl2 を使って処理をしてみる。
ソフトウェア的に画像転送してるので処理としては遅いらしい。

png画像の読み込みには sdl2.sdlimage が利用できるらしい?

動作確認環境:
Windows10 x64 + Python 2.7.13 32bit + PySDL2 0.9.5
"""

import sdl2
import sdl2.sdlimage
import ctypes
import os


# PySDL2 の初期化
sdl2.SDL_Init(sdl2.SDL_INIT_VIDEO)

# ウインドウを作成して表示。
# タイトル文字列、表示位置、ウインドウサイズを指定している
window = sdl2.SDL_CreateWindow(b"Hello World",
                               sdl2.SDL_WINDOWPOS_CENTERED,
                               sdl2.SDL_WINDOWPOS_CENTERED,
                               640, 480,
                               sdl2.SDL_WINDOW_SHOWN)

# ウインドウのサーフェイスを取得
windowsurface = sdl2.SDL_GetWindowSurface(window)

# 画像ファイルのパスを取得
filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                        "res", "hello.png")

# 画像を読み込み
# image = sdl2.SDL_LoadBMP(filepath.encode("utf-8"))
image = sdl2.sdlimage.IMG_Load(filepath.encode("utf-8"))

# 画像サイズを取得してみる
# ctypes を利用しているため、
# image には SDL_Surface ではなく LP_SDL_Surface が入ってる
# 故に image.w では取得できないが image.contents.w にすれば取得できる
w = image.contents.w
h = image.contents.h
print("w, h = %d, %d" % (w, h))

# 画像の転送先領域を指定
x = 24
y = 36
w = image.contents.w
h = image.contents.h
dst_rect = sdl2.rect.SDL_Rect(x, y, w, h)

# 画像を、ウインドウサーフェイスに転送
sdl2.SDL_BlitSurface(image, None, windowsurface, dst_rect)

# ウインドウサーフェイスを更新
sdl2.SDL_UpdateWindowSurface(window)

# RGBサーフェイスを解放
sdl2.SDL_FreeSurface(image)

# イベントループ。閉じるボタンが押されるまで待つ(ループする)
event = sdl2.SDL_Event()
running = True
while running:
    while sdl2.SDL_PollEvent(ctypes.byref(event)) != 0:
        if event.type == sdl2.SDL_QUIT:
            running = False
            break
    sdl2.SDL_Delay(10)

# 終了処理。今まで確保したアレコレを破棄する
sdl2.SDL_DestroyWindow(window)
sdl2.SDL_Quit()

実行してみる。
python helloworld_pysdl2.py
helloworld_pysdl2_ss.png

画像が表示された。

sdl2.ext を使った版と比べると、sdl2 だけで書いた版は、やっぱり面倒臭い感が。画像をサーフェイスに読み込むだけで一体何行書くんだよ、みたいな。

かといって、そこ(画像の読み込みと描画)を「sdl2.ext.SpriteFactory()」でまとめられても、ちと困るよなと…。

こっちは画像をサーフェイスに読み込みたいわけで、スプライトが欲しいわけではないのですよ。なのにどうしてそこでスプライト云々のクラスが登場するのかと。しかもそのスプライトって一体何ができるスプライトなのか、そこもわかんねーよ、みたいな。まとめ過ぎ・包み過ぎだろうと。

印象。 :

pysdl2 も pysdl2 で、ちと筋が悪いような気もしてきたり。

sdl2は、SDL2の各機能を呼び出すことだけを目的にして作ってあるので、これはこれで問題無いのだけど。

sdl2.extは、個人的にどうもしっくりこない…。と言うのも、ゲームプログラムのロジック部分まで無頓着にまとめてしまっている上に、各クラスやメソッドが中で何をしてくれそうなのかよく分からなくて。

結果、謎の呪文だらけのブラックボックス、それも、sdl2.extの作者さんが想定した範囲内でしか使い道がないのであろう、頭が固いガチガチなライブラリになってしまっている気配が…。いやまあ、せっかくPythonで書いてあるのだから、どんどんソースを見て何をしてるか理解すればいいんだろうけど。逆に言うと、ソース見なきゃ役割が分からない、ってのもどうなんだと。

おまじない満載の「これしかできません」的ブラックボックスを使いたいなら、それこそUnityでも使ったほうが…。ウンザリするほどおまじないを暗記させられるけど、その分リターンも期待できそうだよなと。

しかし、Unity 利用時のようなゲーム映像が得られるならともかく、Python + SDL2 程度で、そこでしか通用しないおまじないを覚えさせてみてどうするんだと。コレの使い方を覚えたところで、他で使い道が無い。汎用性が無い。発展が期待できない。使い方を勉強することの旨味が少ない…。 *1

さりとて、sdl2.ext をスルーして sdl2 だけで書こうとすると、やっぱりアレコレ面倒臭いと感じてしまうのも間違いなくて。いやまあ、PythonからSDL2を呼べるだけでも有難いことだけど、でもやっぱりまどろっこしい。画像を一つ出すだけなのにどうしてこんなに行数が必要に、みたいなところが。

もっともこのあたり、DXRuby を普段使わせてもらっているから感じてしまうのだろうけど…。DXRubyに触れてなかったら「この手のライブラリはこんなもんだ」とうっかり思い込んでいたかもしれず。

それにしても…。DXRubyに比べると、SDL や SDL2 を呼び出す系のライブラリは、えてしてラップの仕方・包み方がおかしいところがあるなと。「何故ここを包まない?」と「どうしてここまで包むんだ?」が時々出現してしまう、そんな印象が。(※ 感想には個人差があります。) *2

pongのチュートリアルも勉強。 :

一応、pong game のチュートリアルも写経したので、もったいないから貼っときます。

_The Pong Game - PySDL2 0.9.5 documentation

_pong_pysdl2_6.py
u"""
PySDL2 のテスト.

ポンゲーム。

動作確認環境:
Windows10 x64 + Python 2.7.13 32bit + PySDL2 0.9.5
"""

# sdl2 の全機能にアクセスしたいので、sdl2 もインポートする

import sys
import sdl2
import sdl2.ext

# 白色を定義
WHITE = sdl2.ext.Color(255, 255, 255)


class SoftwareRenderer(sdl2.ext.SoftwareSpriteRenderSystem):

    u"""ソフトウェアレンダラークラス."""

    def __init__(self, window):
        u"""コンストラクタ."""
        super(SoftwareRenderer, self).__init__(window)

    def render(self, components):
        u"""描画処理."""
        # 画面全体を黒く塗りつぶしてから、本来の描画処理を行うようにしている
        sdl2.ext.fill(self.surface, sdl2.ext.Color(0, 0, 0))
        super(SoftwareRenderer, self).render(components)


class MovementSystem(sdl2.ext.Applicator):

    u"""各スプライトの移動処理を担当するクラス."""

    def __init__(self, minx, miny, maxx, maxy):
        u"""コンストラクタ."""
        super(MovementSystem, self).__init__()
        self.componenttypes = Velocity, sdl2.ext.Sprite
        self.minx = minx
        self.miny = miny
        self.maxx = maxx
        self.maxy = maxy

    def process(self, world, componentsets):
        u"""スプライトの移動処理."""
        for velocity, sprite in componentsets:
            # 速度を持っているスプライトに対して、
            # 画面内から飛び出さないように位置を変更する
            swidth, sheight = sprite.size
            sprite.x += velocity.vx
            sprite.y += velocity.vy

            sprite.x = max(self.minx, sprite.x)
            sprite.y = max(self.miny, sprite.y)

            pmaxx = sprite.x + swidth
            pmaxy = sprite.y + sheight
            if pmaxx > self.maxx:
                sprite.x = self.maxx - swidth
            if pmaxy > self.maxy:
                sprite.y = self.maxy - sheight


class CollisionSystem(sdl2.ext.Applicator):

    u"""アタリ処理クラス."""

    def __init__(self, minx, miny, maxx, maxy):
        u"""コンストラクタ."""
        super(CollisionSystem, self).__init__()
        self.componenttypes = Velocity, sdl2.ext.Sprite
        self.ball = None
        self.minx = minx
        self.miny = miny
        self.maxx = maxx
        self.maxy = maxy

    def _overlap(self, item):
        u"""ボールがプレイヤー(パドル)と当たってるか調べて返す."""
        pos, sprite = item
        if sprite == self.ball.sprite:
            # 見ている相手がボールなのでアタリ判定はしない
            return False

        # 相手の範囲とボールの範囲を取得
        left, top, right, bottom = sprite.area
        bleft, btop, bright, bbottom = self.ball.sprite.area

        return (bleft < right and bright > left and
                btop < bottom and bbottom > top)

    def process(self, world, componentsets):
        u"""アタリ判定処理."""
        # ボールと当たってるスプライトを返す
        collitems = [comp for comp in componentsets if self._overlap(comp)]
        if collitems:
            # 何かに(プレイヤーに)当たったらボールのx速度を反転する
            self.ball.velocity.vx = -self.ball.velocity.vx

            # 当たった位置でy速度を変更
            sprite = collitems[0][1]
            ballcentery = self.ball.sprite.y + self.ball.sprite.size[1] // 2
            halfheight = sprite.size[1] // 2
            stepsize = halfheight // 10
            degrees = 0.7
            paddlecentery = sprite.y + halfheight
            if ballcentery < paddlecentery:
                factor = (paddlecentery - ballcentery) // stepsize
                self.ball.velocity.vy = -int(round(factor * degrees))
            elif ballcentery > paddlecentery:
                factor = (ballcentery - paddlecentery) // stepsize
                self.ball.velocity.vy = int(round(factor * degrees))
            else:
                self.ball.velocity.vy = - self.ball.velocity.vy

        # ボールが上下の壁に当たったらボールのy速度を反転
        if (self.ball.sprite.y <= self.miny or
                self.ball.sprite.y + self.ball.sprite.size[1] >= self.maxy):
            self.ball.velocity.vy = - self.ball.velocity.vy

        if (self.ball.sprite.x <= self.minx or
                self.ball.sprite.x + self.ball.sprite.size[0] >= self.maxx):
            self.ball.velocity.vx = - self.ball.velocity.vx


class Velocity(object):

    u"""速度保持クラス.

    コレを持っているクラスは速度があるので、位置が変化するものとして扱われる。
    """

    def __init__(self):
        u"""コンストラクタ."""
        super(Velocity, self).__init__()
        self.vx = 0
        self.vy = 0


class TrackingAIController(sdl2.ext.Applicator):

    u"""敵の思考ルーチン."""

    def __init__(self, miny, maxy):
        u"""コンストラクタ."""
        super(TrackingAIController, self).__init__()
        self.componenttypes = PlayerData, Velocity, sdl2.ext.Sprite
        self.miny = miny
        self.maxy = maxy
        self.ball = None

    def process(self, world, componentsets):
        u"""思考処理."""
        for pdata, vel, sprite in componentsets:
            if not pdata.ai:
                continue

            centery = sprite.y + sprite.size[1] // 2
            if self.ball.velocity.vx < 0:
                # ball is moving away from the AI
                if centery < self.maxy // 2:
                    vel.vy = 3
                elif centery > self.maxy // 2:
                    vel.vy = -3
                else:
                    vel.vy = 0
            else:
                bcentery = self.ball.sprite.y + self.ball.sprite.size[1] // 2
                if bcentery < centery:
                    vel.vy = -3
                elif bcentery > centery:
                    vel.vy = 3
                else:
                    vel.vy = 0


class PlayerData(object):

    u"""パドルががCPUかプレイヤーかを保持するクラス."""

    def __init__(self):
        u"""コンストラクタ."""
        super(PlayerData, self).__init__()
        self.ai = False


class Player(sdl2.ext.Entity):

    u"""プレイヤー(パドル)クラス."""

    def __init__(self, world, sprite, posx=0, posy=0, ai=False):
        u"""コンストラクタ."""
        self.sprite = sprite
        self.sprite.position = posx, posy  # スプライトの位置を設定
        self.velocity = Velocity()         # 速度を持たせる
        self.playerdata = PlayerData()
        self.playerdata.ai = ai            # プレイヤーにするか、CPUにするかを指定


class Ball(sdl2.ext.Entity):

    u"""ボールクラス."""

    def __init__(self, world, sprite, posx=0, posy=0):
        u"""コンストラクタ."""
        self.sprite = sprite
        self.sprite.position = posx, posy
        self.velocity = Velocity()


def run():
    u"""メイン処理."""
    # PySDL2 の初期化
    sdl2.ext.init()

    # ウインドウ作成と表示
    window = sdl2.ext.Window("The Pong Game", size=(800, 600))
    window.show()

    # ワールド作成
    world = sdl2.ext.World()

    # 移動処理担当クラスを生成。左上と右下の座標を渡している
    movement = MovementSystem(0, 0, 800, 600)

    # アタリ判定処理クラスを生成。左上と右下の座標を渡している
    collision = CollisionSystem(0, 0, 800, 600)

    # ソフトウェアレンダラーを生成
    spriterenderer = SoftwareRenderer(window)

    # 敵AI制御クラスを生成
    aicontroller = TrackingAIController(0, 600)

    # 敵AI制御クラス、移動処理担当クラス、
    # アタリ判定クラス、レンダラーをワールドに追加
    world.add_system(aicontroller)
    world.add_system(movement)
    world.add_system(collision)
    world.add_system(spriterenderer)

    # 白色のスプライトを3つ作成
    factory = sdl2.ext.SpriteFactory(sdl2.ext.SOFTWARE)
    sp_paddle1 = factory.from_color(WHITE, size=(20, 100))  # プレイヤー1
    sp_paddle2 = factory.from_color(WHITE, size=(20, 100))  # プレイヤー2
    sp_ball = factory.from_color(WHITE, size=(20, 20))   # ボール

    # プレイヤーを2つ生成
    player1 = Player(world, sp_paddle1, 0, 250)
    player2 = Player(world, sp_paddle2, 780, 250, True)

    # ボールを生成
    ball = Ball(world, sp_ball, 390, 290)
    ball.velocity.vx = -3

    collision.ball = ball
    aicontroller.ball = ball

    running = True     # ループ管理用フラグ

    # メインループ
    while running:
        events = sdl2.ext.get_events()  # イベント取得
        for event in events:
            if event.type == sdl2.SDL_QUIT:
                # 閉じるボタンが押されたらメインループを終了させる
                running = False
                break
            if event.type == sdl2.SDL_KEYDOWN:
                # キーが押された
                if event.key.keysym.sym == sdl2.SDLK_UP:
                    # 上キーだった
                    player1.velocity.vy = -3
                elif event.key.keysym.sym == sdl2.SDLK_DOWN:
                    # 下キーだった
                    player1.velocity.vy = 3
            elif event.type == sdl2.SDL_KEYUP:
                # キーが離された
                if event.key.keysym.sym in (sdl2.SDLK_UP, sdl2.SDLK_DOWN):
                    # 上キーか下キーだった
                    player1.velocity.vy = 0

        # 時間待ち
        sdl2.SDL_Delay(10)

        # ワールドの処理
        world.process()

        # ウインドウのグラフィックバッファを更新
        # window.refresh()

    sdl2.ext.quit()  # SDL2関係の終了処理

    return 0         # 終了コードを返す

if __name__ == "__main__":
    # このスクリプトが単体で呼ばれた時に、ここが処理される
    ret = run()    # メイン処理呼び出し
    sys.exit(ret)  # pythonスクリプトを終了

実行。
python pong_pysdl2_6.py
pong_pysdl2_6_ss.png

これもソースを眺めると…。

「sdl2.ext.World()って何だよ?」
「sdl2.ext.Applicator って何?」

という気分に。やっぱり筋が悪い気が…。いやまあ、「だったら sdl2 だけ使って書いてりゃいいだろ!」って言われるのは分かってますけど。

チュートリアルなのに、ソフトウェアレンダラーを使ってる気配がするのも、なんだか…。だったら pygame でいいじゃん…。SDL2の旨味って、ハードウェア描画が使いやすくなったことじゃなかったのか…。ライブラリの売りを自らスポイルしていくチュートリアルってのも、意味が分からん…。

*1: 例えば、HTMLタグを覚えれば何かしら応用が利くけど、ホームページビルダーの使い方を覚えてもそこから発展しないじゃん、みたいな話かも。
*2: とは言え自分もそのへん全然言えないですけど。クラスを一つ作るたびに「このメソッドの引数はよくない…が、もっとヨサゲなまとめ方が思いつかない…」と悩んだりするわけで。

#2 [prog][dxruby][neta] pysdl2の作者さんがDXRubyを作っていたら

もし、pysdl2の作者さんが DXRuby を作っていたら…。「ウインドウを表示して、閉じるボタンをクリックしたらウインドウが閉じる」だけのスクリプトも、こうなっていたかもな、などと妄想を。
require 'dxruby'

dxruby.ext.init
window = dxruby.ext.window("Hello world", 640, 480)
window.show

world = dxruby.world

running = true
while running do
  dxruby.ext.get_events.each do |event|
    if event.type == dxruby.DXRUBY_QUIT:
      running = false
      break
    end
  end

  world.process

  window.update
  dxruby.ext.delay(16)
end

dxruby.ext.quit

実際の DXRuby ではこうなりますけど。
require 'dxruby'

Window.loop do
  # game main routine
end

なんというか…。このあたり、スクリプトを書く側の思考をシミュレーションしてるかしてないか、という違いがあるように思えてくるわけで。

「俺、ウインドウを表示したいんだ」 ―― そりゃそうだろう。ゲーム画面を表示するためにはウインドウを表示しなきゃいけないし。

「メインループが欲しいなあ」 ―― そりゃそうだろう。60FPSのフレームベースで動くゲームを作りたかったら、メインループは必須だし。

「閉じるボタンをクリックしたらウインドウが閉じてほしい」 ―― そりゃそうだろう。閉じるボタンをクリックしても無反応ではユーザが困惑するよ。

「ウインドウを表示する」「メインループがある」「閉じるボタンをクリックしたらウインドウが閉じる」 ―― こんなの当たり前に実現していて欲しいことで。

そのあたりを考えると…。

ライブラリの初期化、ウインドウの生成、ウインドウの表示、スクリプト終了時の後始末をわざわざ書くのはどうなのだろうと。スクリプトを書く側は、「ゲーム画面を表示できるウインドウが欲しい」のであって、「ライブラリを初期化」「ウインドウ生成」「ウインドウの表示を明示的に指定」「終了時の後始末」をしたいわけじゃないよなと。

while ループをメインループにして、60FPSで回るように時間待ち、てなあたりをわざわざ書かせるのはどうなのかと。昔風の2Dゲームを作りたいならメインループは必須だし、60FPSに調整する処理も必須だよなと。

イベントを取得して、イベントの種類を調べて、閉じるボタンがクリックされたか判別して処理を終了するあたりを毎回書かせるのもどうなのかと。閉じるボタンをクリックしたらフツーはウインドウが閉じることを誰でも期待するもんじゃないのかと。

ということで、DXRuby のソレは、実に当たり前に感じられる仕様だよなと。フツーにそこにあって当たり前のことは、ライブラリ側で済ませてあるというか。

スクリプトを書く側は、メインループの中身を書きたいと思ってるはずで。そこに何を書くかで悩むあたりが、ゲームプログラムを書く時に一番楽しいところだし。プログラマーは、ウインドウ生成表示だの、時間待ち処理だの、イベント種類の判別だの、そのあたりを書きたくて書きたくてたまんねえと思ってるわけでもないし…。

でも、初期化だの、イベント種類の判別だの、アレコレ明示的に書くことを要求するライブラリが多いんですよね…。なんでだろ。 *1

何にせよ、pysdl2 を触っていたら、「DXRuby って良くできてるなー」と再認識してしまったというか。DXRuby、素晴らしい。
*1: もちろんソレは、「デフォルトサイズ以外のウインドウを生成したい」「ウインドウを複数作りたい」「各ウインドウの表示・非表示を制御したい」「メインループを複数作りたい」「閉じるボタンをクリックしたらいきなり閉じずに何かさせたい」等々、「こういう要求があったらどうしよう」と想像して「だったらこうしておくか」となったのだろうと想像するのですが。どんな要求にも応えられる汎用性を持たせた結果、冗長な部分がどんどん増えていく、みたいな。でも、どうもそれだけではないような気もしていたり。「昔風の2Dゲームならフツーはこういう仕様だろう」てな絞り込みや、「2Dゲームアプリなら、このあたりの仕様は実装されてて当然でしょ」てな洗い出しを、そもそも最初から放棄してるのでは、と邪推したりもして…。

2017/04/25(火) [n年前の日記]

#1 [python] pysdl2でSDL_Render云々を調べていたり

まだ pysdl2 を触っていたり。

_SDL2の使い方 - mirichiの日記 によると、「SDL_Renderer と SDL_Texture を使えば SDL2 は高速な描画ができる」という話らしいので、pysdl2 でそのあたりを使えるかどうか試したり。

sdl2を使った版。 :

ウインドウを表示して画像を円運動させるだけのスクリプトを書いてみたり。以下のソースが参考になった。ありがたや。

_Example of render to texture with SDL2

手元のソースは、こんな感じになった。

_texture_render_test.py
_res/hello.png
u"""
PySDL2のテスト.

SDL_Texture と SD_Renderer を使うと高速描画できるらしいので試す。
でも、本当に速くなっているのかな…?

動作確認環境:
Windows10 x64 + Python 2.7.13 32bit版 + PySDL2 0.9.5
"""

import os
import sdl2
import sdl2.sdlimage
import ctypes
import math

SCREEN_SIZE = (640, 480)
# SCREEN_SIZE = (1280, 720)


def main():
    u"""メイン処理."""
    # PySDL2 の初期化
    sdl2.SDL_Init(sdl2.SDL_INIT_VIDEO)

    # ウインドウを作成して表示。
    # タイトル文字列、表示位置、ウインドウサイズを指定している
    window = sdl2.SDL_CreateWindow(b"render test",
                                   sdl2.SDL_WINDOWPOS_CENTERED,
                                   sdl2.SDL_WINDOWPOS_CENTERED,
                                   SCREEN_SIZE[0], SCREEN_SIZE[1],
                                   sdl2.SDL_WINDOW_SHOWN)

    # ウインドウから renderer を取得。ウインドウ、インデックス、フラグを渡す
    # インデックスが-1なら初期化

    # fg = 0
    # fg = sdl2.SDL_RENDERER_PRESENTVSYNC | sdl2.SDL_RENDERER_ACCELERATED
    fg = sdl2.SDL_RENDERER_ACCELERATED
    renderer = sdl2.SDL_CreateRenderer(window, -1, fg)

    # 画像ファイルのパスを取得
    filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                            "res", "hello.png")

    # 画像をサーフェイスとして読み込み
    # png等の読み込みには SDL2_image を利用する
    surface = sdl2.sdlimage.IMG_Load(filepath.encode("utf-8"))

    # 画像サイズを取得しておく
    w = surface.contents.w
    h = surface.contents.h
    print("w,h = %d,%d" % (w, h))

    # サーフェイスをテクスチャに変換
    # SDL_CreateTextureFromSurface() を使えば変換できる
    texture = sdl2.SDL_CreateTextureFromSurface(renderer, surface)

    # テクスチャに変換したからサーフェイスは用済み。解放してやる
    sdl2.SDL_FreeSurface(surface)

    ang = 0

    interval = sdl2.SDL_GetPerformanceFrequency() / 60
    oldtime = sdl2.SDL_GetPerformanceCounter()

    running = True
    while running:
        # メインループ

        # イベントを調べる
        # 閉じるボタンのクリック or ESCキーが押されたらメインループを終了
        event = sdl2.SDL_Event()
        while sdl2.SDL_PollEvent(ctypes.byref(event)) != 0:
            if event.type == sdl2.SDL_QUIT:
                # 閉じるボタンがクリックされた
                running = False
                break
            if event.type == sdl2.SDL_KEYDOWN:
                # 何かのキーが押し下げられた
                if event.key.keysym.sym == sdl2.SDLK_ESCAPE:
                    # ESCキーが押された
                    running = False
                    break

        # テクスチャの表示位置を算出。円運動をさせている
        rad = math.radians(ang)
        r = SCREEN_SIZE[0] / 6
        x = int(r * math.cos(rad) + (SCREEN_SIZE[0] / 2) - (w / 2))
        y = int(r * math.sin(rad) + (SCREEN_SIZE[1] / 2) - (h / 2))
        ang = (ang + 3) % 360

        # 画面をクリア
        sdl2.SDL_RenderClear(renderer)

        # テクスチャの転送元領域を設定
        src_rect = sdl2.SDL_Rect(0, 0, w, h)

        # テクスチャの転送先領域を設定
        dst_rect = sdl2.SDL_Rect(x, y, w, h)

        # テクスチャをウインドウの renderer に転送
        # 転送元領域に NULL を指定すれば、全体を指定できる、はずなのだが…
        # sdl2.SDL_RenderCopy(renderer, texture, None, dst_rect)
        sdl2.SDL_RenderCopy(renderer, texture, src_rect, dst_rect)

        # ウインドウ(renderer)の内容を更新
        sdl2.SDL_RenderPresent(renderer)

        # ループの時間待ち
        while True:
            if sdl2.SDL_GetPerformanceCounter() - oldtime >= interval:
                break
            sdl2.SDL_Delay(0)
        oldtime = sdl2.SDL_GetPerformanceCounter()

    # メインループ終了

    # テクスチャ、renderer、ウインドウの解放
    sdl2.SDL_DestroyTexture(texture)
    sdl2.SDL_DestroyRenderer(renderer)
    sdl2.SDL_DestroyWindow(window)

    # sdl2を終了
    sdl2.SDL_Quit()


if __name__ == '__main__':
    main()

長い。ウンザリしてきた。

以下で実行。
python texture_render_test.py
texture_render_test_ss.png

一応、分かった点について、いくつかメモ。

ウインドウを生成したら、sdl2.SDL_CreateRenderer(ウインドウ, インデックス, フラグ) を使って、SDL_Renderer を得られる。
renderer = sdl2.SDL_CreateRenderer(window, -1, fg)
フラグには、sdl2.SDL_RENDERER_ACCELERATED を指定しておけば、ひとまずハードウェアアクセラレーションが適用、されるのかな。たぶん。 _SDL_RendererFlags を眺めると、vsyncと同期するフラグもあるようで。

テクスチャクラス SDL_Texture は、サーフェイスから変換できる。sdl2.SDL_CreateTextureFromSurface(SDL_Renderer, サーフェイス) を使えばいい。
texture = sdl2.SDL_CreateTextureFromSurface(renderer, surface)

画面への描画の仕方は…。テクスチャを renderer に転送することで描画するようで。
sdl2.SDL_RenderCopy(転送先のSDL_renderer, 転送元テクスチャ, 転送元領域, 転送先領域)
みたいな感じ。

_SDL_RenderCopy

転送元領域や転送先領域は、sdl2.SDL_Rect(x, y, w, h) で用意しておく。のかな。たぶん。

sdl2.extを使った版。 :

sdl2を使った版は、やっぱり色々と記述が面倒臭いので、sdl2.ext を使って少しは行数が短くなるように書いてみたり。

_texture_render_test_ext.py
u"""
PySDL2のテスト.

SDL_Texture と SD_Renderer を使うと高速描画できるらしいので試す。
sdl2.ext を利用して、少しは行数が減るように書いてみる

動作確認環境:
Windows10 x64 + Python 2.7.13 32bit版 + PySDL2 0.9.5
"""

import sdl2
import sdl2.ext
import math

SCREEN_SIZE = (640, 480)
# SCREEN_SIZE = (1280, 720)

# リソース(画像等)の場所を指定
RESOURCES = sdl2.ext.Resources(__file__, "res")


def main():
    u"""メイン処理."""
    # PySDL2 の初期化
    sdl2.ext.init()

    # ウインドウを作成して表示。
    window = sdl2.ext.Window("Hello World", size=SCREEN_SIZE)
    window.show()

    # ウインドウから renderer を取得。ウインドウ、インデックス、フラグを渡す。
    # インデックスとフラグは省略可能。インデックスのデフォルト値は index=-1
    # フラグのデフォルト値は flags=sdl2.SDL_RENDERER_ACCELERATED

    # fg = sdl2.SDL_RENDERER_PRESENTVSYNC | sdl2.SDL_RENDERER_ACCELERATED
    fg = sdl2.SDL_RENDERER_ACCELERATED
    renderer = sdl2.ext.Renderer(window, flags=fg)

    # テクスチャベースのスプライト、を作成できるクラス?、を生成
    factory = sdl2.ext.SpriteFactory(sdl2.ext.TEXTURE, renderer=renderer)

    # スプライトを1つ生成。画像の読み込みも行う
    sprite = factory.from_image(RESOURCES.get_path("hello.png"))

    # スプライト描画用のrendererを生成
    spriterenderer = factory.create_sprite_render_system(renderer)

    # テクスチャサイズを取得しておく
    w, h = sprite.size
    print("w,h = %d,%d" % (w, h))

    ang = 0

    interval = sdl2.SDL_GetPerformanceFrequency() / 60
    oldtime = sdl2.SDL_GetPerformanceCounter()

    running = True
    while running:
        # メインループ

        # イベントを調べる
        # 閉じるボタンのクリック or ESCキーが押されたらメインループを終了
        events = sdl2.ext.get_events()
        for event in events:
            if event.type == sdl2.SDL_QUIT:
                # 閉じるボタンがクリックされた
                running = False
                break
            if event.type == sdl2.SDL_KEYDOWN:
                # 何かのキーが押し下げられた
                if event.key.keysym.sym == sdl2.SDLK_ESCAPE:
                    # ESCキーが押された
                    running = False
                    break

        # テクスチャの表示位置を算出。円運動をさせている
        rad = math.radians(ang)
        r = SCREEN_SIZE[0] / 6
        x = int(r * math.cos(rad) + (SCREEN_SIZE[0] / 2) - (w / 2))
        y = int(r * math.sin(rad) + (SCREEN_SIZE[1] / 2) - (h / 2))
        ang = (ang + 3) % 360

        # スプライトの表示位置を変更
        sprite.position = x, y

        # 画面をクリア
        renderer.clear()

        # スプライトを描画
        spriterenderer.render(sprite)

        # ループの時間待ち
        while True:
            if sdl2.SDL_GetPerformanceCounter() - oldtime >= interval:
                break
            sdl2.SDL_Delay(0)
        oldtime = sdl2.SDL_GetPerformanceCounter()

    # メインループ終了。色々なアレやソレを解放
    sdl2.ext.quit()


if __name__ == '__main__':
    main()

相変わらず長い。いやまあ、一応ちょっとは短くなっているのだけど。

以下で実行。
python texture_render_test_ext.py
texture_render_test_ext_ss.png


sdl2.ext版は、やっぱりスプライトを使わないと短く書けないっぽい。sdl2.ext.SpriteFactory() を使うことで、スプライトの生成ができるっぽいけど…。
# テクスチャベースのスプライト、を作成できるクラス?、を生成
factory = sdl2.ext.SpriteFactory(sdl2.ext.TEXTURE, renderer=renderer)

# スプライトを1つ生成。画像の読み込みも行う
sprite = factory.from_image(RESOURCES.get_path("hello.png"))
sdl2.ext は、スプライトの種類として、
  • ソフトウェアベースのスプライト(sdl2.ext.SOFTWARE)
  • テクスチャベースのスプライト(sdl2.ext.TEXTURE)
この2種類が用意されてるようで。名前からして、おそらく前者は遅いんじゃないかな、と…。

スプライトの描画は、create_sprite_render_system() を呼んで、ソレ用の何かを生成して…。
# スプライト描画用のrendererを生成
spriterenderer = factory.create_sprite_render_system(renderer)
後で、.render(スプライト) を呼べばいいのかな。たぶん。
# スプライトを描画
spriterenderer.render(sprite)

ホントにコレで合ってるのか全く自信は無いけれど、一応はコレで動いてるっぽいし…合ってるんじゃないかなあ…怪しいけど。

メインループの時間待ち処理。 :

メインループが60FPSで回るようにしたかったのだけど、最初は _SDL_Delay() だの _SDL_GetTicks() だのを使っていて。

_SDL:フレームレートの制御 | ジャジャガッチブログ

ただ、どうも動きがガクガクする…。

他の方法はないのかなとググっていたら、以下の記事が。

_Windows8(以降?)でSDL_delayの挙動が変わったのかしら?と言う話 - 今日の雑記
_c++ - Sleep(1) and SDL_Delay(1) takes 15 ms - Stack Overflow

Windows7 と Windows8以降では、動作が変わるとか…。そりゃ困る…。

ということで、件の記事で紹介されてる方法でループの時間待ちを書くことに。

_SDL_GetPerformanceFrequency
_SDL_GetPerformanceCounter

一応簡単に解説すると…。
  • sdl2.SDL_GetPerformanceCounter() を呼べば、現在の高分解能カウンタ値を得られる。
  • sdl2.SDL_GetPerformanceFrequency() は、1秒間に何回、高分解能カウンタでカウントできるかを返す。ということは、この値を60で割れば、1/60秒の間にカウントできるはずの数が得られる。(= インターバル値)
  • メインループ処理を始める前に、カウンタ値を覚えておいて…。
  • メインループ処理の最後のあたりで、覚えておいたカウンタ値と、現在のカウンタ値を使って、カウント数の差 = メインループの処理に何カウントかかったのかを取得する。
  • もし、差が、インターバル値に達してないなら、まだ処理時間は余ってるということ。時間待ちをしないといけない。
  • 差が、インターバル値に達するまで延々とループさせる。一応、ループの中で、SDL_Delay(0) を呼ぶことで、ほんの一瞬ちょっとだけ、なんとなく気持ち的にスリープさせておく。
  • 差が、インターバル値に達したら、1/60秒経過したということ。現在カウンタ値を記録して、メインループをまた実行。
面倒臭いですね。こんなの毎回書かせるとか何なんですかね。ライブラリ側でやってほしいですよね。

vsyncの使い方がよく分からない。 :

SDL2 は vsync を利用できるという話を見かけたので、「vsync で合わせれば確実に60FPSで回ってくれるんじゃないのかなあ」などと思ってしまったのだけど。以下の記事を目にして考えが甘かったことに今頃気づいたり。

_python - Locking the frame rate in pygame? - Game Development Stack Exchange

「PCによってディスプレイのリフレッシュレートって違うだろ」「あんなもんを頼りにしてたら何FPSになるか予測つかんぞ」という、あまりにも当たり前な指摘。ああ、たしかに、そうだった…。PCって、ゲーム機と違って、ディスプレイのリフレッシュレートがバラバラだった…。すっかり忘れてた。

となると、vsync って何に使えるんだろ。いや、画面の書き換えが気にならないように、みたいな使い方はできるだろうけど。メインループは60FPSだけどディスプレイは50FPSとかだったら、スクロールがガクガクしたりせんのかな。どうやって辻褄を…。って今頃になってこんなので悩むとか何十年頭の中が止まってるんだよって感じですな。

さすがにこのあたり、誰か対処方法をまとめてないのかしら。昔、アプローチを3種類ぐらい紹介してた記事を見かけた記憶も…。

でも、今時は、こういうのって気にしなくて済むように、ライブラリ/ゲームエンジン側で実装済みだったりするんじゃないのか。その手の解説記事、残ってるのかな…。

とりあえずここまでかな。 :

画像描画とキー入力ができれば、あくまで見た目だけ、ではあるけれど、リアルタイムゲームっぽいソレを頑張れば作れると思うので…。とりあえず今回はここまで、かなと…。pygame より高速に描画できそうな雰囲気もあるし、これはこれで。

でも、やっぱり記述が面倒臭いなあ…。しかし、sdl2.ext は、ちょっとよく分からんし…。pysdl2 の上に被せるグッドなラッパーが出てくれば、Pythonでその手のゲームを書く行為も、ちょっとはアレになるのだろうか。

そういや、SDL2関連DLLの入手も面倒、だったのだよな…。例えば、pythonスクリプトを一つDLして実行するだけで、各ページからzipを落として展開してまとめてくれる、みたいなソレってPythonで書けないのだろうか。あるいは、関連DLLだけをまとめてインストールできる別パッケージを用意するとか…。いやまあ、そのへん、最初からpysdl2に同梱しといてよ、てな気持ちもあるけれど。

ちなみにDXRubyで書いたら。 :

ちなみに、Ruby + DXRuby で上記のスクリプトと同じ処理を書くと…。
require 'dxruby'

# 画像を読み込み
img = Image.load("res/hello.png")

ang = 0

# メインループ
Window.loop do
  # ESCキーが押されたら終了
  break if Input.keyPush?(K_ESCAPE)

  # 円運動をさせる
  rad = ang * Math::PI / 180.0
  r = Window.width / 6
  x = r * Math.cos(rad) + (Window.width / 2) - (img.width / 2)
  y = r * Math.sin(rad) + (Window.height / 2) - (img.height / 2)
  ang = (ang + 3) % 360

  # ウインドウに画像を描画
  Window.draw(x, y, img)
end

他の言語やライブラリで書いたら、どうなるんだろう…。Python + pyglet、Ruby + gosu、HSP、Processing、enchant.js あたりで…。このくらいなら Processing も短く書けそうですな。 *1

*1: もちろん、「短ければいいってもんでもないだろう」と言われそうでもあり…。一般的に、少ない記述量で書けるってことは、その分何かがトレードオフになってるはずで…。結局そのへんケースバイケース、なのだろうと思いますが。

#2 [zatta][neta] 「東北でよかった」について

復興大臣が、東日本大震災に関して「東北のほうでよかった」と発言したらしいけど。

_今村復興相 震災復興に関連し「東北のほうでよかった」と発言 | NHKニュース
_復興相「震災、東北でよかった」 与党から辞任論も :日本経済新聞

最初にその話を聞いた時、自分も東北人でありながら、「え? それマズいの?」と思ってしまったりもして…。だって、これが東京だの関東だので起きてたらこんなもんじゃ済まなかったのは間違いないし。なので、「それはそれで事実じゃないのか」「事実を述べて叩かれるのは、ちとマズくないか」と、なんだか少々複雑な気分に。

しかし、このあたり、言い方が難しい。「不幸中の幸い」てのもなんか違うし。…いや、なんか違う、どころじゃないな。絶対に違うな。「コレコレこういう状況になることを想像した場合、現状は比較的マシなのかもと思えなくもないような、そうでもないような、どうなんだ」みたいなソレを、どう言えばいいのか…。

アレかな。「関東に比べたら人口が少ない東北ですらこれだけの被害が出てるのだから、まして〜」みたいな言い方で止めておけばよかったのかな。いや、それもそれでなんだか叩かれそうな…。

事実と感想を明確に分けて発言しないといけないのだろうな…。「よかった」は感想の域に足を踏み入れてる言葉だから、そこがいかんのだろう…。とは言え、仮に事実のみを述べたとしてもやっぱり叩かれそうで怖い…。

ニホンゴ、ムズカシイデス。…いや、日本語に限った話じゃないな。たぶん。

2017/04/26(水) [n年前の日記]

#1 [python] pycairoを勉強中

_先日書いたドット絵モドキを生成するRubyスクリプト を、Pythonで書き直し中。

ちなみに、python で cairo を使いたいなら、pycairo というライブラリを導入すればOK…なんだけど、Windows上ではインストール方法が…。

とりあえず自分は、 _PyGTK 経由で、 _http://ftp.gnome.org/pub/GNOME/binaries/win32/pygtk/2.24/ から pygtk-all-in-one をDLしてインストールして済ませてしまった、ような気がするけど、どうだったかな…。たしかそのはず…。昔、GIMP で Python-Fu を動かす時にはコレが必要だったので、ついでに Python 2.7用もインストールしたのではなかったかな…。まあ、Python 2.6/2.7用しか公開されてないし、バージョンも古いみたいだから、最新版をインストールできる方法があるならそちらも試してみたいところ。

rounded_rectangleについて。 :

角丸矩形を描画してくれる rounded_rectangle() が pycairo には無くてちょっと悩んでしまったり。たぶんコレ、Ruby から cairo を使える rcairo というライブラリで、独自に追加実装された機能、なのではないかなと…。

どうやって実装するのだろうとググっていたら、cairo の使用サンプルソースに、同じ処理をするものがあった。

_rounded rectangle

参考にして、Python + pycairo で書き直してみたり。

_rounded_rectangle_test.py
u"""
pycairoの動作テスト.

角が丸い四角を描画して、pngで保存。

動作確認環境:
Windows10 x64 + Python 2.7.13 32bit + pycairo 1.8.10
"""

import cairo
import math


def draw_rounder_rectangle(ctx, x, y, w, h, ra):
    """Set sub path rounded rectangle."""
    deg = math.pi / 180.0
    ctx.new_sub_path()
    ctx.arc(x + w - ra, y + ra, ra, -90 * deg, 0 * deg)
    ctx.arc(x + w - ra, y + h - ra, ra, 0 * deg, 90 * deg)
    ctx.arc(x + ra, y + h - ra, ra, 90 * deg, 180 * deg)
    ctx.arc(x + ra, y + ra, ra, 180 * deg, 270 * deg)
    ctx.close_path()

w, h = 640, 480
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
ctx = cairo.Context(surface)
ctx.set_antialias(cairo.ANTIALIAS_SUBPIXEL)
ctx.set_line_width(6.0)

x, y = 24, 24
w, h = 480, 320
radius = 32
draw_rounder_rectangle(ctx, x, y, w, h, radius)
ctx.set_source_rgb(0, 0, 1)
ctx.fill_preserve()
ctx.set_source_rgb(0, 1, 0)
ctx.stroke()

surface.write_to_png("result.png")

以下で実行すると、描画結果を result.png として出力する。
python rounded_rectangle_test.py
rounded_rectangle_test_ss.png

ちゃんと角丸の四角になってくれた。

#2 [neta] 別の政党の人を大臣にするわけにはいかんのだろうか

バカ妄想です。

日本の場合、各大臣は与党の中から選ばれるのが当たり前だけど。アレって別の政党から選ぶことはできないのかなあ、などとアホなことを妄想してしまったりもして。

ぶっちゃけ自分、自民党の中に復興大臣をやれる人材って居ないんじゃないのと疑ってたりもするわけで。だけど野党の中にはひょっとしたらできそうな人材が居ても…。たぶん、あのへんの政党に任せたら、上からの視点はアレだけど下からの視点についてはカバーしてくれそうな…。

与党だの野党だのに関係なく、「この分野に関しては○○党の○○さんがどう考えても適任やろ。だったらあの人にお願いすべきや。餅は餅屋じゃ。何? 党が違う? そんなもん犬にでも食わせてしまえ」てな人事ができる総理大臣だったら…。自分だったら間違いなく、その総理大臣を100%支持しちゃいますわ。

まあ、そんな展開はあり得ないけど。無理無理。絶対無理。漫画じゃあるまいし。

東映アニメーションがスゴイよなと思っていたり。 :

日本のアニメスタジオの中には、東映アニメーションというスタジオがあって。東映動画を前身とする、歴史あるスタジオなんですが。

事例としては少ないのだろうけど、東映アニメーション所属の演出家さんが、他のアニメスタジオに出向と言う形で入って監督として仕事をしてる時があるらしいのですよ。スタジオジブリに入って仕事した監督さんも居るし、ボンズに入って仕事した監督さんも居るし…。 *1

どういう仕組み・契約になっているのか分からないけど、「お前、ちょっと出向と言う形で○○に行ってこい」と言えてしまう、東映アニメーションのプロデューサーさんってスゴイよなーと思うわけで。

だって、フツー、優秀な人材は自分のところで抱え込んで外には出さないものだろうと。本当は他のスタジオでもちょこちょこ仕事してるけど、一応○○スタジオに所属してる身分だからと、変名でこっそり仕事をしているアニメーターさんや演出家さんがちょくちょく居たりもするわけで。それがフツーだよなと。 *2

「コイツの作風考えたら、○○で仕事をさせたら面白い」と考えられる、その姿勢がまずスゴイ。スタジオの枠を超えて各人材のパフォーマンスを最大限に発揮させるべく動けてしまうなんて…。カッコイイ。いや、もちろん、自社にも利益があるようにアレコレ話をつけてるんだろうとは思いますが。 *3

てなわけで、世の中には一応そういう事例もあったりするので、政治の世界でもそういうのができたらカッコイイだろうなあ、などと子供みたいなことを思っちゃったりもするわけで。

でもまあ、党派を超えて会合を開いたりとかやってたりもするらしいので、やれる範囲ではもちろんやってるよー、って感じなんでしょうけど…。さすがに大臣まで、ってのは無理だよなー。アニメ監督はまだしも、大臣はなー。

まあ、寝言というかバカ妄想です。

いや待て。もしかして、政治の世界にも凄腕プロデューサーが居てくれたら…。なんちてぽっくん。

*1: まあ、実を結ぶ時もあれば、実を結ばなかった時もあるのがアレですけど…。
*2: ていうか昔は、東映所属の監督さんがこっそり変名使ってEVAのコンテ描いてたりもしたし…。
*3: もしかすると業界人さんなら「素人さんはコレだから。アレは全然カッコイイ話じゃないぞ。コレコレこういう、恐ろしい話なんだぞ」と言い出したり…するのだろうか。

#3 [prog] ゲーム制作ライブラリがキー入力をイベントで処理させるのってどうなんだろ

ポエムです。

pysdl2 もそうだし、gosu もそうだったと思うけど…。キーが押された瞬間、または離された瞬間にイベントが発生するから、キー入力の処理をしたいならそれらのイベントに対応した処理を書け、てな作りになっていて。

個人的に、そこらへん、なんだかしっくりこないのですよね…。

いやまあ、今のPC or OSが、キーが押された/離された瞬間にイベントが発生する仕組みになってるから、ゲーム制作ライブラリもそのままになってるのかなと想像するのですが。ずっとPC上でゲーム作ってた人達は、「それのどこがいかんの?」「こんなの当たり前でしょ?」って感覚なのでしょうけど。

ただ…。例えば大昔のゲーム機って、そこらへんが逆だったんですよね。ジョイスティック(ゲームパッド/ジョイパッド)の、各ボタンのON/OFF状態のみをビット列で返してくる。押された瞬間や離された瞬間を知るためには、こっちでビット演算を、てな感じで。

つまり、世の中、キーのON/OFF状態しか返さない機器もあれば、変化のみをイベントで返す機器もあるわけで。 *1

だけど、ゲームを作る際には、 この3つを知る術が欲しいわけで。

仕組みの違う機器がある。そして、最終的に欲しい情報は分かってる。だったら、そこはライブラリが吸収すべきじゃないのかなと思えてきたりもするわけで。

ゲームのルーチンを書く側が欲しいのは、「キーが押された」「キーが離された」「キーがずっと押されてる(or 押されていない)」という情報で。キー入力がイベントで処理されて云々とか、ビット列しか返さないとか、そんな知識が欲しいわけじゃないのですよ。でもまあ、そこを知ってないと本当に欲しい情報が得られないから、仕方なく把握して関連処理を書いてるわけですけど。

ちなみに、DXRuby は、その3つが取得できるので、使ってて便利なのです。キー入力周りを書くたびに、「ホント、気が利いてるよなあ…」と感心していたり。それに比べると、pysdl2 や gosu は、本来包むべき部分を包まないで、ライブラリの利用者側に丸投げしちゃってる印象が。何かこのあたり、「いやいや、そこはあえてそうしているのだよ。何故なら」的な、作者さんの思想でもあるのかしらん。

まあ、個人的に、あのへんなんだかしっくりこないんだよなあ、ってだけの話です。そんだけです。

ポエムでした。
*1: そういえば、「キーが押された瞬間」しか分からない、富士通 FM-8/7 なんてPCもあったっけ…。プレイヤーキャラを動かしたら、移動を止めるためにテンキーの5を押さないといけないという…。ググってみたら、 _開発者は「このPCでゲーム作るヤツなんてまさか出てくるはずがない」と思ってた らしいですな…。

2017/04/27(木) [n年前の日記]

#1 [python] Python + cairo(pycairo) でドット絵モドキを生成

_先日書いたドット絵モドキを生成するRubyスクリプト をPythonで書き直すことができた、と思う。たぶん。

tinypixelartgrad_gui_screenshot.gif

ソースは以下。ソース自体は CC0 / Public Domain ってことで。

_tinypixelartgrad.py / tinypixelartgrad_gui.py

tinypixelartgrad.py と tinypixelartgrad_gui.py をDLして、以下で実行。
python tinypixelartgrad_gui.py

動作には以下が必要。 Winodws10 x64 と Windows7 x64上で動作確認済み。

exe版も作ってみたり。 :

一応、動作確認ぐらいはできるように、Windows上で動作するexe版も置いときます。

_tinypixelartgrad_gui.zip (7.8MB, 解凍すると14MB程度)

解凍して、中に入ってる tinypixelartgrad_gui.exe を実行すれば動くんじゃないかなと。たぶん。動いたらいいな。

exe化は、 _PyInstaller を使用。 *1

実行バイナリは 32bit版になってるはずだけど…。 *2 自分の手元には Windows7/10の64bit版しか無いので、32bit版のOS上で本当に動くかどうかは不明。

しかしコレ、同梱されてるDLLがライセンス的にアレだから、ちゃんとした形での配布は難しい予感。なので、これから GIMP の Python-Fu で書き直してみようかと考えていたり。

動作に必要なアレコレのインストール方法について一応メモ。 :

どうやってインストールしていたのか、そのうち自分も忘れそうなので、一応メモ。

環境は Windows10 x64。

Python本体は、 _Download Python | Python.org から辿っていけば、セットアップファイルを入手できる。今現在は、 _Python Release Python 2.7.13 | Python.org で、Windows x86版(32bit版) や Windows x86-64版(64bit版) が入手できる模様。

_WindowsにPythonをインストールする方法 | UX MILK によると、インストール時に「tcl/tk and IDLE」にチェックを入れれば、Tkinter もインストールされる模様。まあ、「tk」とか「tkinter」という単語が見えたら一緒にインストールしておく、ということでいいのかなと。

pycairoは、 _PyGTK 経由で、 _ftp.gnome.org より pygtk-all-in-one-2.24.2.win32-py2.7.msi をDLしてインストールすれば、PyGTK、PyCairo、PyGObject の3つがインストールできる。のだけど、コレ、導入は楽だけど、バージョンが古くて…。

一応、 _Download GTK+ 経由の _GTK+ Download: Windows というページで、「MSYS2を入れれば最新版のインストールは簡単」みたいなことが書いてあるけど…。手元の環境では MSYS2 だけでも2GB近くHDDに入っていて…。まあ、それぞれ好きな版を使えばいいのでは、みたいな。古い版でも一応こうして動いてるようだし。

Pillowは、Python がインストールされてるなら、pip install Pillow でインストールできる。今、手元で試したら、Pillow 4.1.0 がインストールされた。

*1: 最初は py2exe でexe化を試みたけど、生成されたexeを実行したら強制終了するので諦めました。
*2: 手元でインストールしてあるのは Pythn 2.7 32bit版なので、exe化して出来上がったバイナリも32bit版のはず。

2017/04/28(金) [n年前の日記]

#1 [gimp][python] GIMP上でドット絵モドキを生成するPython-Fuスクリプトを書いた

GIMP上でドット絵モドキを生成するPython-Fuスクリプトを書いてみたり。以下のような画像が作れます。

tinypixelartgen_ss_02.png


スクリプトソースは以下。

_tinypixelartgen.py

動作には、Python-Fu が動いて pycairo も入ってる GIMP が必要、なんだけど、おそらく GIMP 2.8.x 以降なら、Python-Fu も pycairo も同梱されていて動くんじゃないかな、と…。

ライセンスは、CC0 / Public Domain ってことで。「もっとこうしたらそれっぽくなるんじゃね?」と閃いた方はガンガン弄ってガンガン公開しちゃってください。

インストール方法。 :

_tinypixelartgen.py をダウンロードして、GIMPのユーザフォルダ/plug-ins/ にコピーすればインストールできます。

_[Gimp] Pythonでスクリプトをはじめる - Qiita で紹介されてるように、
編集 → 設定 → フォルダー → プラグイン
を見れば、場所が分かるかも。

使い方。 :

GIMPを起動後、何か画像を(512x512ドットぐらいで)作成しておいて、以下でスクリプトを実行。
フィルター → 下塗り → Tiny Pixleart
(Filter -> Render -> Tiny Pixelart)

実行すると、下のようなダイアログが表示されますが…。

tinypixelartgen_ss_01.png


一応説明しておくと…。
  • Width と Height が、ドット絵モドキ1つ分の横幅と縦幅。
  • Row と Column が、横と縦にいくつ並べるか。
  • Randomize を ON(「する」) にすると、実行するたびにランダムな画像が作られる。(ランダムシード(乱数の種)がランダムになる)
  • Randomize を OFF(「しない」) にすると、その下の Random seed の数値に従って、実行するたびに同じ画像が作られる。
  • X mirror、Y mirror は、横方向、縦方向をミラーリングするか否か。
  • Create で Image を選ぶと、画像を新規作成して、そこにドット絵モドキを作る。
  • Create で Layer を選ぶと、現在開いてる画像にレイヤーを新規作成して、そこにドット絵モドキを作る。
  • Layer name は、新規作成されるレイヤーのレイヤー名。
  • Set grid w x h は、ドット絵モドキのサイズをグリッド間隔にも設定するか否か。設定しておけば、表示 → グリッドの表示、表示 → グリッドにスナップ、で、一つ一つが選択しやすくなる。

応用。 :

動作確認をしている最中に気づいたのだけど、何度か生成してレイヤーを重ねると、見た目がゴージャスになっていくなと…。

例えば、以下は1回だけ生成した状態だけど。

tinypixelartgen_ss_03.png

何度か生成してレイヤーが何枚か重なると、以下のように見た目が結構変わってくる。

tinypixelartgen_ss_04.png

更に、各レイヤーのモードを「オーバーレイ」「乗算」等、色々変えてみると…。

tinypixelartgen_ss_05.png

ということで、ランダムに生成してるからパッと見は「なんだコレ?」って感じだけど、もうちょっと一工夫すると次第にそれっぽくなる、のかもしれないです。

#2 [gimp][python] GIMPで「グリッドの表示」をスクリプトから制御する方法が分からず

GIMP上で動くPython-Fuスクリプトを書いてはみたものの、「グリッドの表示」をするための関数が分からず。

以下の2つは分かったのだけど…。
_gimpgrid

しかし、表示を有効化する関数が見つからない。スクリプト側から利用できる形では用意されてないのだろうか…。

できれば「グリッドにスナップ」まで有効化したいのだけど。表示すら有効にできないのではなあ…。

無いのは当然かもしれない。 :

考えてみれば…。何かしらの自動処理を行う場面を想像すると、グリッドの表示や吸着(スナップ)って、制御できても意味が無いもんな…。

アレはあくまで人間様が、目視で何かの作業をしていく際に「あったら便利」な機能で。スクリプトなどは、例えば等間隔で正確無比に仕事をしていく、なんてのが得意なのだから、グリッドなんて最初から要らんわけで…。

自動処理をした後で、「ここから先はグリッドがあったほうが便利だろう」と思えてくる結果を生成してしまう、そんなスクリプトの仕様そのものが間違っている可能性も。

やっぱり制御できないのはおかしい気もする。 :

「グリッドが必要になる結果を生成すること自体がおかしい」と言われても…。

なら、例えば64枚の画像なりレイヤーなりをガーッと生成したとして。それを一覧表示して人間様が眺められる機能をGIMPが持っているのですかと。否。そんなもの、GIMPは持ってないわけで。

だったら、一枚の画像にずらりと並べて、人間様が目視で選んでいくしかねえべや。そして、ずらりと並べたら、一つ一つを選択する際にグリッドがあったほうが便利やろ、となるわけで。

「グリッドなんぞ制御する意味無し」と断言するためには、大量の画像なりレイヤーなりを一覧で目視して選別を可能にする別の機能が必要なのではないかなあ…。でも、ソレを実装する手間暇を考えたら、スクリプトによるグリッドの制御を可能にして「グリッド使ってどうにかして」と言っちゃうほうがはるかに楽であろう予感。

というか、グリッドの間隔やオフセットを設定したり取得したりする機能があるのに、グリッドの表示や吸着を制御する機能が見当たらないのがやっぱりおかしい気もする。「グリッドまで制御する必要は無い」のであれば、間隔やオフセットを設定する関数は何故存在しているのか。それらがあるなら、表示や吸着もありそうなものではないか。

その2つは、想定外の関数名で隠れているのか、それとも実装を忘れただけなのか…。

別アプリと連携できればいいのに。 :

「そういう作業をしたいなら別アプリとして作るべきだ」と言われそうな気もする。が、その別アプリで作成した結果画像を、どのみちGIMPに持っていって編集するわけで。だったらGIMP上で作ってしまうほうが早いじゃん、てなところも。

GIMPが、別アプリと連携して、レイヤーを渡したり持ってきたりすることができればいいのかもしれない。もし、そういう機能があれば、例えば ImageMagick にレイヤーを渡して処理してもらって、その結果をGIMPのレイヤーに書き戻す、てなこともできて便利になったりするのかも。

とは言え、各CGツールが連携して処理をするための画像フォーマットなんて決まっているのだろうか、という疑問も。

クリップボード? アレってアルファチャンネルを持てたっけ?

ググってみたら、ImageMagick は clipboard: なる指定で、クリップボードから画像を読み込んだりできるみたいで。

_ImageMagickを使おう

なるほど、クリップボードを経由してやり取りする手もアリと言えばアリ、なのかなあ…。

もっとも、各アプリがクリップボード経由でデータを渡そうとしてるタイミングで Ctrl + C を押したらどうなるんだろう、という疑問も。

MIDIなんかもそうだったけど、連携できるか否かって、何かしらを作っていくときには結構効いてくる、ような気がする…。

2017/04/29() [n年前の日記]

#1 [gimp][python] ドット絵モドキを生成するGIMPのPython-Fuスクリプトで遊んでみたり

_昨日書いたスクリプト で、もうちょっと実験。

0000.png

0001.png

0002.png

こうして見ると、なんだか圧巻…と言っても質より量って感じではあるけれど。

もちろんこれらの画像は CC0 / Public Domain ってことで…と言っても使い道が無いかもしれんけど。

色々テストしてるうちに分かってきたけど、例えば、件のスクリプトでレイヤーを5枚ほど作って構成を以下のようにすると、なんだかちょっとそれっぽくなる、ような気もしてきたり。 もしかすると、上記のようなレイヤー構成を自動で作るようにすれば、もうちょっと使い道が…いや…どうだろう…。このあたりは目視でイイ感じに調整したほうが良さそうだし…。

2017/04/30() [n年前の日記]

#1 [dxruby][gimp][python] ドット絵モドキ生成スクリプトの結果画像をDXRubyで描画して雰囲気を確認してみたり

_昨日生成したドット絵モドキ画像 を、DXRubyで描画してみて、どんな感じの画面になりそうか確認してみたり。



意外と悪くない、ような気もする。それぞれが動くと一つ一つの質の悪さも結構誤魔化せてしまうのかも。

確認に使ったソースは以下。画像は、 _0000.png を使用。

_test_draw.rb
# Tiny pixelart draw test with Ruby + DXRuby

require 'dxruby'

class MySprite < Sprite

  def initialize(x, y, img)
    super(x, y, img)
    @dx = rand(-0.5..0.5)
    @dy = rand(1..4)
    self.z = @dy * 2
  end

  def update
    super
    self.x += @dx
    self.y += @dy
    @dx *= -1 if self.x < 0 or self.x >= Window.width - self.image.width
    self.vanish if self.y > Window.height
  end
end

imgs = Image.loadTiles("0000.png", 16, 16)
sprs = []
cnt = 0

Window.resize(800, 600)
Window.loop do
  break if Input.keyPush?(K_ESCAPE)

  if cnt % 2 == 0
    w = imgs[0].width
    x = rand() * (Window.width - w * 1.2)
    y = -64
    sprs.push(MySprite.new(x, y, imgs[rand(imgs.size)]))
  end

  Sprite.update(sprs)
  Sprite.draw(sprs)
  Sprite.clean(sprs)

  cnt += 1
end

「なんだよ事前に画像作っておいて読み込んでるだけかよダッセー。その場で自動生成ぐらいしろよ」と思う人も出てくるだろうか…。一応、 _Ruby + cairo(rcairo) で生成してDXRubyで描画する例 もアップしてありますので組み合わせればそういうこともできるんじゃないかな…。ただ、おそらく生成時間が1/60秒では済まない予感も。Rubyは遅いので、というかこの場合使ってるcairoという描画ライブラリがちょっと遅いというか。

「コレのどこがドット絵だよテメーなめとんのか」と思った人はもっとナイスなアルゴリズムを考案してみてほしいなと…。コレは単に、ランダムな位置にランダムな色でグラデ塗りを置いてるだけ、だったりするけど、例えばダンジョン生成等のアルゴリズムを応用して矩形や円の繋がり方・隣接の仕方を調整できればもっとそれらしくなりそうな予感も。ていうか、 _Pixel Ship Generator がソレに近いアルゴリズムらしいけど結構それっぽく見えるというか。そんな感じで色々な案が思いつくんじゃないか、と。

もっとも、こういうのって使い道自体が思いつかないという問題が…。何に使えるんだコレ。

以上、30 日分です。

過去ログ表示

Prev - 2017/04 - Next
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30

カテゴリで表示

検索機能は Namazu for hns で提供されています。(詳細指定/ヘルプ


注意: 現在使用の日記自動生成システムは Version 2.19.6 です。
公開されている日記自動生成システムは Version 2.19.5 です。

Powered by hns-2.19.6, HyperNikkiSystem Project