mieki256's diary



2014/06/01() [n年前の日記]

#1 [cg_tools] Drawgraphic 2を購入してみた

Drawgraphic 2 は、SOURCENEXTから販売されてる、1,980円のドローソフト。1000円割引券とやらを貰えるキャンペーン?があったので購入してみたり。前々から気になってたけど踏ん切りがつかなかったわけで。

感想というか、結論を先に書くけど。Drawgraphic 2 は、ドローソフトのふりをしている、ビットマップ画像編集ソフトじゃないかなと…。買うんじゃなかった。騙された。

操作時の反応。 :

操作時の反応は、結構キビキビしてる印象。
  • Inkscape のように、何をするにしてもワンテンポ遅れる、ということはなく。
  • LibreOffice Draw のように、何かオブジェクトを追加するたびにモードが変わってしまう、てなこともなく。
作業自体はサクサクできる、ほうだと思う。起動も速い。そのあたりは大変グッドなのだけど…。

問題点。 :

数時間触ってたら、問題点がポロポロと。
  • png等、ビットマップ画像で保存する際、アンチエイリアスをかけてくれない。ドローソフトのくせして、出力結果までジャギってるのは酷い。
  • 画像として保存する際に、背景を透明にできない。必ず、何かの色と合成されてしまう。これではアルファチャンネルを持った素材画像を作れない。
  • 半透明塗りが無い。と思う。たぶん。
  • 保存ファイル(.fda)をダブルクリックしても開けない。「プログラムにコマンドを送信しているときに、エラーが発生しました。」とエラーメッセージが表示される。有償ソフトなのに、こういうレベルで不具合が出るとゲンナリする…。
  • ビットマップ画像フォーマットは、そこそこの種類に対応してるのに、ベクターデータフォーマットへの対応数が少ない。ドローソフトなのに…。例えば、svg未対応なあたりは、今時厳しい気がする。
  • pdf保存ができる時と、できない時がある。特定の図形が入っていたり、特定の操作をすると、保存できなくなる?
  • epsで保存してみたら、ベクターデータのはずなのに、ファイルサイズが数十MBもあった。そして、GIMP や Inkscape で該当epsを開いてもアンチエイリアスがかかっていない。要するに、このソフト、レンダリングしたビットマップ画像をそのまま eps 内に突っ込んで「eps保存しました」と嘯いてる。
  • emfで保存して Inkscape で読み込んでみたら、縦横比はおかしいわ、線の変更等もまともにできないわで。(もっとも、これは Inkscape のほうにも原因がありそうな気もするけど…)

ベクターデータ(eps)として保存したつもりが、実は中身はビットマップ画像でした! ババァーン! てのは、さすがに酷過ぎると思いました。お前、本当にドローソフトなの?

でもまあ、巨大なビットマップ画像として保存して、別のソフトで縮小処理をして、とかしてやれば使い道があるかも…と思ったら 342 dpi が指定できる最大値のようで。ダメだこりゃ。さすがソースネクスト。いや、メンテナンスしてるのはファンファーレだと思いますけど。

まあ、元が古いソフトらしいので、仕方ないのかも。値段も安いし。

起動時にエラーが出る問題。 :

.fda をダブルクリックすると、「プログラムにコマンドを送信しているときに、エラーが発生しました。」とエラーメッセージが表示されて開けない件だけど。解決できないか四苦八苦。ちなみに環境は Windows7 x64。

DOS窓で以下を打った。
> assoc .fda
.fda=Scribe.Designing

> ftype Scribe.Designing
Scribe.Designing="C:\Program Files (x86)\SOURCENEXT\DrawGraphic2\DrawGr2.exe" /dde
「/dde」が指定されてるあたりが怪しい。というのも、件のエラーメッセージでググると、えてして Excel で同様の問題が起きる事例に遭遇するわけで。そこでも DDE (Dynamic Data Exchange) が絡んでるらしくて。

とりあえず、管理者権限でDOS窓を開いて、以下のように変更。
> ftype Scribe.Designing="C:\Program Files (x86)\SOURCENEXT\DrawGraphic2\DrawGr2.exe" "%1"
/dde を使わずに、"%1" でファイルのパスを渡すように、関連付けを変更、とでも言えばいいのかな…。

ダメだった。相変わらず同じエラーが出る。ただし、以前はファイルすら開けなかったのが、今回はエラーメッセージが出ている裏で、一応ファイルを開いている状態になった。しかし、まだ何か設定がおかしいのだろう…。

よく分からないのが、以下のようにDOS窓から打ち込むとエラーが出ないこと。
"C:\Program Files (x86)\SOURCENEXT\DrawGraphic2\DrawGr2.exe" "hoge.fda"

また、「送る」フォルダを開いて、 *1 中に DrawGr2.exe のショートカットを作って、「送る」から .fda を開いてみた。これもエラーが出ない。ということは、指定は間違ってないと思えるわけで。であれば、エクスプローラ側の設定がおかしいんだろう。たぶん。

ウチの環境でのみ起きる問題なのかなと思ったけれど、Amazonのレビューでも同様の報告を見かけた。
専用形式で保存したファイルをダブルクリックで起動させるとエラーして開かない。でもそのファイルをショートカットアイコンへドロップするとちゃんとその保存したファイルが開く・・・

Amazon.co.jp: カスタマーレビュー: Drawgraphic2 (説明扉付きスリムパッケージ版) より

どうやらウチだけじゃないな。

とりあえず、 _FileTypesMan という、関連付けを変更できるアプリで、.fda に割り当てられていたアクションを弄ってみたり。
  • 「DrawGraphicで開く(&O)」「open」の2つを無効にする。該当項目を右クリックして「アクションを編集」→ 無効にチェックを入れる。
  • アクションを新規作成。「参照」をクリックして、DrawGr2.exe を指定。自動で「"%1"」も追加される。
  • 新しく作ったアクションを「既定のアクション」に設定。
これでエラーが出ない状態にはなった。

それにしても、どうしてこういうことになるんだろう…? DDEとやらを使わないと、何か別の問題が起きるのかな。

半透明機能は存在してた。 :

オブジェクトを選択した状態で、書式 → 透明度、を選んだら半透明にすることができた。機能を割り当てたツールボタンが見当たらないし、右クリックしてもメニューに無かったものだから、機能として持ってないのだなと勘違いしちゃいました…。

たぶんコレ、後になってから機能をつけたか、当時は実験的機能だったりして、UIへの反映が放置されたんじゃないかな…。

しかしやっぱり、背景を透明にして、ビットマップ画像保存をする方法が分からず。まあ、どうせアンチエイリアスがかからないから、別のソフトで開いて保存したほうがいいんだろうけど。

*1: スタートボタン → shell:sendTo で、「送る」フォルダは開ける。

この記事へのツッコミ

Re: Drawgraphic 2を購入してみた by 名無しさん    2016/08/16 12:21
初めまして
拝見いたしました、ちょっと興味がありましてメールさせていただきます
私もDrawgraphic 2のダウンロード版を使ってみました
というのもMicrographx Windows Draw6を前から使っていて 大変に良いソフトだったのですが今 セブン64Bit 以上で使えないのでこれを使ってみましたがインターネットに繋がっていないと編集が思うように出来なくて困っています 良い方法がないものでしょうか

2014/06/02(月) [n年前の日記]

#1 [dxruby][ruby][cg_tools] DXRubyとcairoでswfのシェイプを描画してみる実験

_2014/05/29 に、DXRubyでswfのシェイプを描画する実験をしたのだけど。あまりにも処理が遅い上に、描画も汚くてしょんぼりだったわけですが。 _cairo と組み合わせて使えば、もっと速く、もっと綺麗にできるんじゃないかと思えたので試してみたり。

こんな感じになりました。右側が、cairoを使って描画した結果。
shapeparse_width_cairo.png
さすがに美しい…。cairo、凄いなあ。

ソースは以下。

_shapeparse_with_cairo.rb
require 'nokogiri'
require 'dxruby'
require 'cairo'
require 'tempfile'
require_relative 'bezierdraw'

# swfのシェイプを解析してImageに描画するクラス(cairo使用版)
#
class ShapeParse
  DBG = false
  DBG2 = true
  TWIP = 20.0
  attr_accessor :id
  attr_accessor :width
  attr_accessor :left
  attr_accessor :top
  attr_accessor :height
  attr_accessor :image
  attr_accessor :draw_data
  attr_accessor :clip_enable
  attr_accessor :surface
  attr_accessor :context

  def initialize(node, clip_enable = true)
    self.clip_enable = clip_enable
    self.id = node["objectID"].to_i
    puts "id[#{self.id}] #{node.name}" if DBG

    n = node.at(".//bounds/Rectangle")
    self.left = (n["left"].to_f / ShapeParse::TWIP).to_i
    self.top = (n["top"].to_f / ShapeParse::TWIP).to_i
    self.width = (n["right"].to_f / ShapeParse::TWIP).to_i
    self.height = (n["bottom"].to_f / ShapeParse::TWIP).to_i
    puts "size : (x,y)=#{self.left},#{self.top} (w,h)=#{self.width},#{self.height}" if DBG

    self.width = Window.width if self.width < Window.width
    self.height = Window.height if self.height < Window.height

    x0, y0 = 0, 0
    x1, y1 = 0, 0
    x2, y2 = 0, 0
    f0_enable = true
    f1_enable = true
    l_enable = true
    fc = [[0, 0, 0, 0], [0, 0, 0, 0]]
    lc = [0, 0, 0, 0]
    lwidth = ShapeParse::TWIP
    lines = []
    self.draw_data = []

    n = node.at(".//shapes/Shape/edges").child

    while n != nil
      case n.name
      when "ShapeSetup"
        unless lines.empty?
          save_draw_data(lines, f0_enable, f1_enable, l_enable,
                         lwidth, fc[0], fc[1], lc)
          lines = []
        end

        s = "#{n.name} "
        x0 = n["x"].to_f if n.key?("x")
        y0 = n["y"].to_f if n.key?("y")
        f0_enable = (n["fillStyle0"] == "1")? true : false
        f1_enable = (n["fillStyle1"] == "1")? true : false
        l_enable =  (n["lineStyle"] == "1")? true : false

        if false
          s += "(x,y)=#{x0/TWIP}, #{y0/TWIP} ("
          s += (f0_enable)? "f0 " : "   "
          s += (f1_enable)? "f1 " : "   "
          s += (l_enable)? "l" : " "
          s += ")"
          puts s
        end

        n.xpath(".//fillStyles").each do |nn|
          nn.xpath(".//Solid/color/Color").each_with_index do |m, i|
            fc[i] = get_color(m)
          end
        end

        n.xpath(".//lineStyles").each do |nn|
          nn.xpath(".//LineStyle").each do |m|
            lwidth = m["width"].to_i if m.key?("width")
            m.xpath(".//color//Color").each do |c|
              lc = get_color(c)
            end
          end
        end

        if n.key?("x") and n.key?("y")
          lines.push([x0, y0])
        end

      when "CurveTo"
        # 二次ベジェ曲線
        x1 = x0 + n["x1"].to_f
        y1 = y0 + n["y1"].to_f
        x2 = x1 + n["x2"].to_f
        y2 = y1 + n["y2"].to_f
        lst = BezierDraw.calc(x0, y0, x1, y1, x2, y2)
        lines.concat(lst.slice(1..-1))
        x0 = x2
        y0 = y2

      when "LineTo"
        # 直線
        x1 = x0 + n["x"].to_f
        y1 = y0 + n["y"].to_f
        lines.push([x1, y1])
        x0 = x1
        y0 = y1

      else
        puts "!! Unknown Tag : #{n.name}"
      end
      n = n.next
    end

    draw(false)
  end

  # 色配列を取得
  #
  # @param [Object] node 「Color」ノード
  # @return [Array] A,R,G,Bが入った配列
  #
  def get_color(node)
    col = [255, 0, 0, 0] # ARGB
    ["alpha", "red", "green", "blue"].each_with_index do |s,i|
      col[i] = node[s].to_f if node.key?(s)
    end
    return col
  end

  # 描画用の直線データを記録
  #
  # @param [Array] lines 直線データ配列
  # @param [Boolean] fe0 塗り潰し0をするか否か
  # @param [Boolean] fe1 塗り潰し1をするか否か
  # @param [Boolean] le 線を描くか否か
  # @param [Number] lw 線幅
  # @param [Array] fc0 塗り潰し0の色配列
  # @param [Array] fc1 塗り潰し1の色配列
  # @param [Array] lc1 線描画の色配列
  #
  def save_draw_data(lines, fe0, fe1, le, lw, fc0, fc1, lc)
    np = []
    ox, oy = nil, nil
    lines.each do |p|
      x, y = p
      x /= TWIP
      y /= TWIP
      np.push([x, y]) if x != ox or y != oy
      ox, oy = x, y
    end

    # 記録
    self.draw_data.push([np, fe0, fe1, le, lw, fc0.dup, fc1.dup, lc.dup])
  end

  # Imageオブジェクトに描画
  #
  # @param [Boolean] clear_enable trueならImageをクリアしてから描画
  #
  def draw(clear_enable = true, scale_x = 1.0, scale_y = 1.0)
    # self.image.clear if clear_enable

    # cairoのサーフェスを確保
    self.surface = Cairo::ImageSurface.new(Cairo::FORMAT_ARGB32,
                                           self.width, self.height)
    self.context = Cairo::Context.new(surface)

    self.draw_data.each do |d|
      p, fe0, fe1, le, lw, fc0, fc1, lc = d
      np = []
      p.each { |q| np.push([q[0] * scale_x, q[1] * scale_y]) }
      draw_line(self.context, np, fe0, fe1, le, lw, fc0, fc1, lc)
    end

    # 一時ファイル作成
    temp = Tempfile.new(['shapeparse', '.png'])
    temp.binmode
    temp.close

    # pngファイルに出力
    self.surface.write_to_png(temp.path)

    # DXRubyで読み込み
    self.image = Image.load(temp.path)
    temp.close(true)
  end

  # 色配列の数値を変換して返す
  #
  # @note 255までの値を持っていた色配列を、1.0までの値に変換する
  #
  # @param [Array] c 色配列ARGB
  # @return [Array] 色配列RGBA
  #
  def get_rgba(c)
    r = []
    c.each do |n|
      m = n / 255.0
      m = 0.0 if m < 0.0
      m = 1.0 if m > 1.0
      r.push(m)
    end
    return r[1], r[2], r[3], r[0]
  end

  # 多角形を描画
  #
  # @param [Object] ct cairoのcontext
  # @param [Array] p 頂点座標が入った配列。
  # @param [Boolean] f0_enable trueなら塗り潰し有、falseなら塗り潰し無
  # @param [Boolean] f1_enable trueなら塗り潰し有、falseなら塗り潰し無
  # @param [Boolean] l_enable trueなら線を描く、falseなら線を描かない
  # @param [Number] lwidth 線幅
  # @param [Array] fc0 色配列。A,R,G,B
  # @param [Array] fc1 色配列。A,R,G,B
  # @param [Array] lc 色配列。A,R,G,B
  #
  def draw_line(ct, p, f0_enable, f1_enable, l_enable,
                lwidth, fc0, fc1, lc)
    return if (!f0_enable and !l_enable)

    ct.set_line_join(Cairo::LineJoin::ROUND) # 結合点の種類を指定
    ct.set_line_cap(Cairo::LineCap::ROUND) # 線の終点の種類を指定

    lw = lwidth / TWIP
    lw = 1.0 if lw < 1.0 # swfは1.0より細い線幅も1.0の線幅で描画するらしい
    ct.set_line_width(lw) # 線幅を指定

    # 塗る色を指定
    if f0_enable
      ct.set_source_rgba(get_rgba(fc0))
    else
      ct.set_source_rgba(get_rgba(lc))
    end

    x0 , y0 = p[0]
    ct.move_to(x0, y0)
    1.step(p.size - 1, 1) do |i|
      ct.line_to(p[i][0], p[i][1])
    end

    if f0_enable
      # 塗り潰し
      ct.fill_preserve
    end

    # 線を描画
    ct.set_source_rgba(get_rgba(lc)) if l_enable

    ct.stroke
  end
end

if $0 == __FILE__
  # ----------------------------------------
  # 動作テスト

  font = Font.new(14)

  # swfmillで swf → xml変換したxmlを解析
  # infile = "swfdata/test_pdr1.xml"
  infile = "swfdata/test_pdr2.xml"
  doc = Nokogiri::XML(File.open(infile)) {|cfg| cfg.noblanks}

  objs = Hash.new
  doc.xpath("//DefineShape3").each do |node|
    pdr = ShapeParse.new(node, false) # ベクターデータを解析して画像化
    objs[pdr.id] = pdr
  end

  redraw_fg = false
  ang = 0
  scale = 1.0

  Window.bgcolor = [0, 190, 118]
  Window.fps = 60

  # メインループ
  Window.loop do
    break if Input.keyPush?(K_ESCAPE)

    # Rキー押しでスケールを変えて再描画
    redraw_fg = (Input.keyDown?(K_R))? true : false

    objs.each_value do |o|
      # ベクターデータを再描画できなくもないが、遅くて実用にならない
      o.draw(true, scale, scale) if redraw_fg
      Window.draw(0, 0, o.image)
    end

    s = "#{Window.real_fps} FPS / CPU: #{Window.getLoad.to_i}%"
    Window.drawFont(4, 4, s, font)

    ang += 10
    scale = Math.cos(ang * Math::PI / 180.0) + 1.5
  end
end

データ一式も置いときます。

_swf_shape_parse_20140602.zip (434KB)

アンチエイリアスをかけながら描画してくれるライブラリなだけあって、さすがに綺麗。しかも、CPU負荷率を見る限りでは、自前で線描画や多角形塗りをするよりも、40倍ぐらい速くなりました。

それでもやっぱり、リアルタイムに描画するのは速度的に無理なので、実用にはならないのですけど。どう考えても、最初からレンダリング済みのビットマップ画像を用意しておくべきだなと。

更に、このやり方、現状ではかなりトホホな点があって。cairo で描画した後、結果を一旦 pngファイルでHDDに保存して、それを DXRuby の Image にロードし直していたりするわけで。

これがもし、cairo の Surface から、pngファイルとして書き出すべきバイナリデータを直接取得できれば…。あるいは、Surface内容をRGBAの配列で取得できれば、わざわざクソ遅いHDDにアクセスしなくても済むのですけど。残念ながら、そのようなメソッドは見つからなくて。

それにしても、swfのシェイプをこうして描画できるということは、svgも描画できたりするのかしら。たしか、svg を解析・描画する Rubyライブラリも存在していたはずなので、ちと試してみたいところ。

メモリマップドファイルは使えないかな。 :

メモリ上に仮想ファイルシステムの類でも作れたら、HDDにアクセスしなくても済むんじゃないかと思えてきたのだけど。そういうことができるライブラリってあるのかしら。

_メモリマップトファイル - Wikipedia には、
Ruby : Mmap というgem(ライブラリ)があり、メモリマップトファイルを実装している。

メモリマップトファイル - Wikipedia より

という記述があったのだけど。

_Rubyist Magazine - Ruby Library Report 【第 4 回】 Win32Utils
_djberg96/win32-mmap - GitHub

もしかして、Windows + Ruby なら、win32-mmap てのが使える…?

DXRuby + cairo の組み合わせ。 :

cairo の使い方については、 _rcairo 事始め - takihiro日記 を参考にさせてもらって試していたのですけど。せっかくだから、DXRuby で結果表示をするサンプルも置いときます。

_test_cairo1.rb
# cairo のテスト
#
# 以下を参考にしました。
#
# rcairo 事始め - takihiro日記
# http://d.hatena.ne.jp/takihiro/20100331/1269992290

require 'cairo'
require 'dxruby'
require 'tempfile'

w, h = 640 - 32, 480 - 32
surface = Cairo::ImageSurface.new(w, h)
context = Cairo::Context.new(surface)

# 背景色を指定
context.set_source_rgba(0, 0, 0, 0) # 透明
context.rectangle(0, 0, w, h)
context.fill

# 四角を書く
context.set_source_color(Cairo::Color::RED)
x, y, rw, rh = 20, 20, w / 2, h / 2
context.rectangle(x, y, rw, rh)
context.fill_preserve
context.set_source_color(Cairo::Color::BLACK)
context.stroke

# 円を書く
context.set_source_color(Cairo::Color::GREEN)
r = h / 3
x, y, rw, rh = 100, 30, 50, 50
context.arc(w/2, h/2, r, 0, 2 * Math::PI)
context.fill_preserve
context.set_source_color(Cairo::Color::BLUE)
context.stroke

# 多角形
context.set_source_color(Cairo::Color::YELLOW)
context.move_to(30, 30)
context.line_to(20, 60)
context.line_to(40, 80)
context.line_to(80, 40)
context.line_to(60, 30)
context.line_to(30, 30)
context.fill_preserve
context.set_source_color(Cairo::Color::PURPLE)
context.stroke

# 重ねる画像を設定
surface2 = Cairo::ImageSurface.from_png('sample2.png')
context.set_source(surface2,
                   w - surface2.width - 24,
                   h - surface2.height - 24)
context.paint

# 文字配置
context.set_source_rgb(255, 255, 255)
context.font_size = 26
context.move_to(10, 150)
context.show_text('文字') # フォントを指定してないので文字化けする

context.move_to(10, 180)
context.select_font_face('メイリオ', 0, 0)
context.show_text('メイリオフォント')

# ----------------------------------------
# 一時ファイルを作成
temp = Tempfile.new(['test', '.png'])
temp.binmode
temp.close

# pngファイルに出力
surface.write_to_png(temp.path)

# DXRubyで読み込み
img = Image.load(temp.path)
temp.close(true)

# DXRubyで表示
Window.bgcolor = C_BLUE
Window.loop do
  break if Input.keyPush?(K_ESCAPE)
  Window.draw(16, 16, img)
end

以下のような感じになります。
cairo_sample.png

#2 [cg_tools] フリーで使えるドローソフトを試用してみたり

Drawgraphic 2 がちょっとアレなソフトだったので、もしかするとフリーソフトのほうがグッドだったりしないのかなと疑念が湧いて。ググっていたら、いくつかのドローソフトが気になったので、それぞれ試用してみたりして。

環境は、Windows7 x64。

ちなみに、自分、普段は、 _Inkscape 0.48.2_LibreOffice Draw を使ってます。

Dynamic Draw を試用。 :

_Dynamic Draw

Drawgraphic 2 との比較だけど。
  • ビットマップ画像保存時、背景を透明にして出力できる。(ファイル → 選択部品の画像をエクスポート、を選べばビットマップ画像として保存できる)
  • ビットマップ画像保存時、アンチエイリアスを有効にして出力できる。
  • svgエクスポートにも対応。
  • 一瞬で起動する。爆速。
このあたりは、Dynamic Draw > Drawgraphic 2 かも。

ただ、操作性については若干難有り。
  • LibreOffice Draw ほど酷くはないけど、Drawgraphic 2 に比べると、色や線幅の指定が時々元に戻ってしまうので、連続での作業がしづらい。
  • メニューの表記が独特・Windowsの一般的なアプリから乖離していて、パッと見で「難しそう」と感じてしまう。ショートカットキーの割り当てを強調して伝えたいのは理解できるけど…。「F.ファイル E.編集 V.表示」より、「ファイル(F) 編集(E) 表示(V)」のほうが良いのでは…。
  • アイコンが全て白黒なので、ツールボタンの場所や定義が把握しにくい。昔の*NIX系ソフトでよく見かけた、ストイックなノリに近いというか。
特にUIの見た目は、親切心でやってみたことが逆効果になってる印象。他の一般的なアプリと合わせたほうがウケがイイ場面ってあるよなと…。まあ、CG関係のソフトは、えてしてUIに独自性を盛り込み過ぎてアレなことになる事例が多いのですけど。例えば Poser とか Bryce なんて、もっと酷かったし。…そのあたりを考えると、Drawgraphic 2 はバランス感覚があるように思えて好印象なのですが。

そんな感じで、操作性・作業のしやすさに関しては、Drawgraphic 2 > Dynamic Draw > LibreOffice Draw、という印象。

他に気づいた点をメモ。
  • 半透明塗りは無いらしい。
  • アンチエイリアス有効、かつ、背景を透明にしてビットマップ画像を出力すると、境界が汚くなる。いわゆるフリンジ?が出ちゃう。
要するに、アルファチャンネルが絡んでくる機能の実装は難有りかなと…。

グリッドをどの階層に描画するかという問題。 :

Drawgraphic 2 も、Dynamic Draw も、図形を塗り潰すとグリッドが隠れてしまって、途端に作業しづらくなるのだけど。

これが Inkscape なら、図形を塗り潰してもグリッド線が上に表示され続けるので、作業が快適だなと気付いたり。その代り(?)、Inkscape は全てがモッサリですが。

Drawgraphic 2 は、図形のアウトラインだけ描画するモード(ワイヤーフレーム表示)があるので、グリッドに頼った作業をするのであれば、そのモードで表示してしまうのもアリかなと。

たしか、FREEHAND にも簡易描画モードがあったような気がする。昔のドローソフトは、PCスペックが低くても作業できるようにするために、描画処理を軽くする仕様も求められたのだろうけど。結果的に、グリッドに頼った作業をする時も、回避策が存在している状態になったと言えるのかも。

Dynamic Draw には、アウトラインだけ描画するモードも、グリッドを最前面に描画する設定も、どちらも無いように見えました。

ParaDrawを試用。 :

_ParaDraw

  • グリッド表示・吸着機能がある。かつ、図形を塗り潰してもグリッドは手前に表示されてるので作業しやすい。
  • ビットマップ画像保存時は背景を透明にすることもできる。
  • アンチエイリアスもかかってる。
ゲーム等の素材画像を作る際には便利に使えるかもしれず。

ただ、ベクターデータとして swf か pdr でしか保存できないあたりが厳しい…。

pdr/swf/svg関係の変換ツール。 :

_PDR2SVG

pdr (ParaDraw形式) → svg変換ができる。試してみたところ、一部の線データが消滅(したように見えた)けど、そこそこsvgに変換してくれた。 _SVG終章 - パラフラノススメ - Yahoo!ブログ という記事によると、Flashとsvgは線の太さ(ストロークの太さ)について仕様が違うので再現は難しいらしい。

_SWF2SVG

swf → svg変換ができる。Adobe AIR製アプリ。 _FlashファイルからシェイプをSVGとして抜き出す - SWF2SVG MOONGIFT によると、コマンドラインツールだそうで、GUIは無し。以下のようなバッチファイルを、PATHの通ったところに置いて使ってみたり。

swf2svg.bat
@set SWF2SVGPATH="C:\Program Files (x86)\SWF2SVG\SWF2SVG.exe"
%SWF2SVGPATH% %1 %2 %3 %4 %5 %6 %7 %8 %9
こちらも比較的、swfの見た目を再現してくれた。

J.S Draw を試用。 :

_J.S Draw
_すらすらプログラマー J.S Draw

操作性・作業のしやすさに関しては、LibreOffice Draw と似た感じで、現時点ではあまりよろしくなく。図形を置くたびに色設定その他がリセットされたり、図形を塗り潰すとグリッドが隠れてしまったりで。

鋭意開発中のソフトらしく、機能面はまだまだこれから、らしい。しかし、それでも…。
  • 描画画面はアンチエイリアスがかかっている。
  • 保存形式としてxmlが選べる。
新世代らしさを感じるドローソフトだなと…。今後の発展に期待。

#3 [anime][neta] 「謎の支援者」ネタ

夢の中で、「盲目の美少女が、周囲からアレコレ助けてもらうのだけど、実はその周囲とは」てなネタを見て。目が覚めてから、ちょっと考え込んだり。

元ネタは、何だろうなあ…。

とりあえず、自分が夢でソレを見たのは、「HUNTERxHUNTER」の影響じゃないかなと思ったのだけど。学生時代にも、山口美由紀先生の短編作品でそういうのを見たような気もするし。子供の頃に、怪談の類でそういうネタを見ていたような気もするし。何にせよ、メジャーな元ネタ、様々なアレンジ作品があるような気がする。

「『周囲』の正体は何か?」という問いに対して、各人がどんな答えを出してくるかで、なんちゃって性格診断もできるかもしれないなと。幽霊だったり、ロボットだったり、モンスターだったり、あるいは、れっきとした人間だけど裏があったり、等々色々思いつくだろうけど。どれを思いついたのかで、その人の嗜好が分かりそう。真っ先に幽霊を思いつく人と、ロボットを思いつく人では、明らかに属性が違うだろから、「貴方は○○派です」と、テキトーな決めつけで分類して会話のネタにできたりしそう。ていうかソレってロールシャッハテストなのかな。

こういうネタは、小説だと表現しやすいのだろうな。「彼」「彼女」とだけ記述しておいて、実はこうだったのです、てな見せ方がしやすいし。小説は、本来の設定とは全く異なるイメージを読者が想像してしまうように、誘導することがたやすい。文字情報しかないから、意図的に誤解させる、という手管が使える。

漫画やアニメだと、ちょっと難しいよなと。「HUNTERxHUNTER」のように、本来そこにある状況をそのまま描く感じになって、怪談っぽい印象が随分と弱くなってしまう。

