#!/usr/bin/python # -*- coding: utf-8 -*- """ The Imlac display. A temporary version that outputs a PPM graphics image. The Imlac dislay X and Y registers are of size 11 bits. The most significant 6 bits are the MSB and the least significant 5 bits are the LSB. A DLXA (or DLYA) loads the display X (or Y) register from the 10 bits of valu, with bit 10 of the X/Y register being set to 0. The display is 2048x2048 pixels. """ from Globals import * # if we don't have log.py, don't crash try: import log log = log.Log('test.log', log.Log.DEBUG) except ImportError: def log(*args, **kwargs): pass # the widget version __version__ = '0.0' # type of events DisplayStop = 1 ################################################################################ # The actual pymlac display widget. # This version is just a debug version that creates PPM files. ################################################################################ class Display(object): # BackgroundColour = 'black' # DrawColour = '#ffff88' SYNC_HZ = 40 SYNC_40HZ_CYCLE_COUNT = int(CYCLES_PER_SECOND / SYNC_HZ) BackgroundColour = 1 DrawColour = 0 # max coordinates of pymlac display ScaleMaxX = 2048 ScaleMaxY = 2048 def __init__(self): # create and initialise the image array # reference .array[y*ScaleMaxX + x] self.array = [self.BackgroundColour] * self.ScaleMaxY * self.ScaleMaxX # set internal state self.running = 0 self.cycle_count = 0 self.Sync40hz = 1 self.next_file_num = 0 # 'dirty' flag set after writing self.dirty = False log('Display: instance created') def write(self): """Write display array to PPM file.""" self.next_file_num += 1 filename = 'pymlac_%06d.ppm' % self.next_file_num with open(filename, 'wb') as handle: # output header data handle.write(bytes('P1\n', 'utf-8')) handle.write(bytes('# created by pymlac %s\n' % __version__, 'utf-8')) handle.write(bytes('%d %d\n' % (self.ScaleMaxX, self.ScaleMaxY), 'utf-8')) # output graphics data for v in self.array: handle.write(bytes('%d\n' % v, 'utf-8')) self.dirty = False log('Display: array written to file %s' % filename) def draw(self, x1, y1, x2, y2, dotted=False): """Draw a line on the screen. x1, y1 start coordinates x2, y2 end coordinates dotted True if dotted line, else False (IGNORED) """ log('Display: drawing (%d,%d) to (%d,%d)' % (x1, y1, x2, y2)) self.dirty = True # draw a straight line using Bresenam self.bresenham(x1, y1, x2, y2) def clear(self): """Clear the display.""" log('Display: clearing the display') # write display to next PPM file first if self.dirty: self.write() self.array = [self.BackgroundColour] * self.ScaleMaxY * self.ScaleMaxX self.dirty = False def syncclear(self): self.Sync40hz = 0 self.cycle_count = self.SYNC_40HZ_CYCLE_COUNT def ready(self): return self.Sync40hz def tick(self, cycles): """Advance the internal state by given cycles.""" self.cycle_count -= cycles if self.cycle_count <= 0: self.Sync40hz = 1 self.cycle_count = 0 def close(self): """Close display, writing data if 'dirty'.""" if self.dirty: self.write() # original algorithm from: # http://www.idav.ucdavis.edu/education/GraphicsNotes/Bresenhams-Algorithm.pdf # Let ∆x = x2 − x1 # Let ∆y = y2 − y1 # Let j = y1 # Let ε = ∆y − ∆x # # for i = x1 to x2−1 # illuminate (i, j) # if (ε ≥ 0) # j += 1 # ε −= ∆x # end if # i += 1 # ε += ∆y # next i # # finish def bresenham(self, x1, y1, x2, y2): """Bresenham algorithm to draw from (x1,y1) to (x2,y2).""" dx = x2 - x1 dy = y2 - y1 # Determine how steep the line is is_steep = abs(dy) > abs(dx) # Rotate line if is_steep: x1, y1 = y1, x1 x2, y2 = y2, x2 # Swap start and end points if necessary and store swap state swapped = False if x1 > x2: x1, x2 = x2, x1 y1, y2 = y2, y1 swapped = True # Recalculate differentials dx = x2 - x1 dy = y2 - y1 # Calculate error error = int(dx / 2.0) ystep = 1 if y1 < y2 else -1 # Iterate over bounding box generating points between start and end y = y1 for x in range(x1, x2 + 1): (xx, yy) = (y, x) if is_steep else (x, y) yyy = self.ScaleMaxX - yy self.array[yyy*self.ScaleMaxX + xx] = self.DrawColour error -= abs(dy) if error < 0: y += ystep error += dx if __name__ == '__main__': """Test case - draw radial lines.""" d = Display() d.draw(0, 0, 1023, 1023) d.draw(255, 0, 1023-255, 1023) d.draw(511, 0, 1023-511, 1023) d.draw(767, 0, 1023-767, 1023) d.draw(1023, 0, 0, 1023) d.draw(0, 255, 1023, 1023-255) d.draw(0, 511, 1023, 1023-511) d.draw(0, 767, 1023, 1023-767) d.clear() d.draw(0, 0, 1023, 1023) d.draw(255, 0, 1023-255, 1023) d.draw(511, 0, 1023-511, 1023) d.draw(767, 0, 1023-767, 1023) d.draw(1023, 0, 0, 1023) d.draw(0, 255, 1023, 1023-255) d.draw(0, 511, 1023, 1023-511) d.draw(0, 767, 1023, 1023-767) d.close()