Project1

标题: 有关截图图片转换为PNG的方法 [打印本页]

作者: API_Studier    时间: 2011-6-19 14:42
标题: 有关截图图片转换为PNG的方法
本帖最后由 fux2 于 2011-6-19 18:38 编辑

本人想把GetBitmapBits得到的BMP格式转化为PNG,可是得到的是BGRA通道,而PNG中是RGBA,所以直接得到的图像很难看(红蓝通道相反),但是如果每个BGRA数组翻转成RGBA时间又太长,如下代码需要约20秒才能生成一个PNG文件,请高人相助!
  1. class << Graphics
  2.   CreateCompatibleBitmap = Win32API.new("gdi32", "CreateCompatibleBitmap", "lll", "l")
  3.   CreateCompatibleDC = Win32API.new("gdi32", "CreateCompatibleDC", "l", "l")
  4.   SelectObject = Win32API.new("gdi32", "SelectObject", "ll", "l")
  5.   BitBlt = Win32API.new("gdi32", "BitBlt", "lllllllll", "l")
  6.   GetBitmapBits = Win32API.new("gdi32", "GetBitmapBits", "llp", "l")
  7.   ScreenToClient = Win32API.new("user32", "ScreenToClient", "ip", "i")
  8.   DeleteDC = Win32API.new("gdi32", "DeleteDC", "l", "l")
  9.   DeleteObject = Win32API.new("gdi32", "DeleteObject", "l", "l")
  10.   CrFnt = Win32API.new("gdi32","CreateFont","l"*13+"p","l")
  11.   SRCCOPY = 0xCC0020
  12.   DwCount = (640 * 480 * 4) * 2
  13.   @@lpBits = "\000" * DwCount
  14.   def bitmap_data(times)
  15.     hScrDC = $hDC
  16.     hMemDC = CreateCompatibleDC.call(hScrDC)
  17.     hBitmap = CreateCompatibleBitmap.call(hScrDC, 640, 480)
  18.     SelectObject.call(hMemDC, hBitmap)
  19.     BitBlt.call(hMemDC, 0, 0, 640*2, 480*2, hScrDC, 0, 0, SRCCOPY)
  20.     rect = [0,0,300,100].pack("llll")

  21.     hfont = CrFnt.call(20,10,50,0,1500,1,0,0,0,0,0,0,0,"Lucida Sans")

  22.     Win32API.new("gdi32","SetBkColor","ll","l").call(hMemDC,255)
  23.     Win32API.new("gdi32","SetTextColor","ll","l").call(hMemDC,16777215)
  24.     SelectObject.call(hMemDC,hfont)
  25.     Win32API.new("user32","DrawText","lplpl","l").call(hMemDC,"2012 Doomsday Replay #{times}:",-1,rect,0x37)

  26.     GetBitmapBits.call(hBitmap, DwCount, @@lpBits)
  27.     DeleteDC.call(hScrDC)
  28.     DeleteDC.call(hMemDC)
  29.     DeleteObject.call(hBitmap)
  30.     DeleteObject.call(hfont)
  31.     for i in 1..307200
  32.       if i%100000 == 0 then Graphics.update end
  33.       @@lpBits[4*i-4,3]=@@lpBits[4*i-4,3].reverse # 翻转红蓝通道,这个时间有点长,求高手帮忙!
  34.                 #RGBA           #BGRA
  35.       @@lpBits[4*i-1]=255
  36.     end
  37.     return @@lpBits
  38.   end
  39. end

  40. class Png_File
  41.   #--------------------------------------------------------------------------
  42.   # ● 主处理
  43.   #--------------------------------------------------------------------------
  44.   def initialize(times)
  45.     @times=times
  46.     f = File.open("Graphics/Pictures/Replay#{@times}.png","wb")#Zlib::GzipWriter.open("Graphics/Pictures/Replay#{times}.png")
  47.     f.write(make_header)
  48.     f.write(make_ihdr)
  49.     f.write(make_idat)
  50.     f.write(make_iend)
  51.     f.close
  52.   end
  53.   #--------------------------------------------------------------------------
  54.   # ● PNG文件头数据块
  55.   #--------------------------------------------------------------------------
  56.   def make_header
  57.     return [0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a].pack("C*")
  58.   end
  59.   #--------------------------------------------------------------------------
  60.   # ● PNG文件情报头数据块(IHDR)
  61.   #--------------------------------------------------------------------------
  62.   def make_ihdr
  63.     ih_size = [13].pack("N")
  64.     ih_sign = "IHDR"
  65.     ih_width = [640].pack("N")
  66.     ih_height = [480].pack("N")
  67.     ih_bit_depth = [8].pack("C")
  68.     ih_color_type = [6].pack("C")
  69.     ih_compression_method = [0].pack("C")
  70.     ih_filter_method = [0].pack("C")
  71.     ih_interlace_method = [0].pack("C")
  72.     string = ih_sign + ih_width + ih_height + ih_bit_depth + ih_color_type +
  73.              ih_compression_method + ih_filter_method + ih_interlace_method
  74.     ih_crc = [Zlib.crc32(string)].pack("N")
  75.     return ih_size + string + ih_crc
  76.   end
  77.   #--------------------------------------------------------------------------
  78.   # ● 生成图像数据(IDAT)
  79.   #--------------------------------------------------------------------------
  80.   def make_idat
  81.     header = "\x49\x44\x41\x54"
  82.     data = make_bitmap_data
  83.     data = Zlib::Deflate.deflate(data, 8)
  84.     crc = [Zlib.crc32(header + data)].pack("N")
  85.     size = [data.length].pack("N")
  86.     return size + header + data + crc
  87.   end
  88.   #--------------------------------------------------------------------------
  89.   # ● 从Bitmap对象中生成图像数据 mode 0
  90.   #--------------------------------------------------------------------------
  91.   def make_bitmap_data
  92.     data1=Graphics.bitmap_data(@times)
  93.     data=""
  94.     for i in 0...480
  95.       data+=[0].pack("C")
  96.       data+=data1[640*4*i,640*4]
  97.     end
  98.     return data
  99.   end
  100.   #--------------------------------------------------------------------------
  101.   # ● PNG文件尾数据块(IEND)
  102.   #--------------------------------------------------------------------------
  103.   def make_iend
  104.     ie_size = [0].pack("N")
  105.     ie_sign = "IEND"
  106.     ie_crc = [Zlib.crc32(ie_sign)].pack("N")
  107.     return ie_size + ie_sign + ie_crc
  108.   end
  109. module RM_ASM
  110.   def self.findWindow(hwnd1 = 0,file = "./Game.ini")
  111.     Win32API.new("user32","FindWindowEx","llpp","l").call(0,hwnd1,"RGSS Player",readIni(file))
  112.   end
  113.   def self.readIni(file)
  114.     buf = 0.chr * 256
  115.     gpps = Win32API.new("kernel32","GetPrivateProfileString","pppplp","l")
  116.     gpps.call("Game","Title","",buf,256,file)
  117.     buf.delete!("\0")
  118.     return buf
  119.   end
  120.   $hWnd = Win32API.new("user32","GetActiveWindow","","l").call
  121.   hwnd2 = findWindow
  122.   if hwnd2 == $hWnd
  123.     hwnd2 = findWindow(hwnd2)
  124.     if hwnd2 != 0
  125.       Win32API.new("user32","SetForegroundWindow","l","l").call(hwnd2)
  126.       exit(2)
  127.     end
  128.   else
  129.     Win32API.new("user32","SetForegroundWindow","l","l").call(hwnd2)
  130.     exit(2)
  131.   end
  132.   $hDC  = Win32API.new("user32","GetDC","l","l").call($hWnd)
  133.   module_function
  134.   def printscreen(times)
  135.     $PNG = Png_File.new(times)
  136.     print "Replay 已保存至 Replay#{times}.png"
  137.   end
  138. end