いや、違う見せ方もあるか…。当人の思い込みをそのまま視覚化しちゃうソレ。読者側はうっすらと状況が分かってるのに、提示されてる光景が異質なので、そのギャップでクラクラしてしまう。高野文子先生の「田辺のつる」…でしたっけか? あの見せ方。

別に、視覚的な見せ方で凝る必要はないのか…。「あしながおじさん」だって、謎の支援者ネタだよな…。

要するに、支援者の正体を隠しておいて、それを開示していくことで、話は作れるのだろうなと。自分が夢で見た「盲目の美少女」というソレは、支援者の姿を隠すために要する設定でしかなく。別の何かで隠せれば、それで済んでしまうのかも。

と、ここまで書いて、ふと思い出したけど。この手の元ネタは、「耳なし芳一」じゃないのかな…。

この記事へのツッコミ

Re: 「謎の支援者」ネタ by 名無しさん    2014/06/03 15:31
星新一でも似たような話がありましたね。
http://occult-atoaji.sakura.ne.jp/?p=8746

2014/06/03(火) [n年前の日記]

#1 [ruby] メモリマップドファイルについて勘違いしてた

mmap って、メモリ上にファイルを作るという話じゃなくて、ファイルをメモリのように扱う、という仕組みだったのですね…。自分がやりたかったことと真逆じゃないか…。トホホ。

それはともかく、とりあえず _win32-mmap_サンプル をコピペして実験。
# win32-mmap のテスト
# ファイルをメモリのように扱う

require 'win32/mmap'

tmpfile = File.join(File.expand_path(File.dirname(__FILE__)), "test.map")

map1 = Win32::MMap.new(:file => tmpfile, :size => 1024)
map1.foo = 'hello'
map1.bar = 77
map1.close

map2 = Win32::MMap.new(:file => tmpfile)
p map2.foo # 'hello'
p map2.bar # 77
map2.close
たしかに、test.map が作られた。

コレ、どんな場面で使うんだろう…。

#2 [dxruby] cairoの出力結果をファイルを作らずにDXRubyに渡せた

_Cairo::Surfaceクラス を眺めていたら、
write_to_png(stream)
write_to_png(filename)

サーフェスの内容をPNG画像として書き出します。 引数がwriteメソッドを持っている場合はそのオブジェクトの writeメソッドを使ってPNG画像を出力します。

Cairo::Surfaceクラス より

という記述に気がついて。さらにググったら、 _class StringIO が使えそうだなと。

以下で試してみたら、DXRuby で表示できました。ヤッター。

_test_cairo1_stringio.rb
# cairo のテスト。
# StringIO で DXRuby の Image にデータを渡してみる版

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

w, h = 640 - 32, 480 - 32
surface = Cairo::ImageSurface.new(w, h)
context = Cairo::Context.new(surface)

# 円を書く
context.set_line_width(12)
context.set_source_color(Cairo::Color::GREEN)
r = h / 3
x, y, rw, rh = 100, 30, 50, 50
context.arc(w/2, h/2, r, 0, 2 * Math::PI)
context.fill_preserve
context.set_source_color(Cairo::Color::RED)
context.stroke

# StringIOを使って png出力、かつ、Image生成
temp = StringIO.new("", 'w+')
surface.write_to_png(temp)
temp.rewind
img = Image.loadFromFileInMemory(temp.read)

# DXRubyで表示
Window.bgcolor = [64, 64, 64]
Window.loop do
  break if Input.keyPush?(K_ESCAPE)
  Window.draw(16, 16, img)
end
しかし、昨日の虎描画版で、同じ処理を試してみても、処理が速くなった感じはしなくて。

もしかすると、ファイル書き込みした直後はキャッシュ?が効いてて、読み込み処理もさほど遅くなかったとか? 実際はどうだか分からんですけど。

#3 [ruby][dxruby] DXRuby と cairo と rsvg2 で svgを描画して表示してみたり

_Rubyist Magazine - cairo: 2 次元画像描画ライブラリ によると、rsvg2 ライブラリを使えば svgファイルを cairo で描画できるらしくて。

であれば、DXRuby上でも、cairo と rsvg2 を使って svgファイルを描画できるのかなと。早速実験。

しかし、Windows + Ruby 1.9.x で、rsvg2 を使おうとしたら、ハマってしまったり。以下のライブラリが必要なのだけど…。

_rsvg2 | RubyGems.org | your community gem host
_gdk_pixbuf2 | RubyGems.org | your community gem host
_glib2 | RubyGems.org | your community gem host

Ruby 2.x の場合。 :

Ruby 2.x は、以下の通りで、すんなりインストールできた。
gem install cairo --platform=x86-mingw32
gem install glib2
gem install gdk_pixbuf2
gem install rsvg2
> gem list | grep "cairo\|glib2\|gdk_pixbuf2\|rsvg2"

cairo (1.12.9 x86-mingw32)
gdk_pixbuf2 (2.2.0 x86-mingw32)
glib2 (2.2.0 x86-mingw32)
rsvg2 (2.2.0 x86-mingw32)

Ruby 1.9.x の場合。 :

Ruby 1.9.x では問題があって。
  • rsvg2 2.2.0 は、cairo 1.12.8 以降を要求する。
  • しかし、Ruby 1.9.x で利用できるのは、cairo 1.12.4 まで。
つまり、このままだと Ruby 1.9.x で rsvg2 は使えない。

試しにバージョンを落としてインストールしてみたり。
gem install cairo --version '=1.12.4'
gem install glib2 --version '=1.2.6'
gem install gdk_pixbuf2 --version '=1.2.6'
gem install rsvg2 --version '=1.2.6'
すると、途中で、glib2 と cairo の最新版を勝手にインストールされてしまった。

ダメ元で、最新版だけをアンインストールしてみる。
> gem uninstall glib2

Select gem to uninstall:
1. glib2-1.2.6-x86-mingw32
2. glib2-2.2.0-x86-mingw32
3. All versions

> 2

Successfully uninstalled glib2-2.2.0-x86-mingw32

> gem uninstall cairo

Select gem to uninstall:
1. cairo-1.12.4-x86-mingw32
2. cairo-1.12.9-x86-mingw32
3. All versions

> 2

Successfully uninstalled cairo-1.12.9-x86-mingw32

以下のような状態になった。
> gem list | grep "cairo\|glib2\|gdk_pixbuf2\|rsvg2"

cairo (1.12.4 x86-mingw32)
gdk_pixbuf2 (1.2.6 x86-mingw32)
glib2 (1.2.6 x86-mingw32)
rsvg2 (1.2.6 x86-mingw32)

この状態にすれば、Ruby 1.9.x でも、rsvg2 が一応使えた、ように見えた。

ただ、最新版じゃないから、何かバグがあったりするのかもしれないけど…。そもそも、こんなインストール作業、許されるのかな…。初心者なので分かりません。

実験に使ったソース。 :

以下のようなソースを書いた。

_svgdisp_width_dxruby.rb
require 'rsvg2'
require 'stringio'
require 'dxruby'

# SVGをrsvg2とcairoで描画してDXRubyのImageにする
#
class Svg2Image
  attr_accessor :handle
  attr_accessor :width
  attr_accessor :height
  attr_accessor :ratio
  attr_accessor :image

  def initialize(infile, ratio = 1.0)
    self.handle = RSVG::Handle.new_from_file(infile)
    make_image(ratio)
  end

  def make_image(ratio = 1.0)
    self.ratio = ratio.to_f
    self.width, self.height, = self.handle.dimensions.to_a
    self.width *= ratio
    self.height *= ratio

    Cairo::ImageSurface.new(Cairo::FORMAT_ARGB32,
                            self.width, self.height) do |surface|
      context = Cairo::Context.new(surface)
      context.scale(self.ratio, self.ratio)
      context.render_rsvg_handle(self.handle)

      temp = StringIO.new("", "w+")
      surface.write_to_png(temp)
      temp.rewind
      self.image.dispose if self.image
      self.image = Image.loadFromFileInMemory(temp.read)
      temp.close
    end

  end
end

if $0 == __FILE__
  # ----------------------------------------
  # 動作テスト

  infile = "Ghostscript_Tiger.svg"

  scale = 0.5
  scale_add = 0.1
  svg = Svg2Image.new(infile, 0.5)

  font = Font.new(14)

  Window.bgcolor = [64, 64, 64]
  Window.loop do
    break if Input.keyPush?(K_ESCAPE)

    # Rキーで拡大縮小再描画
    if Input.keyDown?(K_R)
      svg.make_image(scale)
      scale_add *= -1 if scale <= 0.25 or scale >= 1.5
      scale += scale_add
    end

    Window.draw(0, 0, svg.image)

    s = "#{Window.real_fps.to_i} FPS  #{Window.getLoad.to_i} %"
    s += "  #{svg.image.width} x #{svg.image.height}"
    Window.drawFont(4, 4, s, font)
  end
end
Ghostscript_Tiger.svg は、 _File:Ghostscript Tiger.svg - Wikimedia Commons から入手。

結果は以下の通り。
svgdisp.png
描画できたっぽい感じ。

Ruby 1.9.x を使ってる場合は、ライブラリのインストールが面倒臭いという問題はあるものの。一応、DXRuby でも、この程度の記述で、svgファイルを描画できました。cairo と rsvg2、ありがたいなあ。

でもまあ、DXRuby でベクターデータを描画するメリットって、思いつかないのですけど。あらかじめビットマップ画像にしておいて、ソレを使うほうがいいよな…。

cairo の描画結果を、DXRuby を使うことで即座に表示・確認できる、といった使い方なら、これもアリなのかな…。

#4 [ruby] bundlerについて勉強

Ruby のライブラリをインストールする際、RubyGems なる仕組みが ―― gem というコマンドが使えるわけですけど。Ruby 1.9.3 上で、cairo、glib2、gdk_pixbuf2、rsvg2 のバージョンを決め打ちしてインストールする作業が面倒臭かったので、bundler なる仕組みを使えないものかなと思って調べ始めたり。

とりあえず、bundler をインストール。
gem install bundler

雛形ファイルを作る。
bundle init
Gemfile というファイルが、カレントフォルダに作られた。エディタで開く。

以下のような感じに修正してみたけど…。
source "https://rubygems.org"

platforms :mingw_19 do
  gem "cairo", "1.12.4"
  gem "glib2", "1.2.6"
  gem "gdk_pixbuf2", "1.2.6"
  gem "rsvg2", "1.2.6"
end

# platforms :mingw_20 do
#   gem "cairo"
#   gem "glib2"
#   gem "gdk_pixbuf2"
#   gem "rsvg2"
# end
platforms :mingw_19 do 〜 end で、「Ruby 1.9 mingw32版なら、このライブラリをインストールせよ」と指定をしている、らしい。よく分かってないですが。ちなみに、 _Gemfile(5) - A format for describing gem dependencies for Ruby programs の、PLATFORMS (:platforms) のところに、記述できる種類の一覧があるようで。

あらかじめ、cairo、glib2、gdk_pixbuf2、rsvg2 を、gem uninstall xxxx でアンインストールしておいた上で、以下を実行。
bundle install
これで、バージョン決め打ちでインストール出来たように見えた。

ただ、この指定だと、Rubyのシステム部分?に、ライブラリをインストールしてしまう。今回はシステムにインストールしたかったので、コレでいいのだろうけど。フツーはプロジェクト単位で、どのライブラリを使うのか、分けて管理するらしくて。一般的には、プロジェクトフォルダ/vendor/bundle 以下にインストールする。らしい。たぶん。
bundle install --path vendor/bundle
しかし、このままだと、スクリプト内で require 'hoge' とかしても「ライブラリがねえよ」と言われてしまう。…どうすれば使えるようになるのかは、まだ調べてないです。

それはともかく。一つの Gemfile の中で、 てな感じの指定をしたいのだけど、方法が分からず。前述の Gemfile のコメント部分を有効にしたら、以下のようなエラーメッセージが。
You cannot specify the same gem twice with different version requirements.
You specified: cairo (= 1.12.4) and cairo (>= 0)
さて、どう書けばいいんだろう…。

こんな感じではどうかしら。
source "https://rubygems.org"

platforms :mingw do
  if RUBY_VERSION =~ /^1.9/
    gem "cairo", "1.12.4"
    gem "glib2", "1.2.6"
    gem "gdk_pixbuf2", "1.2.6"
    gem "rsvg2", "1.2.6"
  else
    gem "cairo"
    gem "glib2"
    gem "gdk_pixbuf2"
    gem "rsvg2"
  end
end
コレで合ってるのかな…? よくわからん…。

#5 [cg_tools] Ipx、TpX、LaTeXDrawを試用

_描画・グラフツール - TeX Wiki で、Windows上で使えるフリーのドローソフトがいくつか紹介されてたので、少し試用。

どれもTex?とやらで利用することを前提にしたソフトなのかなと思えたり。何にせよ、フリーで使えるドローソフトも結構選択肢があるのだなと…。ありがたや。

Ipeを試用。 :

_Ipe extensible drawing editor | Free Graphics software downloads at SourceForge.net から、ipe-7.1.5-win.zip をDLして解凍。bin/ipe.exe を実行。
  • UIの見た目は若干カラフル。
  • 中ボタンドラッグでキャンバス?の移動。
  • 左側の菱形っぽいマークをクリックしていくことで、線のみ → 線と塗り → 塗りのみ → 線のみ、と切り替えられる。
  • 線や多角形は、左クリックで点を打つ。右クリックで決定?
  • Ctrl+Eでパス編集。パス編集決定はSpaceキー。
  • xml(おそらくIpeの保存形式)の他、pdf、eps で保存できる。

TpXを試用。 :

_TpX: drawing tool for TeX | Free Graphics software downloads at SourceForge.net から、TpX_ExecDistribution1_5.zip をDLして解凍。TpX.exe を実行。
  • ちょっとストイックなUIの見た目。
  • 図形の塗りは、上のツールバーのバケツみたいなところで指定。
  • 図形を一つ置くたびにモードがリセットされる。(選択ツールに戻ってしまう)
  • グリッドを前面に表示するオプション有。View → Grid on Top
  • eps, pdf, emf, svg, png, bmp等、保存できる種類が多い。latex-dvips なんてものもある。
しかしコレ、ちと問題が。終了時に必ずエラーが出る…。2008/12/07から更新されてないようだから、今後バグ修正される見込みはナサゲ。

LaTeXDrawを試用。 :

_LaTeXDraw | Free Development software downloads at SourceForge.net から、LaTeXDraw-3.1.0-bin.zip をDLして解凍。Windows7 の場合は、install_vista_seven.vbs を実行することでインストールができる。LaTeXDraw.jar を実行。
  • Java製アプリ。
  • 見た目は若干カラフル。
  • 下のツールバー上の各ボタンを選ぶことで、塗り色の変更等ができる模様。
  • svg が保存形式。その他、eps, pdf, jpeg, bmp, png等で書き出しもできるらしい。

2014/06/04(水) [n年前の日記]

#1 [dxruby][ruby] Ruby 1.9.3 上に rsvg2 2.0.2 を入れようとしたのだけど

rsvg2 の依存関係を眺めていたら、rsvg2、gdk_pixbuf2、glib2 の 2.0.2 も入りそうに見えたので、Windows7 x64 + Ruby 1.9.3 上でインストールしてみたのだけど。「gdk_pixbuf2.so がロードできん」とエラーが出て。

ググってみたら、 _[ruby-gnome2-devel-ja] gdk_pixbuf2 windows: 2.0.1以降、単体でrequireするとLoadError という報告が。
Windowsでgdk_pixbuf2を単体でrequireしようとしたところ、 gdk_pixbuf2.soがLoadErrorになりました。2.0.0までは大丈夫 なのですが、2.0.1と2.0.2がだめなようです。

Ruby-GNOME 2 / Mailing Lists より

原因はおそらくlibgdk_pixbufとlibpngの依存関係で、 libgdk_pixbuf-2.0-0.dllがlibpng16-16.dllに依存しているのに、 バンドルされているのがlibng14-14.dllのためだと思います。

Ruby-GNOME 2 / Mailing Lists より

なるほど…。

ということで、Windows7 x64 + Ruby 1.9.3 上で rsvg2、gdk_pixbuf2、glib2 をインストールする時は、1.2.6 じゃないとやっぱりダメっぽいです。とメモ。

#2 [cg_tools] Linux上で動作するドローソフトをいくつか試用したり

_20 Vector Graphics Editors Reviewed | Smashing Magazine という記事を眺めているうちに、Linux上で動作するドローソフトがなんだか気になったので、VMware Player + Ubuntu 12.04 LTS 上でインストールできるドローソフトをいくつか試したり。

karbonを試用。 :

Ubuntuソフトウェアセンターからインストールできた。
  • パッと見、カラフルなUI。
  • 図形を配置する方法がちとよく分からず。右下の Add Shape から、キャンバスにD&Dして、大きさを調整する、という感じでいいのかな?
  • スペースキー + 左ドラッグで、キャンバスを移動。
  • パス編集中、Shift + 左クリックで、頂点の種類を変更。
  • 右上の、塗りと線のアイコンのどちからをクリックすることで、塗り、もしくは線の色を変えられる。右側の色ボタン、もしくはその右の三角を押して色を選択。あるいは、下にあるカラーリスト内をクリックして選択。
  • エクスポート時に選べるベクターデータは、svg か wmf。…wmf は古い規格なので、emf に比べると精度が低くて見た目酷くなるんだけど、いいのかしら。
wmfでエクスポートできるけど、emf でエクスポートできないあたり、どうも Officeソフトと組み合わせて使うことが前提のツールだったのかなという気がする。というか、このソフトが属してる Calligra Suite 自体が、オフィススイートだったか…。

しかしコレ、終了するたびにクラッシュする。うーん。

Xfigを試用。 :

これも Ubuntuソフトウェアセンターからインストールできた。
  • いかにも昔の*NIX向けソフトな感じがする白黒オンリーの殺風景なUI。なんだか昔の Tcl/Tk 等を思い出す。
  • 操作時、右上に、マウスの「左ボタン」「中ボタン」「右ボタン」の割り当てが表示される。中ボタンが決定で、右ボタンがキャンセルになってる場面が多い印象。中ボタンが決定なあたり、実に X Window 用らしい割り当てかなと。
  • メニュー項目を選ぶときは、メニューボタンを押しっぱなしにして操作しないといけない。これが X Window 文化なのだろうか…。
  • 配置した図形を選択する方法が分からない。左下のEDIT等を押して、やっていくしかないのだろうか。
  • eps, emf, svg 等でエクスポート可能。他にもかなりの種類をサポートしている。
  • 見た目はディザ等で表示されてるけど、epsエクスポートしたらグレースケールになっていた。作業中の見た目と出力結果が結構違ってくることを意識しながら作業する必要がありそう。
見た目も操作方法も古臭いのに、svgエクスポートもサポートしてるあたり、なんだか驚いた。ただし、svgでエクスポートすると、パターン塗りはベタ塗りになってしまう。おそらく、svg規格にパターン塗りは入ってないのだろうと思うけど。

Tgifを試用。 :

これも Ubuntuソフトウェアセンターからインストールできた。しかし、起動しない。どうやら起動に必要なフォントが足りてないらしい。以下を参考にして色々インストールした。

_fonts - problem running tgif in ubuntu 12.04.1 LTS - Ask Ubuntu
_[ubuntu] installing tgif
_tgifの起動時のエラー:学びの道案内:So-netブログ

sudo apt-get install ttf-mscorefonts-installer texlive-latex-extra xfonts-100dpi xfonts-75dpi x11-utils texlive-latex-base
インストール後、OS再起動。
  • 昔の*NIX向けソフトらしい白黒UI。
  • ウインドウ下部のステータスバーに、マウスの、左、中、右ボタンの割り当てが表示される。
  • 上のツールバー上に、塗りパターン、色選択ボタンがある。左クリックや右クリックで種類を順々に切り替えていける。選択リスト表示は、中ボタン。
  • 保存ファイル名指定時に、ディレクトリ名もキーボードから入力しないといけないらしい。TABキーで補完は効かない。
  • エクスポート時の操作がちと変わってる。File → Print/Export Format で保存形式(アイコン画像)を選んだうえで、File → Print をすることでベクターデータが保存されるらしい。
  • eps, ps, pdf, svg でエクスポート(Print)可能。
これまた見た目も操作方法も古臭いのに、svgエクスポートをサポートしていて驚いた。もちろん、パターン塗りはベタ塗りになった。

Xfig も Tgif も、実験レポートに貼り付けるための図を描くツール、みたいな印象。

そういや、学生時代、8bit PC しか持ってなかったしプリンタも持ってなかったから、実験レポートに貼る図も、グラフも、手描きだったような気がする…。あの頃に、こういうツールがあったら便利だったのだろうなあ。

sk1も試してみたかったのだけど。 :

_優れたベクターグラフィックス描画ツールsK1 | SourceForge.JP Magazine によると、Skencil を fork した sK1 なるドローソフトもあるらしいのだけど。Ubuntu 12.04 LTS の場合、標準で入ってる Python が新し過ぎて、sK1 が動かない・公式サイトでもパッケージを公開してないらしくて。ちと残念。古いディストリを別途インストールしないと試用すらできないのか…。

#3 [dxruby] 射影変換について勉強中

_平面射影変換 という記事によると、8つの係数を求めれば変換式が使えるそうで、さらに、 _射影変換(ホモグラフィ)について理解してみる - デジタル・デザイン・ラボラトリーな日々 に、8つの係数を求めるメソッドが書いてあったので、コレをそのまま書けば射影変換とやらができるのかなと思えたので、DXRuby上で試してみたり。

なんだか全然違う表示になった。どうも根本的なところを自分は勘違いしてるっぽい。もうちょっと勉強しないと。

2014/06/05(木) [n年前の日記]

#1 [dxruby] DXRuby上で射影変換ができた

元記事・参考記事の内容が全然理解できてないけど。写経してたらそれっぽく描画されるようになったのでアップロード。

projtrans.gif
Window.drawMorph による描画結果とは、ちょっと違うことが分かるかしら。

ソースは以下。

_projtrans.rb.txt
require 'dxruby'

# 4点を指定して射影変換描画
class ProjTrans

  attr_accessor :images
  attr_accessor :src_w
  attr_accessor :src_h
  attr_accessor :divx, :divy
  attr_accessor :mdivx, :mdivy
  attr_accessor :src_pos
  attr_accessor :src_idx

  # コンストラクタ
  #
  # @param [Object] img Imageオブジェクト
  # @param [Number] divx x方向の分割数
  # @param [Number] divy y方向の分割数
  #
  def initialize(img, divx = 8, divy = 8)
    self.divx = divx
    self.divy = divy
    self.src_w, self.src_h = img.width, img.height
    self.images = img.sliceTiles(divx, divy) # 画像を分割

    # 頂点列を登録
    self.src_pos = []
    w, h = 0, 0
    bx, by = 0, 0
    i = 0
    (0..self.divy).each do |iy|
      bx = 0
      (0..self.divx).each do |ix|
        if ix < self.divx and iy < self.divy
          i = iy * self.divx + ix
          w = self.images[i].width
          h = self.images[i].height
        end
        puts "#{i} : (#{bx}, #{by})" if false

        # 元画像のサイズで座標値を割っておく
        self.src_pos.push([bx.to_f / self.src_w, by.to_f / self.src_h])

        bx += w
        i += 1
      end
      by += h
    end

    # 頂点番号を登録
    self.src_idx = []
    self.divy.times do |iy|
      self.divx.times do |ix|
        addy = self.divx + 1
        i = ix + iy * addy
        self.src_idx.push([i, i + 1, i + 1 + addy,  i + addy])
      end
    end

    if false
      self.src_idx.each_with_index {|a, i| puts "#{i} : #{a}" }
    end
  end

  # 描画
  #
  # @param [Number] x1 点1のx座標
  # @param [Number] y1 点1のy座標
  # @param [Number] x2 点2のx座標
  # @param [Number] y2 点2のy座標
  # @param [Number] x3 点3のx座標
  # @param [Number] y3 点3のy座標
  # @param [Number] x4 点4のx座標
  # @param [Number] y4 点4のy座標
  # @param [Number] alpha アルファ値 0-255
  # @param [Number] z 描画深度
  # @param [Number] blend 合成種類
  # @param [Number] mdivx drawMorphに与えるx方向の分割数
  # @param [Number] mdivy drawMorphに与えるy方向の分割数
  #
  def draw(x1, y1, x2, y2, x3, y3, x4, y4,
           alpha = 255, z = 0, blend = :alpha,
           mdivx = 2, mdivy = 2)

    # a から h までの、8つの係数を求める
    sa, sb, sc, sd, se, sf, sg, sh = get_system(x1.to_f, y1.to_f,
                                                x2.to_f, y2.to_f,
                                                x3.to_f, y3.to_f,
                                                x4.to_f, y4.to_f)

    # 射影変換
    d = []
    self.src_pos.each do |x, y|
      m = (x * sg + y * sh + 1)
      u = (x * sa + y * sb + sc) / m
      v = (x * sd + y * se + sf) / m
      d.push([u, v])
    end

    self.src_idx.each_with_index do |a, i|
      i0, i1, i2, i3 = a
      Window.drawMorph(d[i0][0], d[i0][1],
                       d[i1][0], d[i1][1],
                       d[i2][0], d[i2][1],
                       d[i3][0], d[i3][1],
                       self.images[i],
                       :dividex => mdivx, :dividey => mdivy,
                       :alpha => alpha, :z => z, :blend => blend)
    end

  end

  # 8つの係数を求める
  #
  # @note 変換式に与える座標値を元画像のサイズで割らないとこの係数は使えない
  #
  # @param [Number] x0 点1のx座標
  # @param [Number] y0 点1のy座標
  # @param [Number] x1 点2のx座標
  # @param [Number] y1 点2のy座標
  # @param [Number] x2 点3のx座標
  # @param [Number] y2 点3のy座標
  # @param [Number] x3 点4のx座標
  # @param [Number] y3 点4のy座標
  # @return [Array] 8つの係数を配列で返す
  #
  def get_system(x0, y0, x1, y1, x2, y2, x3, y3)
    sx = (x0 - x1) + (x2 - x3)
    sy = (y0 - y1) + (y2 - y3)
    dx1 = x1 - x2
    dx2 = x3 - x2
    dy1 = y1 - y2
    dy2 = y3 - y2
    z = (dx1 * dy2) - (dy1 * dx2)
    g = ((sx * dy2) - (sy * dx2))/z
    h = ((sy * dx1) - (sx * dy1))/z

    return [
            x1 - x0 + g * x1,
            x3 - x0 + h * x3,
            x0,
            y1 - y0 + g * y1,
            y3 - y0 + h * y3,
            y0,
            g,
            h
            ]
  end
end

if $0 == __FILE__
  # ----------------------------------------
  # 動作確認

  img = Image.load("lena.png")
  prt = ProjTrans.new(img)

  if false
    # ----------------------------------------
    # 簡単な使い方の例

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

      alpha = 255
      z = 0
      blend = :alpha
      divx = 2
      divy = 2

      # 左上の点から時計回りに4点を指定
      prt.draw(160, 100,
               320, 20,
               600, 460,
               100, 300,
               alpha, z, blend, divx, divy)
    end

  else
    # ----------------------------------------
    # 動作確認

    font = Font.new(14)
    step = 0
    pos = []
    poly_pos = []
    morph_enable = false
    mdiv = 1

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

      if Input.mousePush?(M_LBUTTON)
        # マウスの左ボタンが押されたので座標を記録
        pos = [] if step == 0
        pos.push([Input.mousePosX, Input.mousePosY])
        step += 1
        if step >= 4
          # 4点が入力された
          step = 0
          poly_pos = pos.dup
        end
      end

      # zキーが押されたら drawMorph で描画するかどうかを切替
      morph_enable = !morph_enable if Input.keyPush?(K_Z)

      # 上下キーで分割数を変更 (1から64まで。128にすると落ちる…)
      mdiv *= 2 if (Input.keyPush?(K_UP) and mdiv < 64)
      mdiv /= 2 if (Input.keyPush?(K_DOWN) and mdiv >= 2)

      if poly_pos.size == 4
        # 4点が決まってるので、画像を変形して描画
        x1, y1 = poly_pos[0]
        x2, y2 = poly_pos[1]
        x3, y3 = poly_pos[2]
        x4, y4 = poly_pos[3]

        unless morph_enable
          # 射影変換で描画する場合
          prt.draw(x1, y1, x2, y2, x3, y3, x4, y4, 255, 0, :alpha, mdiv, mdiv)
        else
          # drawMorphで描画する場合
          Window.drawMorph(x1, y1, x2, y2, x3, y3, x4, y4, img,
                           :dividex => mdiv, :dividey => mdiv)
        end
      end

      # 指定済みの頂点位置を描画
      d = 5
      pos.each do |p|
        x, y = p
        Window.drawLine(x - d, y, x + d, y, C_GREEN)
        Window.drawLine(x, y - d, x, y + d, C_GREEN)
      end

      i = step + 1
      y = 2
      msg = [
        "#{Window.real_fps.to_i} FPS  CPU: #{Window.getLoad.to_i} %",
        "zキー : drawMorph 描画との切替 [#{(morph_enable)? "drawMorph" : "射影変換"}]",
        "↑↓キー : drawMorph 分割数 [#{mdiv}]",
        "時計回りで、4回、マウスクリックしてください (#{step+1}/4)"
      ].each_with_index do |s, iy|
        Window.drawFont(4, 2 + iy * (font.size + 8), s, font)
      end
    end
  end

