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 # Have a reference DC with the desired coordinate system.
77 # Because we want the position of the mouse pointer relative to the
78 # center of this canvas.
79 self.dc = wx.MemoryDC(wx.EmptyBitmap(w, h))
80 self.dc.SetDeviceOrigin(w/2., h/2.)
85 self.loadImage(base_image)
87 # FIXME: fix the file path.
89 # Actually it'd be even better to make the grid drawn to a MemoryDC with
90 # drawGrid(), but this is not working with python-wxgtk2.8 because there
91 # are bugs regarding bitmaps with alpha channels and MemoryDC
92 self.grid_buffer = self.loadGrid("res/grid.png")
94 self.pixels_buffer = wx.EmptyBitmapRGBA(w, h, 0, 0 ,0, 255)
97 def setPixelCoordinates(self, gc):
99 gc.Translate(w/2., h/2.)
100 gc.Rotate(-self.offset_angle)
102 def MakePixelsGC(self, dc_buffer):
103 dc = wx.MemoryDC(dc_buffer)
104 gc = wx.GraphicsContext.Create(dc)
105 self.setPixelCoordinates(gc)
108 def loadGrid(self, file_path):
109 image = wx.Image(file_path)
111 im_w, im_h = image.GetSize()
112 w, h = self.GetSize()
113 if im_w != w or im_h != h:
116 return wx.BitmapFromImage(image)
118 def loadImage(self, file_path):
119 image = wx.Image(file_path)
121 w, h = image.GetSize()
122 if w != self.model.width or h != self.model.height:
125 bitmap = wx.BitmapFromImage(image)
126 bitmap.CopyToBuffer(self.model.pixels_array)
129 def drawGrid(self, gc):
131 gc.SetPen(wx.Pen('#555555', pen_size))
132 gc.SetBrush(wx.Brush(wx.BLACK, wx.TRANSPARENT))
134 for i in range(0, self.model.height):
135 r, theta = self.model.toPolar(0, i)
136 path = gc.CreatePath()
137 path.AddCircle(0, 0, r)
140 # draw the outmost circle
141 r, theta = self.model.toPolar(0, self.model.height)
142 path = gc.CreatePath()
143 path.AddCircle(0, 0, r - pen_size)
146 min_r = self.model.internal_radius
147 max_r = self.model.external_radius - pen_size
148 for i in range(0, self.model.width):
149 r, theta = self.model.toPolar(i, 0)
150 x1, y1 = polar2cartesian(min_r, theta)
151 x2, y2 = polar2cartesian(max_r, theta)
153 path = gc.CreatePath()
154 path.MoveToPoint(x1, y1)
155 path.AddLineToPoint(x2, y2)
158 def _drawPixel(self, gc, x, y, color=None):
161 gc.SetPen(wx.Pen(color, pen_size))
162 gc.SetBrush(wx.Brush(color))
164 gc.SetPen(wx.Pen(wx.RED, pen_size))
165 gc.SetBrush(wx.Brush(wx.BLACK, wx.TRANSPARENT))
167 min_r, theta1 = self.model.toPolar(x, y)
169 max_r = min_r + self.model.ring_width
171 # prevent the outmost circle to overflow the canvas
172 if y == self.model.height - 1:
175 theta2 = theta1 + self.model.sector_width
177 # Draw the circular arc
178 path = gc.CreatePath()
179 path.AddArc(0, 0, min_r, radians(theta1), radians(theta2), True)
180 path.AddLineToPoint(polar2cartesian(max_r, theta2))
181 path.AddArc(0, 0, max_r, radians(theta2), radians(theta1), False)
182 path.AddLineToPoint(polar2cartesian(min_r, theta1))
186 def drawAllPixels(self):
187 gc = self.MakePixelsGC(self.pixels_buffer)
188 for y in range(0, self.model.height):
189 for x in range(0, self.model.width):
190 color = self.model.getPixelColor(x, y)
191 self._drawPixel(gc, x, y, color=color)
193 def drawPixel(self, pixel):
199 gc = self.MakePixelsGC(self.pixels_buffer)
200 color = self.model.getPixelColor(x, y)
201 self._drawPixel(gc, x, y, color)
203 def drawPixelMarker(self, gc, pixel):
208 self._drawPixel(gc, x, y)
210 def drawImage(self, gc):
211 gc.SetPen(wx.Pen('#CCCCCC', 1))
212 gc.SetBrush(wx.Brush(wx.BLACK, wx.TRANSPARENT))
214 w, h = self.GetSize()
215 gc.DrawRectangle(w - self.model.width, 0,
216 self.model.width, self.model.height)
218 bitmap = wx.BitmapFromBuffer(self.model.width, self.model.height,
219 self.model.pixels_array)
220 gc.DrawBitmap(bitmap, w - self.model.width, 0,
221 self.model.width, self.model.height)
223 def drawTag(self, gc):
224 font = wx.Font(12, wx.FONTFAMILY_MODERN, wx.FONTSTYLE_NORMAL,
226 gc.SetFont(font, "#0099FF")
227 w, h = self.GetSize()
229 twidth, theight, descent, externalLeading = gc.GetFullTextExtent(text)
230 gc.DrawText(text, w - twidth - 4, h - theight - 2)
232 def OnPaint(self, event):
233 # make a DC to draw into...
234 if 'wxMSW' in wx.PlatformInfo:
235 dc = wx.BufferedPaintDC(self)
237 dc = wx.PaintDC(self)
239 dc.SetBackground(wx.Brush(wx.BLACK))
242 gc = wx.GraphicsContext.Create(dc)
243 w, h = self.GetSize()
245 gc.DrawBitmap(self.pixels_buffer, 0, 0, w, h)
248 gc.DrawBitmap(self.grid_buffer, 0, 0, w, h)
251 self.setPixelCoordinates(gc)
252 self.drawPixelMarker(gc, self.model.last_pixel)