查看: 10826|回复: 40

[原创发布] 地图截图工具v1.3(猫儿的RMXP工具包第九弹)更新!

[复制链接]

遠航の猫咪

梦石
3
星屑
26947
在线时间
2423 小时
注册时间
2005-10-15
回帖
987

开拓者

发表于 2017-10-8 01:02:55 | 显示全部楼层 |阅读模式

加入我们,或者,欢迎回来。

您需要 登录 才可以下载或查看,没有账号?注册会员

×
本帖最后由 SailCat 于 2017-11-6 01:24 编辑

好像看到有人写过,但是为了给地图截个图自写一个Tilemap,是不是有点太小题大做了……想过渲染Tilemap有多麻烦吗?

于是写了下面这个。Tilemap是什么能吃吗……
500X500的地图亲测可用。

单框脚本,俺就不发测试工程了,免得积分清零了导致各位下载不能。
更新:
v1.3  
再加速PNG处理(加速约6倍,果然优化无上限)
支持M(纯地图)、U(ULDS)、T(攻略)、A(航拍)四种截图模式
全脚本加注释(详细程度可比默认系统,700行脚本有1/3是注释……)

v1.2  
优化缓存,现支持RM理论上限(500X500)的导出;优化进度显示功能

v1.1  
加速PNG处理



执行效率(在M模式下):
90X90以下的地图秒过,250X250大约需要6秒,500X500大约需要25秒(Surface Book i7-6600M CPU测试数据)
我的204张地图的大坑,M模式下47秒全部输出完,其他模式1分多一点点。

[pre lang="ruby"]#==============================================================================
# ■ Map_Snapshot 1.3 (Build 171106)
#------------------------------------------------------------------------------
#  地图截图工具 by SailCat @ Project1
#   该程序能将地图转换为PNG图片格式,包括三层图块和用图块ID绘制的事件,遮挡正确。
#   使用方法:
#   1. 插入本脚本到Game_Temp(不是Main)之前。
#   2. 在插入脚本的下方,输入代码:
#      ms = Map_Snapshot.new
#      ms.set_scale(8) # 将地图缩小8倍,可以设定的值有1、2、4、8,默认为1
#      ms.set_mode("U") # 设置输出模式为 ULDS分层模式
#        支持的模式有"M"普通地图、"U"ULDS分层模式、"T"攻略模式、"A"航拍模式
#        M模式:默认的模式、渲染F5、F6、F7层的全部和F8层的地图元件事件
#        U模式:内容同M模式,但将一张地图渲染成主角上层和主角下层的两张截图
#        T模式:在M模式的基础上,再加上不移动的事件,事件取第1页、初始位置
#        A模式:在M模式的基础上,再加上所有的非空事件,事件取第1页、初始位置
#        注:使用U模式会将地图放大率强制设为1:1
#      ms.snap(6) # 转换Map006到图片
#      ms.snaps(1..19) # 转换Map001到Map019到图片
#      ms.snaps([1, 3, 7, 15]) # 转换Map001, Map003, Map007, Map015到图片
#      ms.snap_all # 转换工程中所有地图到图片
#      exit # 转换工程后退出
#   3. 注释掉700行以下的内容可以屏蔽此工具,正常测试游戏。
#   4. 该工具不修改工程内含文件,故执行完后不需要退出重新打开工程。
#==============================================================================

#==============================================================================
# ■ Scene_Reporter
#------------------------------------------------------------------------------
#  通用工具处理进度显示模块。
#==============================================================================
module Scene_Reporter
  @sprite = nil
  @message = ""
  @value = ""
  #--------------------------------------------------------------------------
  # ● 更新显示
  #     message: 主要信息(滚屏)
  #     value: 次要信息(不滚屏)
  #--------------------------------------------------------------------------  
  def self.update(message = "", value = "")
    # 初期化精灵
    if @sprite == nil
      @sprite = Sprite.new
      @sprite.bitmap = Bitmap.new(640, 480)
      @sprite.bitmap.font.name = "楷体"
      @sprite.bitmap.font.size = 20
      @start = Time.now
      @elapse = -1
    end
    # 信息和所显信息完全相同的情况下,直接返回
    return if (message == "" or @message == message) and @value == value
    # 主信息不相同的情况下,滚屏
    if message != "" and @message != message
      @sprite.bitmap.blt(0, 0, @sprite.bitmap, Rect.new(0, 32, 640, 448))
      @message = message
      @elapse = 0
    end
    # 显示已用时间
    elapse = Integer(Time.now - @start)
    if @elapse < elapse
      @elapse = elapse
      min = @elapse / 60
      sec = @elapse % 60
      @sprite.bitmap.fill_rect(580, 0, 60, 32, Color.new(0, 0, 0))
      @sprite.bitmap.draw_text(580, 0, 60, 32, sprintf("%2d:%02d", min, sec))
    end
    # 显示信息
    @sprite.bitmap.fill_rect(0, 448, 640, 32, Color.new(0, 0, 0))
    @sprite.bitmap.draw_text(0, 448, 640, 32, @message + value)
    @value = value
    # 更新画面
    Graphics.update
  end
  #--------------------------------------------------------------------------
  # ● 结束处理
  #     message: 最后的信息
  #--------------------------------------------------------------------------  
  def self.close(message)
    # 更新最后的信息
    Scene_Reporter.update(message)
    # 等待按键结束
    loop do
      Graphics.update
      Input.update
      # 按下 C 键或 B 键的情况下
      if Input.trigger?(Input::C) or Input.trigger?(Input::B)
        break
      end
    end
    # 释放精灵
    if @sprite != nil
      @sprite.bitmap.dispose
      @sprite.dispose
    end
  end