复制代码
dsu_plus_rewardpost_czw
作者: flzt5354    时间: 2011-6-19 22:26
是吗。用C++不过是眨眼之间的事情罢了。你是调用WINAPI。。效率怎么会这么慢呢
这样还不如自己直接用C重新写个处理的程序
作者: fux2    时间: 2011-6-20 11:40
本帖最后由 fux2 于 2011-6-20 11:43 编辑

算法之类的不太熟悉,记得以前传子姬发过轮回大人写的这玩意,一搜果然搜索到了,
http://rpg.blue/forum.php?mod=vi ... ertype=1&page=1
不过想问一下20秒才生成png不会10秒备份吗?

作者: API_Studier    时间: 2011-6-20 18:40
本帖最后由 API_Studier 于 2011-6-20 18:42 编辑

总共要处理307200(640*480)个点,看这一句:if i%100000 == 0 then Graphics.update end
每100000个点会更新一下。不过感谢你的回复(汗……轮回者的这篇没搜到)
作者: 匿名    时间: 2011-6-21 16:31
本帖最后由 匿名 于 2011-6-21 16:33 编辑

傻瓜化处理方案:
下一个石器,拖走SAPNG.DLL 然后里面据说就一个导出函数(不保证未来- -),传RM的BITMAP地址进去。然后就输出一张PNG。
当然,直接找官方的PNG的库和ZIP的库自己做个DLL也差不多 囧
作者: 苏小脉    时间: 2011-6-23 12:30
本帖最后由 苏小脉 于 2011-6-23 12:34 编辑

