wxpyimgview_gui.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. #!/usr/bin/env python3
  2. ############################################################################
  3. #
  4. # MODULE: wxpyimgview
  5. # AUTHOR(S): Glynn Clements <glynn@gclements.plus.com>
  6. # COPYRIGHT: (C) 2010 Glynn Clements
  7. #
  8. # This program is free software; you can redistribute it and/or modify
  9. # it under the terms of the GNU General Public License as published by
  10. # the Free Software Foundation; either version 2 of the License, or
  11. # (at your option) any later version.
  12. #
  13. # This program is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. # GNU General Public License for more details.
  17. #
  18. # /
  19. # %module
  20. # % description: Views BMP images from the PNG driver.
  21. # % keyword: display
  22. # % keyword: raster
  23. # %end
  24. # %option G_OPT_F_INPUT
  25. # % key: image
  26. # % description: Name of input image file
  27. # %end
  28. # %option
  29. # % key: percent
  30. # % type: integer
  31. # % required: no
  32. # % multiple: no
  33. # % description: Percentage of CPU time to use
  34. # % answer: 10
  35. # %end
  36. import signal
  37. import struct
  38. import sys
  39. import time
  40. import numpy
  41. import wx
  42. import grass.script as grass
  43. from grass.script.setup import set_gui_path
  44. set_gui_path()
  45. from gui_core.wrap import BitmapFromImage # noqa: E402
  46. class Frame(wx.Frame):
  47. title = "Image Viewer"
  48. def __init__(self, app, size):
  49. self.app = app
  50. wx.Frame.__init__(self, None, title=Frame.title, size=size)
  51. self.Create()
  52. def Create(self):
  53. self.Bind(wx.EVT_ERASE_BACKGROUND, self.erase)
  54. self.Bind(wx.EVT_PAINT, self.redraw)
  55. self.Bind(wx.EVT_TIMER, self.tick, id=1)
  56. self.timer = wx.Timer(self, 1)
  57. self.timer.Start(100, True)
  58. # Python doesn't receive signals while wx is waiting for an event
  59. self.Bind(wx.EVT_TIMER, self.dummy, id=2)
  60. self.ticker = wx.Timer(self, 2)
  61. self.ticker.Start(100, False)
  62. def erase(self, ev):
  63. ev.GetDC()
  64. def draw(self):
  65. app = self.app
  66. size = self.GetSize()
  67. x0 = (size.GetWidth() - app.i_width) / 2
  68. y0 = (size.GetHeight() - app.i_height) / 2
  69. dc = wx.PaintDC(self)
  70. data = app.imgbuf.reshape((app.i_height, app.i_width, 4))
  71. data = data[::, ::, 2::-1]
  72. fn = getattr(data, "tobytes", getattr(data, "tostring"))
  73. image = wx.Image(app.i_width, app.i_height, fn())
  74. dc.DrawBitmap(BitmapFromImage(image), x0, y0, False)
  75. def redraw(self, ev):
  76. if self.app.fraction > 0.001:
  77. t0 = time.time()
  78. self.draw()
  79. t1 = time.time()
  80. last = t1 - t0
  81. delay = last / self.app.fraction
  82. self.timer.Start(int(delay * 1000), True)
  83. else:
  84. self.draw()
  85. def tick(self, ev):
  86. self.Refresh()
  87. def dummy(self, ev):
  88. pass
  89. class Application(wx.App):
  90. def __init__(self):
  91. self.image = sys.argv[1]
  92. self.fraction = int(sys.argv[2]) / 100.0
  93. self.HEADER_SIZE = 64
  94. wx.App.__init__(self)
  95. def read_bmp_header(self, header):
  96. magic, bmfh, bmih = struct.unpack("2s12s40s10x", header)
  97. if grass.decode(magic) != "BM":
  98. raise SyntaxError("Invalid magic number")
  99. size, res1, res2, hsize = struct.unpack("<IHHI", bmfh)
  100. if hsize != self.HEADER_SIZE:
  101. raise SyntaxError("Invalid file header size")
  102. (
  103. hsize,
  104. width,
  105. height,
  106. planes,
  107. bpp,
  108. compression,
  109. imsize,
  110. xppm,
  111. yppm,
  112. cused,
  113. cimp,
  114. ) = struct.unpack("<IiiHHIIiiII", bmih)
  115. if hsize != 40:
  116. raise SyntaxError("Invalid info header size")
  117. self.i_width = width
  118. self.i_height = -height
  119. if planes != 1:
  120. raise SyntaxError("Planar data not supported")
  121. if bpp != 32:
  122. raise SyntaxError("Only 32-BPP images supported")
  123. if compression != 0:
  124. raise SyntaxError("Compression not supported")
  125. if imsize != self.i_width * self.i_height * 4:
  126. raise SyntaxError("Invalid image data size")
  127. if size != self.HEADER_SIZE + self.i_width * self.i_height * 4:
  128. raise SyntaxError("Invalid image size")
  129. def map_file(self):
  130. f = open(self.image, "rb")
  131. header = f.read(self.HEADER_SIZE)
  132. self.read_bmp_header(header)
  133. self.imgbuf = numpy.memmap(f, mode="r", offset=self.HEADER_SIZE)
  134. def signal_handler(self, sig, frame):
  135. wx.CallAfter(self.mainwin.Refresh)
  136. def set_handler(self):
  137. if "SIGUSR1" in dir(signal):
  138. signal.signal(signal.SIGUSR1, self.signal_handler)
  139. def OnInit(self):
  140. self.map_file()
  141. size = wx.Size(self.i_width, self.i_height)
  142. self.mainwin = Frame(self, size)
  143. self.mainwin.Show()
  144. self.SetTopWindow(self.mainwin)
  145. self.set_handler()
  146. return True
  147. if __name__ == "__main__":
  148. app = Application()
  149. app.MainLoop()