end
#==============================================================================
# ■ PNG
#------------------------------------------------------------------------------
#    PNG文件输出模块 by SailCat
#    鸣谢:SixRC(取内存地址相关代码)
#    用法:bitmap.output_png(filename)
#     filename: 输出的文件名,可含路径
#==============================================================================
module PNG
  #--------------------------------------------------------------------------
  # ● API 声明
  #--------------------------------------------------------------------------  
  CopyMemory_pi = Win32API.new('kernel32', 'RtlMoveMemory', 'pii', 'i')
  CopyMemory_ii = Win32API.new('kernel32', 'RtlMoveMemory', 'iii', 'i')
  CopyMemory_ip = Win32API.new('kernel32', 'RtlMoveMemory', 'ipi', 'i')
  CallWindowProc_p = Win32API.new('user32.dll','CallWindowProc','pppii','i')
  CallWindowProc_i = Win32API.new('user32.dll','CallWindowProc','ppiii','i')
  #--------------------------------------------------------------------------
  # ● API 调用代码
  #--------------------------------------------------------------------------  
  RGBAConvert = [
    0x55, 0x8B, 0xEC, 0x8B, 0x45, 0x08, 0x33, 0xC9,
    0xEB, 0x0D, 0x8B, 0x10, 0x0F, 0xCA, 0xC1, 0xCA,
    0x08, 0x89, 0x10, 0x41, 0x83, 0xC0, 0x04, 0x3B,
    0x4D, 0x0C, 0x72, 0xEE, 0xC9, 0xC2, 0x10, 0x00].pack('C*')
  GetAddress = [
    0x8B, 0x74, 0x24, 0x08, 0x8B, 0x36, 0x8B, 0x76,
    0x08, 0x8B, 0x76, 0x10, 0x8B, 0x7C, 0x24, 0x04,
    0x89, 0x37, 0xC2, 0x10, 0x00].pack("C*")
  #--------------------------------------------------------------------------
  # ● PNG 常量
  #--------------------------------------------------------------------------  
  PNG_HEADER = "\211\120\116\107\015\012\032\012\000\000\000\015"
  PNG_IHDR_HEAD = "\111\110\104\122"
  PNG_IHDR_TAIL = "\010\006\000\000\000"
  PNG_IEND_CHUNK = "\000\000\000\000\111\105\116\104\256\102\140\202"
  #--------------------------------------------------------------------------
  # ● 生成 PNG 文件情报头数据块(IHDR)
  #--------------------------------------------------------------------------  
  def make_png_ihdr
    data = PNG_IHDR_HEAD + [width, height].pack("N*") + PNG_IHDR_TAIL
    return data + [Zlib.crc32(data)].pack("N")
  end
  #--------------------------------------------------------------------------
  # ● 保存 PNG 文件
  #     filename: 保存的文件名
  #--------------------------------------------------------------------------  
  def output_png(filename)
    # 检查并建立路径
    dir = filename.split("/")
    for i in 0...dir.size - 1
      unless dir == "."
        add_dir = dir[0..i].join("/")
        Dir.mkdir(add_dir) rescue nil
      end
    end
    # 依次写入 PNG 文件内容
    file = File.open(filename,"wb")
    file.write(PNG_HEADER)
    file.write(make_png_ihdr)
    file.write(make_png_idat)
    file.write(PNG_IEND_CHUNK)
    file.close
    Scene_Reporter.update("", "完成")
  end
  #--------------------------------------------------------------------------
  # ● 使用临时文件生成 PNG 图像数据(IDAT)
  #--------------------------------------------------------------------------  
  def make_png_idat
    # 数据头
    header = "IDAT"
    # 输出图像信息为临时文件并获得校验码
    adler = [make_data_file].pack("N")
    # 读取临时文件
    data = "\170\332"
    fsize = File.size("temp.gz")
    File.open("temp.gz", "rb") do |f|
      f.pos = 10
      data.concat(f.read(fsize - 18))
      data.concat(adler)
    end
    # 删除临时文件
    File.delete("temp.gz")
    # 附加校验码
    crc = [Zlib.crc32(header + data)].pack("N")
    size = [data.length].pack("N")
    # 返回数据
    return size + header + data + crc
  end
  #--------------------------------------------------------------------------
  # ● 将内存数据转储为文件,并返回adler32校验值
  #--------------------------------------------------------------------------  
  def make_data_file
    # 建立缓存
    cache = "\000" * 1
    # 保存缓存原有结构信息
    cache_info = [cache.size].pack("L")+ [cache].pack("p")
    cache_len = cache.id * 2 + 8; cache_addr = cache_len + 4
    # 将缓存的指针覆盖使之指向 Bitmap 的图像数据
    byte_size = 4 * width * height
    bitmap_info = [byte_size].pack("L") + [address].pack("L")
    CopyMemory_ip.call(cache_len, bitmap_info, 8)
    # 进度汇报线程
    t = Thread.new do
      loop do; sleep(0.5); Scene_Reporter.update("", "转换格式..."); end
    end
    # BGRA 转 RGBA
    CallWindowProc_p.call(RGBAConvert, cache, width * height, 0, 0)
    # 结束进度汇报
    Thread.kill(t)
    # 准备写入文件
    x = 4 * width; y = [address + byte_size]; null = "\000"
    # adler32 校验码初期化
    adler = Zlib.adler32
    # 将缓存的大小改为单行位图
    CopyMemory_ip.call(cache_len, [x].pack("L"), 4)
    # 打开临时文件
    Zlib::GzipWriter.open("temp.gz", 8) do |gz|
      # 进度汇报线程
      t = Thread.new do
        loop do
          sleep(0.5)
          Scene_Reporter.update("", sprintf("压缩图片...%d%%",
            (byte_size - y[0] + @address) * 100 / byte_size))
        end
      end
      # 逐行上移
      while y[0] > @address
        y[0] -= x
        # 更改缓存指针
        CopyMemory_ip.call(cache_addr, y.pack("L"), 4)
        # 写入文件数据
        gz.write(null)
        adler = Zlib.adler32(null, adler)
        gz.write(cache)
        adler = Zlib.adler32(cache, adler)
      end
      # 结束进度汇报
      Thread.kill(t)
      # 关闭临时文件
      gz.close
    end
    # 恢复指针原始结构信息以便释放
    CopyMemory_ip.call(cache_len, cache_info, 8)
    # 返回校验码
    return adler
  end
  #--------------------------------------------------------------------------
  # ● 位图的数据内存地址
  #--------------------------------------------------------------------------  
  def address
    if @address == nil
      buffer = "xxxx"
      CallWindowProc_i.call(GetAddress, buffer, object_id * 2 + 16, 0, 0)
      @address = buffer.unpack("L")[0]
    end
    return @address
  end
