3 # Copyright (C) 2014 Antonio Ospite <ao2@ao2.it>
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # Polar coordinate system:
26 # http://en.wikipedia.org/wiki/Polar_coordinate_system
29 def cartesian2polar(x, y, offset_angle=0):
30 """return the polar coordinates relative to a circle
31 centered in (0,0) going counterclockwise.
33 returned angle is in degrees
36 theta = atan2(float(y), float(x)) + offset_angle
38 # report theta in the [0,360) range
39 theta = degrees(theta)
46 def polar2cartesian(r, theta, offset_angle=0):
48 theta expected in degrees
50 theta = radians(theta) - offset_angle
57 class CanvasView(wx.Window):
58 def __init__(self, *args, **kwargs):
59 self.model = kwargs.pop('model')
60 base_image = kwargs.pop('base_image', None)
61 wx.Window.__init__(self, *args, **kwargs)
63 # This eliminates flickering on Windows
64 self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
66 w = h = int(self.model.external_radius * 2)
68 self.offset_angle = -pi/2.
70 self.SetInitialSize((w, h))
74 self.Bind(wx.EVT_PAINT, self.OnPaint)
76 # make a DC to draw into...
77 self.buffer = wx.EmptyBitmap(w, h)
78 self.dc = wx.BufferedDC(None, self.buffer)
80 # Because we want the position of the mouse pointer
81 # relative to the center of the canvas
82 self.dc.SetDeviceOrigin(w/2., h/2.)
87 self.loadImage(base_image)
89 # FIXME: fix the file path.
91 # Actually it'd be even better to make the grid drawn to a MemoryDC with
92 # drawGrid(), but this is not working with python-wxgtk2.8 because there
93 # are bugs regarding bitmaps with alpha channels and MemoryDC
94 self.grid_buffer = self.loadGrid("res/grid.png")
96 self.pixels_buffer = wx.EmptyBitmap(w, h, depth=32)
99 def setPixelCoordinates(self, gc):
100 w, h = self.GetSize()
101 gc.Translate(w/2., h/2.)
102 gc.Rotate(-self.offset_angle)
104 def MakePixelsGC(self, dc_buffer):
105 dc = wx.MemoryDC(dc_buffer)
106 gc = wx.GraphicsContext.Create(dc)
107 self.setPixelCoordinates(gc)
110 def loadGrid(self, file_path):
111 image = wx.Image(file_path)
113 im_w, im_h = image.GetSize()
114 w, h = self.GetSize()
115 if im_w != w or im_h != h:
118 return wx.BitmapFromImage(image)
120 def loadImage(self, file_path):
121 image = wx.Image(file_path)
123 w, h = image.GetSize()
124 if w != self.model.width or h != self.model.height:
127 bitmap = wx.BitmapFromImage(image)
128 bitmap.CopyToBuffer(self.model.pixels_array)
131 def drawGrid(self, gc):
133 gc.SetPen(wx.Pen('#555555', pen_size))
134 gc.SetBrush(wx.Brush(wx.BLACK, wx.TRANSPARENT))
136 for i in range(0, self.model.height):
137 r, theta = self.model.toPolar(0, i)
138 path = gc.CreatePath()
139 path.AddCircle(0, 0, r)
142 # draw the outmost circle
143 r, theta = self.model.toPolar(0, self.model.height)
144 path = gc.CreatePath()
145 path.AddCircle(0, 0, r - pen_size)
148 min_r = self.model.internal_radius
149 max_r = self.model.external_radius - pen_size
150 for i in range(0, self.model.width):
151 r, theta = self.model.toPolar(i, 0)
152 x1, y1 = polar2cartesian(min_r, theta)
153 x2, y2 = polar2cartesian(max_r, theta)
155 path = gc.CreatePath()
156 path.MoveToPoint(x1, y1)
157 path.AddLineToPoint(x2, y2)
160 def _drawPixel(self, gc, x, y, color=None):
163 gc.SetPen(wx.Pen(color, pen_size))
164 gc.SetBrush(wx.Brush(color))
166 gc.SetPen(wx.Pen(wx.RED, pen_size))
167 gc.SetBrush(wx.Brush(wx.BLACK, wx.TRANSPARENT))
169 min_r, theta1 = self.model.toPolar(x, y)
171 max_r = min_r + self.model.ring_width
173 # prevent the outmost circle to overflow the canvas
174 if y == self.model.height - 1:
177 theta2 = theta1 + self.model.sector_width
179 # Draw the circular arc
180 path = gc.CreatePath()
181 path.AddArc(0, 0, min_r, radians(theta1), radians(theta2), True)
182 path.AddLineToPoint(polar2cartesian(max_r, theta2))
183 path.AddArc(0, 0, max_r, radians(theta2), radians(theta1), False)
184 path.AddLineToPoint(polar2cartesian(min_r, theta1))
188 def drawAllPixels(self):
189 gc = self.MakePixelsGC(self.pixels_buffer)
190 for y in range(0, self.model.height):
191 for x in range(0, self.model.width):
192 color = self.model.getPixelColor(x, y)
193 self._drawPixel(gc, x, y, color=color)
195 def drawPixel(self, pixel):
201 gc = self.MakePixelsGC(self.pixels_buffer)
202 color = self.model.getPixelColor(x, y)
203 self._drawPixel(gc, x, y, color)
205 def drawPixelMarker(self, gc, pixel):
210 self._drawPixel(gc, x, y)
212 def drawImage(self, gc):
213 gc.SetPen(wx.Pen('#CCCCCC', 1))
214 gc.SetBrush(wx.Brush(wx.BLACK, wx.TRANSPARENT))
216 w, h = self.GetSize()
217 gc.DrawRectangle(w - self.model.width, 0,
218 self.model.width, self.model.height)
220 bitmap = wx.BitmapFromBuffer(self.model.width, self.model.height,
221 self.model.pixels_array)
222 gc.DrawBitmap(bitmap, w - self.model.width, 0,
223 self.model.width, self.model.height)
225 def drawTag(self, gc):
226 font = wx.Font(12, wx.FONTFAMILY_MODERN, wx.FONTSTYLE_NORMAL,
228 gc.SetFont(font, "#0099FF")
229 w, h = self.GetSize()
231 twidth, theight, descent, externalLeading = gc.GetFullTextExtent(text)
232 gc.DrawText(text, w - twidth - 4, h - theight - 2)
234 def OnPaint(self, event):
235 # make a DC to draw into...
236 if 'wxMSW' in wx.PlatformInfo:
237 dc = wx.BufferedPaintDC(self)
239 dc = wx.PaintDC(self)
241 dc.SetBackground(wx.Brush(wx.BLACK))
244 gc = wx.GraphicsContext.Create(dc)
245 w, h = self.GetSize()
247 gc.DrawBitmap(self.pixels_buffer, 0, 0, w, h)
250 gc.DrawBitmap(self.grid_buffer, 0, 0, w, h)
253 self.setPixelCoordinates(gc)
254 self.drawPixelMarker(gc, self.model.last_pixel)