#!/usr/bin/env python # # Copyright (C) 2014 Antonio Ospite # # 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 3 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. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import wx from math import * import CanvasModel # Polar coordinate system: # http://en.wikipedia.org/wiki/Polar_coordinate_system # (r, theta) def cartesian2polar(x, y, offset_angle=0): """return the polar coordinates relative to a circle centered in (0,0) going counterclockwise. returned angle is in degrees """ r = sqrt(x*x + y*y) theta = atan2(float(y), float(x)) + offset_angle # report theta in the [0,360) range theta = degrees(theta) if theta < 0: theta = 360 + theta return r, theta def polar2cartesian(r, theta, offset_angle=0): """ theta expected in degrees """ theta = radians(theta) - offset_angle x = r * cos(theta) y = r * sin(theta) return x, y class CanvasView(wx.Window): def __init__(self, *args, **kwargs): self.model = kwargs.pop('model') base_image = kwargs.pop('base_image', None) wx.Window.__init__(self, *args, **kwargs) # This eliminates flickering on Windows self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) w = h = int(self.model.external_radius * 2) self.offset_angle = -pi/2. self.SetInitialSize((w, h)) self.SetFocus() self.Bind(wx.EVT_PAINT, self.OnPaint) # Have a reference DC with the desired coordinate system. # Because we want the position of the mouse pointer relative to the # center of this canvas. self.dc = wx.MemoryDC(wx.EmptyBitmap(w, h)) self.dc.SetDeviceOrigin(w/2., h/2.) self.draw_grid = True if base_image: self.loadImage(base_image) # FIXME: fix the file path. # # Actually it'd be even better to make the grid drawn to a MemoryDC with # drawGrid(), but this is not working with python-wxgtk2.8 because there # are bugs regarding bitmaps with alpha channels and MemoryDC self.grid_buffer = self.loadGrid("res/grid.png") self.pixels_buffer = wx.EmptyBitmapRGBA(w, h, 0, 0 ,0, 255) self.drawAllPixels() def setPixelCoordinates(self, gc): w, h = self.GetSize() gc.Translate(w/2., h/2.) gc.Rotate(-self.offset_angle) def MakePixelsGC(self, dc_buffer): dc = wx.MemoryDC(dc_buffer) gc = wx.GraphicsContext.Create(dc) self.setPixelCoordinates(gc) return gc def loadGrid(self, file_path): image = wx.Image(file_path) im_w, im_h = image.GetSize() w, h = self.GetSize() if im_w != w or im_h != h: return None return wx.BitmapFromImage(image) def loadImage(self, file_path): image = wx.Image(file_path) w, h = image.GetSize() if w != self.model.width or h != self.model.height: return False bitmap = wx.BitmapFromImage(image) bitmap.CopyToBuffer(self.model.pixels_array) return True def drawGrid(self, gc): pen_size = 1 gc.SetPen(wx.Pen('#555555', pen_size)) gc.SetBrush(wx.Brush(wx.BLACK, wx.TRANSPARENT)) for i in range(0, self.model.height): r, theta = self.model.toPolar(0, i) path = gc.CreatePath() path.AddCircle(0, 0, r) gc.DrawPath(path) # draw the outmost circle r, theta = self.model.toPolar(0, self.model.height) path = gc.CreatePath() path.AddCircle(0, 0, r - pen_size) gc.DrawPath(path) min_r = self.model.internal_radius max_r = self.model.external_radius - pen_size for i in range(0, self.model.width): r, theta = self.model.toPolar(i, 0) x1, y1 = polar2cartesian(min_r, theta) x2, y2 = polar2cartesian(max_r, theta) path = gc.CreatePath() path.MoveToPoint(x1, y1) path.AddLineToPoint(x2, y2) gc.DrawPath(path) def _drawPixel(self, gc, x, y, color=None): pen_size = 1 if color: gc.SetPen(wx.Pen(color, pen_size)) gc.SetBrush(wx.Brush(color)) else: gc.SetPen(wx.Pen(wx.RED, pen_size)) gc.SetBrush(wx.Brush(wx.BLACK, wx.TRANSPARENT)) min_r, theta1 = self.model.toPolar(x, y) max_r = min_r + self.model.ring_width # prevent the outmost circle to overflow the canvas if y == self.model.height - 1: max_r -= pen_size theta2 = theta1 + self.model.sector_width # Draw the circular arc path = gc.CreatePath() path.AddArc(0, 0, min_r, radians(theta1), radians(theta2), True) path.AddLineToPoint(polar2cartesian(max_r, theta2)) path.AddArc(0, 0, max_r, radians(theta2), radians(theta1), False) path.AddLineToPoint(polar2cartesian(min_r, theta1)) path.CloseSubpath() gc.DrawPath(path) def drawAllPixels(self): gc = self.MakePixelsGC(self.pixels_buffer) for y in range(0, self.model.height): for x in range(0, self.model.width): color = self.model.getPixelColor(x, y) self._drawPixel(gc, x, y, color=color) def drawPixel(self, pixel): if not pixel: return x, y = pixel gc = self.MakePixelsGC(self.pixels_buffer) color = self.model.getPixelColor(x, y) self._drawPixel(gc, x, y, color) def drawPixelMarker(self, gc, pixel): if not pixel: return x, y = pixel self._drawPixel(gc, x, y) def drawImage(self, gc): gc.SetPen(wx.Pen('#CCCCCC', 1)) gc.SetBrush(wx.Brush(wx.BLACK, wx.TRANSPARENT)) w, h = self.GetSize() gc.DrawRectangle(w - self.model.width, 0, self.model.width, self.model.height) bitmap = wx.BitmapFromBuffer(self.model.width, self.model.height, self.model.pixels_array) gc.DrawBitmap(bitmap, w - self.model.width, 0, self.model.width, self.model.height) def drawTag(self, gc): font = wx.Font(12, wx.FONTFAMILY_MODERN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD) gc.SetFont(font, "#0099FF") w, h = self.GetSize() text = "ao2.it" twidth, theight, descent, externalLeading = gc.GetFullTextExtent(text) gc.DrawText(text, w - twidth - 4, h - theight - 2) def OnPaint(self, event): # make a DC to draw into... if 'wxMSW' in wx.PlatformInfo: dc = wx.BufferedPaintDC(self) else: dc = wx.PaintDC(self) dc.SetBackground(wx.Brush(wx.BLACK)) dc.Clear() gc = wx.GraphicsContext.Create(dc) w, h = self.GetSize() gc.DrawBitmap(self.pixels_buffer, 0, 0, w, h) if self.draw_grid: gc.DrawBitmap(self.grid_buffer, 0, 0, w, h) gc.PushState() self.setPixelCoordinates(gc) self.drawPixelMarker(gc, self.model.last_pixel) gc.PopState() self.drawImage(gc) self.drawTag(gc)