49f9f649555f37a9261911f5376686259854db02
[PoPiPaint.git] / CanvasModel.py
1 #!/usr/bin/env python
2 #
3 # Copyright (C) 2014  Antonio Ospite <ao2@ao2.it>
4 #
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.
9 #
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.
14 #
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/>.
17
18 import sys
19 from array import array
20 import wx
21 from wx.lib.pubsub import Publisher as pub
22
23
24 class Canvas:
25
26     def __init__(self, width, height, internal_radius, scale=10):
27         self.width = width
28         self.height = height
29         self.scale = scale
30
31         self.internal_radius = float(internal_radius * scale)
32         self.external_radius = float(height + internal_radius) * scale
33
34         self.radius = float(height * scale)
35
36         self.ring_width = self.radius / height
37         self.sector_width = 360. / width
38
39         self.Reset()
40
41     def Reset(self):
42         self.pixels_array = array('B', [0] * self.width * self.height * 3)
43         self.last_pixel = None
44
45     def toCartesian(self, r, theta):
46         x = int(theta / self.sector_width)
47         y = int(r / self.ring_width) - int(self.internal_radius / self.scale)
48         return (x, y)
49
50     def toPolar(self, x, y):
51         r = y * self.ring_width + self.internal_radius
52         theta = x * self.sector_width
53         return (r, theta)
54
55     def setPixelColor(self, r, theta, color):
56         if r < self.internal_radius or r > self.external_radius:
57             print "invalid coordinates (r: %f)" % r
58             return
59
60         x, y = self.toCartesian(r, theta)
61
62         self.last_pixel = (x, y)
63
64         offset = y * self.width * 3 + x * 3
65         self.pixels_array[offset + 0] = color[0]
66         self.pixels_array[offset + 1] = color[1]
67         self.pixels_array[offset + 2] = color[2]
68
69         # now tell anyone who cares that the value has been changed
70         pub.sendMessage("NEW PIXEL", self.last_pixel)
71
72     def getPixelColor(self, x, y):
73         offset = y * self.width * 3 + x * 3
74         color = [self.pixels_array[offset + i] for i in range(0, 3)]
75         return tuple(color)
76
77     def getColors(self):
78         colors = set()
79         for x in range(self.width):
80             for y in range(self.height):
81                 colors.add(self.getPixelColor(x, y))
82         return list(colors)
83
84     # The code below has been copied from the C implementation in PatternPaint:
85     # https://github.com/Blinkinlabs/PatternPaint
86     def saveAsAnimation(self, filename):
87         output_file = open(filename, "w")
88
89         colors = self.getColors()
90
91         output_file.write("const PROGMEM prog_uint8_t animationData[]  = {\n")
92
93         output_file.write("// Length of the color table - 1, in bytes. length: 1 byte\n")
94         output_file.write("   %d,\n" % (len(colors) - 1))
95
96         output_file.write("// Color table section. Each entry is 3 bytes. length: %d bytes\n" % (len(colors) * 3))
97
98         color_map = {}
99         for i, c in enumerate(colors):
100             output_file.write(" %3d, %3d, %3d,\n" % (c[0], c[1], c[2]))
101             color_map[c] = i
102
103         output_file.write("// Pixel runs section. Each pixel run is 2 bytes. length: -1 bytes\n")
104
105         for x in range(self.width):
106             run_count = 0
107             for y in range(self.height):
108                 new_color = color_map[self.getPixelColor(x, y)]
109                 if run_count == 0:
110                     current_color = new_color
111
112                 if current_color != new_color:
113                     output_file.write(" %3d, %3d,\n" % (run_count, current_color))
114                     run_count = 1
115                     current_color = new_color
116                 else:
117                     run_count += 1
118
119             output_file.write(" %3d, %3d,\n" % (run_count, current_color))
120
121         output_file.write("};\n\n")
122
123         output_file.write("#define NUM_FRAMES %d\n" % self.width)
124         output_file.write("#define NUM_LEDS %d\n" % self.height)
125         output_file.write("Animation animation(NUM_FRAMES, animationData, NUM_LEDS);\n")
126         output_file.close()