end
一応、グリッドを上から描き込んだlena様画像も置いときます。

_lena.png (125KB)

それと、他のスクリプトからも、require すれば使えるように書いておきました。
# ProjTransの使用例

require 'dxruby'
require_relative 'projtrans'

img = Image.load("lena.png")
prt = ProjTrans.new(img)

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

  alpha = 255
  z = 0
  blend = :alpha
  divx, divy = 2, 2

  # 左上の点から時計回りに4点を指定
  prt.draw(160, 100, 320, 20, 600, 460, 100, 300,
           alpha, z, blend, divx, divy)
end

スクリプト内でやってることを簡単に説明すると…。
  1. 元画像を 4x4 とか 8x8分割。
  2. 4点を指定することで、射影変換の変換式で使う8つの係数を求める。
  3. 分割された各画像に対して、射影変換後の4点を求める。
  4. その4点で、Window.drawMorph を使って描画。
Window.drawMorph だけで全てを変形描画すると平面的な?変形になってしまいますが。大雑把に、4x4 や 8x8 で分割して射影変換した、そのマス目の中だけを Window.drawMorph で描画すれば、パッと見は遠近感がついてるように見えるだろ、という…。要するに、インチキです。

インチキとは言うけれど、PS1やSSの3Dゲームも似たようなことをして誤魔化してたので、ゲームなら全然アリじゃないかなと。 *1

問題点。 :

一応できたものの、問題が。
  • 予想より、CPU負荷が高い。1枚描画するだけで、CPU使用率が 15〜20% ぐらいになってしまう。
  • 使い道が思いつかない。
2Dゲームの背景演出に使えるんじゃないかと思って試していたのですけど、さて、コレを何に使えるのかなあ、みたいな。

各ポリゴン?の描画面積が小さい場合や、正方形に近い描画の場合は、Window.drawMorph のみで描画しちゃってもいいだろうと思うわけですよ。小さいポリゴンがチラチラ飛んでる分には、どうせ違いなんてパッと見では分かりませんし。おそらく、広い面積を描画する場合に限って、こういった遠近感がついてる描画を使えるのかなと。ただ、どんな演出・背景構成を思いつくかというと、うーん。

それはともかく、これを Shader でやったらどうなるんだろう…。いや、DXRuby のサンプルスクリプトの中に、既にあった気もしますが…。

参考ページ。 :

_射影変換(ホモグラフィ)について理解してみる - デジタル・デザイン・ラボラトリーな日々
_射影変換(ホモグラフィ)について理解してみる その11 - デジタル・デザイン・ラボラトリーな日々
m2とm5は近似値なのに、それ以外は結構数値の大きさが違います。これでなぜ射影変換出来るのかと調べたところ、描画処理の部分が下記のようなっており、なんと描画サイズ(dstWidth,dstHeight)で割っておりました。

射影変換(ホモグラフィ)について理解してみる その11 - デジタル・デザイン・ラボラトリーな日々 より

昨日スクリプトを書いた時は描画がおかしくて首を捻ったけれど。この一文を目にしたので同じことをしてみたら、それっぽく描画されて「おお…」と思いました。

他の参考資料もメモ。

_平面射影変換
_重なる気持ち -台形補正- | _level0 | Kayac Front End Engineer's Blog
_射影変換(ホモグラフィ)について理解してみる その2 - デジタル・デザイン・ラボラトリーな日々
_射影変換(ホモグラフィ)について理解してみる その3 - デジタル・デザイン・ラボラトリーな日々
_射影変換(ホモグラフィ)について理解してみる その4 - デジタル・デザイン・ラボラトリーな日々
_射影変換(ホモグラフィ)について理解してみる その5 - デジタル・デザイン・ラボラトリーな日々
_射影変換(ホモグラフィ)について理解してみる その6 - デジタル・デザイン・ラボラトリーな日々
_射影変換(ホモグラフィ)について理解してみる その7 - デジタル・デザイン・ラボラトリーな日々
_射影変換(ホモグラフィ)について理解してみる その8 - デジタル・デザイン・ラボラトリーな日々
_射影変換(ホモグラフィ)について理解してみる その9 - デジタル・デザイン・ラボラトリーな日々
_射影変換(ホモグラフィ)について理解してみる その10 - デジタル・デザイン・ラボラトリーな日々
_ホモグラフィ - Shogo Computing Laboratory
_Saqoosha :: スーパー高速に射影変換するには
_Processingで射影変換(ホモグラフィ) | United Field:中西泰人研究室
_遠近法の射影変換パラメータ計算の高速化(pdf)
_Homography << HIDIHO!
_Projection Matrix - wonderfl build flash online
_射影変換 (Homography) - jsdo.it - Share JavaScript, HTML5 and CSS
_テクスチャマッピング入門 射影変換(ホモグラフィ) - jsdo.it - Share JavaScript, HTML5 and CSS

*1: PS1もSSも、ハードウェアでは、三角形 or 四角形の平面的な変形描画機能しか持ってなかったので、そのまま広い面積を描画してしまうと妙な画面になってしまうわけで。なので、必要な時はポリゴンを分割して、とかやってました。まあ、3Dゲームの場合は、z値を使って分割が必要かどうか判定してたはずですが。

#2 [ruby] RubyのStructって速度的にはどうなんだろう

上記のスクリプトを書いてた際に、最初は x,y 座標だけを格納するクラスを作って座標管理してたのですけど。速度的によろしくないのかなと配列で管理するようにしちゃったのです。もしかして、クラスじゃなくて Struct を使えば良かったのだろうか…。

と思ってググっていたら、DXRuby作者様が、 _classとStruct - mirichiの日記 において速度測定していて。Ruby 1.9.1 では、Struct のほうがクラスより遅いのか…。

自分も実験。

# class, Struct, Array のベンチマーク

require 'benchmark'

IDX = 0

class Hoge
  attr_accessor :data

  def initialize(d = 0)
    self.data = d
  end
end

Fuga = Struct.new(:data)

Benchmark.bmbm { |x|
  x.report("class-new   ") { 1000000.times { val = Hoge.new(0) } }

  x.report("Struct-new  ") { 1000000.times { val = Fuga.new(0) } }

  x.report("Array-new   ") { 1000000.times { val = [0] } }

  val = Hoge.new
  x.report("class-data= ") { 1000000.times { val.data = 0 } }

  val = Fuga.new
  x.report("Struct-data=") { 1000000.times { val.data = 0 } }

  a = [0, 0, 0, 0]
  x.report("Array-data= ") { 1000000.times { a[IDX] = 0 } }

  v = Hoge.new(0)
  x.report("=class-data ") { 1000000.times { b = v.data } }

  v = Fuga.new(0)
  x.report("=class-data ") { 1000000.times { b = v.data } }

  a = [0, 0, 0, 0]
  x.report("=Array-data ") { 1000000.times { b = a[IDX] } }
}

> ruby --version
ruby 1.9.3p545 (2014-02-24) [i386-mingw32]

> ruby struct_bench.rb
Rehearsal ------------------------------------------------
class-new      0.312000   0.000000   0.312000 (  0.314018)
Struct-new     0.265000   0.000000   0.265000 (  0.261015)
Array-new      0.109000   0.000000   0.109000 (  0.108006)
class-data=    0.172000   0.000000   0.172000 (  0.175010)
Struct-data=   0.187000   0.000000   0.187000 (  0.175010)
Array-data=    0.094000   0.000000   0.094000 (  0.099006)
=class-data    0.078000   0.000000   0.078000 (  0.077004)
=class-data    0.078000   0.000000   0.078000 (  0.077005)
=Array-data    0.062000   0.000000   0.062000 (  0.065004)
--------------------------------------- total: 1.357000sec

                   user     system      total        real
class-new      0.312000   0.000000   0.312000 (  0.310017)
Struct-new     0.250000   0.000000   0.250000 (  0.256015)
Array-new      0.109000   0.000000   0.109000 (  0.106006)
class-data=    0.171000   0.000000   0.171000 (  0.177010)
Struct-data=   0.172000   0.000000   0.172000 (  0.175010)
Array-data=    0.109000   0.000000   0.109000 (  0.099005)
=class-data    0.078000   0.000000   0.078000 (  0.078005)
=class-data    0.078000   0.000000   0.078000 (  0.077004)
=Array-data    0.063000   0.000000   0.063000 (  0.065004)
生成時に値を渡すなら、Struct のほうが速いのか…。でも、Array を生成するほうが、もっと速いな…。値を代入するのも、読み取るのも、Array のほうが速いっぽい。

もちろん、配列ばかり使うと、どこに何の値が入ってるか分かりづらくなるし、途中に何か要素を追加すると修正が大変だし。書きやすさ・メンテナンスと、処理速度の、トレードオフだったりするのかな。

2014/06/06(金) [n年前の日記]

#1 [cg_tools] G.CREW5を試用してみたり

ドローソフトについてググっていたら、G.CREWというドローソフトの情報を目にしたのです。

G.CREW は、Windows95/98時代に、低価格ながらそこそこの機能を持ってるということでウケていたソフト。G.CREW 8 まで出たのだけど、販売会社側で何か色々あったようで、惜しむ声が多い中、販売終了になってしまった悲運のソフト。てな捉え方でいいのでしょうか。分かりませんが。

どんなソフトだったのか少し気になったので、HDDの中をゴソゴソと。G.CREW5 なら当時購入したので持っているのです。 *1 G.CREW5 と G.CREW8 って、機能面はどのくらい違うんだろう。ググっても、資料が無くてわからんのですけど。何にせよ、G.CREW5すらどんなソフトだったのか忘れているわけで、再確認しておきたいなと。

Windows7 x64にインストールを試みたけど。 :

さて、Windows7 x64で使えるかどうか…。Windows 95/NT対応としか書いてないアプリのはずだけど…。

結果。
  • Windows7 x64 ... NG。インストールすらできない。
  • Windows7 x64 + VMWare Player + XPモード ... OK。
  • Windows7 x64 + VMWare Player + Windows 98 ... OK。
G.CREW5のインストーラは16ビットアプリなので、Windows7 x64 上では ―― 64ビットOSでは動かないのです。下の記事で解説されてますけど。

_64bit Windows時代到来:第3回 アプリケーションの互換性 (1/3) - @IT

XPモードは、32ビット版OSなので、インストーラも起動したし、インストールできたし、起動もしました。ということは、もしかすると Windows7 32ビット版なら動いちゃったりするのかな。どうなんだろう。

インストールされたファイル群を、Windows7 x64 上にコピーしたら動いたりしないかなー、と思ったけど、ダメっぽいです。どうやら、G.CREW5 は、以下のファイルをシステムにインストールしてるようで。
  • C:\Windows\system32\ACCUGNT5.DLL
  • C:\Windows\C:\Windows\MET'S\*.ocx
「ACCUGNT5.DLLがねえよ」とか「METIMG.ocxがねえよ」と言われてしまう。*.dll は GCREW32.EXE と同じフォルダに置いたところエラーが出なかったけど、*.ocx は同じフォルダに置いてもダメ。知識が無いので、ここで手詰まり。

感想・印象。 :

とりあえず、XPモード上では起動したので、少し触ってみたけれど…。

操作性はともかく、今なら、機能面については、Inkscape、LibreOffice Draw、Dynamic Draw のどれを使っても事足りるんじゃないか、てな印象でした。

ベクターデータの出力フォーマットも、wmfしかサポートしてないので旨味は無いし。考えてみれば、インストーラが16ビットアプリだったから…。 _Windows Metafile - Wikipedia によると、wmf = 16ビット、emf = 32ビット、らしいので、当時 wmfしかサポートしてないのは当たり前、だったのかもしれず。

LibreOfficeの動作がちと怪しい。 :

G.CREW5 で作った wmf を、LibreOffice 4.2.4.2 の Draw、Writer にD&Dしたら表示がおかしくなった。極一部しか表示されず、かつ、ベクターデータではなくビットマップデータになってるようで。

Microsoft Word 2003 に D&D したら、期待していた表示になったので…。どうも LibreOffice は、wmf 関係の互換性が、まだちょっと怪しいのかなと。

LibreOffice ではwmf が全然使えない、というわけでもなくて。G.CREW5 の CD-ROM に同梱されてた wmf のクリップアートなら、LibreOffice にD&Dしてもそれらしく描画された。

おそらくだけど、LibreOffice は、wmf の仕様に対して、対応済みの部分と、未対応の部分がありそうな予感。G.CREW5 で出力した wmf は、たまたまその未対応仕様を使ってしまったのかもしれないなと。根拠はないけど。

G.CREW5に添付されてるクリップアートのライセンスが分からない。 :

CD-ROMの中にはライセンスに関するドキュメントが一切入ってなくて。このクリップアート群は、どの範囲まで使えるのだろう?

まあ、大体にしてこの手のファイルは、個人利用のみ、商用利用不可、政治宗教関係不可、てなあたりが関の山だろうけど。

絵柄や内容がどうであれ、ライセンスのせいでゴミ同然になっていくデータが多いような気がする。個人で出す年賀状ぐらいにしか使えないのだろうな…。まあ、データを作った人は既に報酬を貰ってるはずなので、後は野とやれ山となれ、ですわな。

*1: セットアップCD-ROMはタンス(?)の中にあるけれど、一々発掘作業を行うのが大変なので、isoにして外付けHDDにバックアップしてある状態。

#2 [cg_tools] Paintgraphic 2 Pro を購入してみたり

Paintgraphic 2 Pro は、SOURCENEXT が販売してる画像編集ソフト。 *1 ほとんど半額に近い割引キャンペーン+1,000円割引券があったので、試しに買ってみたり。どんなソフトなのか、前から気にはなっていたのです。

機能面に関しては…。GIMPがあれば間に合ってしまう印象…。

ただ、フィルタをかける際、使用前と使用後を、左右に画面分割して提示してくれるのはグッドだなと。考えてみたら、その手のTVCMや雑誌広告では、当たり前のように使ってる見せ方なのだから、CGソフトもフツーに採用すべきじゃないのかと気付かされたり。

他に特筆すべき点があるとしたら、起動の速さ。一瞬で立ち上がる。 _基本部分は大昔のソフト との話なので、そのあたりも軽いのかなと。

それはさておき。これでもう ソースネクストが販売してるソフトの中で、気になるソフトは1本も無くなってしまった…。
*1: 開発というか、メンテナンスは、ファンファーレですけど。

#3 [cg_tools] ペイントソフトの起動時間を測定してみた

Paintgraphic 2 Pro の起動速度にちょっと感心したので、他のアプリはどうなのか気になり始めて。ストップウォッチ片手に、手持ちの画像編集ソフトの起動時間を測ってみたり。

環境は以下の通り。
測定結果。
ペイントソフトの起動時間
ソフト名初回起動 (秒)2回目の起動 (秒)
GIMP 2.6.1217827
GIMP 2.8.10 Portable6421
Adobe Photoshop Elements 82515
CELSYS CLIP STUDIO PAINT PRO256
CELSYS Illust Studio104
Paint.NET51
Paintgraphic 2 Pro31
PixBuilder Studio11
AzPainter211
*1

この手のペイントソフト・画像編集ソフトって、1度起動してしまえば、その後はずっとそのソフトを立ち上げたまま作業をするわけで。それを考えると、初回起動で待たされるからソレが一体何なんだ、と言えなくもないのですけど…。

しかし、さすがに、クリックしてから1分経たないと使えないソフトと、クリックして1秒で使えるソフトでは、印象が違っても仕方ないよなと思えてきたり。GIMPを起動するたびに「遅いなあ…」と思っていましたが、まさかこんなに差があるとは。

HDDだからこのような起動時間になるのでしょうけど。SSDならどのくらい違ってくるのでしょうかね…。どのソフトも一瞬で起動するのかしらん。

と思ったけれど、メモリ上にファイルがキャッシュされてるであろう2回目の起動時にもそれなりに時間がかかってるから、SSD環境も、一瞬で起動と言うわけにはいかないのかもしれませんな。
*1: GIMP 2.6.12 の初回起動が約3分かかってるのは、フォント検索がされてしまったから。フォントを頻繁に入れ替える環境ならこのくらいかかるという参考データになりそうなので、フェアではないですけど、あえてメモしました。

#4 [dxruby] DXRubyのShaderについて勉強中

DXRubyサンプルの、shader_sample/sample_vertexshader.rb が、Shaderを使って3D表示をしているサンプル、のように思えるので眺めていたり。

このスクリプト、バーテックスシェーダ(頂点シェーダ)とやらを使ってるのですな…。

ピクセルシェーダとやらであれば、 _DXRuby Wiki - チュートリアル にあるチュートリアルが大変参考になるというか、コピペしてちょっと変えるだけでイイ感じになったのですが。バーテックスシェーダ…ってどう使うのやら。

2014/06/07() [n年前の日記]

#1 [dxruby] DXRubyでラスタースクロール処理に再チャレンジ

_2014/01/12 に、DXRuby の Shader (HLSL)を使ってラスタースクロールの実験を ―― ラスタースクロールだけで疑似3Dっぽい表現ができないか実験していたのですが。ラスタースクロールに使える画像の作り方が分からなくて、諦めてしまったのでした。

ふと、今頃になって、「こういう画像を作ればよかったのではないか?」とピンと来たので試してみたり。

結果的には、こんな感じの画面になりました。
天井と壁と床でラスタースクロール
うむ。ストIIですな。

画像を作る際のコツ。 :

たぶん、こういう画像を作ればいいんじゃないかな…。
ラスタースクロール用画像
ピンク色のところが、最終的に欲しい画像。

例えば、GIMPで作業するなら、
  • 変形ツールの遠近法?だかで、上辺896ドット、下辺1792ドットの台形画像を作成。
  • 真ん中あたりから、896 x 160ドットぐらいをクリップ。
要するに…。
  • 台形画像を作る際、下辺の長さを、上辺の2倍の長さにすると、処理や計算が楽。
  • 最終的に必要な画像の横幅は、ウインドウサイズ+グリッドサイズ(64ドット) x 4 にしておくと楽。

グリッドに合わせて線を引いていくのは面倒臭かったので、下地画像を作成するスクリプトを書いてみたり。

_mk_ras_img.rb
#
# ラスタースクロール用BGの下絵画像を生成して保存する。
# 天井、壁、床、の3つの画像を保存する。
#

require 'dxruby'

# ----------------------------------------
# 定義部分

wdw_w = 640 # ウインドウ横幅
wdw_h = 480 # ウインドウ縦幅

cw = 128 / 2 # 1マス分のドットサイズ

h0 = 32 * 5
h2 = 32 * 5
h1 = wdw_h - h0 - h2

w = wdw_w + cw * 4 # 画像横幅

# 出力ファイル名
output_fname = [
  "bg_ras_a_base.png",
  "bg_ras_b_base.png",
  "bg_ras_c_base.png",
]

# 定義部分ここまで
# ----------------------------------------

# 新規画像作成
img0 = Image.new(w, h0, [0, 0, 0, 0])
img1 = Image.new(w, h1, [0, 0, 0, 0])
img2 = Image.new(w, h2, [0, 0, 0, 0])

# 線を引く
cx = w / 2
0.step(cx, cw) do |d|
  a0 = d
  a1 = d * 2
  img0.line(cx + a1, 0, cx + a0, h0, C_WHITE)
  img0.line(cx - a1, 0, cx - a0, h0, C_WHITE)

  img1.line(cx + a0, 0, cx + a0, h1, C_WHITE)
  img1.line(cx - a0, 0, cx - a0, h1, C_WHITE)

  img2.line(cx + a0, 0, cx + a1, h2, C_WHITE)
  img2.line(cx - a0, 0, cx - a1, h2, C_WHITE)
end

# 保存する
img0.save(output_fname[0])
img1.save(output_fname[1])
img2.save(output_fname[2])

font = Font.new(14)

# 表示して確認
Window.loop do
  break if Input.keyPush?(K_ESCAPE)

  x = (Window.width - w) / 2
  y = 0

  Window.draw(x, y, img0)
  y += img0.height

  Window.draw(x, y, img1)
  y += img1.height

  Window.draw(x, y, img2)
  y += img2.height

  [
    output_fname[0],
    output_fname[1],
    output_fname[2],
    "を保存しました"
  ].each_with_index do |s, i|
    Window.drawFont(4, 4 + i * 20, s, font)
  end
end
実行すると、以下の3つの画像が保存される。

bg_ras_a_base.png

bg_ras_b_base.png

bg_ras_c_base.png


そして、これらの下地画像を参考にしつつ、GIMPで画像を作成。以下のような感じに。

bg_ras_a.png

bg_ras_b.png

bg_ras_c.png

  1. 128x128の画像を作成。これが1マス分。
  2. フィルタ → マップ → 並べる、で、1792 x 640 ドットの画像を作成。
  3. 遠近法ツールで、遠近感がついた台形に変形。ガイドを作って、頂点をガイドに合わせるようにすれば作業が少し楽。x=448,x=1344の位置にガイドを作ればいいのかな。
  4. ガイドの作り方は、画像 → ガイド → 新規ガイド。それと、表示 → ガイドにスナップ、にチェックが入っていること。
  5. 変形できたら、矩形選択ツールで、必要な部分だけコピー。
  6. ファイル → 画像の生成 → クリップボードから、を選べば新規画像ができる。
  7. ブレンドツール(グラデーションを塗るツール)や、色調整で、見た目を修正。

ついでに、柱の画像もテキトーに作成。テクスチャを適当に作って、フィルター → マップ → オブジェクトにマップ → 円柱、とかそんな感じ。

pole.png

スクリプトはこんな感じに。 :

スクリプトはこんな感じに。

_bgrasterscroll3.rb.txt
# Shaderを使ってラスタスクロールのテスト
# 天井、壁、床をラスタースクロールさせるっぽい感じの処理
# 画像を読み込んで表示する版

require 'dxruby'

class BgRasterScroll3

  attr_accessor :gridsize
  attr_accessor :imgs
  attr_accessor :shaders

  # コンストラクタ
  #
  # @param [Number] gridsize 1マス分のドットサイズ
  # @param [Array] imgs 天井、壁、床のImageオブジェクトを配列に入れて渡す
  #
  def initialize(gridsize, imgs)
    hlsl = <<EOS
  float2 size;
  float d;
  float dd;
  texture tex0;

  sampler Samp0 = sampler_state
  {
    Texture =<tex0>;

    // こっちだと画面がチラチラする (´・ω・`)
    // MinFilter = LINEAR;
    // MagFilter = LINEAR;
    // MipFilter = LINEAR;

    // こっちならチラチラしない
    MinFilter = POINT;
    MagFilter = POINT;
    MipFilter = NONE;

    AddressU = WRAP;
    AddressV = WRAP;
  };

  struct PixelIn
  {
    float2 UV : TEXCOORD0;
  };

  struct PixelOut
  {
    float4 Color : COLOR0;
  };

  PixelOut PS1(PixelIn input)
  {
    PixelOut output;
    input.UV.x = input.UV.x + ((d + (d * input.UV.y * dd)) / size.x);
    output.Color = tex2D( Samp0, input.UV );
    return output;
  }

  PixelOut PS2(PixelIn input)
  {
    PixelOut output;
    input.UV.x = input.UV.x + ((d + (d * (1.0 - input.UV.y) * dd)) / size.x);
    output.Color = tex2D( Samp0, input.UV );
    return output;
  }

  PixelOut PS3(PixelIn input)
  {
    PixelOut output;
    input.UV.x = input.UV.x + d / size.x;
    output.Color = tex2D( Samp0, input.UV );
    return output;
  }

//   float4 PS(float2 input : TEXCOORD0) : COLOR0
//   {
//     float4 output;
//     // input.x -= ((d / size.x) + ((d / size.x) * input.y * dd));
//     input.x = input.x - ((d + (d * input.y * dd)) / size.x);
//     output = tex2D( Samp0, input);
//     return output;
//   }

  technique FloorScroll
  {
    pass P0
    {
      PixelShader = compile ps_2_0 PS1();
    }
  }
  technique CeilingScroll
  {
    pass P0
    {
      PixelShader = compile ps_2_0 PS2();
    }
  }
  technique WallScroll
  {
    pass P0
    {
      PixelShader = compile ps_2_0 PS3();
    }
  }
EOS

    core = Shader::Core.new(hlsl, {:size=>:float, :d=>:float, :dd=>:float})
    self.shaders = []
    self.imgs = []
    self.gridsize = gridsize

    shader_names = [
      ["CeilingScroll", 1.0],
      ["WallScroll", 1.0],
      ["FloorScroll", 1.0]
    ]

    shader_names.each_with_index do |dt, i|
      sname, dd = dt
      img = imgs[i]
      self.imgs.push(img)
      shr = Shader.new(core, sname)
      shr.size = [img.width, img.height]
      shr.d = 0 # スクロール量
      shr.dd = dd # 上辺と下辺で、x方向に何ドットずれるか
      self.shaders.push(shr)
    end
  end

  # スクロール量を更新
  #
  # @param [Number] d スクロール量
  #
  def update(d)
    d = (d % self.gridsize)
    self.shaders.each do |shr|
      shr.d = d
    end
  end

  # 描画
  #
  # @param [Number] bx 描画位置 x
  # @param [Number] by 描画位置 y
  #
  def draw(bx, by)
    x, y = bx - self.gridsize * 2, by
    3.times do |i|
      Window.drawShader(x, y, self.imgs[i], self.shaders[i])
      y += self.imgs[i].height
    end
  end

end


if __FILE__ == $0
  # ----------------------------------------
  # 動作確認

  imgs = []

  # 画像ファイル名一覧に従って画像ロード
  [
    "bg_ras_a.png", # 天井
    "bg_ras_b.png", # 壁
    "bg_ras_c.png" # 床
  ].each {|fn| imgs.push(Image.load(fn)) }

  # ラスタースクロール用オブジェクトを発生
  ras = BgRasterScroll3.new(64, imgs)

  # 柱スプライトを発生
  pole = Image.load("pole.png")
  sprs = []
  4.times do |i|
    spr = Sprite.new(i * (96 * 2) + 72, 480 - (160 /2) - 128 + 8, pole)
    sprs.push(spr)
  end

  font = Font.new(14)
  x = 0
  Window.loop do
    break if Input.keyPush?(K_ESCAPE)

    # マウス位置に応じてスクロール量を変化
    dx = ((Window.width / 2) -  Input.mousePosX).to_f / Window.width * 32
    x -= dx
    ras.update(x) # スクロール量を指定

    ras.draw(0, 0) # 描画

    # 柱を描画
    tdx = dx + dx * 0.5
    sprs.each do |spr|
      spr.x += tdx
      spr.x += Window.width + 128 if dx < 0 and spr.x + 64 < 0
      spr.x -= (Window.width + 128) if dx > 0 and spr.x > Window.width
    end

    Sprite.update(sprs)
    Sprite.draw(sprs)

    Window.drawFont(4, 4, "dx = #{dx} , x = #{x}", font)
  end
end

実行すれば、前述のスクリーンショットのような画面になるはず。マウスカーソルのx座標で、スクロール速度が変わります。

他のスクリプトから呼んで使えるようにもしておきました。以下は使用例。
# BgRasterScroll3.rb の呼び出しテスト

require 'dxruby'
require_relative 'bgrasterscroll3'

imgs = []
[
  "bg_ras_a_base.png", # 天井
  "bg_ras_b_base.png", # 壁
  "bg_ras_c_base.png" # 床
].each {|fn| imgs.push(Image.load(fn)) }

ras = BgRasterScroll3.new(64, imgs)

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

  ras.update(x) # スクロール量を指定
  ras.draw(0, 0) # 描画
  x += 3
end


画像も含めて、一式をzipにして置いときます。ライセンスは、Public Domain ってことで。

_bg_raster_scroll3_20140607.zip (2.9MB)

2014/06/08() [n年前の日記]

#1 [dxruby] Shader勉強中

先日書いた射影変換スクリプトを Shader でやれないかなと疑問が湧いたので、DXRuby のサンプル、shader_sample/sample_vertexshader.rb を眺めていたのだけど。考えてみたら、バーテックスシェーダとやらを使わずに、ピクセルシェーダとやらを使うだけでもできそうな気がしてきたり。

とりあえず書いて試してみたけど、なんだか結果がおかしい。どうも変換前の座標と変換後の座標が入れ替わってるような気がする。どうやって解決すべきなのだろう…。

#2 [zatta][neta] 自分の感覚がおかしくなってる気がする

_小学校長がトイレで盗撮容疑逮捕 NHKニュース という記事の中で、
今月5日の昼前、勤務先の学校の1階にある女子トイレに侵入し、女性教諭をスマートフォンで盗撮したとして、建造物侵入と県の迷惑防止条例違反の疑いが持たれています。

小学校長がトイレで盗撮容疑逮捕 NHKニュース より

という記述を見かけたのです。

「小学生児童を撮影してたんじゃなくて、大人の女性を撮影してたなら、別に問題無いんじゃね」と思った直後に、「…ん? なんか変だぞ?」と気がついて。

世の中、その手の犯罪が増え過ぎて、自分の感覚もちょっとおかしくなってるような、そんな気がしてきました…。

2014/06/09(月) [n年前の日記]

#1 [dxruby] DXRubyのShaderで射影変換

_先日書いた射影変換スクリプト をShaderで実現できないか実験中。

一応できた、ような気がする。どうしてこういう式になるのかちゃんと分かってないけど…。