end
#==============================================================================
# ■ Bitmap
#------------------------------------------------------------------------------
#  内部位图类
#==============================================================================
class Bitmap
  #--------------------------------------------------------------------------
  # ● 导入 PNG 模块
  #--------------------------------------------------------------------------  
  include PNG
  #--------------------------------------------------------------------------
  # ● 合成
  #     rect: 合成的矩形
  #     src_bitmap: 合成的位图
  #     src_rect: 合成位图的传送元矩形
  #     opacity: 不透明度
  #     blend_type: 合成方式(1: 加法、2: 减法)
  #--------------------------------------------------------------------------  
  def blend_blt(rect, src_bitmap, src_rect, opacity, blend_type)
    # 纯透明的情况下 不合成
    return if opacity == 0
    # 建立临时位图
    temp_bitmap = Bitmap.new(rect.width, rect.height)
    # 将源位图合成到临时位图,考虑缩放
    if rect.width != src_rect.width or rect.height != src_rect.height
      temp_bitmap.stretch_blt(temp_bitmap.rect, src_bitmap, src_rect, opacity)
    else
      temp_bitmap.blt(0, 0, src_bitmap, src_rect, opacity)
    end
    # 逐点合成
    for i in 0...rect.width
      for j in 0...rect.height
        # 取色
        c1 = self.get_pixel(rect.x + i, rect.y + j)
        c2 = temp_bitmap.get_pixel(i, j)
        # 源位图此点无色的情况下,继续
        next if c2.alpha == 0
        # 加法
        if blend_type == 1
          c1.red += c2.red * c2.alpha / 255 * opacity / 255
          c1.green += c2.green * c2.alpha / 255 * opacity / 255
          c1.blue += c2.blue * c2.alpha / 255 * opacity / 255
        # 减法
        else
          c1.red = [0, c1.red - c2.red * c2.alpha / 255 * opacity / 255].max
          c1.green = [0, c1.green - c2.green * c2.alpha / 255 * opacity / 255].max
          c1.blue = [0, c1.blue - c2.blue * c2.alpha / 255 * opacity / 255].max
        end
        # 设色
        self.set_pixel(rect.x + i, rect.y + j, c1)
      end
    end
    # 释放临时位图
    temp_bitmap.dispose
  end
