mieki256's diary



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

#1 [dxruby][cg_tools] DXRubyでswfアニメを再生する

DXRubyでswf(Flashアニメ)を再生してみたり。ビットマップ画像を表示するswfしか再生できないけど、ある程度動いてるので一応アップロード。

スクリーンショット。 :

以下が、 _元swf を、DXRuby上で再生した際のスクリーンショット。(GIFアニメにするために、減色・フレームレート・画像サイズを落としてあります)

swfparse_demo.gif
雰囲気ぐらいは伝わるかな…。

実験の動機。 :

「DXRubyに動画再生機能をつけてほしい」という要望をどこかで目にして、「動画ファイル再生は無理でも、swf再生ならできるんじゃないかな…。60FPSで画像をグリグリ動かせるんだし…」と思ったのがキッカケ。

実際使うかどうかは別にして、自分の思いつきが、本当に実現可能かどうか、なんだか気になったわけで。

処理の概要。 :

  • swfmill で swf を xml に変換。
  • Ruby + Nokogiri を使って xml を解析しつつ、DXRuby で描画。

制限事項。 :

以下は対応済み。
  • ビットマップ画像の移動・拡大縮小・回転・傾斜・半透明表示
  • 輝度変更 (明度変更)、色変更
  • 簡素な階層のスプライト (MovieClip)
  • 静止テキスト (デバイスフォント "_ゴシック", "_明朝", "_等幅" 等を指定していて、入れ子になってない簡素なもの)

以下は未対応。
  • シェイプ(ベクターデータ)
  • サウンド
  • ActionScript
  • 複雑な階層のスプライト(MovieClip)
  • テキストの拡大縮小・回転・傾斜、入れ子構造

ソースやサンプルデータ一式。ライセンス。 :

zip にして置いときます。 swfや画像のファイルサイズが大き過ぎたので、Dropbox に置いてみました。 Dropboxのサービス内容が一部変わったので自宅サーバに置き直しました。(2017/03/19)


_swf_parse_20140531.zip (67MB)

自分が書いたソースに関しては、Public Domain ってことでよろしくです。

ソース。 :

_playxmlmovie.rb
require "nokogiri"
require "dxruby"
require "find"
require 'benchmark'

