Skip to content

Commit 3967b00

Browse files
committed
mandelbrot-app: use a pixbuf instead of an image surface to speed up drawing
1 parent 486b662 commit 3967b00

File tree

1 file changed

+37
-32
lines changed

1 file changed

+37
-32
lines changed

software/mandelbrot-app.py

Lines changed: 37 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def float2fix(f):
2626

2727
pixel_queue = queue.Queue()
2828

29-
def send_command(bytewidth, view, debug=False):
29+
def send_command(bytewidth, view, iterations=10000, debug=False):
3030
tstart = time.perf_counter()
3131
command_bytes = struct.pack("HHI", view.width-1, view.height-1, view.max_iterations)
3232
command_bytes += view.corner_x.to_bytes(bytewidth, byteorder='little', signed=True)
@@ -43,7 +43,7 @@ def send_command(bytewidth, view, debug=False):
4343
try:
4444
while True:
4545
if debug: print("read")
46-
r = dev.read(0x81, 256, timeout=10)
46+
r = dev.read(0x81, 256, timeout=max(10, iterations//1000))
4747
if debug: print("Got: "+ str(len(r)))
4848
if debug: print(str(r))
4949
result += r
@@ -127,12 +127,14 @@ def to_string(self):
127127
if argv[1] == "gui":
128128
import gi
129129
gi.require_version("Gtk", "3.0")
130-
from gi.repository import GLib, Gtk, Gdk
131-
from gi.repository.Gtk import DrawingArea
130+
from gi.repository import GLib, Gtk, Gdk
131+
from gi.repository.Gtk import DrawingArea
132+
from gi.repository.GdkPixbuf import Pixbuf, Colorspace
133+
from gi.repository.GdkPixdata import Pixdata, PIXBUF_MAGIC_NUMBER, PIXDATA_HEADER_LENGTH, PixdataType
132134
import cairo
133135

134136
class GuiHandler:
135-
surface = None
137+
pixbuf = None
136138
builder = None
137139
canvas = None
138140

@@ -144,6 +146,7 @@ class GuiHandler:
144146

145147
def __init__(self, builder) -> None:
146148
self.builder = builder
149+
self.pixels = None
147150
centerx_widget = builder.get_object("center_x")
148151
centery_widget = builder.get_object("center_y")
149152
radius_widget = builder.get_object("radius")
@@ -158,12 +161,13 @@ def __init__(self, builder) -> None:
158161
radius_widget .set_text(str(radius))
159162
iterations_widget.set_text(str(iterations))
160163

161-
self.updateImageSurfaceIfNeeded()
164+
self.updateImageBufferIfNeeded()
162165

163166
def painter(self):
164167
drawing_start = time.perf_counter()
165168
try:
166-
cr = cairo.Context(self.surface)
169+
channels = 3
170+
rowstride = self.width * channels
167171
pixel_count = 0
168172
while True:
169173
# get() will exit this thread if the
@@ -174,25 +178,24 @@ def painter(self):
174178
x = pixel[0]
175179
y = self.view.height - pixel[1]
176180

177-
red, green, blue = colortable_float[pixel[2] & 0xf]
181+
red, green, blue = colortable[pixel[2] & 0xf]
178182
maxed = pixel[2] >> 7
179183

180-
def draw_pixel():
181-
if not maxed:
182-
cr.set_source_rgb(red, green, blue)
183-
else:
184-
cr.set_source_rgb(0, 0, 0)
184+
pixel_index = y * rowstride + x * channels
185185

186-
cr.rectangle(x, y, 1.5, 1.5)
187-
cr.fill()
188-
if pixel_count % 1000 == 0:
189-
self.canvas.queue_draw()
186+
if maxed:
187+
red = green = blue = 0
190188

191-
return False
189+
if pixel_index + 2 < len(self.pixels):
190+
self.pixels[pixel_index] = red
191+
self.pixels[pixel_index + 1] = green
192+
self.pixels[pixel_index + 2] = blue
193+
194+
if pixel_count % (2 * self.width) == 0:
195+
Gdk.threads_enter()
196+
self.canvas.queue_draw()
197+
Gdk.threads_leave()
192198

193-
Gdk.threads_enter()
194-
draw_pixel()
195-
Gdk.threads_leave()
196199
pixel_queue.task_done()
197200

198201
finally:
@@ -202,12 +205,13 @@ def draw_pixel():
202205
def onDestroy(self, *args):
203206
Gtk.main_quit()
204207

205-
def updateImageSurfaceIfNeeded(self):
208+
def updateImageBufferIfNeeded(self):
206209
self.canvas = canvas = builder.get_object("canvas")
207210
width = canvas.get_allocated_size().allocation.width
208211
height = canvas.get_allocated_size().allocation.height
209-
if self.surface is None or self.width != width or self.height != height:
210-
self.surface = cairo.ImageSurface(cairo.FORMAT_RGB24, width, height)
212+
if (self.pixbuf is None or self.width != width or self.height != height) and width > 0 and height > 0:
213+
print(f"w {width} h {height}")
214+
self.pixels = bytearray((height + 1) * 3 * width)
211215
self.width, self.height = width, height
212216

213217
def getViewParameterWidgets(self):
@@ -221,23 +225,22 @@ def getViewParameters(self):
221225
return map(getValue, self.getViewParameterWidgets())
222226

223227
def onUpdateButtonPress(self, button):
224-
self.updateImageSurfaceIfNeeded()
228+
self.updateImageBufferIfNeeded()
225229

226230
center_x, center_y, radius = self.getViewParameters()
227231
iterations = int (builder.get_object("iterations").get_text())
228232

229233
self.view.update(center_x=center_x, center_y=center_y, radius=radius, width=self.width, height=self.height, max_iterations=iterations)
230234
print(self.view.to_string())
231235

232-
cr = cairo.Context(self.surface)
233-
cr.set_source_rgb(0, 0, 0)
234-
cr.rectangle(0, 0, self.width, self.height)
235-
cr.fill()
236+
# clear out image
237+
for i in range(len(self.pixels)):
238+
self.pixels[i] = 0
236239

237240
view = self.view
238241
view.width = self.width
239242
view.height = self.height
240-
usb_reader = lambda: send_command(9, view, debug=False)
243+
usb_reader = lambda: send_command(9, view, view.max_iterations, debug=False)
241244
usb_thread = threading.Thread(target=usb_reader, daemon=True)
242245
usb_thread.start()
243246

@@ -262,8 +265,10 @@ def onCanvasMotion(self, canvas, event):
262265
canvas.queue_draw()
263266

264267
def onDraw(self, canvas: DrawingArea, cr: cairo.Context):
265-
cr.set_source_surface(self.surface, 0, 0)
266-
cr.paint()
268+
if not self.pixels is None:
269+
pixbuf = Pixbuf.new_from_data(bytes(self.pixels), Colorspace.RGB, False, 8, self.width, self.height + 1, self.width * 3)
270+
Gdk.cairo_set_source_pixbuf(cr, pixbuf, 0, 0)
271+
cr.paint()
267272
if not self.crosshairs is None:
268273
x, y = self.crosshairs[0]
269274
cr.set_source_rgb(1, 1, 1)

0 commit comments

Comments
 (0)