end
#==============================================================================
# ■ Map_Snapshot
#------------------------------------------------------------------------------
#    地图截图渲染引擎
#==============================================================================
class Map_Snapshot
  #--------------------------------------------------------------------------
  # ● 常量
  #--------------------------------------------------------------------------  
  AUTOTILE_EXPAND = [
    555752218, 555752196, 555746586, 555746564, 186653466, 186653444,
    186647834, 186647812, 554310426, 554310404, 554304794, 554304772,
    185211674, 185211652, 185206042, 185206020, 522066200, 522061080,
    186521880, 186516760, 353636110, 185863950, 352980750, 185208590,
    589438236, 587865372, 589438212, 587865348, 757868326, 757868292,
    757859622, 757859588, 589176088, 757862158, 319950092, 185732364,
    387322128, 386535696, 791554344, 791554308, 724182308, 724174116,
    387059980, 724176140, 791292196, 791548176, 791286028, 117833984
  ] # 自动元件的展开方式
  #--------------------------------------------------------------------------
  # ● 初期化
  #--------------------------------------------------------------------------  
  def initialize
    @tilesets = load_data("Data/Tilesets.rxdata")
    @mapnames = load_data("Data/MapInfos.rxdata")
    @mapnames.each {|k, v| @mapnames[k] = v.name.gsub(/[\\\/:*?|<>]/,"_")}
    @map_data = nil
    @output_bitmap = nil
    @tile_bitmap = nil
    @autotiles = [nil, nil, nil, nil, nil, nil, nil]
    @width = 0
    @height = 0
    @map_id = 0
    @scale = 1
    @mode = "M"
  end
  #--------------------------------------------------------------------------
  # ● 设置缩放
  #     scale: 缩放比(1/1、1/2、1/4或1/8)
  #--------------------------------------------------------------------------  
  def set_scale(scale)
    @scale = scale if [1, 2, 4, 8].include?(scale)
  end
  #--------------------------------------------------------------------------
  # ● 设置渲染模式
  #     mode: 渲染模式(M、U、A、T)
  #--------------------------------------------------------------------------  
  def set_mode(mode)
    @mode = mode if ["M", "U", "A", "T"].include?(mode)
  end
  #--------------------------------------------------------------------------
  # ● 地图截图
  #     map_id: 地图ID
  #--------------------------------------------------------------------------  
  def snap(map_id)
    # 如果是ULDS模式,放大率强制为1
    set_scale(1) if @mode == "U"
    # 加载地图数据
    map_name = sprintf("Data/Map%03d.rxdata", map_id)
    if FileTest.exist?(map_name)
      @map_id = map_id
      @map_data = load_data(map_name)
      @width = @map_data.width
      @height = @map_data.height
      @tileset = @tilesets[@map_data.tileset_id]
      @tile_bitmap = RPG::Cache.tileset(@tileset.tileset_name)
      @tile_rect = Rect.new(0, 0, 32, 32)
      # 用于输出的位图
      @output_bitmap = Bitmap.new(@width * 32 / @scale, @height * 32 / @scale)
      for i in 0..6
        @autotiles = RPG::Cache.autotile(@tileset.autotile_names)
      end
      @auto_stretch = {}
      # 渲染
      snapshot
      # 输出截图
      filename = sprintf("MapSnapshots/%03d-%s.png", map_id, @mapnames[map_id])
      @output_bitmap.output_png(filename)
      # 如果是ULDS模式,渲染第二张图
      if @mode == "U"
        @output_bitmap.clear
        snapshot(true)
        f2 = sprintf("MapSnapshots/%03d-%s[U].png", map_id, @mapnames[map_id])
        @output_bitmap.output_png(f2)
      end
      # 释放位图
      @output_bitmap.dispose
      @auto_stretch.each_value {|v| v.dispose}
      for i in 0..6
        @autotiles.dispose
      end
      @autotiles = [nil, nil, nil, nil, nil, nil, nil]
      @tile_bitmap.dispose
      @tile_bitmap = nil
    end
  end
  #--------------------------------------------------------------------------
  # ● 渲染地图
  #     ulds: ULDS第二层渲染专用
  #--------------------------------------------------------------------------  
  def snapshot(ulds = false)
    # 筛选需要渲染的事件集
    @effect_events = @map_data.events.select do |k, v|
      # 事件的地图元件有效的情况下,总是渲染
      effective = (v.pages[0].graphic.tile_id > 0)
      # 分层模式的情况下
      if @mode == "U"
        # 事件还需要满足优先级条件才渲染
        effective &= (v.pages[0].always_on_top == ulds)
      # 攻略模式的情况下
      elsif @mode == "T"
        # 事件不移动且朝向固定也需渲染
        effective |= (v.pages[0].graphic.character_name != "" and
          v.pages[0].move_type == 0)
      # 航拍模式的情况下
      elsif @mode == "A"
        # 所有非空事件一律渲染
        effective |= (v.pages[0].graphic.character_name != "")
      end
      # 筛选
      effective
    end
    # 纯地图模式的情况下,直接按格快速渲染
    return direct_paint if @mode == "M"
    # 分层渲染底图
    unless ulds
      # 渲染没有优先级的图块
      for k in 0...3
        for j in 0...@height
          for i in 0...@width
            # 取得图块
            tile_id = @map_data.data[i, j, k]
            # 优先级为0的情况下
            if tile_id > 0 and @tileset.priorities[tile_id] == 0
              paint(i, j, tile_id)
            end
          end
        end
        # 每层汇报进度
        Scene_Reporter.update(sprintf("正在生成截图Map%03d.png...", @map_id),
          sprintf("渲染下层...%d%%", (k + 1) * 33))
      end
      # 渲染不在最前显示的图块事件
      @effect_events.each do |f|
        e = f[1]; g = e.pages[0].graphic
        if g.tile_id > 0 and not e.pages[0].always_on_top
          paint(e.x, e.y, g.tile_id, g.character_hue, g.opacity, g.blend_type)
        end
      end
      # 分层模式的情况下,渲染结束
      return if @mode == "U"
    end
    # 分层渲染顶图
    # 计算剩余所有图块的 Z 坐标
    z_indexes = {}
    for k in 0...3
      for j in 0...@height
        for i in 0...@width
          # 取得图块
          tile_id = @map_data.data[i, j, k]
          # 优先级不为0的情况下
          if tile_id > 0 and @tileset.priorities[tile_id] > 0
            z = (@tileset.priorities[tile_id] + j) * 32 + 32
            z_indexes[z] = (z_indexes[z] || []).push([i, j, tile_id])
          end
        end
      end
    end
    # 计算所有需要渲染的事件的 Z 坐标
    @effect_events.each do |f|
      e = f[1]; g = e.pages[0].graphic
      # 事件的 Z 坐标
      z = e.pages[0].always_on_top ? 32768 : e.y * 32 + 32
      # 是普通事件的情况下
      if g.tile_id == 0 or e.pages[0].always_on_top
        z_indexes[z] = (z_indexes[z] || []).push([e.x, e.y, g])
      end
    end
    # 按 Z 坐标开始绘制
    l = n = z_indexes.keys.size
    z_indexes.keys.sort.each do |k|
      v = z_indexes[k]
      while not v.empty?
        tile = v.shift
        x = tile[0]; y = tile[1]; g = tile[2]
        # 如果此处是图块
        if g.is_a?(Numeric)
          paint(x, y, g)
        # 如果此处是事件
        elsif g.tile_id > 0
          paint(x, y, g.tile_id, g.character_hue, g.opacity, g.blend_type)
        else
          paint_character(x, y, g)
        end
      end
      n -= 1
      # 已绘制20层的情况下,汇报进度
      if n % 20 == 0
        Scene_Reporter.update("", sprintf("渲染上层...%d%%",(l - n) * 100 / l))
      end
    end
  end
  #--------------------------------------------------------------------------
  # ● 快速逐格渲染(M模式用)
  #--------------------------------------------------------------------------  
  def direct_paint
    # 变换有效事件列表
    events = @effect_events.inject({}) do |s, f|
      e = f[1]
      s[[e.x, e.y]] = [e.pages[0].graphic, e.pages[0].always_on_top]
      s
    end
    # 逐格绘制
    for j in 0...@height
      for i in 0...@width
        # 获得此处的三层图块
        tiles = [@map_data.data[i, j, 0],
          @map_data.data[i, j, 1], @map_data.data[i, j, 2]]
        # 获得此处三层图块的 Z 坐标
        z_indexes = [@tileset.priorities[tiles[0]] * 32,
          @tileset.priorities[tiles[1]] * 32 + 1,
          @tileset.priorities[tiles[2]] * 32 + 2]
        # 如果此处有事件,压入图块数据
        if events.has_key?([i, j])
          tiles.push(events[[i, j]][0].tile_id)
          z_indexes.push(events[[i, j]][1] ? 999 : 3)
        end
        # 从最小值 Z 坐标开始绘制
        while tiles.size > 0
          z_index = z_indexes.min
          index = z_indexes.index(z_index)
          tile_id = tiles[index]
          # 图块是事件的情况下,按事件图块的合成方式绘成
          if z_index == 3 or z_index == 999
            opacity = events[[i, j]][0].opacity
            hue = events[[i, j]][0].character_hue
            blend_type = events[[i, j]][0].blend_type
          else
            # 基本绘成
            hue = blend_type = 0
            opacity = 255
          end
          paint(i, j, tile_id, hue, opacity, blend_type) if tile_id > 0
          tiles.delete_at(index)
          z_indexes.delete_at(index)
        end
      end
      # 每绘制 20 行汇报一次进度
      if j % 20 == 0 or j == @height - 1
        Scene_Reporter.update(sprintf("正在生成截图Map%03d.png...", @map_id),
        sprintf("渲染地图...%d%%", (j + 1) * 100 / @height))
      end
    end
  end
  #--------------------------------------------------------------------------
  # ● 绘制格子
  #     x, y: 地图的坐标
  #     tile_id: 元件 ID
  #     hue_change: 色相
  #     opacity: 不透明度
  #     blend_type: 合成方式 (0: 普通、1: 加法、2: 减法)
  #--------------------------------------------------------------------------  
  def paint(x, y, tile_id, hue_change = 0, opacity = 255, blend_type = 0)
    # 透明的图块不需要绘制
    return if opacity == 0
    # 绘制的原点
    ox = x * 32 / @scale
    oy = y * 32 / @scale
    # 是普通元件的情况下
    if tile_id >= 384
      # 取得源位图
      if hue_change != 0
        (src = @tile_bitmap.clone).hue_change(hue)
      else
        src = @tile_bitmap
      end
      # 传送矩形
      tile_id -= 384
      src_rect = Rect.new((tile_id % 8) * 32, (tile_id / 8) * 32, 32, 32)
      # 绘制合成
      if blend_type == 0
        if @scale == 1
          @output_bitmap.blt(ox, oy, src, src_rect, opacity)
        else
          @output_bitmap.stretch_blt(Rect.new(ox, oy,
            32 / @scale, 32 / @scale), src, rect, opacity)
        end
      else
        @output_bitmap.blend_blt(Rect.new(ox, oy,
          32 / @scale, 32 / @scale), src, rect, opacity, blend_type)
      end
      # 色相有差异的情况下要释放
      src.dispose if hue_change != 0
    # 不是普通元件的情况下(即自动元件)
    else
      # 获得自动元件的展开样式
      autotile = @autotiles[tile_id / 48 - 1]
      tile_form = AUTOTILE_EXPAND[tile_id % 48]
      tile_lu = tile_form & 0xff
      tile_ru = (tile_form & 0xff00) >> 8
      tile_ld = (tile_form & 0xff0000) >> 16
      tile_rd = (tile_form & 0xff000000) >> 24
      # 缩放比为1的情况下,直接拷贝原件
      if @scale == 1
        @output_bitmap.blt(ox, oy, autotile, auto_rect(tile_lu), 255)
        @output_bitmap.blt(ox + 16, oy, autotile, auto_rect(tile_ru), 255)
        @output_bitmap.blt(ox, oy + 16, autotile, auto_rect(tile_ld), 255)
        @output_bitmap.blt(ox + 16, oy + 16, autotile, auto_rect(tile_rd), 255)
      # 缩放比不为1的情况下
      else
        s = 32 / @scale
        temp_bitmap = Bitmap.new(32, 32)
        temp_bitmap.blt(0, 0, autotile, auto_rect(tile_lu), 255)
        temp_bitmap.blt(16, 0, autotile, auto_rect(tile_ru), 255)
        temp_bitmap.blt(0, 16, autotile, auto_rect(tile_ld), 255)
        temp_bitmap.blt(16, 16, autotile, auto_rect(tile_rd), 255)
        @output_bitmap.stretch_blt(Rect.new(ox, oy, s, s),
          temp_bitmap, @tile_rect, 255)
        temp_bitmap.dispose
      end
    end
  end
  #--------------------------------------------------------------------------
  # ● 绘制事件
  #     x, y: 地图的坐标
  #     g: 事件的图像(RPG::Event::Graphic)
  #--------------------------------------------------------------------------  
  def paint_character(x, y, g)
    # 透明的角色不需要绘制
    return if g.opacity == 0
    # 绘制的原点
    bitmap = RPG::Cache.character(g.character_name, g.character_hue)
    rect = char_rect(bitmap.width, bitmap.height, g.direction, g.pattern)
    ox = (x * 32 - rect.width / 2 + 16) / @scale
    oy = (y * 32 - rect.height + 32) / @scale
    w = rect.width / @scale
    h = rect.height / @scale
    # 普通叠加的情况下,传送位图
    if g.blend_type == 0
      if @scale == 1
        @output_bitmap.blt(ox, oy, bitmap, rect, g.opacity)
      else
        @output_bitmap.stretch_blt(Rect.new(ox, oy, w, h), bitmap, rect,
          g.opacity)
      end
    else
      @output_bitmap.blend_blt(Rect.new(ox, oy, w, h), bitmap, rect,
        g.opacity, g.blend_type)
    end
  end
  #--------------------------------------------------------------------------
  # ● 事件的传送矩形
  #     width, height: 事件的位图宽高
  #     direction: 事件的朝向
  #     pattern: 事件的模式
  #--------------------------------------------------------------------------  
  def char_rect(width, height, direction, pattern)
    Rect.new(pattern * width / 4, (direction - 2) * height / 8,
      width / 4, height / 4)
  end
  #--------------------------------------------------------------------------
  # ● 自动元件的传送矩形
  #     region: 自动元件的展开方式(0-47)
  #--------------------------------------------------------------------------  
  def auto_rect(region)
    Rect.new((region % 6) * 16, (region / 6) * 16, 16, 16)
  end
  #--------------------------------------------------------------------------
  # ● 连续截图
  #     range: 截图的地图ID列表
  #--------------------------------------------------------------------------  
  def snaps(range)
    Scene_Reporter.update("准备就绪")
    if range.is_a?(Range) or range.is_a?(Array)
      range.each do |i|
        snap(i)
      end
    end
    Scene_Reporter.close("完成,按B或C键继续")
  end
  #--------------------------------------------------------------------------
  # ● 全部截图
  #--------------------------------------------------------------------------  
  def snap_all
    snaps(1..999)
  end