# swfから変換したxmlを読み込んでDXRubyで再生するクラス
#
# @note swf から xml への変換は、swfmill を利用。
# @note DXRuby、Nokogiri が必要。
# @note License : Public domain
#
# @example
#   require "playxmlmove"
#   m = PlayXmlMovie.new("hoge.xml")
#   m.init
#   Window.loop do
#     break unless m.play
#   end
#
class PlayXmlMovie
  DBG = false
  TMDBG = false
  BMDBG = false

  # 論理ピクセル値
  TWIP = 20.0

  # ----------------------------------------
  # 色変更をするためのShader
  #
  class SpriteShader < Shader

    hlsl = <<EOS
  float4 g_mul;
  float4 g_ofs;
  texture tex0;

  sampler Samp0 = sampler_state
  {
   Texture =<tex0>;
  };

  float4 PS(float2 input : TEXCOORD0) : COLOR0
  {
    float4 output;

    output = tex2D( Samp0, input );
    output.r = (output.r * g_mul[0]) + g_ofs[0];
    output.g = (output.g * g_mul[1]) + g_ofs[1];
    output.b = (output.b * g_mul[2]) + g_ofs[2];
    output.a = (output.a * g_mul[3]) + g_ofs[3];
    return output;
  }

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

    @@core = Shader::Core.new(hlsl, {:g_mul => :float, :g_ofs => :float})

    def initialize
      super(@@core, "TShader")
      self.g_mul = [1.0, 1.0, 1.0, 1.0]
      self.g_ofs = [0.0, 0.0, 0.0, 0.0]
      @col = [1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0]
      class << self
        protected :g_mul, :g_mul=, :g_ofs, :g_ofs=
      end
    end

    def set_color(a)
      v = 256.0
      self.g_mul = [ a[0] / v, a[1] / v, a[2] / v, a[3] / v]
      v = 255.0
      self.g_ofs = [ a[4] / v, a[5] / v, a[6] / v, a[7] / v]
      @col = a
    end
  end

  # ----------------------------------------
  # MATRIX情報格納用クラス
  #
  class TMatrix
    attr_accessor :trans_x, :trans_y
    attr_accessor :scale_x, :scale_y
    attr_accessor :skew_x, :skew_y
    attr_accessor :color

    def initialize(trans_x = 0, trans_y = 0,
                   scale_x = 1, scale_y = 1,
                   skew_x = 0, skew_y = 0,
                   mul_r = 256, mul_g = 256, mul_b = 256, mul_a = 256,
                   ofs_r = 0, ofs_g = 0, ofs_b = 0, ofs_a = 0)
      self.trans_x, self.trans_y = trans_x,trans_y
      self.scale_x, self.scale_y = scale_x, scale_y
      self.skew_x, self.skew_y = skew_x, skew_y
      self.color = [ mul_r, mul_g, mul_b, mul_a, ofs_r, ofs_g, ofs_b, ofs_a]
    end

    #
    # 属性値を更新
    #
    # @param [Hash] v 属性と値のハッシュ
    #
    def set_value(v)
      self.trans_x = v["transX"].to_f if v.has_key?("transX")
      self.trans_y = v["transY"].to_f if v.has_key?("transY")
      self.scale_x = v["scaleX"].to_f if v.has_key?("scaleX")
      self.scale_y = v["scaleY"].to_f if v.has_key?("scaleY")
      self.skew_x = (v.has_key?("skewX"))? v["skewX"].to_f : 0
      self.skew_y = (v.has_key?("skewY"))? v["skewY"].to_f : 0

      [
        "factorRed", "factorGreen", "factorBlue", "factorAlpha",
        "offsetRed", "offsetGreen", "offsetBlue", "offsetAlpha"
      ].each_with_index do |s, i|
        self.color[i] = v[s].to_f if v.has_key?(s)
      end
    end

    #
    # 座標変換
    #
    # @param [Number] x x座標
    # @param [Number] y y座標
    # @return [Array] 座標変換後のx,y座標
    #
    def trans(x, y)
      nx = (x * self.scale_x) + (y * self.skew_y) + self.trans_x
      ny = (x * self.skew_x) + (y * self.scale_y) + self.trans_y
      return nx, ny
    end

    #
    # 色変換パラメータを更新
    #
    # @param [Array] c 色変更情報の配列
    # @return [Array] 変更後の色変更情報配列
    #
    def trans_color(c)
      nc = Array.new(8)
      for i in 0..3 do
        nc[i] = c[i] * self.color[i] / 256.0
        nc[i] = 512 if nc[i] > 512
        nc[i] = -512 if nc[i] < -512
      end
      for i in 4..7 do
        nc[i] = c[i] + self.color[i]
        nc[i] = 255 if nc[i] > 255
        nc[i] = -256 if nc[i] < -256
      end
      return nc
    end

    #
    # 色変換が必要かどうかを返す
    #
    # @param [Array] c 色変換情報配列
    # @return [Boolean] trueなら色変換が必要。falseなら色変換は不要。
    #
    def TMatrix.trans_color?(c)
      [
        [0, 256.0], [1, 256.0], [2, 256.0],
        [4, 0.0], [5, 0.0], [6, 0.0]
      ].each do |n|
        return true if c[n[0]] != n[1]
      end
      return false
    end

    #
    # 色変換情報を格納するハッシュを初期化する
    #
    # @param [Hash] v 色変換情報格納用ハッシュ
    # @return [Hash] 初期化したハッシュを返す
    #
    def TMatrix.init_color(v)
      v.merge!({ "factorRed" => 256, "factorGreen" => 256,
          "factorBlue" => 256, "factorAlpha" => 256,
          "offsetRed" => 0, "offsetGreen" => 0,
          "offsetBlue" => 0, "offsetAlpha" => 0 })
      return v
    end
  end

  # ----------------------------------------
  # 画像を格納するキャラobjクラス
  #
  class CharaObjImage
    attr_accessor :id, :src, :image

    def initialize(id, fname)
      self.id = id
      self.src = fname
      self.image = Image.load(fname)
      # puts "id[#{id}] Load Image ok #{fname}"
    end
  end

  # ----------------------------------------
  # シェイプを格納するクラス
  #
  class CharaObjShape
    attr_accessor :id, :imgs

    def initialize(id, imgs)
      self.id = id
      self.imgs = imgs
    end
  end

  # ----------------------------------------
  # スプライト(MovieClip)を格納するクラス
  #
  class CharaObjSprite
    attr_accessor :id
    attr_accessor :start_node

    def initialize(id, start_node)
      self.id = id
      self.start_node = start_node
    end
  end

  # ----------------------------------------
  # フォント名を格納するクラス
  #
  class CharaObjFont
    attr_accessor :id, :fontname

    def initialize(id, fontname)
      self.id = id
      self.fontname = fontname
      puts "id[#{id}] font [#{fontname}]" if PlayXmlMovie::DBG
    end
  end

  # ----------------------------------------
  # テキストを格納するクラス
  #
  class CharaObjText
    attr_accessor :id, :font, :text, :size, :fontname, :col

    def initialize(id, text, size, fontname, col = [0, 0, 0, 255])
      self.id = id
      self.text = text
      self.size = size
      self.fontname = fontname
      self.font = Font.new(self.size, fontname)
      self.col = col
      if PlayXmlMovie::DBG
        puts "id[#{id}] DefineEditText \"#{text}\",#{size},\"#{fontname}\""
      end
    end
  end

  # ----------------------------------------
  # 描画用クラス
  #
  class MySprite

    attr_accessor :objs
    attr_accessor :cobj
    attr_accessor :pobj
    attr_accessor :z
    attr_accessor :mat
    attr_accessor :sprs
    attr_accessor :rt
    attr_accessor :shader
    attr_accessor :enable_loop

    # コンストラクタ
    #
    # @param [Hash] objs キャラobj情報が入ったハッシュ
    # @param [Number] cid 発生させたいキャラobjのID
    # @param [Object] pobj 親スプライト。nilなら親は居ない
    # @param [Number] depth 描画深度
    # @param [Hash] v MATRIX情報+半透明度を格納したハッシュ
    #
    def initialize(objs, cid, pobj, depth, v)
      self.objs = objs
      self.cobj = self.objs[cid]
      self.pobj = pobj
      self.z = depth
      self.mat = TMatrix.new
      self.sprs = Hash.new
      self.rt = nil
      self.shader = nil
      update(depth, v)
    end

    # 描画に必要な値を設定
    #
    # @param [depth] depth 描画深度
    # @param [Hash] v MATRIX情報と半透明度情報を格納したハッシュ
    #
    def update(depth, v)
      self.z = depth
      self.mat.set_value(v)
    end

    # RenderTargetを解放
    #
    def dispose
      if self.sprs != nil
        self.sprs.keys {|k| self.sprs[k].dispose }
      end

      if self.rt != nil
        self.rt.dispose unless self.rt.disposed?
      end
    end

    # 拡大縮小、半透明、回転描画
    #
    # @param [Object] img Imageクラス
    # @param [Object] im_mat TMatrixクラス
    #
    def draw_image(img, img_mat)
      pobj = self.pobj

      # 矩形の4点を配列にする
      w = img.width
      h = img.height
      p = [0, 0, w, 0, w, h, 0, h]

      c = Marshal.load(Marshal.dump(self.mat.color))

      # Shapeに割り当てられたMATRIXで変形
      4.times do |i|
        p[i * 2], p[i * 2 + 1] = img_mat.trans(p[i * 2], p[i * 2 + 1])
      end

      # 自身のMATRIXで変形
      4.times do |i|
        p[i * 2], p[i * 2 + 1] = self.mat.trans(p[i * 2], p[i * 2 + 1])
      end

      # 親スプライトのMATRIXで変形
      while pobj != nil
        4.times do |i|
          p[i * 2], p[i * 2 + 1] = pobj.mat.trans(p[i * 2], p[i * 2 + 1])
        end
        c = pobj.mat.trans_color(c)
        pobj = pobj.pobj
      end

      # 論理ピクセル値から画面座標に変換
      8.times {|i| p[i] /= PlayXmlMovie::TWIP }

      if TMatrix.trans_color?(c)
        # 色変換が必要
        # 色変更用のShaderはアルファチャンネル付き画像描画がおかしいので、
        # 色変更が必要な時だけ使うことにする。
        self.rt = RenderTarget.new(Window.width, Window.height) if self.rt == nil
        self.shader = SpriteShader.new if self.shader == nil
        self.rt.drawMorph(p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7],
                          img, :dividex => 3, :dividey => 3)
        self.rt.update
        self.shader.set_color(c)
        Window.drawEx(0, 0, self.rt, :shader => self.shader)
      else
        # 色変換は不要
        a = (255 * c[3] / 256.0).to_i
        if a != 255
          # 半透明描画
          Window.drawMorph(p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7],
                           img, :dividex => 3, :dividey => 3,
                           :alpha => a, :blend => :alpha)
        else
          # 不透明描画
          Window.drawMorph(p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7],
                           img, :dividex => 3, :dividey => 3)
        end
      end
    end

    # フォントを描画
    #
    # @param [String] text 描画文字列
    # @param [Object] font Fontオブジェクト
    # @param [Array] c RGBA配列
    #
    def draw_font(text, font, c)
      x = self.mat.trans_x / PlayXmlMovie::TWIP
      y = self.mat.trans_y / PlayXmlMovie::TWIP
      Window.drawFontEx(x, y, text, font,
                        :color => [c[0], c[1], c[2]],
                        :alpha => c[3], :blend => :alpha, :aa => true)
    end

    # 再生描画処理
    #
    # @param [Hash] objs キャラobj情報が入ったハッシュ
    # @return [Boolean] trueなら再生中, falseなら再生終了
    #
    def play
      return true
    end
  end

  # ----------------------------------------
  # 画像描画obj
  #
  class ImageObj < MySprite

    # コンストラクタ
    #
    # @param [Hash] objs キャラobj情報が入ったハッシュ
    # @param [Number] cid 発生させたいキャラobjのID
    # @param [Object] pobj 親スプライト。nilなら親は居ない
    # @param [Number] depth 描画深度
    # @param [Hash] v MATRIX情報+半透明度を格納したハッシュ
    #
    def initialize(objs, cid, pobj, depth, v)
      super(objs, cid, pobj, depth, v)
    end

    # 再生描画処理
    #
    # @return [Boolean] trueなら再生中, falseなら再生終了
    #
    def play
      draw_image(self.cobj.image, TMatrix.new)
      return true
    end
  end

  # ----------------------------------------
  # シェイプ描画obj
  #
  class ShapeObj < MySprite

    # コンストラクタ
    #
    # @param [Hash] objs キャラobj情報が入ったハッシュ
    # @param [Number] cid 発生させたいキャラobjのID
    # @param [Object] pobj 親スプライト。nilなら親は居ない
    # @param [Number] depth 描画深度
    # @param [Hash] v MATRIX情報+半透明度を格納したハッシュ
    #
    def initialize(objs, cid, pobj, depth, v)
      super(objs, cid, pobj, depth, v)
    end

    # 再生描画処理
    #
    # @return [Boolean] trueなら再生中, falseなら再生終了
    #
    def play
      self.cobj.imgs.each do |v|
        draw_image(v[0].image, v[1])
      end
      return true
    end
  end

  # ----------------------------------------
  # スプライト描画obj
  #
  class SpriteObj < MySprite
    attr_accessor :node
    attr_accessor :now_frame
    attr_accessor :enable_loop

    # コンストラクタ
    #
    # @param [Hash] objs キャラobj情報が入ったハッシュ
    # @param [Number] cid 発生させたいキャラobjのID
    # @param [Object] cobj 発生させたいキャラobj
    # @param [Object] pobj 親スプライト。nilなら親は居ない
    # @param [Number] depth 描画深度
    # @param [Hash] v MATRIX情報+半透明度を格納したハッシュ
    # @param [Boolean] looped trueならループ再生
    #
    def initialize(objs, cid, pobj, depth, v, looped = true)
      super(objs, cid, pobj, depth, v)
      self.node = cobj.start_node
      self.now_frame = 0
      self.enable_loop = looped
    end

    # 再生描画処理
    #
    # @return [Boolean] trueなら再生中, falseなら再生終了
    #
    def play
      pobj = self
      endfg = false

      until endfg

        case self.node.name
        when "PlaceObject2"
          # シェイプやスプライトを配置、もしくは移動
          nn = self.node
          rn = nn["replace"].to_i
          depth = nn["depth"].to_i
          id = nn["objectID"]
          id = id.to_i if id != nil

          # 属性を取り出す
          v = {}
          nn.children.each do |n|
            n.children.each do |m|
              if m.name == "Transform"
                # 変形情報
                m.keys.each do |k|
                  a = m.attribute(k)
                  v[a.name] = a.value.to_f
                end
              elsif m.name == "ColorTransform2"
                # 色変換情報
                v = TMatrix.init_color(v)
                m.keys.each do |k|
                  a = m.attribute(k)
                  v[a.name] = a.value.to_f
                end
              end
            end
          end

          if id != nil
            abort "Error: Not found ChrObj[#{id}]" unless self.objs.has_key?(id)

            case self.objs[id]
            when PlayXmlMovie::CharaObjImage
              self.sprs[depth] = ImageObj.new(self.objs, id, pobj, depth, v)
            when PlayXmlMovie::CharaObjShape
              self.sprs[depth] = ShapeObj.new(self.objs, id, pobj, depth, v)
            when PlayXmlMovie::CharaObjSprite
              self.sprs[depth] = SpriteObj.new(self.objs, id, pobj, depth, v)
            when PlayXmlMovie::CharaObjText
              self.sprs[depth] = TextObj.new(self.objs, id, pobj, depth, v)
            end
          elsif rn == 1
            self.sprs[depth].update(depth, v) if self.sprs.has_key?(depth)
          end

          if TMDBG
            s = "PLACE, replace=#{rn}, depth=#{depth}, id=#{id}, {"
            v.each_key {|k| s += "#{k} => #{v[k]}, "}
            s += "}"
            puts s
          end

        when "RemoveObject2"
          # シェイプやスプライトを削除
          depth = node["depth"].to_i
          if self.sprs.has_key?(depth)
            self.sprs[depth].dispose
            self.sprs.delete(depth)
          end
          puts "REMOVE, depth=#{depth}" if TMDBG

        when "ShowFrame"
          # 現在フレームを描画
          self.now_frame += 1
          puts "ShowFrame #{now_frame}" if TMDBG
          endfg = true

        when "End"
          # タグが無くなった
          endfg = true
          puts "End" if TMDBG
          break

        else
          # puts node_name
        end

        # 次のノードを読む
        self.node = self.node.next
      end

      # ウインドウに描画
      key_list = self.sprs.keys.sort # depthでソート
      key_list.each {|k| self.sprs[k].play }

      return true if self.node.name != "End"

      # 再生終了
      sprs = Hash.new
      if self.enable_loop
        self.node = self.cobj.start_node
        self.now_frame = 0
      end
      return false
    end
  end

  # ----------------------------------------
  # テキスト描画obj
  #
  class TextObj < MySprite

    # コンストラクタ
    #
    # @param [Hash] objs キャラobj情報が入ったハッシュ
    # @param [Number] cid 発生させたいキャラobjのID
    # @param [Object] pobj 親スプライト。nilなら親は居ない
    # @param [Number] depth 描画深度
    # @param [Hash] v MATRIX情報+半透明度を格納したハッシュ
    #
    def initialize(objs, cid, pobj, depth, v)
      super(objs, cid, pobj, depth, v)
    end

    # 再生描画処理
    #
    # @return [Boolean] trueなら再生中, falseなら再生終了
    #
    def play
      o = self.cobj
      draw_font(o.text, o.font, o.col)
      return true
    end
  end

  # ----------------------------------------
  # コンストラクタ
  #
  # @param [String] infile xmlファイルのパス
  #
  def initialize(infile)
    @infile = infile
    abort "Error : Not found #{infile}" unless File.exist?(infile)

    if BMDBG
      Benchmark.bm(15) do |x|
        x.report("xml parse:") {
          @doc = Nokogiri::XML(File.open(infile)) {|config| config.noblanks}
        }

        x.report("get fps") {
          @framerate, @frames = get_framerate(@doc)
          @wdw_w, @wdw_h = get_widnow_size(@doc)
          @bgcol = get_bgcolor(@doc)
        }

        @objs = Hash.new

        x.report("load image:") { load_image(@doc, infile) }
        x.report("load font:") { load_font(@doc) }
        x.report("load shape:") { load_shape(@doc) }

        x.report("load sprite:") {
          load_sprite(@doc)
          n = @doc.at("//swf/Header/tags/PlaceObject2")
          @objs[0] = CharaObjSprite.new(0, n)
        }

      end
    else
      # xmlを解析
      @doc = Nokogiri::XML(File.open(infile)) {|config| config.noblanks}
      @framerate, @frames = get_framerate(@doc)
      @wdw_w, @wdw_h = get_widnow_size(@doc)
      @bgcol = get_bgcolor(@doc)

      # 画像、シェイプ、スプライトを登録
      @objs = Hash.new
      load_image(@doc, infile)
      load_font(@doc)
      load_shape(@doc)
      load_sprite(@doc)

      # rootスプライトを登録
      puts "\nid[0] DefineSprite" if DBG
      n = @doc.at("//swf/Header/tags/PlaceObject2")
      @objs[0] = CharaObjSprite.new(0, n)
    end

  end

  #
  # フレームレート、フレーム数を取得
  #
  # @param [Object] doc xmlノード情報
  # @return [Array] フレームレート, フレーム数を配列で返す
  #
  def get_framerate(doc)
    n = doc.at("//Header")
    fpsv = n["framerate"].to_i
    frms = n["frames"].to_i
    puts "framerate=#{fpsv} , frames=#{frms}" if DBG
    return fpsv, frms
  end

  #
  # 画面サイズを取得
  #
  # @param [Object] doc xmlノード情報
  # @return [Array] 画面横幅, 画面縦幅を配列で返す
  #
  def get_widnow_size(doc)
    n = doc.at("//Header/size/Rectangle")
    # x = n["left"].to_i / TWIP
    # y = n["top"].to_i / TWIP
    w = n["right"].to_i / TWIP
    h = n["bottom"].to_i / TWIP
    puts "Window size : #{w}, #{h}" if DBG
    return w, h
  end

  #
  # 背景色を取得
  #
  # @param [Object] doc xmlノード情報
  # @return [Array] 背景色red, green, blueを配列で返す
  #
  def get_bgcolor(doc)
    n = doc.at("//SetBackgroundColor/color/Color")
    c = []
    ["red", "green", "blue"].each {|k| c.push(n[k].to_i)}
    return c
  end

  #
  # 画像をロード
  #
  # @param [Object] doc xmlノード情報
  # @param [String] infile xmlファイルパス
  #
  def load_image(doc, infile)
    imglist = get_img_file_list(infile) # 画像ファイルパスの一覧を取得

    # 検索するタグ名を列挙
    taglst = "//DefineBitsJPEG2|//DefineBitsLossless2|//DefineBitsLossless"

    doc.xpath(taglst).each do |n|
      id = n["objectID"].to_i
      n.content = ""

      unless imglist.has_key?(id)
        # IDと一致する画像ファイルパスが存在しない
        puts "Not found image file : id = #{id}"
        next
      end

      # IDと一致する画像ファイルパスが存在する
      fn = imglist[id]
      n["src"] = fn
      @objs[id] = CharaObjImage.new(id, fn)
      puts "id[#{id}] Load Image : #{fn}" if DBG
    end
  end

  #
  # xmlファイルパスを元にして、全画像ファイルのパス一覧を取得
  #
  # @param [String] xmlファイルパス
  # @return [Hash] 画像ファイルパスを格納したハッシュ
  #
  def get_img_file_list(infile)
    bsn = File.basename(infile, ".xml") # ファイルの拡張子を取り除いた文字列を取得
    dir = File::dirname(infile) # ファイルのフォルダパスを取得
    imglist = {}

    # サブフォルダ以下も探索して、拡張子が画像ファイル形式なら記録していく
    Find.find(dir) do |f|
      next unless FileTest.file?(f)
      imglist[$1.to_i] = f if /#{bsn}\[(\d+)\]\.(png|jpg|bmp|gif)/ =~ f
    end

    return imglist
  end

  #
  # フォント情報やテキスト情報を読み込み
  #
  # @param [Object] doc xmlノード情報
  #
  def load_font(doc)
    lst = Hash.new
    lst.merge!(
      {
        "_ゴシック" => "MS UI Gothic",
        "_明朝" => "MS P明朝",
        "_等幅" => "MS ゴシック",
        "_sans" => "Arial",
        "_serif" => "Times New Roman",
        "_typewriter" => "Courier"
      })

    # 使用している全フォント名を取得
    doc.xpath("//DefineFont2|//DefineFont3").each do |n|
      id = n["objectID"].to_i
      fnt = n["name"].encode("Windows-31J") # xmlはutf-8なのでsjisに変換
      if lst.has_key?(fnt)
        fnt = lst[fnt]
        @objs[id] = CharaObjFont.new(id, fnt)
      else
        @objs[id] = CharaObjFont.new(id, fnt)
      end
    end

    # 使用している静止テキスト情報を取得
    doc.xpath("//DefineEditText").each do |n|
      id = n["objectID"].to_i
      fid = n["fontRef"].to_i # フォント種類ID
      abort "Error: Not found font [#{fid}]" unless @objs.has_key?(fid)

      size = n["fontHeight"].to_f / PlayXmlMovie::TWIP

      # Flash CS5で作成したswfは、initialText に html が入っている。
      # htmlタグを除去して描画文字列を取得する
      nm = Nokogiri::HTML(n["initialText"])
      text = nm.content.encode("Windows-31J")

      col = [0, 0, 0, 255]
      c = n.at(".//color/Color")
      if c != nil
        col[0] = c["red"].to_i
        col[1] = c["green"].to_i
        col[2] = c["blue"].to_i
        col[3] = c["alpha"].to_i
      end
      fontname = @objs[fid].fontname
      @objs[id] = CharaObjText.new(id, text, size, fontname, col)
    end
  end

  #
  # 画像を内包するShapeを生成
  #
  # @param [Object] doc xmlノード情報
  #
  def load_shape(doc)
    doc.xpath("//DefineShape|//DefineShape2").each do |n|
      id = n["objectID"].to_i
      sn = n.xpath(".//bounds/Rectangle")[0]
      # x = sn["left"].to_i / TWIP
      # y = sn["top"].to_i / TWIP
      w = sn["right"].to_i / TWIP
      h = sn["bottom"].to_i / TWIP

      imgs = []
      bn = n.xpath(".//styles/StyleList/fillStyles")
      bn.xpath(".//ClippedBitmap|.//ClippedBitmap2").each do |m|
        img_id = m["objectID"].to_i
        if img_id == 65535
          # objectID が 65535 の時がある。正体不明
          puts "id[#{id}] Unknown Image [#{img_id}]" if DBG
          next
        end

        unless @objs.has_key?(img_id)
          # 画像が存在しない
          abort "Error : id[#{id}] Not found Image [#{img_id}] #{w} x #{h}"
        end

        # 画像が存在するので、シェイプに割り当てる
        # MATRXI情報も反映させる
        puts "id[#{id}] Shape = bitmap[#{img_id}]" if DBG

        dic = {}
        mn = m.at(".//matrix/Transform")
        mn.keys.each do |k|
          atr = mn.attribute(k)
          dic[atr.name] = atr.value.to_f
        end
        v = TMatrix.new
        v.set_value(dic)
        imgs.push([@objs[img_id], v])
      end
      @objs[id] = CharaObjShape.new(id, imgs)
    end
  end

  #
  # スプライトを登録
  #
  def load_sprite(doc)
    doc.xpath("//DefineSprite").each do |n|
      id = n["objectID"].to_i
      frames = n["frames"].to_i
      puts "\nid[#{id}] DefineSprite, frames=#{frames}" if DBG
      node = n.at(".//PlaceObject2")
      @objs[id] = CharaObjSprite.new(id, node)
    end
  end

  # 再生開始のための初期化処理
  #
  # @note fpsや背景色が変更されるので、再生が終わったら本来のfps・背景色に戻すこと
  #
  # @param [Boolean] init_size ウインドウサイズをxmlに従って変更するかどうか
  # @param [Boolean] init_fps フレームレートをxmlに従って変更するかどうか
  # @param [Boolean] init_bgcol 背景色をxmlに従って変更するかどうか
  #
  def init(init_size = false, init_fps = true, init_bgcol = true)
    @now_frame = 0
    @root_sprite = SpriteObj.new(@objs, 0, nil, 0, {})

    Window.resize(@wdw_w, @wdw_h) if init_size
    Window.fps = @framerate if init_fps
    Window.bgcolor = [@bgcol[0], @bgcol[1], @bgcol[2]] if init_bgcol
  end

  # 再生描画処理
  #
  # @note メインループから毎フレーム呼ぶこと
  #
  # @return [Boolean] trueなら再生中, falseなら再生終了
  #
  def play
    return @root_sprite.play
  end

  #
  # 多少整理したxmlを標準出力にダンプする
  #
  def dump
    puts @doc.to_xml
  end