projtransshader_ss.gif

ソースは以下。

_projtransshader.rb
require 'dxruby'
require 'matrix'

# 4点を指定して射影変換描画。Shaderを使って描画する版
#
class ProjTransShader

  # wrapする版
  @@HLSL1 = <<EOS
  float2 size, uvadd;
  float4 g_a, g_e;
  texture tex0;

  sampler Samp = sampler_state {
   Texture =<tex0>;
   // MipFilter = LINEAR;
   // MinFilter = LINEAR;
   // MagFilter = LINEAR;
   // MipFilter = POINT;
   // MinFilter = POINT;
   // MagFilter = POINT;
   AddressU = WRAP;
   AddressV = WRAP;
  };

  float4 PS(float2 input : TEXCOORD0) : COLOR0 {
    float2 p;
    p.x = input.x * size.x;
    p.y = input.y * size.y;
    input.x = ((p.x * g_a[0] + p.y * g_a[1] + g_a[2]) / (p.x * g_e[2] + p.y * g_e[3] + 1.0) + uvadd.x) / size.x;
    input.y = ((p.x * g_a[3] + p.y * g_e[0] + g_e[1]) / (p.x * g_e[2] + p.y * g_e[3] + 1.0) + uvadd.y) / size.y;
    return tex2D( Samp, input );
  }

  technique {
    pass {
      PixelShader = compile ps_2_0 PS();
    }
  }
EOS

  # wrapしない版
  @@HLSL2 = <<EOS
  float2 size, uvadd;
  float4 g_a, g_e;
  texture tex0;

  sampler Samp = sampler_state {
   Texture =<tex0>;
   AddressU = BORDER;
   AddressV = BORDER;
  };

  float4 PS(float2 input : TEXCOORD0) : COLOR0 {
    float2 p;
    p.x = input.x * size.x;
    p.y = input.y * size.y;
    input.x = ((p.x * g_a[0] + p.y * g_a[1] + g_a[2]) / (p.x * g_e[2] + p.y * g_e[3] + 1.0) + uvadd.x) / size.x;
    input.y = ((p.x * g_a[3] + p.y * g_e[0] + g_e[1]) / (p.x * g_e[2] + p.y * g_e[3] + 1.0) + uvadd.y) / size.y;
    return tex2D( Samp, input );
  }

  technique {
    pass {
      PixelShader = compile ps_2_0 PS();
    }
  }
EOS

  attr_accessor :image
  attr_accessor :shader_wrap
  attr_accessor :shader_no_wrap
  attr_accessor :tilew, :tileh
  attr_accessor :rt
  attr_accessor :cof
  attr_accessor :wrap_enable

  # コンストラクタ
  #
  # @param [Object] img Imageオブジェクト
  # @param [Boolean] wrap_enable trueならwrap、falseならwrapしない
  #
  def initialize(img, wrap_enable = false)
    self.wrap_enable = wrap_enable
    self.image = img

    self.shader_wrap = Shader.new(Shader::Core.new( @@HLSL1, {
                                                      :size=>:float,
                                                      :uvadd=>:float,
                                                      :g_a=>:float,
                                                      :g_e=>:float
                                                    }))
    self.shader_no_wrap = Shader.new(Shader::Core.new(@@HLSL2, {
                                                        :size=>:float,
                                                        :uvadd=>:float,
                                                        :g_a=>:float,
                                                        :g_e=>:float
                                                      }))

    # 画面を覆い隠すタイル数を求める
    self.tilew = Window.width.quo(self.image.width).ceil
    self.tileh = Window.height.quo(self.image.height).ceil

    # RenderTargetを生成
    rw = self.image.width * self.tilew
    rh = self.image.height * self.tileh
    self.rt = RenderTarget.new(rw, rh)

    self.shader_wrap.size = [rw, rh]
    self.shader_wrap.uvadd = [0, 0]
    self.shader_no_wrap.size = [rw, rh]
    self.shader_no_wrap.uvadd = [0, 0]
    update(0, 0, 640, 0, 640, 480, 0, 480)
  end

  # 射影変換の式に与える8つの係数を求めてShaderに渡す
  #
  # @param [Number] x1 点1のx座標
  # @param [Number] y1 点1のy座標
  # @param [Number] x2 点2のx座標
  # @param [Number] y2 点2のy座標
  # @param [Number] x3 点3のx座標
  # @param [Number] y3 点3のy座標
  # @param [Number] x4 点4のx座標
  # @param [Number] y4 点4のy座標
  #
  def update(x1, y1, x2, y2, x3, y3, x4, y4)

    # self.cof = get_system(x1.to_f, y1.to_f, x2.to_f, y2.to_f,
    #                       x3.to_f, y3.to_f, x4.to_f, y4.to_f)

    src = [
      [0.0, 0.0],
      [self.image.width.to_f, 0.0],
      [self.image.width.to_f, self.image.height.to_f],
      [0.0, self.image.height.to_f]
    ]

    dst = [
      [ x1.to_f, y1.to_f ],
      [ x2.to_f, y2.to_f ],
      [ x3.to_f, y3.to_f ],
      [ x4.to_f, y4.to_f ]
    ]

    self.cof = get_proj_param(dst, src)

    self.shader_wrap.g_a = self.cof[0..3]
    self.shader_wrap.g_e = self.cof[4..7]
    self.shader_no_wrap.g_a = self.cof[0..3]
    self.shader_no_wrap.g_e = self.cof[4..7]
  end

  # uvスクロール値を設定
  #
  # @param [Number] u u値
  # @param [Number] v v値
  #
  def set_scroll_uv(u, v)
    self.shader_wrap.uvadd[0] = u
    self.shader_wrap.uvadd[1] = v
    self.shader_no_wrap.uvadd[0] = u
    self.shader_no_wrap.uvadd[1] = v
  end

  # uvスクロール値を加算
  #
  # @param [Number] u u加算値
  # @param [Number] v v加算値
  #
  def add_scroll_uv(u, v)
    self.shader_wrap.uvadd[0] += u
    self.shader_wrap.uvadd[1] += v
    self.shader_no_wrap.uvadd[0] += u
    self.shader_no_wrap.uvadd[1] += v
  end

  # 描画
  #
  # @param [Number] x0 描画x座標
  # @param [Number] y0 描画x座標
  # @param [Number] alpha アルファ値 0-255
  # @param [Number] z 描画深度
  # @param [Number] blend 合成種類
  #
  def draw(x0, y0, alpha = 255, z = 0, blend = :alpha)
    tw, th = 1, 1
    shader = self.shader_no_wrap
    if self.wrap_enable
      tw, th = self.tilew, self.tileh
      shader = self.shader_wrap
    end

    # Shader が参照する画像を RenderTarget に作成
    self.rt.drawTile(0, 0, [[0]], [self.image], 0, 0, tw, th).update

    # Shader を使って描画
    Window.drawEx(x0, y0, self.rt, :shader=>shader,
                  :alpha=>alpha, :z=>z, :blend=>blend)
  end

  # wrapの切り替え
  #
  # @param [Boolean] wrap_enable trueならwrap有効、falseならwrap無効
  #
  def set_wrap(wrap_enable)
    self.wrap_enable = wrap_enable
  end

  # 変換式用の8つの係数を取得
  #
  # @note 変換式に与える座標値を元画像のサイズで割らないとこの係数は使えない
  #
  # @param [Number] x0 点1のx座標
  # @param [Number] y0 点1のy座標
  # @param [Number] x1 点2のx座標
  # @param [Number] y1 点2のy座標
  # @param [Number] x2 点3のx座標
  # @param [Number] y2 点3のy座標
  # @param [Number] x3 点4のx座標
  # @param [Number] y3 点4のy座標
  # @return [Array] 8つの係数を配列で返す
  #
  def get_system(x0, y0, x1, y1, x2, y2, x3, y3)
    sx = (x0 - x1) + (x2 - x3)
    sy = (y0 - y1) + (y2 - y3)
    dx1 = x1 - x2
    dx2 = x3 - x2
    dy1 = y1 - y2
    dy2 = y3 - y2
    z = (dx1 * dy2) - (dy1 * dx2)
    g = ((sx * dy2) - (sy * dx2)) / z
    h = ((sy * dx1) - (sx * dy1)) / z

    return [
            x1 - x0 + g * x1,
            x3 - x0 + h * x3,
            x0,
            y1 - y0 + g * y1,
            y3 - y0 + h * y3,
            y0,
            g,
            h
            ]
  end

  # 変換式用の8つの係数を取得
  #
  # @note 参考ページ
  #       月の杜工房 - 射影変換のパラメータを求める
  #       http://mf-atelier.sakura.ne.jp/mf-atelier/modules/tips/program/algorithm/a6.html
  #
  # @param [Array] s 変換前の4点座標配列([[x1,y1],[x2,y2],...)
  # @param [Array] d 変換後の4点座標配列([[x1,y1],[x2,y2],...)
  # @return [Array] 8つの係数を配列で返す
  #
  def get_proj_param(s, d)
    x0, y0 = d[0]
    x1, y1 = d[1]
    x2, y2 = d[2]
    x3, y3 = d[3]

    m = Matrix[
               [ s[0][0], s[0][1], 1.0, 0.0, 0.0, 0.0, -x0 * s[0][0], -x0 * s[0][1] ],
               [ s[1][0], s[1][1], 1.0, 0.0, 0.0, 0.0, -x1 * s[1][0], -x1 * s[1][1] ],
               [ s[2][0], s[2][1], 1.0, 0.0, 0.0, 0.0, -x2 * s[2][0], -x2 * s[2][1] ],
               [ s[3][0], s[3][1], 1.0, 0.0, 0.0, 0.0, -x3 * s[3][0], -x3 * s[3][1] ],
               [ 0.0, 0.0, 0.0, s[0][0], s[0][1], 1.0, -y0 * s[0][0], -y0 * s[0][1] ],
               [ 0.0, 0.0, 0.0, s[1][0], s[1][1], 1.0, -y1 * s[1][0], -y1 * s[1][1] ],
               [ 0.0, 0.0, 0.0, s[2][0], s[2][1], 1.0, -y2 * s[2][0], -y2 * s[2][1] ],
               [ 0.0, 0.0, 0.0, s[3][0], s[3][1], 1.0, -y3 * s[3][0], -y3 * s[3][1] ]
              ]

    n = m.inv # 逆行列を取得

    g_a = n[0,0] * x0 + n[0,1] * x1 + n[0,2] * x2 + n[0,3] * x3 + n[0,4] * y0 + n[0,5] * y1 + n[0,6] * y2 + n[0,7] * y3
    g_b = n[1,0] * x0 + n[1,1] * x1 + n[1,2] * x2 + n[1,3] * x3 + n[1,4] * y0 + n[1,5] * y1 + n[1,6] * y2 + n[1,7] * y3
    g_c = n[2,0] * x0 + n[2,1] * x1 + n[2,2] * x2 + n[2,3] * x3 + n[2,4] * y0 + n[2,5] * y1 + n[2,6] * y2 + n[2,7] * y3
    g_d = n[3,0] * x0 + n[3,1] * x1 + n[3,2] * x2 + n[3,3] * x3 + n[3,4] * y0 + n[3,5] * y1 + n[3,6] * y2 + n[3,7] * y3
    g_e = n[4,0] * x0 + n[4,1] * x1 + n[4,2] * x2 + n[4,3] * x3 + n[4,4] * y0 + n[4,5] * y1 + n[4,6] * y2 + n[4,7] * y3
    g_f = n[5,0] * x0 + n[5,1] * x1 + n[5,2] * x2 + n[5,3] * x3 + n[5,4] * y0 + n[5,5] * y1 + n[5,6] * y2 + n[5,7] * y3
    g_g = n[6,0] * x0 + n[6,1] * x1 + n[6,2] * x2 + n[6,3] * x3 + n[6,4] * y0 + n[6,5] * y1 + n[6,6] * y2 + n[6,7] * y3
    g_h = n[7,0] * x0 + n[7,1] * x1 + n[7,2] * x2 + n[7,3] * x3 + n[7,4] * y0 + n[7,5] * y1 + n[7,6] * y2 + n[7,7] * y3

    return [g_a, g_b, g_c, g_d, g_e, g_f, g_g, g_h]
  end

  # 1点を射影変換する
  #
  # @param [Number] x x座標
  # @param [Number] y y座標
  # @param [Array] cof 8つの係数配列
  # @return [Array] 射影変換後のx,y座標を配列で返す
  #
  def Projection(x, y, cof)
    mm = (x.to_f * cof[6] + y.to_f * cof[7] + 1.0)
    u = (x.to_f * cof[0] + y.to_f * cof[1] + cof[2]) / mm
    v = (x.to_f * cof[3] + y/to_f * cof[4] + cof[5]) / mm
    return u, v
  end
end

if $0 == __FILE__
  # ----------------------------------------
  # 動作確認

  if false
    # ----------------------------------------
    # 簡単な使い方の例

    img = Image.load("lena.png")
    prt = ProjTransShader.new(img)

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

      # 左上の点から時計回りに4点を指定
      prt.update(160, 100, 320, 20, 600, 460, 100, 300)
      prt.draw(0, 0) # 描画
    end

  else
    # ----------------------------------------
    # 動作確認

    morph_enable = false
    wrap_enable = false

    img = Image.load("lena.png")
    prt = ProjTransShader.new(img, wrap_enable)

    alpha = 255
    z = 0
    blend = :alpha

    font = Font.new(12)
    step = 0
    pos = []
    poly_pos = []

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

      if Input.mousePush?(M_LBUTTON)
        # マウスの左ボタンが押されたので座標を記録
        pos = [] if step == 0
        pos.push([Input.mousePosX, Input.mousePosY])
        step += 1
        if step >= 4
          # 4点が入力された
          step = 0
          poly_pos = pos.dup
        end
      end

      # zキーが押されたら drawMorph で描画するかどうかを切替
      morph_enable = !morph_enable if Input.keyPush?(K_Z)

      # wキーが押されたら wrap 切り替え
      wrap_enable = !wrap_enable if Input.keyPush?(K_W)

      if poly_pos.size == 4
        # 4点が決まってるので、画像を変形して描画
        x1, y1 = poly_pos[0]
        x2, y2 = poly_pos[1]
        x3, y3 = poly_pos[2]
        x4, y4 = poly_pos[3]

        unless morph_enable
          # 射影変換で描画する場合
          prt.set_wrap(wrap_enable)
          #prt.set_scroll_uv(2, 2)
          prt.update(x1, y1, x2, y2, x3, y3, x4, y4)
          prt.draw(0, 0, alpha, z, blend)
        else
          # drawMorphで描画する場合
          Window.drawMorph(x1, y1, x2, y2, x3, y3, x4, y4, img,
                           :dividex => 4, :dividey => 4)
        end
      end

      # 指定済みの頂点位置を描画
      d = 10
      pos.each do |p|
        x, y = p
        Window.drawLine(x - d, y, x + d, y, C_RED)
        Window.drawLine(x, y - d, x, y + d, C_RED)
      end

      i = step + 1
      y = 2
      msg = [
        "#{Window.real_fps.to_i} FPS  CPU: #{Window.getLoad.to_i} %",
        "z : 描画切替 [#{(morph_enable)? "drawMorph" : "射影変換"}]",
        "w : wrap切替 [#{(wrap_enable)? "Wrap" : "No wrap"}]",
        "時計回りで4回、マウスクリック (#{step+1}/4)"
      ].each_with_index do |s, iy|
        Window.drawFont(4, 2 + iy * (font.size + 8), s, font)
      end
    end
  end

end

他のスクリプトから呼び出して使う場合は以下のような感じ。
# ProjTransShaderの使用例

require 'dxruby'
require_relative 'projtransshader'

img = Image.load("lena.png")
prt = ProjTransShader.new(img)

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

  alpha = 255
  z = 0
  blend = :alpha

  # 左上の点から時計回りに4点を指定
  prt.update(160, 100, 320, 20, 600, 460, 100, 300)
  prt.draw(0, 0, alpha, z, blend)
end

実験用のlena様画像も置いときます。

_lena.png (125KB)

参考ページは、 _mieki256のブックマーク - "射影変換" を見ていただければと。

変換式のための8つの係数を求める際、Ruby の Matrix ライブラリを使っているけど。Ruby の Matrix ライブラリは pure Ruby なので処理が遅いという話も見かけたわけで。もし、大量にこの手のポリゴンを描画したい場合は、もしかすると高速なMatrixライブラリを探すか、バーテックスシェーダとやらを使ってどうにかするとか考えないといかんのかな、という気もしたのですけど実際どうなのかはよく分かりません。

#2 [ruby] NMatrixについて調べてたり

Ruby で高速な行列計算をできる NMatrix なるライブラリがあるらしいと知ったのだけど。ググってるうちに2種類あると知り。 「競合してしまったので NArray を削除した」てな記事も見かけて困ってしまった。どれを使えばいいんだろう…。

_SciRuby/narray - GitHub を眺めると、SciRuby にも NArray がある…? どのライブラリをどんな風に呼べばいいのか分かりません。

2014/06/10(火) [n年前の日記]

#1 [ruby] 配列に入ってる整数を全て実数にしたい

Ruby で、配列に入った値を整数から実数にしたいときがあるのですけど。今まで以下のように書いてたわけで。
  x1, y1 = a[0][0].to_f, a[0][1].to_f
  x2, y2 = a[1][0].to_f, a[1][1].to_f
  x3, y3 = a[2][0].to_f, a[2][1].to_f
  x4, y4 = a[3][0].to_f, a[3][1].to_f
もっといい書き方ないのかなと。

色々ググってたら、こういう書き方もできそうだなと。
  x1, y1, x2, y2, x3, y3, x4, y4 = a.flatten.map {|item| item.to_f }

_flatten, flatten! (Array) - Rubyリファレンス
flattenメソッドは、配列の配列を平坦化した新しい配列を返します。配列中に含まれる配列からすべて要素を取り出して、親の配列の中に並べます。

flatten, flatten! (Array) - Rubyリファレンス より


_map, map! (Array) - Rubyリファレンス
mapメソッドは、要素の数だけ繰り返しブロックを実行し、ブロックの戻り値を集めた配列を作成して返します。collectメソッドの別名です。

map, map! (Array) - Rubyリファレンス より


しかし、ベンチマークを取ってみたら、flatten と map を使った書き方は微妙に遅くて。

# 配列内の値を実数にする際のベンチマークを測定

require 'benchmark'

def hoge(a)
  x1 = a[0][0].to_f
  x2 = a[1][0].to_f
  x3 = a[2][0].to_f
  x4 = a[3][0].to_f
  y1 = a[0][1].to_f
  y2 = a[1][1].to_f
  y3 = a[2][1].to_f
  y4 = a[3][1].to_f
  x1 + x2 + x3 + x4 + y1 + y2 + y3 + y4
end

def fuga(a)
  x1, y1 = a[0][0].to_f, a[0][1].to_f
  x2, y2 = a[1][0].to_f, a[1][1].to_f
  x3, y3 = a[2][0].to_f, a[2][1].to_f
  x4, y4 = a[3][0].to_f, a[3][1].to_f
  x1 + x2 + x3 + x4 + y1 + y2 + y3 + y4
end

def piyo(a)
  x1, y1, x2, y2, x3, y3, x4, y4 = a.flatten.map {|item| item.to_f }
  x1 + x2 + x3 + x4 + y1 + y2 + y3 + y4
end

dst = [ [160, 100], [320, 20], [600, 460], [100, 300]]

n = 1000000
Benchmark.bm do |x|
  x.report("a[0][0].to_f A: ") { n.times {|i| hoge(dst) } }
  x.report("a[0][0].to_f B: ") { n.times {|i| fuga(dst) } }
  x.report("flatten.map:    ") { n.times {|i| piyo(dst) } }
end
       user     system      total        real
a[0][0].to_f A:   1.061000   0.000000   1.061000 (  1.056060)
a[0][0].to_f B:   1.139000   0.000000   1.139000 (  1.143066)
flatten.map:      2.870000   0.000000   2.870000 (  2.872164)

微妙に遅いというか、3倍遅いのかな。見た目アホっぽく、ベタッと書いたほうが、まだ速い場面もあるのだなと…。

もっと違う書き方はできないかしら。

#2 [ruby] NArrayをインストールしてみた

Windows7 x64 + Ruby 1.9.3 p545 mingw32版で、NArray を試用。
gem install narray-ruby19 --no-ri --no-rdoc

速度を測定してみた。 :

# NMatrix (NArray) のテスト

require 'benchmark'

require 'matrix' # 標準添付のMatrixライブラリ

require 'narray' # NArray

# require 'narray/narray' # SciRubyのNArray
# require 'nmatrix' # SciRubyのNMatrix

def get_proj_param(s, d)
  # x0, y0, x1, y1, x2, y2, x3, y3 = d.flatten.map {|item| item.to_f }
  # sx0, sy0, sx1, sy1, sx2, sy2, sx3, sy3 = s.flatten.map {|item| item.to_f }
  x0 = d[0][0].to_f
  x1 = d[1][0].to_f
  x2 = d[2][0].to_f
  x3 = d[3][0].to_f
  y0 = d[0][1].to_f
  y1 = d[1][1].to_f
  y2 = d[2][1].to_f
  y3 = d[3][1].to_f
  sx0 = s[0][0].to_f
  sx1 = s[1][0].to_f
  sx2 = s[2][0].to_f
  sx3 = s[3][0].to_f
  sy0 = s[0][1].to_f
  sy1 = s[1][1].to_f
  sy2 = s[2][1].to_f
  sy3 = s[3][1].to_f

  m = Matrix[
             [ sx0, sy0, 1.0, 0.0, 0.0, 0.0, -x0 * sx0, -x0 * sy0 ],
             [ sx1, sy1, 1.0, 0.0, 0.0, 0.0, -x1 * sx1, -x1 * sy1 ],
             [ sx2, sy2, 1.0, 0.0, 0.0, 0.0, -x2 * sx2, -x2 * sy2 ],
             [ sx3, sy3, 1.0, 0.0, 0.0, 0.0, -x3 * sx3, -x3 * sy3 ],
             [ 0.0, 0.0, 0.0, sx0, sy0, 1.0, -y0 * sx0, -y0 * sy0 ],
             [ 0.0, 0.0, 0.0, sx1, sy1, 1.0, -y1 * sx1, -y1 * sy1 ],
             [ 0.0, 0.0, 0.0, sx2, sy2, 1.0, -y2 * sx2, -y2 * sy2 ],
             [ 0.0, 0.0, 0.0, sx3, sy3, 1.0, -y3 * sx3, -y3 * sy3 ]
            ]

  n = m.inv # 逆行列を取得

  g_a = n[0,0] * x0 + n[0,1] * x1 + n[0,2] * x2 + n[0,3] * x3 + n[0,4] * y0 + n[0,5] * y1 + n[0,6] * y2 + n[0,7] * y3
  g_b = n[1,0] * x0 + n[1,1] * x1 + n[1,2] * x2 + n[1,3] * x3 + n[1,4] * y0 + n[1,5] * y1 + n[1,6] * y2 + n[1,7] * y3
  g_c = n[2,0] * x0 + n[2,1] * x1 + n[2,2] * x2 + n[2,3] * x3 + n[2,4] * y0 + n[2,5] * y1 + n[2,6] * y2 + n[2,7] * y3
  g_d = n[3,0] * x0 + n[3,1] * x1 + n[3,2] * x2 + n[3,3] * x3 + n[3,4] * y0 + n[3,5] * y1 + n[3,6] * y2 + n[3,7] * y3
  g_e = n[4,0] * x0 + n[4,1] * x1 + n[4,2] * x2 + n[4,3] * x3 + n[4,4] * y0 + n[4,5] * y1 + n[4,6] * y2 + n[4,7] * y3
  g_f = n[5,0] * x0 + n[5,1] * x1 + n[5,2] * x2 + n[5,3] * x3 + n[5,4] * y0 + n[5,5] * y1 + n[5,6] * y2 + n[5,7] * y3
  g_g = n[6,0] * x0 + n[6,1] * x1 + n[6,2] * x2 + n[6,3] * x3 + n[6,4] * y0 + n[6,5] * y1 + n[6,6] * y2 + n[6,7] * y3
  g_h = n[7,0] * x0 + n[7,1] * x1 + n[7,2] * x2 + n[7,3] * x3 + n[7,4] * y0 + n[7,5] * y1 + n[7,6] * y2 + n[7,7] * y3

  return [g_a, g_b, g_c, g_d, g_e, g_f, g_g, g_h]
end

def get_proj_param_nmatrix(s, d)
  x0 = d[0][0].to_f
  x1 = d[1][0].to_f
  x2 = d[2][0].to_f
  x3 = d[3][0].to_f
  y0 = d[0][1].to_f
  y1 = d[1][1].to_f
  y2 = d[2][1].to_f
  y3 = d[3][1].to_f
  sx0 = s[0][0].to_f
  sx1 = s[1][0].to_f
  sx2 = s[2][0].to_f
  sx3 = s[3][0].to_f
  sy0 = s[0][1].to_f
  sy1 = s[1][1].to_f
  sy2 = s[2][1].to_f
  sy3 = s[3][1].to_f

  # m = NArray::NMatrix[
  m = NMatrix[
              [ sx0, sy0, 1.0, 0.0, 0.0, 0.0, -x0 * sx0, -x0 * sy0 ],
              [ sx1, sy1, 1.0, 0.0, 0.0, 0.0, -x1 * sx1, -x1 * sy1 ],
              [ sx2, sy2, 1.0, 0.0, 0.0, 0.0, -x2 * sx2, -x2 * sy2 ],
              [ sx3, sy3, 1.0, 0.0, 0.0, 0.0, -x3 * sx3, -x3 * sy3 ],
              [ 0.0, 0.0, 0.0, sx0, sy0, 1.0, -y0 * sx0, -y0 * sy0 ],
              [ 0.0, 0.0, 0.0, sx1, sy1, 1.0, -y1 * sx1, -y1 * sy1 ],
              [ 0.0, 0.0, 0.0, sx2, sy2, 1.0, -y2 * sx2, -y2 * sy2 ],
              [ 0.0, 0.0, 0.0, sx3, sy3, 1.0, -y3 * sx3, -y3 * sy3 ]
                       ]


  n = m.inverse # 逆行列を取得

  g_a = n[0,0] * x0 + n[1,0] * x1 + n[2,0] * x2 + n[3,0] * x3 + n[4,0] * y0 + n[5,0] * y1 + n[6,0] * y2 + n[7,0] * y3
  g_b = n[0,1] * x0 + n[1,1] * x1 + n[2,1] * x2 + n[3,1] * x3 + n[4,1] * y0 + n[5,1] * y1 + n[6,1] * y2 + n[7,1] * y3
  g_c = n[0,2] * x0 + n[1,2] * x1 + n[2,2] * x2 + n[3,2] * x3 + n[4,2] * y0 + n[5,2] * y1 + n[6,2] * y2 + n[7,2] * y3
  g_d = n[0,3] * x0 + n[1,3] * x1 + n[2,3] * x2 + n[3,3] * x3 + n[4,3] * y0 + n[5,3] * y1 + n[6,3] * y2 + n[7,3] * y3
  g_e = n[0,4] * x0 + n[1,4] * x1 + n[2,4] * x2 + n[3,4] * x3 + n[4,4] * y0 + n[5,4] * y1 + n[6,4] * y2 + n[7,4] * y3
  g_f = n[0,5] * x0 + n[1,5] * x1 + n[2,5] * x2 + n[3,5] * x3 + n[4,5] * y0 + n[5,5] * y1 + n[6,5] * y2 + n[7,5] * y3
  g_g = n[0,6] * x0 + n[1,6] * x1 + n[2,6] * x2 + n[3,6] * x3 + n[4,6] * y0 + n[5,6] * y1 + n[6,6] * y2 + n[7,6] * y3
  g_h = n[0,7] * x0 + n[1,7] * x1 + n[2,7] * x2 + n[3,7] * x3 + n[4,7] * y0 + n[5,7] * y1 + n[6,7] * y2 + n[7,7] * y3

  return [g_a, g_b, g_c, g_d, g_e, g_f, g_g, g_h]
end

def dump_cof_diff(cof1, cof2)
  puts
  s = "abcdefgh"
  8.times do |i|
    d = cof1[i] - cof2[i]
    puts "#{s[i, 1]} = diff: #{d} , #{cof1[i]}, #{cof2[i]}"
  end
end


src = [[0, 0],[512, 0], [512, 512], [0, 512]]
dst = [ [160, 100], [320, 20], [600, 460], [100, 300]]

cof1 = []
cof2 = []
n = 10000
Benchmark.bm do |x|
  x.report("Array:  ") { n.times {|i| cof1 = get_proj_param(src, dst)} }
  x.report("NArray: ") { n.times {|i| cof2 = get_proj_param_nmatrix(src, dst) } }

  dump_cof_diff(cof1, cof2)
end
> ruby test_narray.rb
       user     system      total        real
Array:    3.042000   0.000000   3.042000 (  3.049174)
NArray:   0.421000   0.000000   0.421000 (  0.430025)