end
ms = Map_Snapshot.new
ms.set_scale(1)
ms.set_mode("T")
ms.snap_all
exit
[/pre]

运行时截图:
p1.png

截图样例:
019-Serpent Shrine - Floor 2_海蛇祠 2层.png
152-Camien Wasteland Fortress_卡米安荒原要塞.png
099-Mt. Leest - Hidden Cave B2F_利斯特山 秘密洞穴地下2层.png
128-Locrata Castle_洛克塔王城.png

评分

参与人数 4星屑 +200 赞 +4 收起 理由
KFLYlzzq + 1 精品文章
RyanBern + 100 + 1 精品文章pplus
hys111111 + 100 + 1 精品文章
89444640 + 1 我很赞同

查看全部评分

SailCat (小猫子·要开心一点) 共上站 24 次,发表过 11 篇文章 上 次 在: [2006年01月28日11:41:18 星期六] 从 [162.105.120.91] 到本站一游。
梦石
0
星屑
35248
在线时间
4171 小时
注册时间
2007-12-15
回帖
9489
发表于 2017-10-8 06:24:20 | 显示全部楼层
本帖最后由 89444640 于 2017-10-8 06:27 编辑

非常感谢,测试了一下,用目前做过的最大的地图,250*256。输出正常,不过这个大小- -b所有地图都这么渲染……累死人。
01.jpg

