import pygame, math, sys from pygame.locals import * from PIL import Image from collections import deque WIDTH = 640 HEIGHT = 200 DEG_PER_CIRCLE = 360 DEG_TO_RAD = math.pi*2/DEG_PER_CIRCLE UMAX = 0.436 VMAX = 0.615 ntscRGB = [[(0, 0, 0) for y in xrange(4)] for p in xrange(32)] ntscErr = [(0, 0, 0) for p in xrange(WIDTH)] ntscOutput = [] def yuv2rgb(y, u, v): u *= UMAX v *= VMAX r = max(0.0, y + 1.14 * v) g = max(0.0, y - 0.395 * u - 0.581 * v) b = max(0.0, y + 2.033 * u) return r, g, b def luv2rgb(l, u, v): r = max(0.0, l + v) g = max(0.0, l - 0.707 * u - 0.707 * v) b = max(0.0, l + u) return r, g, b def ntscInitRGB(angle): YScale = [0.0, 0.25, 0.50, 1.0]#[0.0, 0.3333, 0.6667, 1.0] redSum = 0.0 grnSum = 0.0 bluSum = 0.0 rgb = [] for pix in xrange(32): # # U, V for this chroma angle # u = math.cos(angle * DEG_TO_RAD) v = math.sin(angle * DEG_TO_RAD) # # Calculate and NTSC RGB for this SHR pixel # red, grn, blu = luv2rgb(1.0, u, v) redSum += red grnSum += grn bluSum += blu rgb.append((red, grn, blu)) # # Next NTSC chroma pixel # angle = angle - 78.75 if angle > 360.0: angle -= 360.0 if angle < 0.0: angle += 360.0 # # Normalize the RGB values of each NTSC pixel component so they add up to white # redScale = 255.0 * 7.0 / redSum grnScale = 255.0 * 7.0 / grnSum bluScale = 255.0 * 7.0 / bluSum for lum in xrange(4): for pix in xrange(len(rgb)): ntscRGB[pix][lum] = (min(255,int(rgb[pix][0]*redScale*YScale[lum])), min(255,int(rgb[pix][1]*grnScale*YScale[lum])), min(255,int(rgb[pix][2]*bluScale*YScale[lum]))) def ntscInitPrev(): global ntscOutput ntscOutput = deque([(128, 128, 128) for p in xrange(4)]) def ntscPrev(): # # Return previous NTSC chroma cycle output # l = len(ntscOutput) red = ntscOutput[0][0] * 0.57 grn = ntscOutput[0][1] * 0.57 blu = ntscOutput[0][2] * 0.57 for p in xrange(1, l): red += ntscOutput[p][0] grn += ntscOutput[p][1] blu += ntscOutput[p][2] return (min(255,red), min(255,grn), min(255,blu)) def ntscBest(ntsc, rgb): nearest = 195075 lumBest = 0 for lum in xrange(4): red = rgb[0] - ntsc[lum][0] grn = rgb[1] - ntsc[lum][1] blu = rgb[2] - ntsc[lum][2] dist = red*red + grn*grn + blu*blu if dist < nearest: nearest = dist lumBest = lum # # Update ouput list # ntscOutput.popleft() ntscOutput.append(ntsc[lumBest]) return lumBest def ntscMapRGB(rgb, err, x): shr = [] redErr = err[0] grnErr = err[1] bluErr = err[2] for p in xrange(len(rgb)): redErr += ntscErr[x + p][0] grnErr += ntscErr[x + p][1] bluErr += ntscErr[x + p][2] prev = ntscPrev() red = min(255,max(0, rgb[p][0] + redErr - prev[0])) grn = min(255,max(0, rgb[p][1] + grnErr - prev[1])) blu = min(255,max(0, rgb[p][2] + bluErr - prev[2])) shr.append(ntscBest(ntscRGB[p], (red, grn, blu))) redErr = int((red - ntscRGB[p][shr[p]][0]) / 4.57 / 1.0) grnErr = int((grn - ntscRGB[p][shr[p]][1]) / 4.57 / 1.0) bluErr = int((blu - ntscRGB[p][shr[p]][2]) / 4.57 / 1.0) ntscErr[x + p] = (redErr, grnErr, bluErr) return shr, (redErr, grnErr, bluErr) def displayBars(): for l in xrange(4): for bar in xrange(len(ntscRGB)): surface.fill(ntscRGB[bar][l], pygame.Rect(bar * 20, l * 50, 20, 50)) pygame.display.flip() if len(sys.argv) > 1: imagefile = sys.argv[1] else: imagefile = "image.jpg" image = Image.open(imagefile) if image.size[0] <> WIDTH or image.size[1] <> HEIGHT: image = image.resize((WIDTH, HEIGHT), Image.ANTIALIAS) pygame.init() surface = pygame.display.set_mode((WIDTH,HEIGHT)) surfpix = pygame.PixelArray(surface) ntscInitRGB(103.0) ntscInitPrev() displayBars() for y in xrange(HEIGHT): print '; scanline: ', y errRight = (0, 0, 0) for x in xrange(0, WIDTH, len(ntscRGB)): rgb = [] for p in xrange(len(ntscRGB)): rgb.append(image.getpixel((x + p, y))) shr, errRight = ntscMapRGB(rgb, errRight, x) # # Copy displayable SHR pixels # for p in xrange(len(shr)): surfpix[p + x][y] = (ntscRGB[p][shr[p]][0], ntscRGB[p][shr[p]][1], ntscRGB[p][shr[p]][2]) # # Output SHR pixels for assembly # print '\t!BYTE\t$%02X, ' % ((shr[0] << 6) | (shr[1] << 4) | (shr[2] << 2) | shr[3]), print '$%02X, ' % ((shr[4] << 6) | (shr[5] << 4) | (shr[6] << 2) | shr[7]), print '$%02X, ' % ((shr[8] << 6) | (shr[9] << 4) | (shr[10] << 2) | shr[11]), print '$%02X, ' % ((shr[12] << 6) | (shr[13] << 4) | (shr[14] << 2) | shr[15]), print '$%02X, ' % ((shr[16] << 6) | (shr[17] << 4) | (shr[18] << 2) | shr[19]), print '$%02X, ' % ((shr[20] << 6) | (shr[21] << 4) | (shr[22] << 2) | shr[23]), print '$%02X, ' % ((shr[24] << 6) | (shr[25] << 4) | (shr[26] << 2) | shr[27]), print '$%02X' % ((shr[28] << 6) | (shr[29] << 4) | (shr[30] << 2) | shr[31]) pygame.display.flip()