Permanent
Creation

RPGツクールXP/VX用のRGSS素材です。
一部英語版対応です。 (A few materials can be used for English RPG MAKER XP/VX.)
競合率はルファーの独断と偏見なのでたまにハズレます。


#==============================================================================
# ■ PNG 出力ライブラリ
#------------------------------------------------------------------------------
#  ビットマップオブジェクトを PNG (*.png) ファイルに出力します。
# 対応RGSS : 1 2
# (C) ルファー
# WebSite https://tkoolerlufar.pages.dev/
#==============================================================================

=begin
  =対応RGSS
  1 2

  =必要なライブラリ
  ルファー作スクリプト素材共通ライブラリ

  =他の素材との競合の可能性
  ... 考える必要ナシ

  =RPGツクールXPでの導入方法
  Main より上に新セクションを作成して、このプログラムをそこにコピーする

  =使い方。
  Bitmap クラスの「write_to_png_file」メソッドを呼び出すことで、
  そのビットマップを png ファイルに出力します。
  文法:
    write_to_png_file(file[, show_progress])
    * file          : 出力先ファイルのパスまたは出力先 IO
    * show_progress : 進歩状況を表示するかどうか (初期値 true)
  show_progress を true にしても、メモリが無いと進行状況を表示しません。
  なお、RPGツクールXPでは、この引数に false を与えると
  ハングアップすることがあるので気をつけてください。

  =出力する PNG について。
  ビットマップオブジェクトの幅か高さのいずれかが
  2,147,483,647 ピクセルを超えると出力できません。
  ま、そんなビットマップ作らないと思うけど。
  解像度は、はがきサイズ (横10cm X 縦14.1cm) に合わせます。

  =参考サイト。ありがとうございます!
  Sophia Web
    http://homepage2.nifty.com/sophia0/
    http://homepage2.nifty.com/sophia0/png.html
  PNG (Portable Network Graphics) Specification, version 1.0
    http://www.libpng.org/pub/png/spec/1.0/
  RFC 1950, 1951
    http://www.rfc-editor.org/rfc/rfc1950.txt
    http://www.rfc-editor.org/rfc/rfc1951.txt
  futomi's CGI Cafe
    トップページ
      http://www.futomi.com/
    RFC 1950, 1951 の和訳 (参考用)
      http://www.futomi.com/lecture/japanese/rfc1950.html
      http://www.futomi.com/lecture/japanese/rfc1951.html

  =更新履歴
  2010/06/01 解像度が、はがきの背景サイズに合わせていたのを前景サイズに修正
             その他の表記ミスも訂正

  2010/04/13 公開

  =新規定義しているメソッド
  Bitmap#update_crc
  Bitmap#crc
  Bitmap#write_to_pngfile

  =新規定義している定数
  Bitmap::CRC_TABLE