一些需要氛围的小型图可能会以弄一下。这种大的就拉倒吧

点评

其实输出的 PNG 可以用外部程序再压缩一下……  发表于 2017-10-8 13:35
另外,大地图你可以调整那个set_scale,比如设定为4甚至8,如果是为了做攻略什么的话。感谢测试。  发表于 2017-10-8 10:04
渲染bitmap用不了多少时间,主要是在压缩PNG上面。我太久不来6R,回头找找有没有更高效的PNG算法(现在用的还是07年轮回者的)  发表于 2017-10-8 10:02
回复

使用道具 举报

梦石
0
星屑
9557
在线时间
5074 小时
注册时间
2013-6-21
回帖
3459

开拓者贵宾剧作品鉴家

发表于 2017-10-8 10:45:05 | 显示全部楼层
记得当年我写过的一个,怕是猫大说的“有人专门写了 Tilemap”应该不是我。因为我的脑回路应该也是根据数据绘制 bitmap 这种的,同样担心地图大了会不会爆炸。
具体的难点应该有两个:
1. 自动元件。因为当时不清楚自动元件的排号规律,所以一个个试导致搞了好久才知道是怎么回事(虽然现在已经100%忘记了)。当时找这个规律非常痛苦。
2. 输出 PNG。这个当然是仁者见仁智者见智了。当时我受到路路大大的提示,使用了外接 DLL。感觉上能好一些吧。

帖子在这,似乎可以参考一下:https://rpg.blue/forum.php?mod=viewthread&tid=374269

评分

参与人数 1赞 +1 收起 理由
89444640 + 1 这个我也存下~谢谢

查看全部评分

回复

使用道具 举报

梦石
0
星屑
35248
在线时间
4171 小时
注册时间
2007-12-15
回帖
9489
发表于 2017-10-8 14:51:18 | 显示全部楼层
本帖最后由 89444640 于 2017-10-8 15:04 编辑

