|
@@ -0,0 +1,173 @@
|
|
|
+#!/usr/bin/env python
|
|
|
+
|
|
|
+############################################################################
|
|
|
+#
|
|
|
+# MODULE: wxpyimgview
|
|
|
+# AUTHOR(S): Glynn Clements <glynn@gclements.plus.com>
|
|
|
+# COPYRIGHT: (C) 2010 Glynn Clements
|
|
|
+#
|
|
|
+# This program is free software; you can redistribute it and/or modify
|
|
|
+# it under the terms of the GNU General Public License as published by
|
|
|
+# the Free Software Foundation; either version 2 of the License, or
|
|
|
+# (at your option) any later version.
|
|
|
+#
|
|
|
+# This program is distributed in the hope that it will be useful,
|
|
|
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
+# GNU General Public License for more details.
|
|
|
+#
|
|
|
+#############################################################################/
|
|
|
+
|
|
|
+#%Module
|
|
|
+#% description: View BMP images from the PNG driver.
|
|
|
+#% keywords: display
|
|
|
+#% keywords: raster
|
|
|
+#%End
|
|
|
+#%Option
|
|
|
+#% key: image
|
|
|
+#% type: string
|
|
|
+#% required: yes
|
|
|
+#% multiple: no
|
|
|
+#% description: Image file
|
|
|
+#% gisprompt: old_file,file,input
|
|
|
+#%End
|
|
|
+#%Option
|
|
|
+#% key: percent
|
|
|
+#% type: integer
|
|
|
+#% required: no
|
|
|
+#% multiple: no
|
|
|
+#% description: Percentage of CPU time to use
|
|
|
+#% answer: 10
|
|
|
+#%End
|
|
|
+
|
|
|
+import sys
|
|
|
+import struct
|
|
|
+import numpy
|
|
|
+import time
|
|
|
+import signal
|
|
|
+
|
|
|
+import wxversion
|
|
|
+wxversion.select('2.8')
|
|
|
+import wx
|
|
|
+
|
|
|
+class Frame(wx.Frame):
|
|
|
+ title = "Image Viewer"
|
|
|
+
|
|
|
+ def __init__(self, app, size):
|
|
|
+ self.app = app
|
|
|
+ wx.Frame.__init__(self, None, title = Frame.title, size = size)
|
|
|
+ self.Create()
|
|
|
+
|
|
|
+ def Create(self):
|
|
|
+ self.Bind(wx.EVT_ERASE_BACKGROUND, self.erase)
|
|
|
+ self.Bind(wx.EVT_PAINT, self.redraw)
|
|
|
+
|
|
|
+ self.Bind(wx.EVT_TIMER, self.tick, id = 1)
|
|
|
+ self.timer = wx.Timer(self, 1)
|
|
|
+ self.timer.Start(100, True)
|
|
|
+
|
|
|
+ # Python doesn't receive signals while wx is waiting for an event
|
|
|
+ self.Bind(wx.EVT_TIMER, self.dummy, id = 2)
|
|
|
+ self.ticker = wx.Timer(self, 2)
|
|
|
+ self.ticker.Start(100, False)
|
|
|
+
|
|
|
+ def erase(self, ev):
|
|
|
+ ev.GetDC();
|
|
|
+
|
|
|
+ def draw(self):
|
|
|
+ app = self.app
|
|
|
+ size = self.GetSize()
|
|
|
+ x0 = (size.GetWidth() - app.i_width) / 2
|
|
|
+ y0 = (size.GetHeight() - app.i_height) / 2
|
|
|
+ dc = wx.PaintDC(self)
|
|
|
+ data = app.imgbuf.reshape((app.i_height, app.i_width, 4))
|
|
|
+ data = data[::,::,2::-1]
|
|
|
+ image = wx.ImageFromData(app.i_width, app.i_height, data.tostring())
|
|
|
+ dc.DrawBitmap(wx.BitmapFromImage(image), x0, y0, False)
|
|
|
+
|
|
|
+ def redraw(self, ev):
|
|
|
+ if self.app.fraction > 0.001:
|
|
|
+ t0 = time.time()
|
|
|
+ self.draw()
|
|
|
+ t1 = time.time()
|
|
|
+
|
|
|
+ last = t1 - t0
|
|
|
+ delay = last / self.app.fraction
|
|
|
+ self.timer.Start(int(delay * 1000), True)
|
|
|
+ else:
|
|
|
+ self.draw()
|
|
|
+
|
|
|
+ def tick(self, ev):
|
|
|
+ self.Refresh()
|
|
|
+
|
|
|
+ def dummy(self, ev):
|
|
|
+ pass
|
|
|
+
|
|
|
+class Application(wx.App):
|
|
|
+ def __init__(self):
|
|
|
+ self.image = sys.argv[1]
|
|
|
+ self.fraction = int(sys.argv[2]) / 100.0
|
|
|
+ self.HEADER_SIZE = 54
|
|
|
+ wx.App.__init__(self)
|
|
|
+
|
|
|
+ def read_bmp_header(self, header):
|
|
|
+ magic, bmfh, bmih = struct.unpack("2s12s40s", header)
|
|
|
+
|
|
|
+ if magic != 'BM':
|
|
|
+ raise SyntaxError("Invalid magic number")
|
|
|
+
|
|
|
+ size, res1, res2, hsize = struct.unpack("<IHHI", bmfh)
|
|
|
+
|
|
|
+ if hsize != self.HEADER_SIZE:
|
|
|
+ raise SyntaxError("Invalid file header size")
|
|
|
+
|
|
|
+ hsize, width, height, planes, bpp, compression, imsize, xppm, yppm, cused, cimp = \
|
|
|
+ struct.unpack("<IiiHHIIiiII", bmih)
|
|
|
+
|
|
|
+ if hsize != 40:
|
|
|
+ raise SyntaxError("Invalid info header size")
|
|
|
+
|
|
|
+ self.i_width = width
|
|
|
+ self.i_height = -height
|
|
|
+
|
|
|
+ if planes != 1:
|
|
|
+ raise SyntaxError("Planar data not supported")
|
|
|
+ if bpp != 32:
|
|
|
+ raise SyntaxError("Only 32-BPP images supported")
|
|
|
+ if compression != 0:
|
|
|
+ raise SyntaxError("Compression not supported")
|
|
|
+ if imsize != self.i_width * self.i_height * 4:
|
|
|
+ raise SyntaxError("Invalid image data size")
|
|
|
+ if size != self.HEADER_SIZE + self.i_width * self.i_height * 4:
|
|
|
+ raise SyntaxError("Invalid image size")
|
|
|
+
|
|
|
+ def map_file(self):
|
|
|
+ f = open(self.image, 'r')
|
|
|
+
|
|
|
+ header = f.read(self.HEADER_SIZE)
|
|
|
+ self.read_bmp_header(header)
|
|
|
+
|
|
|
+ self.imgbuf = numpy.memmap(f, mode = 'r', offset = self.HEADER_SIZE)
|
|
|
+
|
|
|
+ def signal_handler(self, sig, frame):
|
|
|
+ wx.CallAfter(self.mainwin.Refresh);
|
|
|
+
|
|
|
+ def set_handler(self):
|
|
|
+ if 'SIGUSR1' in dir(signal):
|
|
|
+ signal.signal(signal.SIGUSR1, self.signal_handler)
|
|
|
+
|
|
|
+ def OnInit(self):
|
|
|
+ self.map_file()
|
|
|
+
|
|
|
+ size = wx.Size(self.i_width, self.i_height)
|
|
|
+ self.mainwin = Frame(self, size)
|
|
|
+ self.mainwin.Show()
|
|
|
+ self.SetTopWindow(self.mainwin)
|
|
|
+
|
|
|
+ self.set_handler()
|
|
|
+
|
|
|
+ return True
|
|
|
+
|
|
|
+if __name__ == "__main__":
|
|
|
+ app = Application()
|
|
|
+ app.MainLoop()
|