a = diff: -1.1102230246251565e-16 , 0.07848173515981738, 0.07848173515981749
b = diff: -5.551115123125783e-17 , -0.2090468036529681, -0.20904680365296804
c = diff: 0.0 , 160.0, 160.0
d = diff: 2.7755575615628914e-17 , -0.1708761415525114, -0.17087614155251143
e = diff: -5.551115123125783e-17 , 0.1150470890410959, 0.11504708904109595
f = diff: 0.0 , 100.0, 100.0
g = diff: 0.0 , -0.0007313070776255703, -0.0007313070776255703
h = diff: 0.0 , -0.0009185930365296805, -0.0009185930365296805

Ruby標準の Matrix を使うより、NArrayの NMatrix を使ったほうが、約7倍ほど速い、という結果になった。

ただ、Matrix と NMatrix は、添字の順番が逆になるらしい。そこは注意しないといかんようで。

SciRuby の narray-nmatrix もインストールしてみた。 :

_SciRuby/narray - GitHub を参考にインストール。
gem install narray-nmatrix --pre --no-ri --no-rdoc

NArray を使うか、SciRuby を使うかは、選べるらしい。

NArray を使う場合。
require 'narray'

SciRuby の NMatrix を使う場合。
require 'narray/narray'
require 'nmatrix'

名前空間?を使って指定する場合。
NArray::NMatrix   # NArray の NMatrix を使う場合
NMatrix           # SciRuby の NMatrix を使う場合

#3 [ruby] docoptが気になる

_docopt/docopt.rb - GitHub
_docopt | RubyGems.org | your community gem host
_Pythonのコマンドライン引数処理の決定版 docopt (あとJuliaに移植したよ) - りんごがでている
_docopt - shkh's blog
_docopt・language for description of command-line interfaces

一般的に、コマンドラインツールのオプション指定って、スクリプト内でどんな種類があるかチマチマと指定していくわけだけど。docoptなるライブラリを使えば、ヘルプメッセージをそのまま記述することで、どんなオプション指定があり得るのかを解析・反映してくれるらしくて。

世の中には頭がいい人が居るもんだなと、大変感心いたしました。その発想は無かった。たしかにそのほうが、記述が分かりやすい。そして、色々な場所で応用できそうな考え方のように思えたり。

いや、考えてみたら、Markdown記法もソレか…。出来上がりがざっくりと想像しやすい、そんな見た目になるような記法のルールを作る ―― コンピュータの都合で記法を考えるんじゃなくて、人間がパッと見でどう感じるか、そちらを優先して記法を作る。というアプローチだよなと。

2014/06/11(水) [n年前の日記]

#1 [neta] SwiftのWindows版って無いのかなあ

有るわけないか。Appleの言語だもんな。

なんだか最近、Mac版、Linux版はあるけどWindows版はありません、てな環境が増え続けているような気がする。

#2 [ruby] 8x8行列と、4x4行列と2x2行列と、Matrixと、NMatrix

射影変換で8つの係数を求める際に、8x8の行列を作って逆行列を求めて、みたいなことをしているのですが。8x8行列を、4x4行列と2x2行列の組み合わせにすると高速化できるよ、という話があって。

_遠近法の射影変換パラメータ計算の高速化 (pdf)
_射影変換(ホモグラフィ)について理解してみる その5 - デジタル・デザイン・ラボラトリーな日々

どのくらい速くなるのか、Ruby上で試してみたのです。

_test_narray.rb を使用。
> ruby test_narray.rb
                           user     system      total        real
Matrix 8x8:            3.026000   0.000000   3.026000 (  3.033174)
Matrix 4x4 2x2:        1.264000   0.000000   1.264000 (  1.257072)
NMatrix 8x8:           0.421000   0.000000   0.421000 (  0.418023)
NMatrix 4x4 2x2:       0.515000   0.000000   0.515000 (  0.515030)

なんだか妙な結果に。 件のソレは、高速化に繋がらない場合もある、ということになるのかな…。それとも測定方法が間違っているのかしら。

#3 [windows] Windows 98上で動くPDF出力仮想プリンタとPDFビューワ

VMware Player上で Windows 98を動かしているのだけど。色々問題が。

仮想プリンタ。 :

プリンタが繋がってないので、アプリ上で印刷プレビューが呼び出せないなと。どうしたもんか。

PDFを出力する仮想プリンタをインストールすればいいんじゃないかと閃いた。ググってみたら、PrimoPDF 3.1 なら Windows 98 でも動くらしい。

_XLsoft エクセルソフト : activePDF 無料 PDF 作成/変換ソフトウェア PrimoPDF 4.1 日本語版 - サポート から辿って、PrimoPDF 3.1 日本語版をDL。Windows 98 上でインストール。

設定ダイアログの下の方が切れてしまっている…。けど、一応出力できたからいいか…。

PDFビューワ。 :

PDFを出力しても、閲覧できないのではよろしくないなと思えてきたので、Windows 98上でも動く Adobe Acrobat Reader を探す。

_Widows95,Windows98,WindowsMeでPDFファイルを開きたいでも開けない場合の対処法: パソコンの使い方からパソコンの使いこなし方ホームページ活用法と解決法
_Old and New Version of Adobe Reader Download - OldApps.com

Windows 98 の場合、Adobe Reader 5.05 までなら動く。以下のFTPサーバから入手。

_ftp://ftp.adobe.com/pub/adobe/acrobatreader/win/
_ftp://ftp.adobe.com/pub/adobe/acrobatreader/win/5.x/

ar505jpn.exe をDLして、Windows 98上でインストール。

6.0 は、Windows 98 SE 以上じゃないと動かない。最初分からなくて、インストールしようとしたら、「こんなOS知らねえよ」と怒られてしまった。

解凍ツール。 :

zipすら解凍できないのはよろしくないなと。何か解凍ツールを入れないと。

Explzh はどうかな、6.06 までは Windows 98 でも動くらしいし。と思ったのだけど、DLしてインストールしてみたら起動しない。おそらく、Explzh 6.06 が対応してるのは、Windows 98 SE じゃないのかな…。Windows 98 には対応してない気がする。

もう少しググってみたら、 _Lhaplus が対応してるらしいと分かった。1.59 をインストール。

ブラウザ。 :

IEを使うより、古い Opera を使ったほうが多少はマシらしい。

_Windows 98 関連ソフトウェア - CELLA.DAT

「最後の正式対応バージョンは9.64」と書いてあったので、ソレをインストールしておくことにする。

_Index of /pub/opera/win/964/int

Opera_964_int_Setup.exe をDLしてインストール。

#4 [nitijyou] 自転車で買い物に

親父さんの電動自転車を借りてホーマックとヨークベニマルに。ホーマックで靴を、ヨークベニマルで夜食や食材等を購入。

店から出たら雨が降ってた。トホホ。まあ、曇りだし、雲は厚いから、そのうち降るだろうとは思ってたけど。

2014/06/12(木) [n年前の日記]

#1 [windows] Wordでアルバム一覧っぽいページを作るにはどうしたらいいんだろう

親父さんから、「小さい写真をずらずらと並べて一覧にした印刷物を作るにはどうしたらいいのか」という質問を受けて、ちょっと悩んでしまったり。

親父さんの友人が過去にそういう印刷物を作っていたらしいのだけど。そこで使っていたのは Word らしいので、Word で作れるのだろうけど…。どんな作業をすればいいんだろう。

自分も Word で試してみたり。 :

自分も Word 2003 を起動して試してみたりして。 *1

とりあえず、表を挿入して、各セルに画像を挿入していけばどうにかなりそうかなと。ただ、エクスプローラから画像ファイルをD&Dした際に、元々挿入済みだった画像がD&Dした画像で上書き消去されてしまう時があって悩んだり。

なんとなく分かってきた。あらかじめ、挿入したいセルをクリックしてカーソル位置を合わせておいて、その状態でD&Dすれば狙ったセルに対して挿入できる模様。

ググってみたら、 _アルバム作成Wordマクロ なるものを公開してくれている方が。ありがたや。しかし試してみたところ、画像の縦横比がおかしなことになってしまってガックリ。縦横比は元画像の縦横比を維持してほしいのだけど。

OpenOffice Draw で作業してもらったり。 :

親父さんも Word + 表で作業したのだけど、やはり何かの拍子に、表内がグチャグチャになったそうで、こんなことやってられないと。

別のソフトを使ったほうがいいのかなと思えてきたので、OpenOffice Draw を薦めたり。
  • OpenOffice Draw なら、自由自在に画像を配置できる。
  • グリッドや補助線を表示すれば、配置作業も楽。
  • 変更 → 配置 → 中央揃え(垂直)、を選べば、縦方向の位置合わせができる。
  • 変更 → 分布 → 左右 → 間隔、を選べば、横方向の位置合わせもできる。

いざとなれば、好きなだけチマチマと、手作業で位置を調整すれば済むし。

配置時の自由度・妙な動作に遭遇せずに済むことを考えたら、OpenOffice Draw や LibreOffice Draw は、見た目を気にする印刷物を作成する場合の選択肢として、結構アリなんじゃないかと思えてきたり。

ダミーテキストの作成。 :

操作方法を調べてる最中、ダミーテキストが欲しくなったのでググってみたら、Word も OpenOffice Writer もそういう機能があるようで。ありがたや。

_3分LifeHacking:オフィスソフトで100段落の“長文”ダミーテキストを自動生成する - 誠 Biz.ID
_一目で分かるOpenOffice 2.x の使い方 - Writer 第7回 新聞のようなレイアウト:ITpro

  • Word の場合は、=rand()[Enter]
  • OpenOffice Writer の場合は、dt[F3キー]、can[F3]、q[F3]、cp[F3] 等
OpenOffice のソレは入力支援機能らしいけど。

*1: 自分は Word 2007 も持っているので、セキュリティ面を考慮すれば本来そちらを使うべきだろうけど。周囲で Word 2003 を使ってる事例が多いこと、また、Word 2003 / 2007 は起動するたびに、「.doc に対するデフォルトアプリは俺だ!」とレジストリの書き換え合戦をし始めるので、Word を起動する場合は仕方なく Word 2003 を起動することが多かったりするのです。まあ、そもそも、自分しか使わない文書を作るのであれば、自分は LibreOffice Writer を起動してしまうのですけど…。

#2 [zatta] 画像の縦横比を気にしない人をたまに見かけるのは何故なんだろう

ウチの親父さんもそうだし、お袋さんもそうなのだけど、画像・映像の縦横比をほとんど気にしないのが不思議なのです。ブラウン管TVで地上波TV放送を見ていた頃、4:3映像を16:9で表示していても、二人とも気にしていなかったりで。

そして、どうも世の中、画像の縦横比がおかしいことをおかしいと思わない、そんな方々が結構多いような気もするわけで。

例えば、上記記事でメモしたアルバム作成Wordマクロも、画像の縦横比を維持せずに全画像を同じ縦横比にしちゃうあたり、その事例じゃないのかなと。フツー、元画像の縦横比ぐらいは維持するもんだよなあ…。見た目が明らかにおかしくなるのだし…。いや、おかしいと思わないから、そういう仕様なのだろうけど。

更にこのあたり、何十年も前から一定数、そういう方々が存在するような気もしていて。例えば、8bit PC + BASIC で、ミンキーモモとかリンミンメイとか描いてた時代も、そういう事例を見かけた記憶があって。PC雑誌上で、明らかにモニタの縦横比がおかしいまま画面撮影しちゃって、そのまま紙面に載せていた事例を思い出したりするわけですよ。

もしかすると、色盲・色覚異常みたいな感じで、縦横比盲・縦横比覚異常、という属性も世間にはあったりするんじゃないのかと…。そうとでも思わないと、この状況は理解しがたいというか。

まあ、人間誰しも異常な部分をどこかしらに持っているのが正常、だと思いますけど。 *1 縦横比に対する感覚を測定できるテスト、とか無いのかな…。
*1: 自分も、学生時代に友人から色彩感覚が狂ってることを指摘されたし、あるいはラクガキをしていると人体のバランスがおかしいままでも全然気づかなかったりするわけで。でもまあ、せめてどこの感覚がおかしいのか、自覚ぐらいはしておきたいなと…。

2014/06/13(金) [n年前の日記]

#1 [pc] OpenOfficeのマクロを勉強中

OpenOffice というか LibreOffice のマクロを勉強中。ooo basic、という呼び方でいいのかな。

アルバムページ作成マクロを書きたい。 :

先日見かけたアルバム作成マクロは、画像の縦横比がおかしい状態で並べてしまうので、そこをどうにかできたらなと。しかし、マクロにパスワードがかけられているようで、内容を確認したり、修正したりはできないようで。

仕方ないので、自分で最初から書いてみようと思ったものの…。色々ハマっている状態。

入門。ハローワールド。 :

ググった感じでは、そもそもどうやってマクロを書き始めればいいのか、そこからして分からなかったのだけど。 _Macroの一歩(「Macro使い」への8分間) で Hello World の流れが解説されていて助かりました。ありがたや。
  • LibreOffice Writer を起動。
  • 文書を新規作成して、名前をつけて保存。
  • ツール → マクロ → マクロの管理 → LibreOffice Basic。
  • 左側のツリーで、現在開いてる文書を選択 → 右側の「新規作成ボタン」をクリック。任意の名前(モジュール名)を指定。
  • ooo basic IDE が開く。

リストやら入力ボックスやらを並べたUIが欲しい場合は、ダイアログを作成する。
  • ツール → マクロ → ダイアログの管理。
  • 現在開いてる文書を選択 → 「新規作成」ボタン → 任意のダイアログ名を指定。
  • ダイアログ名を選択 → 「編集」ボタンをクリック。
  • ダイアログをデザインできるウインドウが開く。

LibreOfficeの「Basic IDE オプション」が気になる。 :

LibreOffice のオプション項目を眺めていたら、「Basic IDE オプション」なる項目があったのだけど。コードの補完等、なんだか魅力的な項目が並んでいるものの、全項目がグレーアウトしていて、チェックを入れることができなくて。

コレは一体何だろう…。有効にすることが可能なのか、不可能なのか…。

もしかすると LibreOffice だからダメなのか、OpenOffice なら選べるのかと気になったので、OpenOffice もインストールして確認してみたのだけど。OpenOffice のほうは、そもそも「Basic IDE オプション」なる項目自体が存在しなかった。おそらく、LibreOffice で追加された部分、なのかな…。

ハマった点。 :

  • ダイアログ上のコンポーネントに対してアイテム追加や値を取得する処理を書く場合、Writer文書上からマクロを実行しないと正常動作しない。IDE上でF5キーを押して動作確認しようとしても、ダイアログ上のコンポーネントにアクセスできない。
  • 表内にカーソルがある状態で、oDoc.Text.insertTextContent(oViewCursor, oImage, False) 等をしても Runtime Error になる。表の外で行うと動作する・挿入できるのだけど…。

表内で挿入ができない件は、 _[Solved] Writer macro to insert an image into a table cell (View topic) - Apache OpenOffice Community Forum に説明があった。

以下はエラーが出る。
oCursor = oTable.getCellByPosition(0,0).createTextCursor()
oDoc.getText.insertTextContent( oCursor, oImage, False )

以下ならエラーが出ない。
oCell = oTable.getCellByPosition(0,0)
oCell.getText.insertTextContent( oCell, oImage, False )

この記事へのツッコミ

Re: OpenOfficeのマクロを勉強中 by 名無しさん    2014/08/05 09:42
はじめまして。
Option→詳細→実験的(不安定)なオプションを有効にすると利用できましたよ

2014/06/14() [n年前の日記]

#1 [libreoffice] OpenOffice/LibreOffice Writerでアルバムページを作成するマクロ

たぶんそれっぽく書けた、ような気がするので一応アップロード。

概要。 :

LibreOffice Writer 上で、画像一覧が並んだ、アルバムっぽいページを作成するマクロです。

以下のようなダイアログ上で操作をすれば…。
ooo_album_make_ss01.png

以下のようなページが作れます。
ooo_album_make_ss02.png

操作方法。 :

_album_make_macro_20140614.zip をDL・解凍して、album_make.odt を、OpenOffice / LibreOffice Writer で開いてみてください。

  1. 「マクロを有効化するか?」と尋ねてくるので、有効を選びます。
  2. 「アルバムを挿入」ボタンを押せば、ダイアログが開きます。
  3. 「画像を追加」ボタンを押して、画像を選んでください。複数の画像を選択できます。
  4. 列数その他を入力して、「画像サイズ算出」ボタンを押してください。目標画像サイズが大雑把に計算されます。
  5. 「アルバム作成」ボタンを押せば、文書の最後に、表形式でアルバムが挿入されます。

「アルバムを挿入」ボタンそのものは、印刷には出ない設定にしてありますので、印刷時にわざわざボタンを削除しなくてもOKです。

動作確認環境。 :

  • Windows7 x64
  • LibreOffice Writer 4.2.4.2
  • Apache OpenOffice Writer 4.1.0
Apache OpenOffice 上で動かすと、画像の追加時に反応が鈍いようです。リストボックス内の表示がぺろんぺろんとのんびり変化していくようで…。

ソース。 :

ooo BASIC 部分は以下の通り。

_make_album.bas

ダイアログ部分は以下の通り。

_MakeAlbumDialog.xdl

一応、ソースの中身も列挙しておきます。実験用のコードも多々残ってますが…。
OPTION EXPLICIT

Public oDialog as Object
Public oListBox as Object
Public oField1 as Object
Public oField2 as Object
Public oField3 as Object
Public oField4 as Object
 
Sub Main

    ' 表示するダイアログを指定
    DialogLibraries.LoadLibrary("Standard")
    oDialog = CreateUnoDialog(DialogLibraries.Standard.MakeAlbumDialog)
    
    oListBox = oDialog.getControl("ListBox1")
    oField1 = oDialog.getControl("Field1")
    oField2 = oDialog.getControl("Field2")
    oField3 = oDialog.getControl("Field3")
    oField4 = oDialog.getControl("Field4")
    
    ' ダイアログを表示
    If oDialog.execute() = 1 Then
        ' OKボタンが押された
        
        Dim itemSize as Integer
        
        itemSize = oListBox.getItemCount()  ' リストボックス項目数を取得
        If itemSize > 0 Then
            Dim oDoc as Object
            Dim oTbl as Object
            Dim oUndoMng as Object
            
            oDoc = ThisComponent
            
            ' Undo制御
            oUndoMng = oDoc.getUndoManager()
            
            ' undo コンテキストの開始
            oUndoMng.enterUndoContext("Undo Make Album Macro")

            ' 表を新規作成
            MakeNewtable(GetTableColSize(), itemSize, GetTableMargin())
            
            oTbl = GetTable
            If IsEmpty(oTbl) Then
                MsgBox "表が見つかりません"
            Else
                ' 表内に要素を挿入
                AccessRowColumns(oTbl, GetImageTargetSize())
            End If
            
            ' undo コンテキストを終了
            oUndoMng.leaveUndoContext()
        End If
    Else
        ' Cancelボタンが押された
        Exit Sub
    End If
    
End Sub

' 表の列数を取得
Function GetTableColSize
    GetTableColSize = CInt(oField1.getText())
End Function

' 画像目標サイズを取得
Function GetImageTargetSize
    GetImageTargetSize = CInt(oField2.getText())
End Function

' 表の左右マージン値を取得
Function GetTableMargin
    GetTableMargin = CInt(oField3.getText())
End Function

' 画像の左右マージン値を取得
Function GetImageMargin
    GetImageMargin = CInt(oField4.getText())
End Function

' ----------------------------------------
' ファイル選択ダイアログを開く

Sub SelectFile
    Dim f as Object
    
    f = createUnoService("com.sun.star.ui.dialogs.FilePicker")
    f.initialize( Array(com.sun.star.ui.dialogs.TemplateDescription.FILEOPEN_SIMPLE))
    
    ' ファイルの複数選択を可能にする
    f.setMultiSelectionMode(True)
    
    ' 表示フィルタを追加
    f.appendFilter("All Files (*.*)", "*.*")
    f.appendFilter("Image File", "*.bmp;*.jpg;*.gif;*.png")
    f.setCurrentFilter("Image File")    ' デフォルトフィルタを指定
    
    If f.execute() = 1 Then
        ' 選択された
        Dim sz as Integer
        Dim i as Integer
        Dim sFilePath as String
        
        sz = Ubound(f.selectedFiles())
        
        For i = 0 To sz
            Dim nCnt as Integer
            
            ' sFilePath = ConvertFromURL(f.selectedFiles(i)) 
            sFilePath = f.selectedFiles(i)
            
            ' リストボックスに追加
            nCnt = oListBox.getItemCount()
            oListBox.addItem(sFilePath, nCnt)
        Next i
    End If
        
End Sub

' ----------------------------------------
' 1セルの幅を大雑把に取得

Sub CalcImageSize
    Dim pw as Long
    Dim cols as Integer
    Dim iw as Long
    
    ' ページ横幅を取得
    pw = GetPagePrintWidth()
    
    ' 表の左右マージン分を減らす
    pw = pw - (GetTableMargin() * 2)
    
    ' 表の列数を取得
    cols = CInt(oField1.getText())

    ' 列数で割る
    iw = pw / cols
    
    ' 画像の左右マージン分を減らす
    iw = iw - (GetImageMargin() * 2)

    ' 画像サイズ指定用 InputBox に代入
    oField2.setText(CStr(iw))
End Sub

' ----------------------------------------
' 最後に挿入した表を取得

Function GetTable
    Dim oDoc as Object
    Dim oTbls as Object
    
    oDoc = ThisComponent
    oTbls = oDoc.TextTables
    If oTbls.hasElements = False Then
        ' 表が無い
        GetTable = Empty
    Else
        ' 1つ目の表オブジェクトを取得する場合
        ' oTbl = oTbls.getByIndex(0)
        
        ' 最後に挿入した表を取得する場合
        GetTable = oTbls.getByIndex(oTbls.getCount() -1)
    End If
End Function

' ----------------------------------------
' 表を新規作成
'
Sub MakeNewTable(iCols as Integer, iCellMax as Integer, margin as Integer)
    Dim oDoc as Object
    Dim iRows as Integer
    Dim oTbl as Object
    Dim oCurs as Object
    
    ' 文書を新規作成する場合
    ' Dim Dummy()
    ' oDoc = StarDesktop.loadComponentFromURL("private:factory/swriter", "_blank", 0, Dummy())
    
    ' 現在開いてる文書を対象にする場合
    oDoc = ThisComponent

    ' 行数を算出    
    iRows = iCellMax / iCols
    If iCellMax - (iRows * iCols) > 0 Then iRows = iRows + 1
    iRows = iRows * 2

    ' 表オブジェクトを作成
    oTbl = oDoc.createInstance("com.sun.star.text.TextTable")
    oTbl.setName("AlbumTable")
    oTbl.initialize(iRows, iCols)

    ' サイズを指定
    oTbl.HoriOrient = 0 ' com.sun.star.text.HoriOrientation.NONE
    oTbl.LeftMargin = margin
    oTbl.RightMargin = margin
    
    ' 文書の最終位置に表オブジェクトを挿入
    oDoc.Text.insertTextContent(oDoc.Text.getEnd(), oTbl, False)
    
    ' 枠線を設定
    ' SetTableBorder(oTbl)
End Sub

' ----------------------------------------
' 表の枠線設定を変更する
'
Sub SetTableBorder(oTbl)
    Dim border as Object
    
    border = oTbl.TableBorder
    border.TopLine.OuterLineWidth = 0
    border.BottomLine.OuterLineWidth = 0
    oTbl.TableBorder = border
End Sub

' ----------------------------------------
' 行と列にアクセスする
'
Sub AccessRowColumns(oTbl as Object, iw as Long)
    Dim ridx as Integer
    Dim cidx as Integer
    Dim rcnt as Integer
    Dim ccnt as Integer

    Dim i as Integer
    i = 0

    rcnt = oTbl.getRows.getCount()      ' 行数
    ccnt = oTbl.getColumns.getCount()   ' 列数

    For ridx = 1 to rcnt
        For cidx = 1 to ccnt
        
            Dim cellname as String
            Dim cell as Object
            
            cellname = Chr(64 + cidx) & ridx    ' "A1" "B2" 等のセル名を生成
            cell = oTbl.getCellByName(cellname) ' セルにアクセス
            
            If ridx mod 2 = 0 Then
                ' 作者名等記入欄
                
                ' セル内を縦方向で上揃えにする
                cell.VertOrient = com.sun.star.text.VertOrientation.TOP
            Else
                ' 画像欄
                
                If i < oListBox.getItemCount() Then
                    
                    ' 画像ファイル名をリストボックス内から取得
                    Dim sUrl as String
                    oListBox.selectItemPos(i, True)
                    sUrl = oListBox.getSelectedItem()
        
                    ' セル内容を変更する例。行:列を挿入
                    ' cell.string = CStr(ridx) & " : " & CStr(cidx)
                    
                    ' 画像を挿入
                    InsertTextGraphic(oTbl, cidx - 1, ridx - 1, sUrl, iw)
                    
                    i = i + 1
                End If
                
                ' セル内を縦方向でセンタリング
                cell.VertOrient = com.sun.star.text.VertOrientation.CENTER
            End If
            
            ' セル内を横方向でセンタリング
            cell.createTextCursor().paraAdjust = com.sun.star.style.ParagraphAdjust.CENTER
        Next
    Next
End Sub

' ----------------------------------------
' 画像を挿入

Sub InsertTextGraphic(oTbl, col, row, sUrl, iTargetSize)
    Dim oDoc as Object
    Dim dispather as Object
    Dim oText as Object
    Dim oViewCursor as Object
    Dim aSize
    Dim aArgs(0) As New com.sun.star.beans.PropertyValue
    Dim oGp as Object
    Dim oImage as Object
    Dim oTxtGrp as Object
    
    oDoc = ThisComponent
    oViewCursor = oDoc.getCurrentController().getViewCursor()
    
    oGp = CreateUnoService("com.sun.star.graphic.GraphicProvider")
    
    aArgs(0).Name = "URL"
    aArgs(0).Value = sUrl
    oImage = oGp.queryGraphic(aArgs)
    oTxtGrp = oDoc.createInstance("com.sun.star.text.TextGraphicObject")
    oTxtGrp.Graphic = oImage
    aSize = oImage.Size
    ' Print aSize.Width & " x " & aSize.Height

    Dim fScale as Double
    If aSize.Width > aSize.Height Then
        fScale = CDbl(iTargetSize) / aSize.Width
    Else
        fScale = CDbl(iTargetSize) / aSize.Height
    End If
    aSize.Width = CLng(aSize.Width * fScale)
    aSize.Height = CLng(aSize.Height * fScale)
    
    With oTxtGrp
        .setSize(aSize)
        .VertOrient = com.sun.star.text.VertOrientation.CENTER
        .HoriOrient = com.sun.star.text.HoriOrientation.CENTER
        ' .VertOrientRelation = com.sun.star.text.RelOrientation.PAGE_PRINT_AREA
        ' .HoriOrientRelation = com.sun.star.text.RelOrientation.PAGE_PRINT_AREA
        ' .Surround = com.sun.star.text.WrapTextMode.THROUGHT
        
        ' 画像を文字として挿入
        .AnchorType = com.sun.star.text.TextContentAnchorType.AS_CHARACTER
    End With
    
    Dim oCell as Object
    oCell = oTbl.getCellByPosition(col, row) ' 列、行で指定
    ' oDoc.getCurrentController().select(oCell)
    
    oCell.getText.insertTextContent(oCell, oTxtGrp, True)
End Sub

' 画像挿入のテスト
Sub InsertTextGraphicTest
    Dim oTbl as Object
    Dim sUrl as String
    Dim fScale as Single
    sUrl = "file:///C:/home/Pictures/pub_pictures/pcd01.jpg"
    fScale = 2540.0 / 3
    oTbl = GetTable
    InsertTextGraphic(oTbl, 0, 0, sUrl, fScale)
End Sub

' ----------------------------------------
' 表内のカーソル位置を取得、その1

Sub GetPosInTable
    Dim oDoc as Object
    Dim oTbl as Object
    Dim oVCur as Object
    Dim oCurCell as Object
    Dim oDisp as String
    
    oDoc = ThisComponent
    oVCur = oDoc.getCurrentController().getViewCursor()
    If IsEmpty(oVCur.TextTable) Then
        Print "The cursor is NOT in a table"
    Else
        oTbl = oVCur.TextTable
        oDisp = "The cursor is in cell " & oVCur.Cell.CellName
        Msgbox(oDisp, 0, "Curor Position in Table")
    End If
End Sub

Function InTable
    Dim oDoc as Object
    Dim oVCur as Object
    
    oDoc = ThisComponent
    oVCur = oDoc.getCurrentController().getViewCursor()
    If IsEmpty(oVCur.TextTable) Then
        CheckPosInTable = Empty
    Else
        CheckPosInTable = oVCur.Cell
    End If
End Function

' ----------------------------------------
' 表内に画像を文字として挿入してみる例

Sub InsertGraphicToTbl
    Dim oDoc as Object
    Dim oTbl as Object
    Dim oImage as Object
    Dim oCell as Object
    Dim sUrl as String
    
    sUrl = "file:///C:/home/Pictures/pub_pictures/pcd02.jpg"
    oDoc = ThisComponent
    oTbl = GetTable
    oImage = oDoc.createInstance("com.sun.star.text.GraphicObject")
    oImage.GraphicURL = sUrl
    oImage.AnchorType = com.sun.star.text.TextContentAnchorType.AS_CHARACTER
    oImage.Width = 3000
    oImage.Height = 3000
    
    oCell = oTbl.getCellByPosition(0,0)
    oCell.getText.insertTextContent( oCell, oImage, False )