请教几个与这个脚本密切相关问题,有时雾图形会在读存档时候消失,如何确保雾图形不消失?并行处理不可取过分消耗系统。
输出时候无法输出事件位置,个别事件为宝箱,其实加个高光提示一下更好,能否继续改进可以输出事件位置,便于在事件位置加高光提示。


做了一下实验,以前没画过这种效果,只用更改不透明度+叠加方式,用橡皮擦出来的。 43454.png
图像中的人物位置是固定的,如果无法输出,只能凭印象来处理,小型图还好,大型地图较为困难。
可否考虑事件位置描绘处一个高饱和度的方块?





点评

辛苦辛苦~静候佳音  发表于 2017-10-8 15:11
事件和雾已经在更新中了,主要是叠加原则(因为我不手写Tilemap),不过也就是体力活……  发表于 2017-10-8 15:09
回复

使用道具 举报

遠航の猫咪

梦石
3
星屑
26947
在线时间
2423 小时
注册时间
2005-10-15
回帖
987

开拓者

 楼主| 发表于 2017-10-8 15:11:07 | 显示全部楼层
RyanBern 发表于 2017-10-8 10:45
记得当年我写过的一个,怕是猫大说的“有人专门写了 Tilemap”应该不是我。因为我的脑回路应该也是根据数据 ...

自动元件早就被我拆透了…… 000-Test.jpg

把上述图片灌入tileset,然后双击自动元件展开小窗口,你会发现自动元件就没有一点秘密了……

然后顺便发现了xp的自动元件从原理上优于va系……

点评

这么一说我想起来了,XP自动原件确实7个,造成只能用切换场景换不同自动原件。好多都只好放到地图中了,绘制起来特别麻烦--b  发表于 2017-10-9 08:31
@89444640 不是太小了是太少了,每地图限定7个,VA的自动元件还没XP大,架不住人家数量多……  发表于 2017-10-8 17:43
但是XP的自动原件太小了,本质上每个单元才16*16,很多草皮、转角之类的都得画到地图上,只能静态,无法做成有4帧循环播放的动态。好歹32才好画  发表于 2017-10-8 17:13
学到了,自动元件展开。之前一直没注意过这个功能  发表于 2017-10-8 16:28

评分

参与人数 1赞 +1 收起 理由
RyanBern + 1 精品文章

查看全部评分

SailCat (小猫子·要开心一点) 共上站 24 次,发表过 11 篇文章 上 次 在: [2006年01月28日11:41:18 星期六] 从 [162.105.120.91] 到本站一游。
回复

使用道具 举报

遠航の猫咪

梦石
3
星屑
26947
在线时间
2423 小时
注册时间
2005-10-15
回帖
987

开拓者

 楼主| 发表于 2017-10-9 13:12:45 | 显示全部楼层
更新顶贴
回复

使用道具 举报

梦石
0
星屑
13562
在线时间
2753 小时
注册时间
2014-10-4
回帖
705

R考场第七期纪念奖

发表于 2017-10-9 21:12:43 | 显示全部楼层
bitmap=>png 简版
从来没有最啊...

点评

实验失败 确实32位只有4G寻址空间 能申请出来的更是少的可怜 一次性取确实行不通 暂无想法  发表于 2017-10-10 01:24
bitmap转png最花时间的部分在于BGRA->RGBA,其他拷贝内存都快的很。  发表于 2017-10-9 21:26
你确定这个能做16000X16000的bitmap?500X500的地图其截图就有这么大 我和Zip::Deflate.deflate折腾了半天结论是out of memory,只能用临时文件+自算alerc32才行  发表于 2017-10-9 21:24
回复

使用道具 举报

梦石
0
星屑
13562
在线时间
2753 小时
注册时间
2014-10-4
回帖
705

R考场第七期纪念奖

发表于 2017-10-10 05:57:52 | 显示全部楼层
本帖最后由 SixRC 于 2017-10-16 20:20 编辑

关于内存的问题,想到解决方案了,比较满意
本地测试15000*15000只需10秒 笔记本 你可以测试
理论上只要bitmap能生成 就能生成png
似乎超过1G rm就会炸掉 16000*16000绝对是上限了
除非不生成一个大的bitmap
流程是开两次exe
第一次生成bitmap对象 并且保存程序句柄 位图数据起始段址 位图宽高 到文件
第二次是判断那个文件是否存在 存在则
读取第一次程序的内存 然后分次取出数据到 gz