先玩个魔术——在你调用你的子例程前,加上一行:
  1. $VERBOSE = nil
复制代码
再看看执行效率如何?

尽管很不明显,但主楼代码的 I/O 瓶颈要大于 CPU 瓶颈。

问题的根源在于类变量 @@lpBits 的访问处于单例上下文中。在 Ruby 中,单例上下文中定义的类变量最终属于顶层,而不是按常理推想的属于单例类(这多少有些违背最小惊讶原则),也就是这里的 @@lpBits 并不属于 `class << Graphics; end'。然而,从某一个 Ruby 版本开始,在单例环境中使用类变量就被废弃了(包括 RM 使用的 Ruby 1.8.1)。虽然为了后向兼容性,在单例上下文中仍然可以使用类变量,但强行这么做将会使 Ruby 抛出一个警告,输出到标准错误流($stderr)。

  1. class << self
  2.   @@foo = 1
  3.   p @@foo
  4. end
复制代码
这段代码输出:
2: warning: class variable access from toplevel
3: warning: class variable access from toplevel


可见不论是读还是写,一旦在单例环境中访问了类变量,Ruby 都会输出这个警告。从语法上的 `class << Graphics' 开始就进入了 Graphics.class 的单例上下文,而
  1. @@lpBits = "\000" * DwCount
复制代码
这一行决定了 @@lpBits 的归属,那就是顶层。之后在 Graphics.class 的单例方法 bitmap_data 中每访问一次 @@lpBits,都会输出一次警告。由于在 for 循环内部有对 @@lpBits 的访问,bitmap_data 就包含了线性时间的 I/O 操作,这就是这段代码主要的瓶颈。RM 程序的标准错误流是没有回显的,所以在 RM 中 Ruby 的警告信息默认是不可见的,也就会产生“这段代码遇到了 CPU 瓶颈”的假象。

把 $VERBOSE 设为 nil,其实就是在运行时告诉解释器不抛出任何警告,自然也就避免了循环内部的对用户透明的 I/O 操作。