end

if $0 == __FILE__
  # ----------------------------------------
  # 使い方の例

  m = PlayXmlMovie.new("swfdata/movie0.xml")
  # m = PlayXmlMovie.new("swfdata/movie1.xml")
  # m = PlayXmlMovie.new("swfdata/movie2.xml")
  # m = PlayXmlMovie.new("swfdata/movie3.xml")
  # m = PlayXmlMovie.new("swfdata/movie4.xml")

  # 再生開始のための初期化
  # fpsや背景色が変更されるので、
  # 再生が終わったら本来のfps・背景色に戻すこと
  m.init

  # メインループ
  Window.loop do
    break if Input.keyPush?(K_ESCAPE) # ESCキーで終了

    # 1フレーム分描画
    unless m.play
      m.init # 再生が終了したら、再度頭から再生
    end

  end
end

使い方の例。
require_relative "playxmlmovie"

m = PlayXmlMovie.new("hoge.xml")
m.init

Window.loop do
    break unless m.play
end

その他詳細は、 _readme.html を読んでください。

備考。 :

サンプルswf内の静止画像作成については、 _マウスコンピューター G-Tuneシリーズ公式キャラクター G-Tuneちゃん (Tuneちゃん?) を使わせてもらいました。…と書いておけばいいのかな? リンクも貼ってあるから、これで利用条件は満たしてるはず。たぶん。