下面是代码
[fold=code][pre lang="ruby"]#-------------------------------------------------------------------#
#  请运行两次exe
#  第一次是为了生成bitmap对象
#  第二次会读取第一次程序的内存防止内存崩溃
#  11行pl是保存的文件名 例子在下面
#  27行interval是每次保存的高度数 请根据图片大小自行调整 得小于图片高度
#                                    -SixRC   2017.10.10            #
#-------------------------------------------------------------------#
if File.exist?("test.txt")
  #ttt=Time.now
  pl="t.png"
  f=open("test.txt",'r').read.split("/")
  gwtp=Win32API.new('user32','GetWindowThreadProcessId','lp','l')
  op=Win32API.new('kernel32','OpenProcess','lll','l')
  rpm=Win32API.new('kernel32','ReadProcessMemory','llpll','l')
  hwnd=f[0].to_i
  addr=f[1].to_i
  width=f[2].to_i
  height=f[3].to_i
  s="\0"*4
  gwtp.call(hwnd,s)
  hw=op.call(2035711,0,s.unpack('l')[0])
  CWP = Win32API.new('user32.dll','CallWindowProc','pppii','i')
  GetData=[85,137,229,139,125,8,49,210,139,101,20,137,227,68,139,69,16,137,198,193,230,3,193,224,2,15,175,224,139,69,12,1,196,139,77,16,66,41,244,88,15,200,193,200,8,137,4,23,141,82,4,73,117,241,75,117,232,137,236,93,194,16,0].pack("C*")
  gz=Zlib::GzipWriter.open("temp.gz",9)
  xy=[]
  interval=2500
  adler = Zlib.adler32
  for i in 0...height/interval
    xy.unshift([interval*i,interval*(i+1)])
  end
  i+=1
  xy.unshift([interval*i,height]) unless interval*i==height
  for i in xy
    gap=i[1]-i[0]
    temp1="\0"*gap*(width*4)
    temp2="\0"*gap*(width*4+1)
    rpm.call(hw,addr+i[0]*width*4,temp1,width*4*gap,0)
    CWP.call(GetData,temp2,temp1,width,gap)
    adler=Zlib.adler32(temp2,adler)
    gz.write(temp2)
    Graphics.update
  end
  gz.close
  fsize = File.size("temp.gz")
  f = File.open("temp.gz", "rb")
  f.pos = 10
  data2 = "\170\332"
  data2+=f.read(fsize - 18)
  data2+=[adler].pack("N")
  temp1=temp2=nil
  D1="\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x0\x0\x0\xdIHDR"
  D2="\x8\x6\x0\x0\x0"
  D3="\x00\x00\x00\x00\x49\x45\x4E\x44\xAE\x42\x60\x82"
  @kg=[width].pack("N")+[height].pack("N")
  c1="IHDR"+@kg+D2
  @crc1=[Zlib.crc32(c1)].pack("N")
  crc2=[Zlib.crc32("IDAT"+data2)].pack("N")
  sod=[data2.length].pack("N")
  File.open(pl,"wb"){|i|i.write(D1+@kg+D2+@crc1+sod+"IDAT"+data2+crc2+D3)}
# p Time.now-ttt
  exit
end

class Bitmap
  CWP = Win32API.new('user32.dll','CallWindowProc','ppiii','i')
  GetAddr=[139,116,36,8,139,54,139,118,8,139,118,16,139,124,36,4,137,55,194,16,0].pack("C*")
  def addr
    s="\0"*4
    CWP.call(GetAddr,s,object_id*2+16,0,0)
    s.unpack("L")[0]
  end
end
#测试用图
#b=Bitmap.new(15000,15000)
#c=Bitmap.new("1.png")
#r=Rect.new(0,0,200,200)
#for i in 0..74
#  for j in 0..74
#    b.blt(i*200,j*200,c,r)
#  end
#end
b=Bitmap.new("tt.png")
Hwnd=Win32API.new("user32", "GetActiveWindow", 'v', 'L').call
f=open("test.txt",'w')
f.write(Hwnd.to_s+"/"+b.addr.to_s+"/"+b.width.to_s+"/"+b.height.to_s)
f.close
loop{Graphics.update}[/pre][/fold]
随便找张图
第一次运行等txt出现
第二次运行就会保存png
txt请自己删除..测试原因没有自动删它
睡觉去了






点评

嗯 我修改好了 开始我直接打开看没问题 没有用rm读取 所以不知道有问题 是我的错 有其余问题请提  发表于 2017-10-11 12:54
我明天会更改测试的  发表于 2017-10-11 00:21
要adler32校验码的话 直接算一下就好 每个xy数组里的循环都会取出一段interval高度的数据并处理好放到temp2里  发表于 2017-10-11 00:14
不清楚gz结构 不过本地测试确实是成功的 关于cwp 你可以查阅用它调用汇编 以上  发表于 2017-10-10 23:48
CallWindowProc第一个参数是指向函数句柄,然后试你这生成的PNG挂掉了,因为你压进去的IDAT数据最后没有加adler32校验码……从gzip文件里取出来的值是裸的  发表于 2017-10-10 22:39

评分

参与人数 1星屑 +20 收起 理由
RyanBern + 20 赞认真的讨论交流

查看全部评分

回复

使用道具 举报

梦石
0
星屑
35248
在线时间
4171 小时
注册时间
2007-12-15
回帖
9489
发表于 2017-10-30 09:12:03 | 显示全部楼层
楼主你好~请问存档后,地图上的雾图形可能消失的问题,有解决方法了吗?

点评

刚才试了一下,果然是更改地图设置闹的,听你提到刷新我才想起来有这回事,谢谢啦。  发表于 2017-10-30 11:16
那个,会不会是我动了地图设置造成的,在存档后发现地图某处需要修改,再读取时候雾图形就消失了。我先试一下吧。  发表于 2017-10-30 10:56
好的 我整出一个,你试一下。  发表于 2017-10-30 10:48
能给个存档和游戏的测试链接吗?查了下game_map的默认刷新机制,不太可能出现这种问题啊。  发表于 2017-10-30 09:59
回复

使用道具 举报

遠航の猫咪

梦石
3
星屑
26947
在线时间
2423 小时
注册时间
2005-10-15
回帖
987

开拓者

 楼主| 发表于 2017-11-6 01:26:39 | 显示全部楼层
update 1.3,完全测试无误的事件遮挡规则,四个模式的导出。

评分

参与人数 1赞 +1 收起 理由
89444640 + 1 塞糖

查看全部评分

SailCat (小猫子·要开心一点) 共上站 24 次,发表过 11 篇文章 上 次 在: [2006年01月28日11:41:18 星期六] 从 [162.105.120.91] 到本站一游。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册会员

本版积分规则

Powered by Discuz! X5.0 © 2001-2026 Discuz! Team.

在本版发帖返回顶部