但是通过 `$VERBOSE = nil' 来解决这个问题毕竟并非正途,最好的办法自然是从根本杜绝警告的产生。只要 @@lpBits 属于某个类而不是顶层,就不会有这个问题了:

  1. module Graphics
  2.   @@lpBits = ...
  3.   class << self
  4.     ...
  5.     # 访问 @@lpBits
  6.     ...
  7.   end
  8. end
复制代码
除此之外,还有一些相对来说比重更小的优化可以做:

1、
@@lpBits[4*i-4,3] 调用的是 String#[] 方法,@@lpBits[4*i-4,3].reverse 调用的是 String#reverse,两者都是非变异性方法。前者如果传递两个整型参数,返回的就是接收者的子字符串对象;而后者返回的也是字符串对象。两者返回的这些临时对象对垃圾回收工作产生了不必要的压力。这里如果仅仅是把 BGRA 转为 RGBA,那(用破坏性方法)互换 B 和 R 的位置就行了。而且,@@lpBits 是很大的一块连续内存,对其进行原地操作可以更有效地利用 Cache 的访问局部性。动态分配、回收、再分配、再回收的过程迭代进行,访问局部性就大大地降低了,这也必然导致更多的 Cache miss。

3、
4*i-4 有一次冗余计算,4*i 有两次, @@lpBits[4*i-4,3] 有一次。RM 嵌入的 Ruby 1.8 是纯粹的抽象语法树求值器,所以不能指望 Ruby 代码在解释前有任何程度的优化,哪怕只是针对冗余而进行的代码 Caching 和分解。1.9 有预编译,相对好一点。
  1.     for i in 0...307200
  2.       a = i * 4
  3.       b = a + 2
  4.       tmp = @@lpBits[a]
  5.       @@lpBits[a] = @@lpBits[b]
  6.       @@lpBits[b] = tmp
  7.       @@lpBits[b + 1] = 255
  8.     end
复制代码
这样会好一些。在 Ruby 1.8 中,如果只传递一个整数给 String#[],那它返回的就是表示了一个字节的 Fixnum,而 Fixnum 是直接值,这就避免了分配、回收临时对象的开销。Ruby 1.9 里 String#[] 永远返回 String 对象,所以需要用 String#setbyte 和 String#getbyte。
作者: API_Studier    时间: 2011-7-2 09:05
本帖最后由 API_Studier 于 2011-7-2 16:38 编辑

谢谢苏小脉的回答!(最近有些忙,未能及时查看回复,见谅)


API_Studier于2011-7-2 16:31补充以下内容:
刚刚试了下,整个速度快了约15秒,十分感谢!


API_Studier于2011-7-2 16:35补充以下内容:
  1. module RM_ASM
  2.   def self.findWindow(hwnd1 = 0,file = "./Game.ini")
  3.     Win32API.new("user32","FindWindowEx","llpp","l").call(0,hwnd1,"RGSS Player",readIni(file))
  4.   end
  5.   def self.readIni(file)
  6.     buf = 0.chr * 256
  7.     gpps = Win32API.new("kernel32","GetPrivateProfileString","pppplp","l")
  8.     gpps.call("Game","Title","",buf,256,file)
  9.     buf.delete!("\0")
  10.     return buf
  11.   end
  12.   Win32API.new("kernel32","CopyFile","ppl","l").call("Movie.exe","Save/Movie-Copy.exe",0) # 备份Movie.exe
  13.   $hWnd = Win32API.new("user32","GetActiveWindow","","l").call
  14.   hwnd2 = findWindow
  15.   if hwnd2 == $hWnd
  16.     hwnd2 = findWindow(hwnd2)
  17.     if hwnd2 != 0
  18.       Win32API.new("user32","SetForegroundWindow","l","l").call(hwnd2)
  19.       exit(2)
  20.     end
  21.   else
  22.     Win32API.new("user32","SetForegroundWindow","l","l").call(hwnd2)
  23.     exit(2)
  24.   end
  25.   $hDC  = Win32API.new("user32","GetDC","l","l").call($hWnd)
  26.   module_function
  27.   def printscreen(times)
  28.     Png_File.new(times)
  29.     print "Replay 已保存至 Replay#{times}.png"
  30.   end
  31. end

  32. unless FileTest.exist?('Data/UR.rxdata')
  33.   file = Zlib::GzipWriter.open('Data/UR.rxdata',9)
  34.   @string = 'Unknowrank_is_false'
  35.   Marshal.dump(@string,file)
  36.   file.close
  37. end

  38. print "本游戏有关影片为《2012世界末日》,与最近播出的《2012》无关系,不清楚的请补好功课!"


  39. module Graphics
  40.   DwCount = (640 * 480 * 4) * 2
  41.   @@lpBits = "\000" * DwCount
  42.   class << self
  43.     CreateCompatibleBitmap = Win32API.new("gdi32", "CreateCompatibleBitmap", "lll", "l")
  44.     CreateCompatibleDC = Win32API.new("gdi32", "CreateCompatibleDC", "l", "l")
  45.     SelectObject = Win32API.new("gdi32", "SelectObject", "ll", "l")
  46.     BitBlt = Win32API.new("gdi32", "BitBlt", "lllllllll", "l")
  47.     GetBitmapBits = Win32API.new("gdi32", "GetBitmapBits", "llp", "l")
  48.     ScreenToClient = Win32API.new("user32", "ScreenToClient", "ip", "i")
  49.     DeleteDC = Win32API.new("gdi32", "DeleteDC", "l", "l")
  50.     DeleteObject = Win32API.new("gdi32", "DeleteObject", "l", "l")
  51.     CrFnt = Win32API.new("gdi32","CreateFont","l"*13+"p","l")
  52.     SRCCOPY = 0xCC0020
  53.     def bitmap_data(times)
  54.       hScrDC = $hDC
  55.       hMemDC = CreateCompatibleDC.call(hScrDC)
  56.       hBitmap = CreateCompatibleBitmap.call(hScrDC, 640, 480)
  57.       SelectObject.call(hMemDC, hBitmap)
  58.       BitBlt.call(hMemDC, 0, 0, 640*2, 480*2, hScrDC, 0, 0, SRCCOPY)
  59.       rect = [0,0,300,100].pack("llll")

  60.       hfont = CrFnt.call(20,10,50,0,1500,1,0,0,0,0,0,0,0,"Lucida Sans")

  61.       Win32API.new("gdi32","SetBkColor","ll","l").call(hMemDC,255)
  62.       Win32API.new("gdi32","SetTextColor","ll","l").call(hMemDC,16777215)
  63.       SelectObject.call(hMemDC,hfont)
  64.       Win32API.new("user32","DrawText","lplpl","l").call(hMemDC,"2012 Doomsday Replay #{times}:",-1,rect,0x37)

  65.       GetBitmapBits.call(hBitmap, DwCount, @@lpBits)
  66.       DeleteDC.call(hScrDC)
  67.       DeleteDC.call(hMemDC)
  68.       DeleteObject.call(hBitmap)
  69.       DeleteObject.call(hfont)
  70.       for i in 1..307200
  71.         a = i * 4
  72.         b = a + 2
  73.         tmp = @@lpBits[a]
  74.         @@lpBits[a] = @@lpBits[b]
  75.         @@lpBits[b] = tmp
  76.         @@lpBits[b + 1] = 255
  77.       end
  78.       return @@lpBits
  79.     end
  80.   end
  81. end

  82. class Png_File
  83.   #--------------------------------------------------------------------------
  84.   # ● 主处理
  85.   #--------------------------------------------------------------------------
  86.   def initialize(times)
  87.     @times=times
  88.     f = File.open("Graphics/Pictures/Replay#{@times}.png","wb")#Zlib::GzipWriter.open("Graphics/Pictures/Replay#{times}.png")
  89.     f.write(make_header)
  90.     f.write(make_ihdr)
  91.     f.write(make_idat)
  92.     f.write(make_iend)
  93.     f.close
  94.   end
  95.   #--------------------------------------------------------------------------
  96.   # ● PNG文件头数据块
  97.   #--------------------------------------------------------------------------
  98.   def make_header
  99.     return [0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a].pack("C*")
  100.   end
  101.   #--------------------------------------------------------------------------
  102.   # ● PNG文件情报头数据块(IHDR)
  103.   #--------------------------------------------------------------------------
  104.   def make_ihdr
  105.     ih_size = [13].pack("N")
  106.     ih_sign = "IHDR"
  107.     ih_width = [640].pack("N")
  108.     ih_height = [480].pack("N")
  109.     ih_bit_depth = [8].pack("C")
  110.     ih_color_type = [6].pack("C")
  111.     ih_compression_method = [0].pack("C")
  112.     ih_filter_method = [0].pack("C")
  113.     ih_interlace_method = [0].pack("C")
  114.     string = ih_sign + ih_width + ih_height + ih_bit_depth + ih_color_type +
  115.              ih_compression_method + ih_filter_method + ih_interlace_method
  116.     ih_crc = [Zlib.crc32(string)].pack("N")
  117.     return ih_size + string + ih_crc
  118.   end
  119.   #--------------------------------------------------------------------------
  120.   # ● 生成图像数据(IDAT)
  121.   #--------------------------------------------------------------------------
  122.   def make_idat
  123.     header = "\x49\x44\x41\x54"
  124.     data = make_bitmap_data
  125.     data = Zlib::Deflate.deflate(data, 8)
  126.     crc = [Zlib.crc32(header + data)].pack("N")
  127.     size = [data.length].pack("N")
  128.     return size + header + data + crc
  129.   end
  130.   #--------------------------------------------------------------------------
  131.   # ● 从Bitmap对象中生成图像数据 mode 0
  132.   #--------------------------------------------------------------------------
  133.   def make_bitmap_data
  134.     data1=Graphics.bitmap_data(@times)
  135.     data=""
  136.     for i in 0...480
  137.       data+=[0].pack("C")
  138.       data+=data1[640*4*i,640*4]
  139.     end
  140.     return data
  141.   end
  142.   #--------------------------------------------------------------------------
  143.   # ● PNG文件尾数据块(IEND)
  144.   #--------------------------------------------------------------------------
  145.   def make_iend
  146.     ie_size = [0].pack("N")
  147.     ie_sign = "IEND"
  148.     ie_crc = [Zlib.crc32(ie_sign)].pack("N")
  149.     return ie_size + ie_sign + ie_crc
  150.   end
  151. end

  152. module RM_ASM
  153.   $hWnd = Win32API.new("user32","GetActiveWindow","","l").call
  154.   $hDC  = Win32API.new("user32","GetDC","l","l").call($hWnd)
  155.   module_function
  156.   def printscreen(times)
  157.     Png_File.new(times)
  158.     print "Replay 已保存至 Replay#{times}.png"
  159.   end
  160. end
复制代码
使用时可以:RM_ASM.printscreen(times),保存为png格式,从此再也无需动态链接库了!




欢迎光临 Project1 (https://rpg.blue/) Powered by Discuz! X3.1