#! /usr/bin/python3 import random import sys import os import atexit # suppress pygame self-advertisement # https://stackoverflow.com/questions/51464455/why-when-import-pygame-it-prints-the-version-and-welcome-message-how-delete-it#51470016 with open(os.devnull, 'w') as f: # disable stdout oldstdout = sys.stdout sys.stdout = f import pygame # enable stdout sys.stdout = oldstdout import cgi import cgitb import tempfile import subprocess import time import email cgitb.enable() # template definition template_file = 'template.jpg' # pixel coordinates of the dot matrix corners top_left = [1011.0, 1011.0] bottom_left = [1009.0, 1097.0] top_right = [2175.0, 1048.0] bottom_right = [2174.0, 1129.0] # dot matrix resolution = [125.0, 7.0] radius = 5.5 # convert from dot coordinates to image pixel coordinates, using # two-dimensional linear interpolation def dot2pixel(dot): assert (dot[0] >= 0 and dot[0] < resolution[0]) assert (dot[1] >= 0 and dot[1] < resolution[1]) # scale incoming dots to floating point interval [0,1] rightness = dot[0]/(resolution[0]-1.0) leftness = 1.0-rightness bottomness = dot[1]/(resolution[1]-1.0) topness = 1.0-bottomness # print '%g-%g %g-%g' % (leftness,rightness, topness,bottomness) pixx = (top_left[0] * leftness * topness + top_right[0] * rightness * topness + bottom_left[0] * leftness * bottomness + bottom_right[0] * rightness * bottomness) pixy = (top_left[1] * leftness * topness + top_right[1] * rightness * topness + bottom_left[1] * leftness * bottomness + bottom_right[1] * rightness * bottomness) return [pixx, pixy] # draw an approximate circle near the pixel coordinates of the given dot def splotch(sfc, dot, col): pixcenter = dot2pixel(dot) # add a little bit of offset noise amplitude=0.9 pixx = pixcenter[0] + (random.random()-0.5)*amplitude pixy = pixcenter[1] + (random.random()-0.5)*amplitude # add a little bit of radius noise amplitude = 0.8 myradius = radius + (random.random()-0.5)*amplitude r = pygame.Rect (int(pixx-myradius), int(pixy-myradius), int(myradius*2), int(myradius*2)) # add a little bit of color noise col2 = col.correct_gamma (2.0 + 3*(random.random()-0.5)) pygame.draw.ellipse (sfc, col2, r) # add a little highlight for i in range(0,4): myradius = (myradius + (random.random()-0.5)*amplitude) * 0.9 r = pygame.Rect (int(pixx-myradius/2), int(pixy-myradius/2), int(myradius), int(myradius)) col = pygame.Color((255+col.r)/2, (255+col.g)/2, (255+col.b)/2) pygame.draw.ellipse (sfc, col, r) # blank the given dot by an oversize black circle def unsplotch(sfc, dot, col): pixcenter = dot2pixel(dot) margin=4 r = pygame.Rect (int(pixcenter[0]-radius-margin), int(pixcenter[1]-radius-margin), int(radius*2+2*margin), int(radius*2+2*margin)) pygame.draw.ellipse (sfc, col, r) # The Font font = { '\'': ["11", "11"], ' ': [" "], '#': [" "], # half-width space '.': [" ", " ", " ", " ", " ", "11", "11"], ',': [" ", " ", " ", " ", " 11", " 11", "11"], '-': ["", "", "", "1111"], '!': ["11", "11", "11", "11", " ", "11", "11"], '?': [" 1111", "11 11", " 11", " 111", " 11 ", " ", " 11 "], '1': [" 11", " 111", "1111", " 11", " 11", " 11", " 11"], '2': [" 1111", "11 11", " 11", " 111 ", " 11 ", "11 ", "111111"], '3': [" 1111", "11 11", " 11", " 111 ", " 11", "11 11", " 1111"], '4': [" 111", " 1111", "11 11", "111111", " 11", " 11", " 11"], '5': ["111111", "11 ", "11", "11111 ", " 11", "11 11", " 1111"], '6': [" 1111", "11 11", "11", "11111 ", "11 11", "11 11", " 1111"], '7': ["111111", " 11", " 11", " 11", " 11", "11", "11"], '8': [" 1111", "11 11", "11 11", " 1111 ", "11 11", "11 11", " 1111"], '9': [" 1111", "11 11", "11 11", " 11111", " 11", "11 11", " 1111"], '0': [" 1111", "11 11", "11 11", "11 11", "11 11", "11 11", " 1111"], 'A': [" 11 ", " 1111 ", "11 11", "11 11", "111111", "11 11", "11 11"], 'B': ["11111", "11 11", "11 11", "11111 ", "11 11", "11 11", "11111"], 'C': [" 1111 ", "11 11", "11 ", "11 ", "11 ", "11 11", " 1111 "], 'D': ["11111", "11 11", "11 11", "11 11", "11 11", "11 11", "11111"], 'E': ["111111", "11 ", "11 ", "11111 ", "11 ", "11 ", "111111"], 'F': ["111111", "11 ", "11 ", "11111 ", "11 ", "11 ", "11 "], 'G': [" 1111 ", "11 11", "11 ", "11 ", "11 111", "11 11", " 1111 "], 'H': ["11 11", "11 11", "11 11", "111111", "11 11", "11 11", "11 11"], 'I': ["1111", " 11 ", " 11 ", " 11 ", " 11 ", " 11 ", "1111"], 'J': [" 1111", " 11 ", " 11 ", " 11 ", " 11 ", "11 11 ", " 111"], 'K': ["11 11", "11 11", "1111", "111 ", "1111", "11 11", "11 11"], 'L': ["11", "11", "11", "11", "11", "11", "111111"], 'M': ["11 11", "111 111", "1111111", "11 1 11", "11 11", "11 11", "11 11"], 'N': ["11 11", "111 11", "1111 11", "11 1111", "11 111", "11 11", "11 11"], 'O': [" 11111 ", "11 11", "11 11", "11 11", "11 11", "11 11", " 11111 "], 'P': ["11111 ", "11 11", "11 11", "11111 ", "11 ", "11", "11"], 'Q': [" 11111 ", "11 11", "11 11", "11 11", "11 111", "11 11", " 1111 11"], 'R': ["11111 ", "11 11", "11 11", "11111 ", "1111 ", "11 11", "11 11"], 'S': [" 1111", "11 11", "11", " 1111", " 11", "11 11", " 1111"], 'T': ["111111", " 11 ", " 11 ", " 11 ", " 11 ", " 11 ", " 11 "], 'U': ["11 11", "11 11", "11 11", "11 11", "11 11", "11 11", " 11111"], 'V': ["11 11", "11 11", "11 11", "11 11", "11 11", " 1111", " 11 "], 'W': ["11 11", "11 11", "11 11", "11 1 11", "1111111", "111 111", "11 11"], 'X': ["11 11", "11 11", " 1111 ", " 11 ", " 1111 ", "11 11", "11 11"], 'Y': ["11 11", "11 11", " 1111 ", " 11 ", " 11 ", " 11 ", " 11 "], 'Z': ["111111", " 11", " 11", " 11 ", " 11 ", "11 ", "111111"], } # look up the width of the first line of the glyph def glyphwidth(char): try: return max(len(x) for x in font[char]) except: return 0 def unsplotch_all(sfc): for y in range(0, int(resolution[1])): for x in range(0, int(resolution[0])): unsplotch(sfc, [x, y], pygame.Color(16,17,9)) # clear spot, official background brown # layout and draw given text on surface def splotch_string(sfc, text, faulty): # pass 1: compute total width width=sum(glyphwidth(x) for x in text) + len(text) - 1 assert (width <= resolution[0]) # pass 2: draw them, character by character startx = (resolution[0]/2)-(width/2) for char in text: charwidth = glyphwidth(char) # draw each row for y in range(0, int(resolution[1])): # draw each column for x in range(0, charwidth): try: pixel = font[char][y][x] if (faulty and random.random()<0.01): pixel=' ' # burned out elif (faulty and random.random()<0.005): pixel='#' # stuck on if (not pixel.isspace()): splotch(sfc, [startx+x, y], pygame.Color(255,192,9)) # the official orange except: pass startx += charwidth + 1 # char just drawn + space form = cgi.FieldStorage() scale = int(form.getfirst("scale","25")) / 100.0 assert (scale > 0.0 and scale <= 1.0) koan = form.getfirst("koan","") assert (len(koan) > 0 and len(koan) <= 400) faulty = (form.getfirst("faulty","off") == "on") sfc = pygame.image.load(template_file) size = (int(sfc.get_width()*scale), int(sfc.get_height()*scale)) tmpdir=tempfile.mkdtemp() atexit.register(lambda: subprocess.call(["rm","-rf",tmpdir])) index=0 for line in koan.split('\n'): unsplotch_all(sfc) try: splotch_string(sfc,line.upper(),faulty) except: print ('Content-Type: text/plain\n') print ('Please break up long line %s before trying again.' % line) exit(0) disp_surf = pygame.transform.smoothscale(sfc, size) index = index + 1 fname='%s/%03d.jpg' % (tmpdir,index) pygame.image.save(disp_surf, fname) # sys.stderr.write('wrote inglis koan %s frame %s: %s\n' % (line, fname, os.stat(fname))) anifname = "%s/animation.gif" % tmpdir rc = subprocess.call(["convert","-delay","150","-loop","0","-comment","http://web.elastic.org/~fche/inglis/","%s/*.jpg" % tmpdir,anifname]) # sys.stderr.write('wrote inglis animation %s: %s\n' % (anifname, os.stat(anifname))) #assert (rc == 0) sys.stdout.buffer.write (b'Content-Type: image/gif\n') sys.stdout.buffer.write (b'Cache-Control: public, max-age=31536000\n') # print ('Last-Modified: '+email.Utils.formatdate()) sys.stdout.buffer.write (b'\n') f = open(anifname, 'rb') sys.stdout.buffer.write(f.read()) # test with # env QUERY_STRING=koan=foo inglis.py