End Sub

' ----------------------------------------
' 画像をリンク形式で挿入する例

Sub InsertTextGraphicLink(sUrl as String, enableChara as Boolean)
    Dim oDoc as Object
    Dim oViewCursor as Object
    Dim oImage as Object
    Dim aSize As New com.sun.star.awt.Size
    
    oDoc = ThisComponent
    oViewCursor = oDoc.getCurrentController().getViewCursor()
    aSize.Width = 2000
    aSize.Height = 2000
    oImage = oDoc.createInstance("com.sun.star.text.TextGraphicObject")
    oImage.GraphicURL = sUrl
    oImage.Size = aSize
    
    If enableChara Then
        ' 画像を文字として挿入
        oImage.AnchorType = com.sun.star.text.TextContentAnchorType.AS_CHARACTER
    End If
    
    ' 表内には挿入できない
    oDoc.Text.insertTextContent(oViewCursor, oImage, False)
End Sub

Sub InsertTextGraphicLinkTest
    InsertTextGraphicLink("file:///C:/home/Pictures/pub_pictures/pcd03.jpg", True)
End Sub

' ----------------------------------------
' 画像のサイズを取得
'
' OOoBasic/Generic/Image - ...?
' http://hermione.s41.xrea.com/pukiwiki/index.php?OOoBasic%2FGeneric%2FImage

Function GetImageSize(sUrl)
    Dim oGP as Object
    Dim oSFA as Object
    Dim oImgInput as Object
    Dim oGraphic as Object
    Dim aSizePixel as Object
    Dim aSize(2) as Integer
    
    oGP = CreateUnoService("com.sun.star.graphic.GraphicProvider")
    oSFA = CreateUnoService("com.sun.star.ucb.SimpleFileAccess")
    oImgInput = oSFA.openFileRead(sUrl)
    
    Dim aImgDesc(0) As New com.sun.star.beans.PropertyValue
    aImgDesc(0).Name = "InputStream"
    aImgDesc(0).Value = oImgInput
    oGraphic = oGP.queryGraphic(aImgDesc)
    aSizePixel = oGraphic.SizePixel

    aSize(0) = aSizePixel.Width
    aSize(1) = aSizePixel.Height
    oImgInput.closeInput()
    GetImageSize = aSize
End Function

Sub GetImageSizeTest
    Dim ret(2) as Integer
    ret = GetImageSize("file://C:/home/Pictures/pub_pictures/pcd09.jpg")
    msgbox "Width x height = " & CStr(ret(0)) & " x " & CStr(ret(1))
End Sub

' ----------------------------------------
' 表の幅を取得してみるテスト
'
' 表を新規作成した場合、巨大な値しか返ってこない…

Sub GetCellSize
    Dim oDoc as Object
    Dim oTbl as Object
    Dim csize as Integer
    Dim w as Integer
    Dim tw as Long
    Dim margin as Long

    margin = 100
    
    oDoc = ThisComponent
    oTbl = GetTable
    
    ' print oTbl.TableColumnRelativeSum
    
    If False Then
        ' 表の幅を、左マージン・右マージン指定により設定し直す
        oTbl.HoriOrient = com.sun.star.text.HoriOrientation.NONE
        oTbl.LeftMargin = margin
        oTbl.RightMargin = margin
    End If

    ' 列数を取得
    csize = oTbl.getColumns.getCount()

    ' 表の幅を取得
    tw = oTbl.Width
    ' tw = oTbl.RelativeWidth

    Dim oTblColSeps
    oTblColSeps = oTbl.TableColumnSeparators 
    ' print "ColumnSeparators (1) = " & oTblColSeps(1).Position

    w = tw / csize - 400
    print "Table Width = " & tw & "  cell w = " & w
    
End Sub

' ----------------------------------------
' ページの印刷可能範囲の横幅を取得

Function GetPagePrintWidth
    Dim oDoc As Object
    Dim oStyleFamilies As Object
    Dim oPageStyles As Object
    Dim oPageStyle As Object
    Dim pagePrintWidth as Long
    
    oDoc = ThisComponent
    oStyleFamilies = oDoc.StyleFamilies
    oPageStyles = oStyleFamilies.getByName("PageStyles")
    oPageStyle = oPageStyles.getByName("Standard")
    
    pagePrintWidth = oPageStyle.Width - oPageStyle.LeftMargin - oPageStyle.RightMargin
    
    ' print pagePrintWidth
    
    GetPagePrintWidth = pagePrintWidth
End Function

' ----------------------------------------
' ページスタイルを取得してみるテスト

Sub pagestylescontainer_elements
    Dim oDoc As Object
    Dim oStyleFamilies As Object
    Dim oPageStyles As Object
    Dim oPageStyle As Object
    Dim aStyleNames() As String
    Dim i As Integer
    Dim str as String
    Dim pagePrintWidth as Long
    
    oDoc = ThisComponent
    oStyleFamilies = oDoc.StyleFamilies
    oPageStyles = oStyleFamilies.getByName("PageStyles")
    aStyleNames() = oPageStyles.getElementNames()

    str = ""
    For i = 0 To UBound(aStyleNames())
        str = str & aStyleNames(i) & Chr(10)
    Next i
    MsgBox str
    
    oPageStyle = oPageStyles.getByName("Standard")
    print "page width = " & oPageStyle.Width
    print "page left  margin = " & oPageStyle.LeftMargin
    print "page right margin = " & oPageStyle.RightMargin
    
    pagePrintWidth = oPageStyle.Width - oPageStyle.LeftMargin - oPageStyle.RightMargin
    print "page width = " & oPageStyle.Width & "   print width = " & pagePrintWidth
End Sub

参考ページ。 :

まだ分かってない点。 :

どう書けばいいのか分からなくて結局諦めてしまったのですが、LibreOffice Writer 上で、表(TextTable)の横幅、もしくはセルの横幅が取得できないという問題が残っていて。セル一つ分の横幅を取得したいけどやり方が分からず。であれば、表の横幅を列数で割れば求められるかな、と思ったのだけど、これも上手く行かず。

表を新規作成して挿入した直後、.Width で表の横幅を取得すると、とんでもなく大きな値(Long値)が返ってきてしまう。これでは計算に使えない…。ややこしいことに、マウスで表の外枠をちょっとでもドラッグして表のサイズを変更した後なら、Integer で収まる値が返ってくるようになる。どうしてこんなことになるのか…。訳が分かりません。

仕方ないので、ページ横幅やページ余白から、表の横幅を大雑把に求めて誤魔化してみたけれど。表の横幅、もしくはセルの横幅を取得できればもっとすっきり書けそう。

困ったことに、「openoffice cell width」等で検索しても、Openoffice Calc (表計算ソフト)の情報ばかり出てきてしまう。Calc じゃなくて Writer(ワープロソフト)上でどう書けばいいのかを知りたいのだけど…。

分かった点をメモ。 :

ダイアログ上で、OKボタン、キャンセルボタンを押した際に違う値が返ってくる、と解説ページには書いてあったのだけど。そのOKボタン、キャンセルボタンってどれよ…。と思ったら、フツーにボタンを配置して、ボタンの種類を、OK/キャンセルに設定すればよかったようで。

Writer上の表・セルの中で、縦方向にセンタリング(中央揃え)をする方法が分からなかったのだけど。
 cell.VertOrient = com.sun.star.text.VertOrientation.CENTER
といった指定でできるらしい、とメモ。

2014/06/15() [n年前の日記]

#1 [python] Python 2.7.6でsetuptoolsやpipをインストールしようとしてハマった

以下の症状でハマりました。

_2013/12/19 Python-2.7.6がWindows環境でmimetypes.init()に失敗する場合がありsetuptoolsがインストールできない話 - 清水川Web
_[Python-ml-jp 5663] Windows 用の Python 2.7.6 に setuptools 2.0.1 を設置できなかった話
_[Python-ml-jp 5664] Re: Windows 用の Python 2.7.6 に setuptools 2.0.1 を設置できなかった話
_姫踊子草の楽屋裏 Windows 用の Python 2.7.6 に setuptools 2.0.1 を設置できなかった話
_Issue 9291: mimetypes initialization fails on Windows because of non-Latin characters in registry - Python tracker
_cpython: 18cfc2a42772

以下を参考にして作業。
ノート 2014/5/5追記