=end
#----------------------------------------------------------------------------
# ◆ Bitmap クラス
#----------------------------------------------------------------------------
class Bitmap
  #--------------------------------------------------------------------------
  # ○ 定数
  #--------------------------------------------------------------------------
  CRC_TABLE = Array.new(0x100) do |n|     # CRC 表
    c = n
    010 .times do |k|
      if c & 1 != 0
        c = 0xedb88320 ^ (c >> 1)
      else
        c = c >> 1
      end
    end
    c
  end.freeze
  #--------------------------------------------------------------------------
  # ○ CRC の更新
  #     crc : CRC
  #     buf : バッファ
  #--------------------------------------------------------------------------
  def update_crc(crc, buf)
    c = crc
    buf.length.times do |n|
      c = CRC_TABLE[(c ^ buf[n]) & 0xff] ^ (c >> 010)
    end
    return c
  end
  #--------------------------------------------------------------------------
  # ○ CRC の取得
  #     buf : バッファ
  #--------------------------------------------------------------------------
  def crc(buf)
    return  update_crc(0xffffffff, buf) ^ 0xffffffff
  end
  #--------------------------------------------------------------------------
  # ○ PNG ファイルへの書き込み
  #     file          : 出力先ファイルのパスまたは出力先 IO
  #     show_progress : 進歩状況を表示するかどうか (初期値 true)
  #     戻り値 : 成功すれば偽。画像が大きすぎれば真。
  #--------------------------------------------------------------------------
  def write_to_pngfile(file, show_progress = true)
    if width > 0x7fffffff or height > 0x7fffffff
      raise(RuntimeError, "ビットマップが大きすぎます。", caller)
    end
    begin
      # 「お待ちください」
      if show_progress
        begin
          colorless = Color.new(0.0, 0.0, 0.0, 0.0)
          sprite = Sprite.new
          sprite.z = 077777
          sprite.bitmap = Bitmap.new(screen_width, screen_height)
          sprite.bitmap.fill_rect(0, 0, screen_width, screen_height,
            colorless)
          sprite.bitmap.draw_text(0, screen_height / 2 - 32, screen_width, 32,
            "WRITING A BITMAP TO A PNG IMAGE. PLEASE WAIT....", 1)
          sprite.bitmap.font.color = Color.new(128.0, 128.0, 128.0)
        rescue RGSSError
          raise if $!.message !~ /^failed to create /
          show_progress = false
        end
      end
      refresh_progress = Proc.new {|str|
        next unless show_progress
        sprite.bitmap.fill_rect(0, screen_height / 2, screen_width, 32,
          colorless)
        sprite.bitmap.draw_text(0, screen_height / 2, screen_width, 32, str)
      }
      # 何秒もビジー状態になると不安になってくるので
      # 別スレッドで進歩状況を表示しながら処理
      processing = true
      thread_which_update_frame = Thread.start do
        unless show_progress
          Thread.stop
          Thread.exit
        end
        frame_count = Graphics.frame_count
        frame_rate = Graphics.frame_rate
        Graphics.frame_count = 0
        Graphics.frame_rate = 10
        while processing
          sprite.visible = Graphics.frame_count & 0b10 != 0
          Graphics.update
          sleep(0.5 / Graphics.frame_rate)
        end
        Graphics.frame_count = frame_count
        Graphics.frame_rate = frame_rate
        sprite.bitmap.dispose
        sprite.dispose
      end
      # メイン処理
      ## 識別子
      refresh_progress.call("signature")
      signature = "\211PNG\r\n\C-z\n"
      ## IHDR チャンク
      refresh_progress.call("image header")
      packed_IHDR = [
        self.width,   # 幅
        self.height,  # 高さ
        8,            # ビット深度 [ピクセル/サンプル]
        6,            # カラータイプ
        0,            # 圧縮方式
        0,            # フィルター方式
        0             # インターレース方式
      ]
      data_IHDR = packed_IHDR.pack("NNccccc")
      chunk_IHDR = String.new
      chunk_IHDR << [data_IHDR.length].pack("N")
      chunk_IHDR << "IHDR"
      chunk_IHDR << data_IHDR
      chunk_IHDR << [crc("IHDR" + data_IHDR)].pack("N")
      ## pHYs チャンク
      refresh_progress.call("physical pixel dimensions")
      resolution =
        [self.width * 10, Integer(self.height * 5.0 * Math.sqrt(2.0))].max
      packed_pHYs = [
        resolution,  # 横解像度 [ピクセル/単位]
        resolution,  # 縦解像度
        1            # 単位 (0:不明 1:メートル)
      ]
      data_pHYs = packed_pHYs.pack("NNC")
      chunk_pHYs = String.new
      chunk_pHYs << [data_pHYs.length].pack("N")
      chunk_pHYs << "pHYs"
      chunk_pHYs << data_pHYs
      chunk_pHYs << [crc("pHYs" + data_pHYs)].pack("N")
      ## IDAT チャンク
      refresh_progress.call("image data")
      packed_IDAT = [
        0x78,        # 圧縮方式とフラグ
        (04 << 5),   # フラグ
        String.new,  # 圧縮されたデータ
        0            # ADLER32 チェックサム
      ]
      # イメージサンプル
      image_sample = String.new
      self.height.times do |y|
        refresh_progress.call(sprintf("image data (creating sample:%d%%)",
          y * 100 / self.height))
        # フィルター形式
        image_sample << 0
        self.width.times do |x|
          # ピクセルの色
          pixel = self.get_pixel(x, y)
          image_sample << pixel.red.to_i
          image_sample << pixel.green.to_i
          image_sample << pixel.blue.to_i
          image_sample << pixel.alpha.to_i
        end
      end
      # zlib デフレート・インフレート圧縮
      refresh_progress.call("image data (compressing)")
      packed_IDAT[1] |= 31 - (packed_IDAT[0]*256 + packed_IDAT[1]) % 31
      deflate_blocks = Array.new
      nth = 0
      while nth < image_sample.length
        len = [image_sample.length - nth, 0xFFFF].min
        deflate_blocks << [len, ~len, image_sample[nth, len]].pack("vva*")
        nth += 0xFFFF
      end
      for block in deflate_blocks
        header = 0
        header |= 1 << 2 if block.equal?(deflate_blocks[-1])
        packed_IDAT[2] << (header << 5)
        packed_IDAT[2] << block
      end
      refresh_progress.call("image data (check sum)")
      s1 = 1
      s2 = 0
      image_sample.each_byte {|ch| s2 += s1 += ch }
      s1 %= 65521
      s2 %= 65521
      packed_IDAT[3] = s2*65536 + s1
      refresh_progress.call("image data")
      data_IDAT = packed_IDAT.pack("CCa*N")  # 圧縮データ
      chunk_IDAT = String.new
      chunk_IDAT << [data_IDAT.length].pack("N")
      chunk_IDAT << "IDAT"
      chunk_IDAT << data_IDAT
      chunk_IDAT << [crc("IDAT" + data_IDAT)].pack("N")
      ## IEND チャンク
      refresh_progress.call("image trailer")
      chunk_IEND = String.new
      chunk_IEND << [0].pack("N")
      chunk_IEND << "IEND"
      chunk_IEND << [crc("IEND")].pack("N")
      ## 書く
      refresh_progress.call("")
      out = file.instance_of?(String) ? File.open(file, "wb") : file
      out.print(
        signature + chunk_IHDR + chunk_pHYs + chunk_IDAT + chunk_IEND)
      out.close if file.instance_of?(String)
      processing = false
      thread_which_update_frame.run.join
    rescue SystemCallError
      processing = false
      $@ = caller
      thread_which_update_frame.run.join
      raise
    end
  end
end

戻る