ここまで書いといてアレだけど。 :

自分でコレを使うかというと、たぶん使わないような気がする…。たしかにグリグリ動いたけど…。

ただ、コレを書いてみたことで、巷で見かける swf → HTML5 + JavaScript 変換ツールが、大体どんな感じのことをしているのか、そのあたりぼんやり分かってきたように思います。結構勉強になったというか。

Ruby でSWFバイナリを直接扱える、 _SwfRuby なるライブラリも使えたら、swfmill で一旦xmlに変換しなくてもいいのかもしれんですけど。
  • SwfRuby は RMagick が必要。
  • しかし、Windows で RMagick を導入するのは面倒。ImageMagick の特定バージョンを要求される。
という問題があるので…。そう考えると、xml に変換しといてソレを渡す、こういうやり方もアリかなと。xmlをエディタで開けば、どこで何の指定がされてるか調べられるというメリットもありますし。

2017/03/19追記。 :

DropboxのPublicフォルダが死んだのでファイルを置き直し。

以上です。

過去ログ表示

Prev - 2014/05 - 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 31

カテゴリで表示

検索機能は Namazu for hns で提供されています。(詳細指定/ヘルプ


注意: 現在使用の日記自動生成システムは Version 2.19.6 です。
公開されている日記自動生成システムは Version 2.19.5 です。

Powered by hns-2.19.6, HyperNikkiSystem Project