fx92-interpreter/drawing.py
Lephe a983faedeb
add relations, and most functions
This change adds a [cond] grammar symbol corresponding to binary
relations in IF, IFELSE and WHILE conditions. It also adds support for
unary functions with parenthesis syntax. Other functions will need
specific rules depending on their operator precedence level.

Also adds the Window.save() function that implements the --save option
to save the output of the program into a bitmap file. This will be used
to perform automated unit tests.
2019-10-04 12:44:27 +02:00

156 lines
4.4 KiB
Python

# fx-92 Scientifique Collège+ language interpreter: On-screen drawing
from sdl2 import *
class Window:
"""
An SDL window with pixel scaling.
"""
BLACK = (0, 0, 0, 255)
WHITE = (255, 255, 255, 255)
def __init__(self, width, height, scale, quiet=False):
self.width = width
self.height = height
self.scale = scale
self.quiet = quiet
def __enter__(self):
"""
Enter window context: allocate and create a window.
"""
if SDL_WasInit(SDL_INIT_VIDEO):
raise Exception("Cannot create two windows")
if SDL_Init(SDL_INIT_VIDEO) < 0:
raise Exception("Failed to initialize SDL")
# Create the window
mode = SDL_WINDOW_HIDDEN if self.quiet else SDL_WINDOW_SHOWN
self.w = SDL_CreateWindow("fx-92 Scientifique Collège+".encode(),
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
self.width*self.scale, self.height*self.scale, mode)
if self.w is None:
raise Exception("Failed to create window")
# Create the renderer
self.r = SDL_CreateRenderer(self.w, -1, SDL_RENDERER_ACCELERATED)
if self.r is None:
SDL_DestroyWindow(self.w)
raise Exception("Failed to create renderer")
# Create the base texture where things will be rendered at scale 1
self.t = SDL_CreateTexture(self.r, SDL_PIXELFORMAT_ARGB8888,
SDL_TEXTUREACCESS_TARGET, self.width, self.height)
if self.t is None:
SDL_DestroyRenderer(r)
SDL_DestroyWindow(w)
raise Exception("Failed to create texture")
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, b'0')
SDL_SetRenderTarget(self.r, self.t)
self.clear(self.WHITE)
self.update()
return self
def __exit__(self, *args):
"""
Exit context: close and destroy window.
"""
if self.t is not None:
SDL_SetRenderTarget(self.r, None)
SDL_DestroyTexture(self.t)
if self.r is not None:
SDL_DestroyRenderer(self.r)
if self.w is not None:
SDL_DestroyWindow(self.w)
SDL_Quit()
def clear(self, color):
"""Clear screen in a uniform color."""
SDL_SetRenderDrawColor(self.r, *color)
SDL_RenderClear(self.r)
def line(self, x1, y1, x2, y2, color):
"""Draw a straight line."""
SDL_SetRenderDrawColor(self.r, *color)
# Bresenham line drawing algorithm
sgn = lambda x: -1 if x < 0 else (1 if x > 0 else 0)
x, y = x1, y1
dx, dy = x2 - x1, y2 - y1
sx, sy = sgn(dx), sgn(dy)
dx, dy = abs(dx), abs(dy)
cumul = 0
SDL_RenderDrawPoint(self.r, x, y)
if dx >= dy:
cumul = dx // 2
for i in range(dx):
x += sx
cumul += dy
if cumul > dx:
cumul -= dx
y += sy
SDL_RenderDrawPoint(self.r, x, y)
else:
cumul = dy // 2
for i in range(dy):
y += sy
cumul += dx
if cumul > dy:
cumul -= dy
x += sx
SDL_RenderDrawPoint(self.r, x, y)
def wait(self):
"""Wait for the window to be closed."""
if self.quiet:
return
event = SDL_Event()
while 1:
SDL_WaitEvent(event)
if event.type == SDL_QUIT:
break
def save(self, out):
"""Save the output into a BMP file."""
SDL_SetRenderTarget(self.r, self.t)
surface = SDL_CreateRGBSurface(0, self.width, self.height, 32,0,0,0,0)
fmt = surface.contents.format.contents.format
pixels = surface.contents.pixels
pitch = surface.contents.pitch
SDL_RenderReadPixels(self.r, None, fmt, pixels, pitch);
SDL_SaveBMP(surface, out.encode())
SDL_FreeSurface(surface)
def update(self):
"""Push window contents on-screen."""
# Target the window with scaling
SDL_SetRenderTarget(self.r, None)
SDL_RenderSetScale(self.r, self.scale, self.scale)
# Push that to the screen
SDL_RenderCopy(self.r, self.t, None, None)
SDL_RenderPresent(self.r)
# Go back to the texture
SDL_SetRenderTarget(self.r, self.t)
SDL_RenderSetScale(self.r, 1, 1)