Python本家のバグチケット( http://bugs.python.org/issue9291 )がクローズされたようです。 http://hg.python.org/cpython/rev/18cfc2a42772 にある差分(か http://hg.python.org/cpython/raw-file/18cfc2a42772/Lib/mimetypes.py のファイル) をPython-2.7.6に適用したところ、問題が再現しなくなりました。

2013/12/19 Python-2.7.6がWindows環境でmimetypes.init()に失敗する場合がありsetuptoolsがインストールできない話 - 清水川Web より


_http://hg.python.org/cpython/raw-file/18cfc2a42772/Lib/mimetypes.py から、mimetypes.py をDLして、Python2.7.6インストールフォルダ\Lib\mimetypes.py を置き換えました。

と、ここまでやってたら、 _Windows 7 / Python 2.7.6 にて ez_setup.py にて UnicodeDecodeError が出て困ったけど解決した件 - secretbase.log という記事に遭遇。
Python 2.7.7 にて下記不具合として修正されていますので、2.7.7を使いましょう。

Windows 7 / Python 2.7.6 にて ez_setup.py にて UnicodeDecodeError が出て困ったけど解決した件 - secretbase.log より

2.7.7が出てたのかー。

Python 2.7.7 をインストール。 :

Python 2.7.6 をアンインストールして、Python 2.7.7 をインストールした。

ふと気づいたら、GIMP 2.6.12 の Python-fu が動かなくなっていた。Python 2.6.6 をアンインストールしてインストールし直したり。

相変わらず Python-fu が動かない。何度かアンインストール、再インストールを繰り返したけど問題解決せず。

ふと、別のショートカットファイルから GIMP を起動したら、Python-fu が動くことに気付いた。ショートカットファイルの設定で、作業フォルダに %USERPROFILE% を指定している場合は、Python-fu が動くらしい。…なんでだろ。

#2 [windows] Windows7 x64 に curl をインストール

Python 関係のパッケージ?インストール解説記事中で curl というコマンドラインのダウンローダ?を使っていたので、試しに自分も導入。

_cURL - Download の下の方にある 「Win32 - Generic」のどれかを選択。

今回は、 _cURL groks URLs - CURL 7.37.0 から、curl_737_0_ssl.zip をDLした。

解凍して、curl.exe を、パスの通ったところにコピー。

このままだと、https でアクセスできないらしいので、以下を参考に作業。

_Why can't cURL properly verify a certificate on Windows? - Super User
_cURL - Extract CA Certs from Mozilla

cacert.pem をDLして、curl.exe と同じ階層に、curl-ca-bundle.crt というファイル名でコピー。

あるいは、--insecure をつけることでも、https でアクセスできる?

_WindowsR環境にcURLコマンドを実行できるようにインストールする手順 | ええかげんブログ(本店)

curl --insecure https://www.google.co.jp/
といった指定でもイケるらしい。

#3 [python] 画像の周辺の黒い部分をトリミングするPythonスクリプト

Python + PIL で、トリミング作業の実験。
from optparse import OptionParser
import os.path
import glob
try:
    import Image
except ImportError:
    from PIL import Image

ver = "0.0.1"

def chk_args():
    """コマンドラインオプションを解析."""

    parser = OptionParser(usage = "%prog [--trim] img\*.png",
        version = "%prog " + ver)
    parser.add_option("--trim", dest="trim",
        action="store_true", default=False, help="Trim exec")

    (opts, args) = parser.parse_args()
    if len(args) == 0:
       parser.print_help()
       exit
    return (opts, args)

def open_image(fn, enable_trim):
    """画像ファイルを開いて情報を調べる."""

    if not os.path.isfile(fn):
        print "Not Found %s" % fn
        return

    im = Image.open(fn).convert('RGB')
    w, h = im.size
    xmin, ymin, xmax, ymax = im.getbbox()
    xw = xmax - xmin
    yh = ymax - ymin
    print "DrawArea (%d, %d) - (%d, %d) : %d x %d : Img Size %d x %d : %s" % \
        (xmin, ymin, xmax, ymax, xw, yh, w, h, fn)

    if enable_trim:
        region = im.crop((xmin, ymin, xmax, ymax))
        dn = os.path.dirname(fn)
        bn = os.path.basename(fn)
        new_fn = os.path.join(dn, "trim_%s" % bn)
        region.save(new_fn)

def main():
    (opts, args) = chk_args()
    for fng in args:
        for fn in glob.glob(fng):
            open_image(fn, opts.trim)

if __name__ == '__main__':
    main()

getbbox() で、周辺の黒い部分を取り除いた領域の値を得られるらしい。

そんな便利なメソッドがあったとは知らなかったものだから、画像のドット数分、getpixel() を使って1ドットずつチマチマと全部チェックして、コレはさすがに処理が遅すぎるわ、高速化できんかなー、とか思ってました。getbbox() を使ったら一瞬で処理が終わってしまった…。

2014/06/17追記。 :


2014/06/16(月) [n年前の日記]

#1 [nitijyou] 朝方に地震

AM03:19と、AM05:14頃に地震。特に後者は揺れが激しく。急いで部屋の出口まで走ったぐらいに揺れました。

#2 [golang] Go言語を少しだけ勉強中

昨日 Python + PIL でちょっとしたスクリプトを書いた際に処理が遅いなと感じたので、もっと高速に処理ができて、かつ、簡単に書ける何かって無いのかなと興味が湧いたわけで。ということで、今頃になって、Go言語とやらをちょっと調べて触ってみたり。

フツーに「go」で検索すると関係ないページが次々にリストアップされてしまうので、「golang」と呼んでる場合が多いらしい。「Processing」を「proce55ing」と呼んだりするようなもんかな…。どうしてこの手の言語は、検索しにくい単語をわざわざ選ぶんだろう。まあ、「Apple」とか「Windows」も、たいがいにせえやって感じではありますけど。

コンパイラのインストール。 :

_Downloads - The Go Programming Language から、go1.2.2.windows-386.msi をDLして実行。C:\Go\ にインストールした。.msi でインストールしたので、環境変数 PATH も設定してくれた。

DOS窓で go[Enter] と入力。ヘルプが表示されたらインストールは成功している。

以下のソースを hello.go として作成。
package main

import "fmt"

func main() {
     fmt.Printf("Hello World\n")
}
go run hello.go を実行。「Hello World」とメッセージが表示された。ビルドも実行も成功しているらしい。

Go言語用のIDEを導入。 :

Go言語には、LiteIDE なるIDEが存在するらしいので、試しに導入してみる。

_liteide | Free software downloads at SourceForge.net から、liteidex22.windows.7z をDLして解凍。任意のフォルダにコピー。

インストールフォルダ\liteide\bin\liteide.exe を実行すれば、起動する。

環境変数 GOROOT を設定する。ツールバー上で「System」を選んで、右側のボタンを押す。設定ファイルが開かれるので、GOROOT と書かれてる行を修正。C:\Go\ を設定すればいいのかな。たぶん。

環境変数 GOPATH を設定する。View → Manage GOPATH → 下の欄に追加。自分のプロジェクトを保存する場所を指定すればいいらしいけどよくわかってない。

Mercurial をインストール。 :

godef なる便利ツールをインストールする際に Mercurial というバージョン管理システムが必要になるらしいので、一応インストール。

Mercurial をインストールする場合、一般的には TortoiseHg という、GUIで操作可能、かつ、Windowsのエクスプローラに組み込むような感じのツールを導入すると簡単らしいのだけど。自分の環境は、既に _TortoiseSVN_TortoiseGit をインストールしてあって。これ以上エクスプローラにこの手のツールを組み込むとますます不安定になりそうなので、今回 TortoiseHg はインストールしないことにした。

_JapaneseDownload - Mercurial から、mercurial-3.0.1-x86.msi をDLして実行。デフォルト設定でインストールした。C:\Program Files (x86)\Mercurial\ にインストールされたらしい。.msi でインストールしたので、環境変数 PATH も設定された模様。DOS窓で hg help とか hg version と打ち込んだら色々表示された。

EmacsにGo言語用のアレコレを設定。 :

勉強するだけなら LiteIDE を使えば十分のような気もするのだけど、EmacsでGo言語を書くためのlispが用意されてるらしいので試しにそちらも導入。

C:\Go\misc\emacs\*.el を emacs のロードパスの通った場所にコピー。

gocode と godef をインストール。この段階で、Mercurial (hg)が必要になる。また、環境変数 GOPATH が設定されてる必要があるらしい。
go get code.google.com/p/rog-go/exp/cmd/godef
go get -u github.com/nsf/gocode

gocode に何か色々設定。
> gocode set propose-builtins true
propose-builtins true

> gocode set lib-path "C:\home\mieki256\gocode\pkg\windows_386"
lib-path "C:\home\mieki256\gocode\pkg\windows_386"

> gocode set
propose-builtins true
lib-path "C:\home\mieki256\gocode\pkg\windows_386"
autobuild false

go-autocomplete.el も、emacsのロードパスの通った場所にコピー。%GOPATH%\src\github.com\nsf\gocode\emacs\go-autocomplete.el からコピーしてくる。

~/.emacs に以下を追加。
;; ----------------------------------------
;; Golang

(require 'go-mode-load)
(require 'go-autocomplete)
(require 'auto-complete-config)

;; (speedbar-add-supported-extension ".go")

(add-hook
'go-mode-hook
'(lambda()
     (auto-complete-mode 1)
     (setq ac-sources '(ac-source-go))
     (setq c-basic-offset 4)
     (setq indent-tabs-mode t)
     (local-set-key (kbd "M-.") 'godef-jump)
     (local-set-key (kbd "C-c C-r") 'go-remove-unused-imports)
     (local-set-key (kbd "C-c i") 'go-goto-imports)
     (local-set-key (kbd "C-c d") 'godoc)
     (define-key ac-mode-map (kbd "M-TAB") 'auto-complete)))

(add-hook 'before-save-hook 'gofmt-before-save)

*.go を開いたら色分け表示されるようになった。また、例えば fmt. と打つと候補がリストアップされた。auto-complete.el とやらを有効にしてる場合は、そういう動作になるらしい。

参考ページ。 :


2014/06/17(火) [n年前の日記]

#1 [golang] Go言語とGUI

Go言語を使ってGUIアプリは作れないのかなと気になったのです。ググってみたら、以下のページで解説が。

_WindowsでGo言語でGUIするにはWALKがいいかもしれない - Qiita
_Golang GUI (Windows) | Labelier
_Go Bindings for Various External APIs

Windows環境の場合、walk というライブラリを使うといいらしいので、少し触ってみようかなと。

インストールは以下。
go get github.com/lxn/walk

例えば、hoge.go , hoge.exe を作るなら、hoge.exe.manifest も必要になる。

サンプルは、%GOPATH%\src\github.com\lxn\walk\examples\ 以下に入ってる。manifestの書き方も、サンプルを参考にすればいい。

*.go が置いてあるフォルダで、
go build
または
go build -ldflags="-H windowsgui"
でビルドできる。(.exeが出来上がる)

サンプルをビルドして確認していたけど。たしかに、VisualuRuby っぽい感じがする…。

go-gtkも気になるのだけど。 :

go-gtk も Windows上で動くらしいので気になったのだけど、導入の仕方がさっぱり分からなくて諦めました。たぶん GTK からしてインストールしないといかんのだろうけど、バージョン決め打ちだったりしそう…。

ていうか個人的に GTK はなんだか怖くて。

以前 Windows環境で GTK をインストール・アンインストールしたら、環境変数 PATH を全消去してくれやがったのです。つまりはOSをぶっ壊しやがったんです。まあ、GTK自体がアレじゃなくて、インストーラのバグだったらしいですけど。しかし、そんな怖いバグ入り版を配布していても、誰も気づいてなかった・報告しなかった・改善版が出てこなかったあたりが、ますます怖くて。開発状況はどうなってるんだろう…。

自分の環境だけなら、その手の問題があってもどうにかできるかもしれんけど。他の環境でも使えるようにと考えたりするなら、ちょっと怖いよなと…。

GUIとクロスプラットフォーム。 :

Go言語自体は、Linux、Mac、Windows で使えるらしいけど。GUIライブラリを使おうとすると、途端にクロスプラットフォームではなくなってしまうあたりが厳しいなと。

クロスプラットフォームでGUIアプリを、と考えたら Java でも使うしかないのかな…。

#2 [anime][neta] ラブリーゴブリン

幸福充填プリキュアのOPで、毎回「ラブリーゴブリン」と歌っているように聞こえていたのですけど。

今日、たまたま画面を見ていたら、「ラブリーコンプリート」と歌詞テロップが表示されていて。自分の耳はもうダメだと思いました。

やっぱり歌詞テロップって大事だなと。

それはともかく今回のプリキュアって、ずっと作画が怪しいというか微妙な感じが続いているのだけど、一体どうしちゃったんだろう…。

#3 [firefox] Firefoxのスタートページのアニメ画像がウザい

ここ数日、Firefox起動時に、サッカーっぽい画像が強制的に表示されるのですけど。自分、スポーツは大嫌いなので、サッカー画像を目にするだけでゲンナリしていて。この動作、設定で変えられないのかな…。

「そのくらいでガタガタ言うな」とか「サッカーのどこがいかんのだ」とか「皆盛り上がってるだろ」とか言い出すヤツが居そうだけど。そういう考え方ではあかんよと。例えば、阪神ファンのブラウザ画面に巨人選手の画像を毎回強制表示するブラウザ仕様だったらどうよ? 「あかんわー。そりゃあかんですわー」ってわかるでしょ。要するに「自分の趣味が他の全員にも受け入れられるはずだと思い込んで」「ソレを不特定多数に強制的に見せつける」ことを「あかんわ」と思わないようではあかんわけですよ。だから、Firefox起動時のスタートページのサッカー画像はNG。趣味を強制してんじゃねえよと。

まあ、こんなの各個人の趣味に合わせてカスタマイズができれば済んでしまう話なんですけど…。

しかし、Firefox はそのあたり融通が利かなくて。ホームページそのものを変更する手段しか用意されてない。Firefox標準のスタートページって、下部の方によく使う機能へのショートカットが並んでたりするので、アニメ画像だけ差し替えられたらありがたいのだけど…。実機能と、趣味要素の強い画像を、同列に扱って1ページ内にレイアウトしちゃうなんて、一体何を考えてるんだ。バカじゃねえのか。プンスカ。

せめて、Google並みに、1日おきに画像を変える、てな根性見せるならともかく…。何日もの間、毎回必ずソレが出るからあかんわけですよ。頻繁に変化があるなら、「まあ、今日一日だけだし…」と我慢もできるわけですよ。しかし、そういうことができるほどのリソースもないくせして、なんとなくでGoogleの真似してんじゃねえよと。薄っぺらいんだよ。と思います。

ということで色々ググってみたけど、画像だけ差し替える設定は見つからず。これはもう、スタートページそのものを変更するしか手はないのかな…。

アドオンでどうにかなりそう。 :

_Mozilla Re-Mix: Firefoxの[新規タブ]ページと[ホーム]ページを統合できるアドオン「Homepage New Tab」 経由で、 _Homepage New Tab :: Add-ons for Firefox をインストールしてみたり。

ウザいサッカー画像が消えてくれた。しばらくはこれで様子を見よう…。ホームページを表示するたび、再レイアウトするあたりがちょっと気持ち悪いけど。サッカー画像のほうがよほど気持ち悪いし。

#4 [nitijyou] 自転車で買い物に

リオンドールまで夕飯の食材を買いに行ったら、会計を済ませた頃に外は突然の豪雨。降水確率が20%前後だったから大丈夫かなと思ってたけど甘かった。

30分ほど入口で立ち往生。幸い小降りになってきたので急いで帰宅。しかし、駐輪場の自転車は突風で倒れて、ハンドルが微妙に曲がってしまって…。風で倒れるのはこれで2度目。電動自転車だから、充電池がついてる分、上の方に重心があって、そのせいで倒れやすいのだろうか。

2014/06/18(水) [n年前の日記]

#1 [ruby] 今時のRuby用GUIライブラリって何があるんだろう

もしかすると Ruby/Tk あたりが充実していたりしないのかなと思ったのだけど。ググってみたら、Windows + Ruby 1.9用の Tk拡張 (tcltklib.so) はあるけど、Rub 2.0用は無いらしくて。どうやら Ruby/SDL や Star Ruby と同様に、Ruby 2.0 には追随してこないライブラリになってしまったのかな…。

結局、今時の Windows + Ruby用のGUIライブラリって、何があるんだろう…。ほとんどが、Ruby 1.8、1.9 で開発停止になってるみたいだし…。

#2 [python] Python + tkinter が気になり始めたり

_Need a GUI Builder for Tkinter / Python - Stack Overflow というページを眺めていたら、Python+ tkinter 用のRADツール(ビルダー?)が紹介されていて、なんだか tkinter が気になってきました。

_Rapyd-Tk
_PAGE | Free Business & Enterprise software downloads at SourceForge.net
_Visual Tkinter Python IDE | Free software downloads at SourceForge.net
_alejandroautalan/pygubu
_SpecTcl - Browse /GUI Builder/2.5.20070129 at SourceForge.net
_SpecTcl Home Page

少し試用してみたり。環境は Windows7 x64。

pygubu をインストール。 :

_alejandroautalan/pygubu から zip をDLして解凍。Python 2.7 以上を使って、python setup.py install を実行。Python 2.6 以下は対応してない。エラーが出る。

Pythonインストールフォルダ\Scripts\pygubu-designer.py を実行すれば起動する。動作はちょっとモッサリしてるけど、今風な操作感覚のように思えた。

Rapyd-Tk をインストール。 :

_Rapyd-Tk から rapyd-1-0-0.zip をDLして解凍。rapyd.py を実行。

起動したように見えたのだけど、Project を Create new すると落ちる…。Project が作れないのでは話にならない…。

Python + GUI。 :

_GuiProgramming - Python Wiki を眺めてクラクラしてきた。比較的充実してるように見えるなあ…。

2014/06/19(木) [n年前の日記]

#1 [emacs] NTEmacsのフォント設定を見直し

NTEmacs 24.3 上で、英数字フォントと日本語フォントの幅が合わないことに気がついたので、~/.emacs 内のフォント設定をちょっと見直し。

とりあえず、以下のような記述にした。
(setq w32-enable-synthesized-fonts t)

(set-default-font "Source Code Pro-9")

(defvar japanese-font "MeiryoKe_Console")

(dolist (target '(jisx0201
                  latin-jisx0201
                  japanese-jisx0208
                  japanese-jisx0208-1978
                  japanese-jisx0212
                  japanese-jisx0213-1
                  japanese-jisx0213-2
                  japanese-jisx0213-a
                  japanese-jisx0213.2004-1
                  katakana-sjis
                  katakana-jisx0201
                  cp932-2-byte
                  cp932
                  (#x3040 . #x309f)
                  (#x30a0 . #x30ff)
                  (#xff00 . #xffef)
                  (#x0370 . #x03ff)))
  (set-fontset-font (frame-parameter nil 'font) target japanese-font))

(setq face-font-rescale-alist '(("MeiryoKe_Console" . 1.12)))
(setq face-font-rescale-alist '(("MeiryoKe_Gothic" . 1.12))) で、日本語フォントを微妙に大きくしてるのかな?

IME使用時のフォントの大きさが微妙に違ってしまうあたりも気になったので、色々試して以下のように。
(setq default-frame-alist
      (append
       '((ime-font . "MeiryoKe_Console-10"))
       default-frame-alist))
これで合ってるのか・正しい指定なのかは分からないけど。

2014/06/20(金) [n年前の日記]

#1 [emacs] NTEmacs上でrobeなるソレを使おうとしてるけど上手く行かず

以下のページを参考にしつつ、NTEmacs上で使おうとしてみたのだけど。

_auto-complete + rsense の代わりに auto-complete + robe をつかう - LGTM
_Emacs の ruby-mode で補完するのに『robe』を使ってみるメモ - 牌語備忘録 - pygo

M-x robe-start で動くはず、なのだけど、そんなコマンドは無いよと言われてしまったり、No matching directory found と言われてしまったりで。バージョンによって仕様が違っているのかな…。何にせよ、動いてる気配がありません。残念です。

#2 [anime] ピンポン最終回を視聴

なんだかスゴイアニメだったなと。映像がとにかく異質で。

漫画のようなコマ割りをしながら映像を見せていくあたりが好みでした。カッカッカッ、てな卓球の音と一緒に画面分割が進む、みたいな。

日本のアニメと言うと、萌えアニメっぽい、いかにもな方向でしかもう作れないのかな、などと思ってしまう時もあるのだけど。こういうアニメを目にすると、まだ日本のアニメは大丈夫っぽいなと…。

2014/06/21() [n年前の日記]

#1 [webgl] IE11だけガルガンティアが遅いんだけどなんでだろ

_翠星のガルガンティア 〜キミと届けるメッセージ〜 という、WebGLで動くゲームページがあるわけですけど。

IEのWebGL対応を宣伝するサイトのはずなのだけど、ウチの環境、Windows7 + IE11 で動かすと、3Dゲーム画面がガクガクしちゃってゲームどころではなくて。体感では、1〜2 FPSしか出てない印象。CPU使用率も4コア全部100%で回りっぱなしで、グラフ表示は真っ赤。

しかし、Google Chrome や Firefox で動かすとスルスル動くのです。CPU使用率もかなり低い。どうしてこんな結果になるんだろう?

IE11の設定がおかしいのかと思ってググってみたけど、それらしい情報に遭遇せず。F12キーを押して表示される開発者ツールで確認したけど、ドキュメントモードは Edgeになってるし…。アドオン?を色々無効にしてみたけど変化は無く。

そもそも、他のWebGLデモページに対しても、IE11で開くとCPU使用率が跳ね上がるのに、Google Chrome や Firefox は軽々と動くことに気付いたり。

ウチのメインPCは、GeForce 9800GTGE が載ってるのですけど。もしかして IE11 は、古いGPUは無視してCPUでレンダリングでもしてるんでしょうか…? あるいは、Windows7 + IE11 はソフトウェアレンダリングで、Windows8 + IE11 ならGPUを使うとか? そういう部分で、Windows7 と Windows8 の差別化をしてる?

もしかして、コレかな。

_【レビュー】Windows 7版Internet Explorer 11ファストレビュー - 高速化したJavaScript実行を検証 - ライブドアニュース
このセキュリティホールの問題があってか、WebGLコンテンツ実行をする前に内容をスキャンし、安全が確認されない場合はソフトウェアベースのレンダラーを用いるという。

【レビュー】Windows 7版Internet Explorer 11ファストレビュー - 高速化したJavaScript実行を検証 - ライブドアニュース より

ガルガンティアは、IE11から「安全性が確認されないWebGLコンテンツ」として扱われているから、ソフトウェアレンダリングになっているのだろうか。

IE11がソフトウェアレンダリングしてるかどうかを判別する方法って無いのかな…?

2014/06/22() [n年前の日記]

#1 [dxruby] DXRubyで通路の奥に進むようなソレ

DXRubyを使って、通路の奥に進んでいくようなソレを実験。以下のような感じに。

passageway_ss.gif
うむ。レイフォースですな。あるいはギャラクシーフォース。

やってることは、真ん中に穴が開いた1枚画を、z値に基づいて、拡大縮小表示してるだけ。

予想では、描画面積がとんでもなく大きくなるから処理落ちするんじゃないか ―― 複数画像に分割して組み合わせて描画して、最低限の面積を描画するようにしないと実用にならないのでは、と思っていたけど。おそらくGPUがイイ感じに頑張ってくれているのか、分割せずに1枚画を連続でドドドと描画しても全然処理落ちせずに済みました。ありがたや。

ソースは以下。

_passagewaytypea.rb
require 'dxruby'

#
# 通路を奥に進んでいく背景演出
#
class PassageWayTypeA
  attr_accessor :walls

  #
  # 壁1枚分
  #
  class Wall
    attr_accessor :image
    attr_accessor :x, :y, :z
    attr_accessor :sz
    attr_accessor :angle_z
    attr_accessor :shader

    # 明度調整用
    @@hlsl = <<EOS
  float v;
  texture tex0;

  sampler Samp0 = sampler_state
  {
   Texture =<tex0>;
  };

  float4 PS(float2 input : TEXCOORD0) : COLOR0 {
    float4 output;
    output = tex2D( Samp0, input );
    output.rgb *= v;
    return output;
  }

  technique {
   pass {
    PixelShader = compile ps_2_0 PS();
   }
  }
EOS

    #
    # コンストラクタ
    #
    # @param [Object] img Imageオブジェクト
    # @param [Number] x 初期位置x
    # @param [Number] y 初期位置y
    # @param [Number] z 初期位置z
    # @param [Number] scr_z 視点からスクリーン(画面)までの距離
    #
    def initialize(img, x, y, z, scr_z)
      self.image = img
      self.sz = scr_z
      self.angle_z = 0
      self.x = x
      self.y = y
      self.z = z
      core = Shader::Core.new(@@hlsl, {:v => :float})
      self.shader = Shader.new(core)
      self.shader.v = 1.0
    end

    #
    # 座標を更新
    #
    # @param [Number] dx 速度x
    # @param [Number] dy 速度y
    # @param [Number] dz 速度z
    # @param [Number] d_ang_z z軸回転速度
    #
    def update(dx, dy, dz, d_ang_z = 0)
      self.x += dx
      self.y += dy
      self.z += dz
      self.angle_z += d_ang_z
    end

    #
    # 描画
    #
    # @param [Number] bx 描画位置オフセットx
    # @param [Number] by 描画位置オフセットy
    #
    def draw(bx, by)
      sx = (self.sz * (bx + self.x) / self.z) + Window.width / 2
      sy = (self.sz * (by + self.y) / self.z) + Window.height / 2
      scale = self.sz / self.z
      a = 1.0 - (1.0 * (self.z - 300) / 2900.0)
      a = 0 if a < 0
      a = 1.0 if a > 1.0
      self.shader.v = a
      Window.drawEx(sx, sy, self.image,
                    :scale_x => scale, :scale_y => scale,
                    :center_x => self.image.width / 2,
                    :center_y => self.image.height / 2,
                    :angle => self.angle_z,
                    :offset_sync => true,
                    :z => -self.z,
                    :shader => self.shader)
    end
  end

  #
  # コンストラクタ
  #
  # @param [Array] imgs Imageオブジェクトの配列
  # @param [Number] scr_z 視点からスクリーン(画面)までの距離
  # @param [Number] num 壁の枚数
  #
  def initialize(imgs, scr_z, num = 8)
    self.walls = []
    z = 3200.0
    zadd = 3200 / num
    num.times do |i|
      x, y = 0, 0
      self.walls.push(Wall.new(imgs[i % imgs.length], x, y, z, scr_z))
      z += zadd
    end
  end

  #
  # 座標を更新
  #
  # @param [Number] dx 速度x
  # @param [Number] dy 速度y
  # @param [Number] dz 速度z
  # @param [Number] d_ang_z z軸回転速度
  # @param [Boolean] del_enable trueなら、スクリーン手前に来た段階で壁を消去
  # @return [Number] 壁の数を返す
  #
  def update(dx, dy, dz, d_ang_z = 0, del_enable = false)
    self.walls.each do |spr|
      spr.update(dx, dy, dz, d_ang_z)
      if spr.z < spr.sz - 100
        # 投影面より手前に来たので
        if del_enable
          self.walls.delete(spr)
        else
          # 遠方に配置し直し
          spr.z += 3200.0
          spr.x, spr.y = 0, 0
        end
      end
    end

    return self.walls.size
  end

  #
  # 描画
  #
  # @param [Number] bx 描画位置オフセットx
  # @param [Number] by 描画位置オフセットy
  #
  def draw(bx, by)
    self.walls.each { |spr| spr.draw(bx, by) }
  end
end

if $0 == __FILE__
  # ----------------------------------------
  # 使用例

  font = Font.new(14)
  wall_max = 8
  scrz = 300

  imgs = [
    Image.load("wall1.png"),
    Image.load("wall2.png"),
  ]
  bg = PassageWayTypeA.new(imgs, scrz, wall_max)

  floor = Image.load("floor_fg.png")

  step = 0
  mouse_move = false

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

    # Xキーでマウス移動を反映させるか否かを反転
    mouse_move = !mouse_move if Input.keyPush?(K_X)

    if mouse_move
      mx = Input.mousePosX - Window.width / 2
      my = Input.mousePosY - Window.height / 2
    else
      mx, my = 0, 0
    end

    case step
    when 0
      # 通常描画

      bg.update(0, 0, -25)
      bg.draw(mx, my)

      # zキーが押されたら壁が消えていく処理へ移行
      step += 1 if Input.keyPush?(K_Z)

    when 1
      # 壁が徐々に消えていく処理

      if bg.update(0, 0, -25, 0, true) <= 0
        # 壁がすべて消えた
        step += 1
      end
      bg.draw(mx, my)

    when 2
      # 壁を再発生

      bg = PassageWayTypeA.new(imgs, scrz, wall_max)
      step = 0
    end

    Window.draw(rand(8) - 4, rand(3), floor)

    s = "#{Window.real_fps.to_i} FPS  CPU: #{Window.getLoad.to_i} %"
    Window.drawFont(4, 4, s, font)
  end
end

他のスクリプトからも呼び出せるようにしておきました。以下のような感じで呼び出せます。
require "dxruby"
require_relative "passagewaytypea"

imgs = [
  Image.load("wall1.png"),
  Image.load("wall2.png"),
]
bg = PassageWayTypeA.new(imgs, 300)

floor = Image.load("floor_fg.png")

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

  bg.update(0, 0, -25)
  bg.draw(0, 0)

  Window.draw(rand(8) - 4, rand(3), floor)
end

使用画像も置いときます。

_wall1.png
_wall2.png
_floor_fg.png

ソースと画像のライセンスは、 Public Domain または CC0 ってことで。

2014/06/23(月) [n年前の日記]

#1 [dxruby] DXRubyで円柱っぽいBG描画を実験中

円柱と言うか、パイプと言うか、スペースコロニー内部と言うか、そういう感じのBG描画を Shader を使って実験中。

_GeoGebra で図を描きながら、どういう処理をしたらいいのか悩んでおります。

2014/06/24(火) [n年前の日記]

#1 [dxruby] DXRubyで円柱っぽいBG描画をしてみたり

円柱と言うか、パイプと言うか、スペースコロニー内部と言うか、そういう感じのBG描画を DXRuby の Shader を使って試してみたり。

こんな感じになりました。
pipe_bg_ss.gif

ソースは以下。

_pipebg.rb
require 'dxruby'

#
# パイプ内のような背景描画を行う
#
class PipeBg
  @@hlsl = <<EOS
  float sz;
  float r;
  float angle_max;
  float start_x;
  float start_y;
  texture tex0;

  sampler Samp = sampler_state {
    Texture =<tex0>;
    MinFilter = LINEAR;
    MagFilter = LINEAR;
    MipFilter = LINEAR;
    // MinFilter = POINT;
    // MagFilter = POINT;
    // MipFilter = POINT;
    AddressU = BORDER;
    AddressV = WRAP;
  };

  float4 PS(float2 input : TEXCOORD0) : COLOR0
  {
    input.x = (input.x - 0.5) * (r * cos(atan2(input.y - 0.5, sz))) / sz + start_x + 0.5;
    input.y = (atan2(input.y - 0.5, sz) / angle_max) + start_y;
    return tex2D( Samp, input );
  }

  technique {
    pass {
      PixelShader = compile ps_2_0 PS();
    }
  }
EOS

  attr_accessor :sz
  attr_accessor :r
  attr_accessor :angle_max
  attr_accessor :start_x
  attr_accessor :start_y
  attr_accessor :shader

  #
  # コンストラクタ
  #
  # @param [Number] sz スクリーンまでの距離
  # @param [Number] r 壁までの距離
  # @param [Number] start_x 初期位置 x
  # @param [Number] start_y 初期位置 y
  #
  def initialize(sz = 160, r = 280, start_x = 0.0, start_y = 0.0)
    self.sz = sz
    self.r = r
    self.start_x = start_x
    self.start_y = start_y

    core = Shader::Core.new(@@hlsl,{
                              :sz=>:float,
                              :r=>:float,
                              :angle_max=>:float,
                              :start_x=>:float,
                              :start_y=>:float
                            })
    self.shader = Shader.new(core)
    self.shader.sz = self.sz.to_f / (Window.height / 2)
    self.shader.r = self.r.to_f / (Window.width / 2)
    self.shader.start_x = self.start_x
    self.shader.start_y = self.start_y
    self.angle_max = Math.atan2(Window.height / 2, sz)
    # self.angle_max = 90.0 * Math::PI / 180.0
    self.shader.angle_max = self.angle_max
  end

  #
  # 描画開始位置を更新
  #
  # @param [Number] dx テクスチャ取得位置 x 増分(1.0より小さい値が望ましい)
  # @param [Number] dy テクスチャ取得位置 y 増分(1.0より小さい値が望ましい)
  #
  def update(dx, dy)
    self.start_x += dx
    self.start_y += dy
    self.shader.start_x = self.start_x
    self.shader.start_y = self.start_y
  end

  #
  # 描画
  #
  # @param [Number] x 描画位置 x
  # @param [Number] y 描画位置 y
  #
  def draw(x, y, img)
    Window.drawEx(x, y, img, :shader => self.shader)
  end
end


if $0 == __FILE__
  # ----------------------------------------
  # 使用例

  font = Font.new(14)

  img = Image.load("bg_960x480.png")
  bg = PipeBg.new(200, 360, 0.0, 0.0)

  Window.loop do
    break if Input.keyPush?(K_ESCAPE)
    bg.update(0.0, -0.005)
    bg.draw(-Window.width / 4, 0, img)

    s = "#{Window.real_fps.to_i} FPS CPU: #{Window.getLoad.to_i} % x = #{bg.start_x.to_i}"
    Window.drawFont(4, 4, s, font)
  end
end

使用画像も置いときます。画像サイズは 960x480ドット。

_bg_960x480.png

他のスクリプトからも呼び出せるようにしておきました。以下は使用例。

_pipebg_test_main.rb
#
# pipebg.rb の呼び出しテスト
#

require 'dxruby'
require_relative 'pipebg'

# 背景マップデータ
mapdata = [
  [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, nil, nil, nil, nil],
  [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 1, 1, 1, 2, 2, 2, 1, 1, 2, 1, 1, 1, 2, 1, 2, 2, 2, 1, 1, 2, nil, nil, 2, 1, 2, 2, 2, 1, 1, 2, nil, nil, nil, 2, 1, 1, 1, 1, 1, 1, 1, 1, nil, nil],
  [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 1, 1, 2, nil, nil, 2, 1, nil, 2, 1, 2, nil, 1, 2, nil, nil, 2, 1, 2, nil, nil, 2, 1, 2, nil, nil, 2, 1, 2, nil, nil, nil, 2, 1, 3, 3, 3, 3, nil, nil, nil, nil, nil],
  [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 1, 2, nil, nil, 2, 1, nil, nil, 2, nil, nil, 1, 2, nil, nil, 2, 1, 2, nil, nil, 2, 1, 2, nil, 2, 1, 1, 1, 2, nil, 2, 1, 1, 3, 3, 3, 3, 3, nil, nil, nil, nil],
  [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 1, 2, nil, nil, 2, 1, nil, nil, 2, nil, nil, 1, 2, 2, 2, 1, 1, 2, nil, nil, 2, 1, 2, nil, nil, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 3, nil, nil, nil],
  [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 1, 1, 2, nil, nil, 2, 1, nil, 2, 1, 2, nil, 1, 2, nil, 2, 1, 1, 2, nil, nil, 2, 1, 2, nil, nil, 2, 1, 1, 1, 2, 1, nil, nil, nil, nil, 1, 3, 3, nil, nil, nil, nil],
  [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 1, 1, 1, 2, 2, 2, 1, 1, 2, 1, 1, 1, 2, 1, 2, nil, nil, 2, 1, 1, 2, 2, 1, 1, 2, 2, 2, 1, 1, 1, 1, 2, 1, nil, nil, nil, 3, 3, 3, nil, nil, nil, nil, nil],
  [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, nil]
]

font = Font.new(14)

bg = PipeBg.new(160, 280)

imgs = Image.loadTiles("bg_chip.png", 4, 1, true)
star_bg = Image.load("bg_star.png")
rt = RenderTarget.new(64 * 15, 64 * 8)

bg_x, bg_y = 0, 0

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

  # 星BG描画
  Window.drawTile(0, 0, [[0, 0]], [star_bg], bg_x / 3, 0, 2, 1)

  # ReanderTargetにタイルBGを描画
  rt.drawTile(0, 0, mapdata, imgs, bg_x, bg_y, 15, 8).update

  # RenderTarget を円柱のように描画
  bg.draw(-Window.width / 4, -32, rt)

  bg_x += 3
  bg_y += 2

  s = "#{Window.real_fps.to_i} FPS CPU: #{Window.getLoad.to_i} %"
  Window.drawFont(4, 4, s, font)
end

使用画像。

_bg_chip.png
_bg_star.png

以下のような感じの画面になります。
pipe_bg_ss_2.gif


ソースや画像のライセンスは、Public Domain / CC0 ってことで。

一応、マップデータ(Tiled形式。.tmx) その他も含めて、一式置いときます。

_pipe_bg_20140624.zip (120KB)

処理内容について。 :

どうやって実現したのか自分自身が忘れそうなので、考え方をメモ。

Shader (HLSL) で、Input.x と Input.y の値を変化させてやれば、こういう円柱っぽい表示ができるわけですが…。

とりあえず、横から見た図で考えてみるとして。
pipe_side_proj2.png
  • 原点 (0,0) が、視点。
  • スクリーンまでの距離が、sz。テキトーに決めておく。
  • 青い線で描かれた円が、壁に相当するところ。半径 r の円。

点B、原点、点A でできる角度 a は、アークタンジェントを使えば求められます。この場合は、atan2(sy, sz) で得られるはず。

この、角度 a を、テクスチャを参照する際のuv値 ―― Input.y を変化させる値としてそのまま利用すれば、y方向に関しては円柱っぽい見た目になりそうだなと。Input.y は、0,0〜1.0 の値を取るので、角度 a も 0,0〜1.0 に収まるように変換してやればいいわけで。

さらに、角度 a が分かれば、点Aを通る直線と円の交点、のz値も、r * cos(a) で求められます。z値が得られたら、各ラスターを横方向にどれだけ拡大縮小すればいいのか計算可能になるわけで。

今度は上から見た図で考えるとして。
pipe_top_proj.png
  • z値は ―― 図で言うところの pz 値は、r * cos(a) で得られた。
  • sz はテキトーに決めてある。
  • sx も HLSL内では Input.x として得られると分かってる。
後は px を求めるだけ。
sx : sz = px : pz
sz * px = sx * pz
px = sx * pz / sz
px が求まったら、それを Input.x に反映してやれば、拡大縮小ができるわけで。

注意点としては…。Input.x も Input.y も、0.0〜1.0 として得られるので、0.5 を引くことで、画面の中心が 0.0 になるようにしてから計算に使ってます。

また、Shader は、与えられた画像 or RenderTarget を円柱っぽく描画する処理しかしていません。スクロール処理は…。
  • テクスチャuv値をちょっと弄る。
  • もしくは、RenderTarget にスクロールした状態の画を描画しておいて、その RenderTarget を Shader に渡す。
という仕組みでやってます。

課題。 :

y値に応じてラスター単位で明るさを変えたら、もうちょっと見た目がリアルっぽくなるのかもしれず。

実装してみたものの。 :

この手の処理はどうやって実現していたのか、個人的に気になっていたので、今回一応試してみたものの。例えば Unityあたりを使うなら、円柱っぽいモデルを配置・回転させて、それで終わりだろうなと予想するわけで。

3D描画が当たり前の現代においては、こういう処理ができたからと言って、何に使えるのか、どこで使えるのかと言う問題があるなと…。

でもまあ、プログラマーにとっての頭の体操、プログラマー向けのパズル、としてならアリかなと…。

#2 [dxruby] マップエディタ Tiled でエクスポートしたjsonに対して処理

マップエディタ _Tiled Map Editor は、json ファイルでエクスポートすることもできるのだけど。その json ファイルを Ruby で読み込んで、DXRuby で使える形に変換して出力できないかなと。

とりあえず、レイヤー情報だけでも取り出せないか、少し実験。

_convtmxjson.rb
# マップエディタ Tiled でエクスポートした json から
# DXRuby用のマップデータを取り出してみるテスト

require 'json'
require 'pp'

if ARGV.size == 0
  puts "usage: ruby #{$0} json_file"
  exit
end

infile = ARGV[0]
unless File.file?(infile)
  puts "#{infile} not found"
  exit 1
end

d = JSON.parse(File.open(infile).read)

# レイヤー枚数分ループ
d["layers"].each do |layer|
  name = layer["name"]
  w = layer["width"].to_i
  h = layer["height"].to_i

  ndt = layer["data"].map {|c| (c == 0 or c== 1)? nil : (c - 1)}
  a = ndt.each_slice(w).to_a

  puts "# name=#{name},w=#{w},h=#{h}"
  puts "mapdata = ["
  a.each { |aa| puts "  #{aa}," }
  puts "]"
end

exit

使い方。
ruby convtmxjson.rb hoge.json
ruby convtmxjson.rb hoge.json > map.txt

例えば、以下のような json ファイルを読みこませると。

_bg.json
{ "height":8,
 "layers":[
        {
         "data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 2, 2, 3, 2, 2, 2, 3, 2, 3, 3, 3, 2, 2, 3, 1, 1, 3, 2, 3, 3, 3, 2, 2, 3, 1, 1, 1, 3, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 3, 1, 1, 3, 2, 1, 3, 2, 3, 1, 2, 3, 1, 1, 3, 2, 3, 1, 1, 3, 2, 3, 1, 1, 3, 2, 3, 1, 1, 1, 3, 2, 4, 4, 4, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 1, 1, 3, 2, 1, 1, 3, 1, 1, 2, 3, 1, 1, 3, 2, 3, 1, 1, 3, 2, 3, 1, 3, 2, 2, 2, 3, 1, 3, 2, 2, 4, 4, 4, 4, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 1, 1, 3, 2, 1, 1, 3, 1, 1, 2, 3, 3, 3, 2, 2, 3, 1, 1, 3, 2, 3, 1, 1, 3, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 3, 1, 1, 3, 2, 1, 3, 2, 3, 1, 2, 3, 1, 3, 2, 2, 3, 1, 1, 3, 2, 3, 1, 1, 3, 2, 2, 2, 3, 2, 1, 1, 1, 1, 2, 4, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 2, 2, 3, 2, 2, 2, 3, 2, 3, 1, 1, 3, 2, 2, 3, 3, 2, 2, 3, 3, 3, 2, 2, 2, 2, 3, 2, 1, 1, 1, 4, 4, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1],
         "height":8,
         "name":"layer1",
         "opacity":1,
         "type":"tilelayer",
         "visible":true,
         "width":60,
         "x":0,
         "y":0
        }],
 "orientation":"orthogonal",
 "properties":
    {

    },
 "tileheight":64,
 "tilesets":[
        {
         "firstgid":1,
         "image":"bg_chip.png",
         "imageheight":64,
         "imagewidth":256,
         "margin":0,
         "name":"bg_chip",
         "properties":
            {

            },
         "spacing":0,
         "tileheight":64,
         "tilewidth":64
        }],
 "tilewidth":64,
 "version":1,
 "width":60
}

以下のような出力結果に。
# name=layer1,w=60,h=8
mapdata = [
  [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, nil, nil, nil, nil],
  [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 1, 1, 1, 2, 2, 2, 1, 1, 2, 1, 1, 1, 2, 1, 2, 2, 2, 1, 1, 2, nil, nil, 2, 1, 2, 2, 2, 1, 1, 2, nil, nil, nil, 2, 1, 1, 1, 1, 1, 1, 1, 1, nil, nil],
  [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 1, 1, 2, nil, nil, 2, 1, nil, 2, 1, 2, nil, 1, 2, nil, nil, 2, 1, 2, nil, nil, 2, 1, 2, nil, nil, 2, 1, 2, nil, nil, nil, 2, 1, 3, 3, 3, 3, nil, nil, nil, nil, nil],
  [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 1, 2, nil, nil, 2, 1, nil, nil, 2, nil, nil, 1, 2, nil, nil, 2, 1, 2, nil, nil, 2, 1, 2, nil, 2, 1, 1, 1, 2, nil, 2, 1, 1, 3, 3, 3, 3, 3, nil, nil, nil, nil],
  [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 1, 2, nil, nil, 2, 1, nil, nil, 2, nil, nil, 1, 2, 2, 2, 1, 1, 2, nil, nil, 2, 1, 2, nil, nil, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 3, nil, nil, nil],
  [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 1, 1, 2, nil, nil, 2, 1, nil, 2, 1, 2, nil, 1, 2, nil, 2, 1, 1, 2, nil, nil, 2, 1, 2, nil, nil, 2, 1, 1, 1, 2, 1, nil, nil, nil, nil, 1, 3, 3, nil, nil, nil, nil],
  [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 1, 1, 1, 2, 2, 2, 1, 1, 2, 1, 1, 1, 2, 1, 2, nil, nil, 2, 1, 1, 2, 2, 1, 1, 2, 2, 2, 1, 1, 1, 1, 2, 1, nil, nil, nil, 3, 3, 3, nil, nil, nil, nil, nil],
  [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, nil],
]

コピーして、DXRuby スクリプトに貼り付ければOK。

しかしコレ、jsonファイルをDXRubyスクリプトから直接読み込んで使ったほうがいいのかも…? Ruby 1.9 以降は、jsonを扱うためのライブラリが標準添付されているらしいので、新規にライブラリをインストールする必要もなさそうだし。ただ、Tiled で毎回、json のエクスポート操作をするのが、ちょっと面倒臭い気もする。

#3 [pc][neta] OneDrive(SkyDrive)とGoogleドライブの同期ソフトをアンインストールした

_米Microsoft、無料で使えるOneDrive容量を15GBに -INTERNET Watch という記事を見かけたのだけど。はてブのコメントを眺めてたら、「OneDriveはエロ画像を入れてるとアカウント削除されるから気をつけろ」という話を見かけて、なんだか気になったわけで。

ググってみたら、実際そうらしく。利用規約に違反する画像を入れてるだけで、問答無用で何の連絡も無くアカウント削除されるそうで。

_MicrosoftのOneDrive、検閲されている模様。エロ画像をアップロードすると垢停止
_クラウドにエロ画像を上げると非公開であってもBANされることが判明 不便すぎワロタ
_やはり画像が原因か - Aruyo
_Windows8.1に統合されたSkyDriveに間違ってもエロ絵を入れてはいけない - 仮想と現実
_ITライフハック |突然のアカウントBANを防ぐ その便利さの裏にある危険「Google+」の設定を確認すべし
_え、この写真がポルノ扱い? 非公開なのに? Googleアカウントを停止された とあるユーザーの悲劇 - エキサイトニュース(1/2)
_tappli blog: Googleアカウントを消されてしまった話

非公開フォルダに入れていてもアカウント削除・アカウント停止されるなんて、酷い話だなと。

自分はその手の画像なんか入れてないつもりだけど、しかし全然安心できないよなと。と言うのも、その手の画像かどうか判別する作業は、コンピュータ上の画像認識プログラムを使ってやってるらしいので。であれば、ガンガン誤認識するだろうし、バグも残ってるはずだよなと。

例えば、 _相撲取りが光学合成エフェクトを出しながら戦うアニメgif などを、面白いからと、うっかり OneDrive や Google ドライブに保存してしまったら、Microsoft や Google のプログラムは「肌色成分が多いからコイツはエロ画像だ! けしからん!」と誤認識してアカウント削除しやがるだろうなと予想できるわけで。

ということで、そんな酷い目には会いたくないので、OneDrive も Googleドライブも、今後は画像保存場所として使わないことにしました。OneDrive と Googleドライブの同期ソフトを、メインPC上からアンインストール。

そもそも、Windowsをログオフする際、その手のソフトだけが妙に終了処理を遅らせていたりもしたので、これはこれで問題が一つ解消できたような気もしてきたり。

zipを置いておくとか、そういう用途なら使えるのかしら。でも、実はzipの中身まで検閲して、しかも誤認識したら嫌だなあ…。一体何が置けるんだよ。

余談。

_孫が水浴びしている写真をパソコンの中に保存したおじいさん、児童ポルノ単純所持の罪で警察に捕まり起訴される - GIGAZINE

家庭内でそういう目に会う児童が多いから、という事情があるのだろうけど。それにしてもこの世界はもう滅茶苦茶だなと思います。

2014/06/25(水) [n年前の日記]

#1 [dxruby] DXRubyでラスタースクロールを使ったレースゲームの画面を再現できないか実験してみたり

昔、ラスタースクロールを利用した、レースゲーム・ドライブゲームが結構ありましたけど。例えば、 _ポールポジション とか。 _アウトラン とか。…ラスタースクロールと言うか、ラスター割り込み、水平帰線期間割り込み処理を利用したレースゲーム、と言えばいいのかな。

個人的に、あの仕組み、よく分からんのですよ…。ラスター単位で上手いことやればできるよーと先輩に教えてもらった記憶はあるのだけど、具体的にどういう処理をすればそうなるのか分かってなくて。

気になり始めてググってみたところ、一応、解説ページは見かけたのですが。

_Lou's Pseudo 3d Page
_Code inComplete - How to build a racing game
_Code inComplete - How to build a racing game - straight roads
_Code inComplete - How to build a racing game - curves
_Code inComplete - How to build a racing game - hills
_Code inComplete - How to build a racing game - conclusion

英文ページなので、何が何やら。

とりあえず、まっすぐな道路なら、以下のような仕組みで表現できそうかなと思えたので、DXRuby で実験開始。 *1

raster_drive_ss1.gif

raster_drive_ss2.gif


_raster_drive.rb
require 'dxruby'

font = Font.new(24)

imgs = Image.loadTiles("road_bg.png", 1, 480)
bgimg = Image.load("road_bg.png")

Window.bgcolor = [68, 97, 255]
mode = 0
bz = 0
start_y = 1

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

  # Zキーで描画を変更
  mode = (mode + 1) % 6 if Input.keyPush?(K_Z)

  case mode
  when 0
    Window.draw(-320, 0, bgimg)
    Window.drawFont(4, 4, "BG全体を描画", font)

  when 1
    start_y.step(240-1, 1) { |y|
      i = y
      Window.draw(-320, y + 240, imgs[i])
    }
    Window.drawFont(4, 4, "上半分だけ描画", font)

  when 2
    start_y.step(240-1, 1) { |y|
      i = y + 240
      Window.draw(-320, y + 240, imgs[i])
    }
    Window.drawFont(4, 4, "下半分だけ描画", font)

  when 3
    start_y.step(240-1, 1) { |y|
      i = y + 240 * ((y / 16) & 0x01)
      Window.draw(-320, y + 240, imgs[i])
    }
    Window.drawFont(4, 4, "上と下を、ラスター単位で交互に描画 (一定間隔)", font)

  when 4
    sz = 320
    h = 240
    start_y.step(240-1, 1) { |y|
      z = (h * sz / y) # y座標毎のz値を求める
      i = y + 240 * ((z.to_i / 128) & 0x01)
      Window.draw(-320, y + 240, imgs[i])
    }
    Window.drawFont(4, 4, "上と下を、ラスター単位で交互に描画 (z値に基づく)", font)

  when 5
    sz = 320
    h = 240
    start_y.step(240-1, 1) { |y|
      z = (h * sz / y) + bz
      i = y + 240 * ((z.to_i / 128) & 0x01)
      Window.draw(-320, y + 240, imgs[i])
    }
    Window.drawFont(4, 4, "上と下を、ラスター単位で交互に描画 (z値を加算)", font)
    bz += 8
  end
end
背景画像は以下。

road_bg.png

スクリプトを実行して、Zキーを押していけば描画が変わります。

ちなみに、z値は以下の式で求められます。
h : 視点の高さ
sz : 視点からスクリーンまでの距離
y : スクリーン上のy座標

z = h * sz / y
_Code inComplete - How to build a racing game - straight roads の図が分かりやすい、かもしれず。

さて、ストレートな道路なら、これでなんとかなりそうだけど。カーブや丘は、どう表現すればいいのやら…。
*1: レースゲーム・ドライブゲームと言えば3DCGで表現するのが当たり前になってしまったこの御時勢に、自分は一体何をやってるのかという気もするけれど。仕組みが気になり始めてしまったのだから仕方ない。考えてみたら、30〜40年前の技術、というか工夫なんだよなあ…。

2014/06/26(木) [n年前の日記]

#1 [dxruby] カーブの表現で悩んでいたり

_昨日の日記 で、ストレートな道路の表現はできたのだけど。カーブをどうやって表現したらいいのか、そのあたりで悩んでいたり。

_Code inComplete - How to build a racing game - curves で解説されているのだけど、英文ページなので、何が何やら。とりあえず、「セグメント」「二次曲線」という単語が鍵っぽい気がしますけどよく分かりません。

とりあえず、件のページに「ease In」「ease Out」「ease In Out」なる単語もあったので、そのあたりから調べてみようかと。

_イージング処理の計算式 - 強火で進め
_Easing Equations
_anything from herejquery.js のアニメーションコードの解読 ( 10 ) 番外編 easing関数解読

おそらくだけど、 quadratic easing in とやらの式が使えそう。

#2 [prog] Light Game Programmingとやらが少し気になる

色々ググってたら、Light Game Programming なる、ゲーム開発にフォーカスしたBASIC環境があるらしいと知ったのです。

_LightGameProgramming - Wikipedia
_プログラミング/言語・コンパイラ/LGP - game-develop.com wiki

exe化ができるらしいあたりはなんだかヨサゲ。

パッと見、HSPと似てるように思えたのだけど。開発が始まったのも結構古い時期らしいし。しかし、どうしてHSPほど広まってないのだろう…? 気になってググっていたら、どうやら最初の頃はシェアウェアだったようで。

_窓の杜 - 【NEWS】BASIC風言語によるDirectXゲーム作成ソフト「Light Game Programming」

もしかすると、そのせいで普及しなかった・ブレーキがかかってしまったのかな。他にも、環境によってはフリーズするバグがいつまでも放置されてた時期があった、てな話も見かけたり。また、ライブラリ種類も、HSPに比べたらはるかに少なく。マニュアルを眺めてみたけど、自作ハードウェアの制御、簡易GUI作成等はできないように見えた。

HSPならゲーム以外のアプリも作成できるけど、LGPは、よりゲーム作成に特化したツールとして捉えるのが正しいのでしょうな。

Basic4GLも気になる。 :

_Basic4GL - 年金ロボットをめざして
_Basic4GL

_Basic4GL Demo を眺める限りでは、なんだか色々できそうな感じ。ただ、2008年で更新が止まってるっぽい。

DarkBasicなるものも見かけたけど。 :

_DarkBASIC Electronic - The Game Creators

これは有償ソフトらしい。

Basic って、人気があるんだな…。

2014/06/27(金) [n年前の日記]

#1 [dxruby] DXRubyでラスタースクロールを使ったレースゲームの画面を再現できないか実験その2

一応見た目だけはカーブっぽくなった、のかな。どうなんだろう。ちょっと自信無いですけど。

raster_drive2_ss.gif


ソースは以下。

_raster_drive2.rb
# ラスター処理でドライブゲームの画面を作る
# 左右に移動できる処理を追加

require 'dxruby'

def ease_in(a, b, percent); return a + (b - a) * (percent.to_f ** 2); end
def ease_in_cubic(a, b, percent); return a + (b - a) * (percent.to_f ** 3); end
def ease_out(a, b, percent); return a + (b - a) * (1 - ((1 - percent).to_f ** 2)); end
def ease_in_out(a, b, percent); return a + (b - a) * ((-Math.cos( percent * Math::PI ) / 2 ) + 0.5); end

font = Font.new(12)
imgs_a = Image.loadTiles("road_bg1.png", 1, 480)
imgs_b = Image.loadTiles("road_bg2.png", 1, 480)

Window.bgcolor = [37, 150, 255]
mode = 0
bz = 0

start_y = 1
z_spd = 1.0
z_spd_add = 0.5

x = 0
x_spd = 48
x_max = 3000

curve_dx = 0
curve_dx_spd = 16
curve_dx_max = 480

h = 1000 # 道路までの距離(高さ)
fov = 100
sz = (640 / 2) / Math.tan((fov / 2) * Math::PI / 180.0) # 画面までの距離

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

  # 上下キーで、奥方向の速度を変更
  z_spd += z_spd_add if Input.keyDown?(K_UP)
  z_spd -= z_spd_add if Input.keyDown?(K_DOWN)

  # 左右キーで、横方向の移動量を変更
  x += x_spd if Input.keyDown?(K_RIGHT)
  x -= x_spd if Input.keyDown?(K_LEFT)
  x = -x_max if x < -x_max
  x = x_max if x > x_max

  # D,Aキーで、カーブの量を変更
  curve_dx += curve_dx_spd if Input.keyDown?(K_D)
  curve_dx -= curve_dx_spd if Input.keyDown?(K_A)
  curve_dx = curve_dx_max if curve_dx > curve_dx_max
  curve_dx = -curve_dx_max if curve_dx < -curve_dx_max

  # 道路を描画
  start_y.step(240-1, 1) { |y|
    z = (h * sz / y) # y値に対応したz値を得る

    sx = x * sz / z # 横方向のスクリーン移動量を得る

    # カーブらしく見せるための横方向の移動量を得る
    if curve_dx == 0
      cx = 0
    else
      cx = ease_in_cubic(0, curve_dx, 1.0 - (y / 240.0))
    end

    # 2枚の画像のどちらを描くか、z値を見て判別
    i = y + 240 * (((z + bz).to_i / 320) & 0x01)

    Window.draw(0, y + 240, imgs_b[i]) # 地面を描く
    Window.draw(-160 + sx + cx, y + 240, imgs_a[i]) # 道路を描く
  }

  bz += z_spd # 速度を加算

  [
    "#{Window.real_fps.to_i} fps  CPU: #{Window.getLoad.to_i} %",
    "Push Up, Down, Left, Right, A, D key",
  ].each_with_index {|s,i| Window.drawFont(4, 4 + i * 20, s, font) }
end
使用画像は以下。

_road_bg1.png
_road_bg2.png


横方向への移動は、y座標毎に求めたz値から移動量を求める、みたいな感じでやってるけど。考えてみたら画面の中央から斜めの直線を引くような感じでずらせばいいだけのような気もしてきたり。

カーブは、道路の画像をラスター単位で横方向にずらすことでそれっぽく見せるわけだけど。最初は x = y * y でずらしてみたけど、何か不自然な感じがしたので、x = y * y * y にしてみたり。

curve1.png

不自然さはあるものの、カーブまではなんとかなったとして。丘というか、坂の表現はどうすればいいのやら…。

2014/06/28() [n年前の日記]

#1 [unity] Unity 4.5 の奇妙な挙動

_2014/02/17の日記 で公開してた、Unityのプロジェクトフォルダ一式について。コメント欄で、「DLしたけどキャラクター達が地面の下に落ちていく」と報告していただけたので、自分も検証作業を。

Unity を、4.3 → 現行版 4.5.1f3 に更新してから試したのですけど。自分の環境でも、キャラクター達が地面の下に落ちていくことを確認しました。 要するに、Unity を最新版にしたら地面をすり抜けるようになったのでした。何故。

地面の下に落ちていく条件、落ちていかない条件について調べましたが、どうやら Plane + Box Collider 2D を使って地面を表現しようとすると、すり抜けてしまうようで。
で、どうしてこうなるかと言うと。 そのあたりが原因かなと。

Unity 4.3 の頃は、Plane を回転しても、Box Collider 2D のアタリ範囲までは回転しなかったので、Plane を使って地面を表現して、アタリ判定もできていた・地面の上に立っていたのですが。Unity 4.5 は、そのあたりの仕様が変わってしまったようです。…コレ、仕様なのかバグなのか、分かりませんけど。

ということで。とりあえずですが、Plane を Quad に変更した版をアップロードしておきました。

最新版の Unity を使うと、過去に作ったプロジェクトを読み込んでも正常動作しない、ってのはなんだか痛いですな…。

2014/06/29() [n年前の日記]

#1 [cg_tools] アプリの普及はどこで明暗が分かれるのだろう

_2Dキャラアニメ作成ツール「えもふり」公開!なにこれすごい | オタク.com という記事を眺めてるうちに、なんだか疑問が湧いて。

随分前から、Xprite という、同種の処理をするソフトがあったわけですけど。

_STUDIO yu- | Xprite特設ページ
_【レビュー】ゲームやアニメに使える2Dスプライトアニメーション作成ツール「Xprite」 - 窓の杜

でも、あまり注目されてる感じがしなくて。しかし、「えもふり」は、なんだか注目されてる…。一体どこで差がついてるんだろう…。

パッと見の印象が違うのだろうか。しかし、どっちも女の子を動かしてるように見えるのだけど。

それとも、仕様が関係してるのかな。「えもふり」の、「psdファイルを渡せば作業が始められる」てなあたりが効いてたり? あるいは、人の顔を動かすことに特化してるように見せたあたりがポイントなのかしら。

そこそこ似たようなことをしていても、注目のされ方が違うあたり、なんだか気になります。一体どこらへんが肝なんだろう。

とりあえず、 _E-mote があまり話題になってなかったのに、無料版の「えもふり」が出てきたら急に話題になったあたり、無料版が存在すると宣伝効果がかなり期待できるのは間違いなさそうだなと思いました。

考えてみたら Unity もそうか。Unity も、仮に有償版しか無かったら、あれほど注目されてなかっただろうな…。

SpriteStudioてのもあるけれど。 :

_ウェブテクノロジ OPTPiX SpriteStudio も同種の処理をしていると捉えることもできそうな気もする。ただ、ここまで来ると、Adobe Flash との違いが分かりづらいけど。

自由度が高いと、何に使えるのかイメージしづらくて不利になる、てなところもあるのかな。分かりませんけど。

#2 [pc][web] 体験版のダウンロード時までメールアドレスや名前の入力を要求する必要はないだろうと思うのだけど

SpriteStudio についてメモってる際になんとなく思い出したのでメモ。メモと言うか、愚痴と言うか。

例えば、ウェブテクノロジは体験版のダウンロード時ですらメールアドレスや名前の入力を要求してくるのですが。大変失礼ながらアレはアホだわと常々思っておりまして。特に、コミPo!の体験版すらメールアドレスを要求することには呆れていて。 初っ端から印象最悪というか、萎えるというか。無意味なハードルをわざわざ設置してる余裕なんかない商品ジャンルだと思うのだけど。

例えばの話、デパ地下で、客が試食しようとするたびに、一々記帳を要求してたら、売れる商品も売れないだろうなと。皆、味見もせずに立ち去るよなと。無意味な記帳を要求することで、一体どれだけ客になりそうな人をみすみす逃しているのか、少し想像したら、たちまち血の気が引いていきそうなものだけど。

まあ、件の会社に限った話じゃないですけど、そういうアレなことを平気でしてる会社が多過ぎて。「ちゃんと考えた上でその情報を集めてんの?」と問い詰めたくなる時が多々あって。
そのあたりを考えたら、「集めてる意味、ホントにあるの?」と。どうしても必要な時だけ、最後の最後で、個人情報を取得するように切り替えたほうがいいだろうと思うのですけど。

例えば Unity は、ダウンロード時にメールアドレスや名前なんか要求してません。Unity って1GB以上もあるアプリですけど、ダウンロードページから、「たったのワンクリック」でダウンロードできる。それでいて、あんなに注目されてるし、話題にもなってる。 *1

そういう事例と比べると、体験版ダウンロード時にメールアドレスを要求する会社なんて、こりゃアホですわ。いい製品作ってるのに、もったいない。せっかく製品に興味を持ってくれた人を追い返してどうするんだろうと。

でもまあ、あの状態でも飯が食えてるのだろうから、このあたり余計なお世話だろうな…。おそらく、競争相手が居ないブルーオーシャンだから、のんびりしていられるのかもしれず。そもそも、あの製品群、個人相手に商売する気も無さそうだし…。
*1: ただ、Unity は、インストール後に、何か登録作業があったような気もする…。

#3 [anime] プリキュアの髪の毛のグラデーション

幸福充填プリキュアを眺めていたら、髪の毛にグラデーションがかかっていることに気がついて。

プリキュアに、グラデーション塗りって必要なのかな…。個人的には、要らないんじゃないかと一瞬思えてしまったり。と言うのも、(大変失礼ながら)作画はヘロヘロなのにグラデはキッチリかけるとか、ちょっと馬鹿馬鹿しいよなと。別のところに力を入れたほうがいいんじゃないのかと。

でも、原動画ではない部署で手を入れることができるから、これはこれでアリなのかな。よくよく見てみたら、グラデというより、色の境界線をぼかすような処理に近い気もするし。このくらいならフィルタをかければ一発だからそんなに負担ではない、みたいな話かも。であれば、やらないよりはやったほうがいいのだろうか。どうなんだろう。

そういえば、番組の冒頭で登場する過去作品のキャラ達も、それっぽい処理をかけた状態で仕上げてるようで。一番最初の黒いプリキュアですら、見た目艶々していて。当時の映像と比較してみたい気もしてきたり。

何時頃からこういう塗りになったのか気になってググってみたけど、前作の「ドキドキプリキュア」の時点ではパキッとした塗りがほとんどのように見えた。 *1 どうして今作からこういう塗りに…? 今作から3DCG映像が本編に挿入されるから、せめて塗りだけでも3DCGに近づけよう、てな試みだったりするのかな。

このあたり、 _1stガンダムのアレなカットの数々にレタッチした例の画像 を思い出して、なんだか考え込んでしまうわけで。例の画像を見て…。 さて、どっちなんだろうと…。原動画がヘロヘロだからこそ、塗りにこういった一工夫を加えて、見た目の印象を底上げすべし、なのか。それとも、そんな作業は無意味なのか。効果はあるのか、それとも無いのか。どう捉えるのが妥当なのかなと。
*1: 昔の作品も、変身バンク等では、ぼかしだかグラデだかがかかってるみたいだけど。

#4 [zatta][neta] サイコって何だろう

_「このタイミングでサッカー嫌いを表明する人間はサイコ」 なる言説を目にして、先日このタイミングでサッカー嫌いをわざわざ表明する羽目になってしまった自分としてはちょっとグサッときたのだけど。

Firefoxが毎回起動するたびにサッカー画像をネット上からロードして表示してしまうから、自分だってそんなことをわざわざこのタイミングで表明する羽目になったわけで。あっちから強制的に見せつけといて、「いや、俺はソレ興味無いんで。ていうか嫌いなんで」と少し洩らしただけなのにサイコ呼ばわりなんて酷いなー、てなことを思いました。

それはともかく、そこで言ってるサイコって何だろう? と疑問が湧いたわけで。言葉としてはよく聞くのだけど、恥ずかしながら正確な意味・定義を知らないのですよ…。

ググってみたけどよくわからず。 :

_サイコ - Wikipedia を眺めたら、たくさんあり過ぎて、何が何やら。

とりあえず、 _サイコ (1960年の映画) のヒットで、この単語が普及したらしいけど。恥ずかしながら自分は未見で。映画を見ていれば定義が分かるのだろうか。でもホラー映画だから…今後も怖くて見ないであろう予感。

「サイコパス」で辿ってみたら、 _精神病質 - Wikipedia に転送されたのだけど、眺めているうちにゲンナリ。これに全く当てはまらない人が居たら、ソイツはもう人間じゃねえよ…。いやまあ、それぞれの要素について社会的許容範囲を超えたらこういう分類されちゃうよ、という話に過ぎないのだろうけど。

よくよく読んでみると、「サイコパス」と「サイコ」は違うんじゃないかと思えてきたり。件の文章に「サイコパス」の定義を当てはめてみると、文として成立しない感じ。

正確な定義を知っていても意味が無いことに気付いた。 :

考えてみたら、「サイコ」発言してる各人にとって、「サイコ」の定義がそれぞれ違うだろうから、調べてみても意味が無いような気がしてきたり。

例えば、「ハッカー」「ホームページ」「インターネット」と呼んでる人相手に、「クラッカー」「Webサイト」「電子メール」等の正しい定義を持ち出してみても意味が無いわけで。「サイコ」も同じだよなと。相手がどんな定義で使ってるつもりなのか、こっちが空気を読んで察してあげないといかんのではないか、と。

おそらくだけど、世間一般では、「キチガイ」の代わりに、上品(?)ぶって「サイコ」と言ってるだけじゃないか、という気もしてきたり。「お前、おかしいよ」と言いたいけれど、そのままだとあまりに素朴な物言いでダサイから、「サイコ」という外来語を持ち出してちょっとカッコイイ物言いをしたつもりになってみました、みたいな。

コンサルタントが「ソリューション」「シナジー」「コンプライアンス」とかその手の単語をやたらと使って武装するソレに近いんじゃないか。たぶん。まあ、自分もそういう物言いはよくやっちゃってる気がしますけど。

てな想像をしているうちに、もしかするとそのうち「サイコ」という単語も余計なトラブルを招くことが多くなって、別の単語を使い始めたりしないだろうか、てなくだらない妄想を。…星新一作品等で既にそういう作品がありそうな。

その手のくだらない妄想を作品レベルまで持っていける・昇華できる人なら、サイコ呼ばわりされずに済むのかしら。例えば宮崎駿監督が名監督と呼ばれてるように、創作側になれたら社会的に許容される幅が大きくなるような気がする。というか創作者なら、サイコ呼ばわりされても、それがむしろ、ある種の称号・褒め言葉になる時もありそうな。

こんな風にどうでもいいことをだらだらメモってるだけで、またサイコ呼ばわりされそう。ていうか長文ブロガーと呼ばれる方々も、ほとんどがサイコ扱いされそうな気もしてきたり。気がするだけで実際どうかは分かりませんが。

#5 [web][neta] ネット上ではどうして正直に全部喋ってしまうんだろう

「サッカーに限らずスポーツは嫌い」と、自分は正直に表明しちゃってるけど。これがネット上ではなくて、リアルワールドだったら、「いやあ、自分、○○は全然詳しくないんですけど…。日本って結構強いんですか?」と、完全に興味ゼロのくせして実に白々しく相手に質問したりして会話を繋げるんだろうなー、と思えてきたわけで。

ネット上の振舞いを、リアルワールドでもそのまんましてるわけねえよな…。世間話の最中に「○○は嫌い」と言い出して、場の雰囲気を悪化させることなんて、そんなに無いはず…。いや、うっかりやらかして、数日経ってから「うわああ」と頭を壁に打ち付けたくなる時も結構あるのですけど、それでも基本的には、相手のソレを否定も肯定もせず、上手に受け流せるのが賢いやり取り、と思いながら会話を試みているつもりで…。

なのにどうしてネット上では、正直に好き嫌いまで言ってしまって、余計なトラブルを招いてしまうのだろう。

待てよ? 考えてみたら、ネット上ではそもそも会話なんかしてないのか。blog にしろ、Web日記にしろ、自分はこう思った、こういうことがあった、とかそういうことしか書いてないわけだし。コレ、会話じゃなくて記録だよな。だから正直に書いてしまうのかも。

これが逆に、正直になるどころか、自身のWeb日記にあらゆる嘘八百を書き連ねてる姿がそこにあったとしたら、ソレもちょっとどうかと思えるし。あるいは、自身の考えや感想を一切書かず、ひたすら大量にはてブしてたりする姿も、なんだか気持ち悪いものがあるし。そう考えると、まだ正直にアレコレ書き残してるほうが、人としてはマシだったりするのかなと思えてきたりもしたけどよくわからないです。仮面をつけて、相手の顔色を伺ってばかりいるほうが、人としては正常なのだろうか…?

てなことをぼんやり思ったとメモ。オチなんか無いです。ココは、自分の思考メモ・作業メモを書き散らかすだけの場でしかないので。

2014/06/30(月) [n年前の日記]

#1 [cg_tools] えもふりを試用

この手のツールは触ってみないと何とも言えないところがあるので、試しにインストールして、少し触ってみたり。

同梱のサンプルファイルを読みこんでちょっと動かしてみたけれど。なるほど、これは面白いなあ…。静止画がぬるぬる動き始めるあたりに、純粋な驚きが。いやまあ、仕組みは別に新しいわけではないのですけど。既存技術のまとめ方、あるいは見せ方が上手い、と思ったわけで。

可愛い女の子をぬるぬる動かせるという点を前面に押し出しているあたりはグッドのような気がしたり。同じ仕組みでも、狼、スライム、実写取り込みの外国人の顔が動くデモ映像より、人目を引くよなと。

全パーツを1つのpsdファイルでインポートするあたり、賢いなと感心。これがもし…。 そういった、他のインポートの仕方を想像すれば、psdファイル1つで済ませられる仕様は素晴らしい。

ただ、Adobeのクローズドなフォーマットであるpsdファイルを頼ってしまって大丈夫なのかな…。と思ったけど、ググってみたら今時は _Adobe Photoshop File Formats Specification で仕様が公開されてるの? 知らなかった! だったら問題無いか…。レイヤーを内包できて、そこそこ普及しちゃってるメジャーな画像フォーマットと言うとpsdしか無いわけで。今後もその手の用途では、psd一択になりそうな。それにしても何時公開したんだろう…。ググってみても、ニュースになってないみたいだけど…。

各パーツの可動範囲を、レイヤー上の矩形領域で指定するあたりも、これは頭がいいなと。富豪的プログラミングに近い発想を感じたり。リソースをたくさん使うことで利便性が増すなら、今時ソレをやらない理由はないですな。

何より、 _E-mote/えもふりが元々は多関節キャラエディタだった という話を目にして、なんだか目頭が熱く…。メガドラのタイトルを作ってた頃、先輩が、「本当は多関節キャラエディタを作って、プログラマー以外も多関節キャラを作れるようにしたいんだけど、そこまで手が回らないんだ」と悔やんでたことを思い出したりして。やっぱりエディタが存在する・エディタを作っておくのが正解だよなと。どう考えてもM2さんは正しい。それにしても、あの頃どうにかその手のエディタを作っておけば、3Dときメモを作り始める前に、2Dでぬるぬる動くときメモを出せて、展開が違っていたのかも、などと妄想したりもして。 *1

仕組みについて。 :

仕組み自体は、失礼ながら、別に新しくもないよなと。 _Live2D も似たような仕組みだろうし。

また、Flashアニメが流行ってた頃の、 _なつみSTEP! でも、こういう仕組みで動かしてるカットが多数あるし。あの作品って、何年頃に制作されたんでしたっけか。少なくとも10年以上前だと思うけど…。

そもそも、宮崎駿監督が絶賛したと言われる、 _霧につつまれたハリネズミ(霧の中のハリネズミ) も同じ仕組みだし。1975年のフィルム作品でもやってるわけで。

なので、「技術の進歩ってスゲー」等の感想を見かけると、なんだかもやもやしちゃったり。やってる人達は随分前からやってたわけで。ここに来て突然出現したものでもないよなと。

しかし…。
  • 専用ツールをちゃんと作って結構な部分を自動化できた点。
  • やれることを制限した代わりに操作手順量を激減させて、誰でもできそうな雰囲気にした点。
  • 昔のフィルム作品でやってたソレを、現代のデジタル機器の上でリアルタイムにやってみせるという点。
そのあたりが素晴らしいと個人的には思うのでした。パッと見た瞬間に、「ああ、こんな風に動かせるんだ」「こういう使い方ができるんだ」と分かるのは、これは間違いなく意義があることじゃないのかと。

Flash、After Effects、 _Anime Studio _(Anime Creator) 等で、似たようなことはやれなくもないけど。それらは汎用性がある代わりに手間が増えて、「なんだか大変そう」「これは自分には無理じゃないか」と思えてくるわけで。

「このくらいなら自分もできるんじゃないか」と思ってもらえそうか、そこは大事だよなと。えもふりは、そこらへんがイイ感じだよなと。…考えてみたら、iPhone等のタッチパネル端末がウケたのもソレなのかな。既存の技術を上手に組み合わせて、「自分もできそう」と思わせられるところまで持っていけるかどうか、みたいな。

更に、Twitter や pixiv で、アニメgif相当を貼れるようになったタイミングで、無料版を発表できたあたりも上手いなと。とにかく、無料版の存在・出現は大きい。 _年間契約は最低でも30万円 てな状態では、個人で少し遊んでみるなんて無理なのでスルーするしか無く。

同じ技術も、周囲の状況をどう整えるかで、注目されたりされなかったりしてしまうのだなと再認識、でした。良い製品を作っていても、上手に宣伝できなかったら埋もれちゃうよなと。

妄想。 :

触っているうちに少し妄想。

自分は絵描きじゃないので、元画像を用意するのがちと大変というか、若干くじけてしまったので、サンプルpsdを少し多目にどこかで公開しておいたほうがいいような気もしたり。「絵描きさんしか使えないツールなのだ」と思い込まれたら、それはよろしくないよなと。

MMDと似たような感じで、色んな絵描きさんが「俺の描いたpsdを動かしてみてくれ」と公開する流れができたら、もう少し盛り上がりそうだけど。そこまで行くかどうかは、ちとよく分からず。

どこかのアニメ番組で、えもふりで動かすこと前提のpsdファイルを公式サイトで配布をしたら、宣伝効果が期待できないかなと思ったり。
  • プリキュアのように3DCGモデルを用意して配布、とかになってしまうと作業が大変過ぎるし、何に使われるか分からんという不安も出てくるので、とても無理だけど。
  • これがもし、手描きでパーツ分けしたpsdを用意するだけなら、アニメーターさんにお願いすればどうにかなるし。決まった角度内で動かすことしかできないから、使われる範囲にも自ずと制限を与えられるし。
壁紙と一緒に、パーツ分けしたpsdを置いておくだけなら、マイナーなアニメの公式サイトでも十分実現できるのではないかなと。

いや、考えてみたら、アニメじゃなくてもいいのか。実写ドラマの俳優さんをレタッチしてパーツ分けpsdにして番組の公式サイトで公開、てのも宣伝になるかもしれない。おそらくキモイ動きになるあたり、少しは喜ばれそう。

でもまあ、わざわざpsdをDLさせなくても、公式サイトに JavaScript か Flash で貼っとけばいいだけの話なのかな。…そういう形にすると、ライセンス料が高くなりそうだし、インパクトも弱まりそうだけど。

Webサイト上のソレと、ローカルにDLするソレ。やれることは同じだとしても、ローカルにDLして好き勝手やれる、とユーザに思い込んでもらえるかどうかが結構大事だったりしないのかな。それとも、そのあたりの感覚を持ってるのはおじさんだけで、スマホでWebサービス・クラウドを使いこなす若い世代には、もう無かったりするのかな。どうなんだろう。

*1: けれど、あの頃はそういうツールを動かせそうなハードが無かった・PCのハードウェアスペックが低かったから、やっぱり実現性は低かったかもしれない。いや、PC-9801じゃなくて、X68Kが開発機材として渡されてたら、違ったのかな。

#2 [nitijyou] 首が痛い

首なのか右肩なのか分からないけど、なんだかそのあたりが痛いので寝ます。

犬の散歩をした際に、グイグイ引っ張られて、どこか痛めてしまったのかもしれない。

以上、30 日分です。

過去ログ表示

Prev - 2014/06 - 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