From 0dcc35b7d902c31954cdf8200ed2625df1e0104e Mon Sep 17 00:00:00 2001 From: David Schmenk Date: Thu, 20 Jul 2017 14:33:07 -0700 Subject: [PATCH] Initial check-in --- makefile | 36 ++++++++++ ntsc-140.py | 169 ++++++++++++++++++++++++++++++++++++++++++++++ ntsc-160.py | 174 ++++++++++++++++++++++++++++++++++++++++++++++++ ntsc-640-err.py | 168 ++++++++++++++++++++++++++++++++++++++++++++++ ntsc-640.py | 156 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 703 insertions(+) create mode 100755 makefile create mode 100644 ntsc-140.py create mode 100644 ntsc-160.py create mode 100755 ntsc-640-err.py create mode 100644 ntsc-640.py diff --git a/makefile b/makefile new file mode 100755 index 0000000..9a6706b --- /dev/null +++ b/makefile @@ -0,0 +1,36 @@ +.SUFFIXES = +AFLAGS = -o $@ +IMGVIEW = IMGVIEW.SYSTEM\#FF2000 +# +# Image filetypes for Virtual ][ +# +PLATYPE = .\$$ED +BINTYPE = .BIN +SYSTYPE = .SYS +TXTTYPE = .TXT +# +# Image filetypes for CiderPress +# +#RELTYPE = \#FE1000 +#INTERPTYPE = \#050000 +#BINTYPE = \#060000 +#SYSTYPE = \#FF2000 +#TXTTYPE = \#040000 + +all: $(IMGVIEW) +bin: imgview.bin + +clean: + -rm *.o *~ *.a *.bin + +image.asm: ntsc.py + python ntsc.py > image.asm + +$(IMGVIEW): imgview.asm image.asm + acme --setpc 8192 -o $(IMGVIEW) imgview.asm + ac -d IMGVIEW.PO IMGVIEW.SYSTEM + ac -p IMGVIEW.PO IMGVIEW.SYSTEM SYS < IMGVIEW.SYSTEM#FF2000 + +imgview.bin: imgview.asm image.asm + acme --setpc 4096 -o imgview.bin imgview.asm + diff --git a/ntsc-140.py b/ntsc-140.py new file mode 100644 index 0000000..e3a72c2 --- /dev/null +++ b/ntsc-140.py @@ -0,0 +1,169 @@ +import pygame, math, sys +from pygame.locals import * +from PIL import Image + +WIDTH = 140 +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)] +ntscPixel = [[(0, 0, 0)] for m in xrange(7)] +ntscRange = [0, 4, 9, 13, 18, 23, 27] +shrPixels = [] +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(y, u, v): + r = max(0.0, y + v) + g = max(0.0, y - 0.707 * u - 0.707 * v) + b = max(0.0, y + u) + return (r, g, b) + +def ntscInitRGB(angle): + YScale = [0.0, 0.25, 0.50, 1.0]#[0.0, 0.3334, 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 lumInc(seq): + s = 0 + seq[s] += 1 + while seq[s] == 4: + seq[s] = 0 + s += 1 + if s == len(seq): + return False + seq[s] += 1 + return True + +def ntscInitPixels(): + # + # NTSC pixel mapping to SHR range + # + for pix7 in xrange(7): + shrpix = [0 for p in xrange(5)] + while lumInc(shrpix): + red = 0 + grn = 0 + blu = 0 + for s in xrange(len(shrpix)): + red += ntscRGB[ntscRange[pix7]+s][shrpix[s]][0] + grn += ntscRGB[ntscRange[pix7]+s][shrpix[s]][1] + blu += ntscRGB[ntscRange[pix7]+s][shrpix[s]][2] + ntscPixel[pix7].append((min(255,int(red * 0.914)), min(255,int(grn * 0.914)), min(255,int(blu * 0.914)))) # 0.914 = 5 / 4.57 + +def ntscMapRGB(rgb, x): + global shrPixels + + nearest = 195075 + nBest = 0 + errRed = 0 + errGrn = 0 + errBlu = 0 + pix7 = x % 7 + if pix7 == 0: + shrPixels = [0 for n in xrange(32)] + # + # Look for best RGB match + # + redMatch = rgb[0] + grnMatch = rgb[1] + bluMatch = rgb[2] + for n in xrange(len(ntscPixel[pix7])): + red = redMatch - ntscPixel[pix7][n][0] + grn = grnMatch - ntscPixel[pix7][n][1] + blu = bluMatch - ntscPixel[pix7][n][2] + dist = red*red + grn*grn + blu*blu + if dist < nearest: + nearest = dist + nBest = n + errRed = red + errGrn = grn + errBlu = blu + rgbBest = ntscPixel[pix7][nBest] + # + # Convert to SHR pixels + # + for n in xrange(5): + shrPixels[ntscRange[pix7] + n] = nBest & 0x03 + nBest >>= 2 + # + # Output SHR pixels for assembly + # + if pix7 == 6: + print '\t!BYTE\t$%02X, ' % ((shrPixels[0] << 6) | (shrPixels[1] << 4) | (shrPixels[2] << 2) | shrPixels[3]), + print '$%02X, ' % ((shrPixels[4] << 6) | (shrPixels[5] << 4) | (shrPixels[6] << 2) | shrPixels[7]), + print '$%02X, ' % ((shrPixels[8] << 6) | (shrPixels[9] << 4) | (shrPixels[10] << 2) | shrPixels[11]), + print '$%02X, ' % ((shrPixels[12] << 6) | (shrPixels[13] << 4) | (shrPixels[14] << 2) | shrPixels[15]), + print '$%02X, ' % ((shrPixels[16] << 6) | (shrPixels[17] << 4) | (shrPixels[18] << 2) | shrPixels[19]), + print '$%02X, ' % ((shrPixels[20] << 6) | (shrPixels[21] << 4) | (shrPixels[22] << 2) | shrPixels[23]), + print '$%02X, ' % ((shrPixels[24] << 6) | (shrPixels[25] << 4) | (shrPixels[26] << 2) | shrPixels[27]), + print '$%02X' % ((shrPixels[28] << 6) | (shrPixels[29] << 4) | (shrPixels[30] << 2) | shrPixels[31]) + return rgbBest + +def displayBars(): + for bar in xrange(32): + for lum in xrange(4): + surface.fill(ntscRGB[bar][lum], pygame.Rect(bar*4.57, lum*50, 5, 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) +ntscInitPixels() +displayBars() +for y in xrange(HEIGHT): + print '; scanline: ', y + for x in xrange(WIDTH): + surfpix[x][y] = ntscMapRGB(image.getpixel((x, y)), x) + pygame.display.flip() diff --git a/ntsc-160.py b/ntsc-160.py new file mode 100644 index 0000000..41a8045 --- /dev/null +++ b/ntsc-160.py @@ -0,0 +1,174 @@ +import pygame, math, sys +from pygame.locals import * +from PIL import Image + +WIDTH = 160 +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)] +ntscPixel = [[(0, 0, 0)] for m in xrange(8)] +shrPixels = [] +ntscPrev = (0, 0, 0) + +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(y, u, v): + r = max(0.0, y + v) + g = max(0.0, y - 0.707 * u - 0.707 * v) + b = max(0.0, y + u) + return (r, g, b) + +def ntscInitRGB(angle): + YScale = [0.0, 0.25, 0.50, 1.0]#[0.0, 0.3334, 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 lumInc(seq): + s = 0 + seq[s] += 1 + while seq[s] == 4: + seq[s] = 0 + s += 1 + if s == len(seq): + return False + seq[s] += 1 + return True + +def ntscInitPixels(): + # + # NTSC pixel mapping to SHR range + # + for pix8 in xrange(8): + shrpix = [0 for p in xrange(4)] + while lumInc(shrpix): + red = 0 + grn = 0 + blu = 0 + for s in xrange(len(shrpix)): + red += ntscRGB[pix8*4+s][shrpix[s]][0] + grn += ntscRGB[pix8*4+s][shrpix[s]][1] + blu += ntscRGB[pix8*4+s][shrpix[s]][2] + ntscPixel[pix8].append((red, grn, blu)) + +def ntscMapRGB(rgb, x): + global shrPixels + global ntscPrev + + pix8 = x % 8 + if pix8 == 0: + shrPixels = [0 for n in xrange(32)] + # + # Adjust source color based on reduced destination precision + # + redMatch = max(0,int(rgb[0] - ntscPrev[0] * 0.57)) + grnMatch = max(0,int(rgb[1] - ntscPrev[1] * 0.57)) + bluMatch = max(0,int(rgb[2] - ntscPrev[2] * 0.57)) + # + # Look for best RGB match + # + nearest = 195075 + nBest = 0 + for n in xrange(len(ntscPixel[pix8])): + red = redMatch - ntscPixel[pix8][n][0] + grn = grnMatch - ntscPixel[pix8][n][1] + blu = bluMatch - ntscPixel[pix8][n][2] + dist = red*red + grn*grn + blu*blu + if dist < nearest: + nearest = dist + nBest = n + # + # Update ouput list + # + rgbBest = ntscPixel[pix8][nBest] + # + # Convert to SHR pixels + # + for n in xrange(4): + shr = nBest & 0x03 + shrPixels[pix8*4 + n] = shr + nBest >>= 2 + # + # Save last pixel + # + ntscPrev = ntscRGB[(x*4 + 3) & 0x1F][shr] + # + # Output SHR pixels for assembly + # + if pix8 == 7: + print '\t!BYTE\t$%02X, ' % ((shrPixels[0] << 6) | (shrPixels[1] << 4) | (shrPixels[2] << 2) | shrPixels[3]), + print '$%02X, ' % ((shrPixels[4] << 6) | (shrPixels[5] << 4) | (shrPixels[6] << 2) | shrPixels[7]), + print '$%02X, ' % ((shrPixels[8] << 6) | (shrPixels[9] << 4) | (shrPixels[10] << 2) | shrPixels[11]), + print '$%02X, ' % ((shrPixels[12] << 6) | (shrPixels[13] << 4) | (shrPixels[14] << 2) | shrPixels[15]), + print '$%02X, ' % ((shrPixels[16] << 6) | (shrPixels[17] << 4) | (shrPixels[18] << 2) | shrPixels[19]), + print '$%02X, ' % ((shrPixels[20] << 6) | (shrPixels[21] << 4) | (shrPixels[22] << 2) | shrPixels[23]), + print '$%02X, ' % ((shrPixels[24] << 6) | (shrPixels[25] << 4) | (shrPixels[26] << 2) | shrPixels[27]), + print '$%02X' % ((shrPixels[28] << 6) | (shrPixels[29] << 4) | (shrPixels[30] << 2) | shrPixels[31]) + return rgbBest + +def displayBars(): + for bar in xrange(32): + for lum in xrange(4): + surface.fill(ntscRGB[bar][lum], pygame.Rect(bar * 5, lum*50, 5, 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) +ntscInitPixels() +displayBars() +for y in xrange(HEIGHT): + print '; scanline: ', y + for x in xrange(WIDTH): + surfpix[x][y] = ntscMapRGB(image.getpixel((x, y)), x) + pygame.display.flip() diff --git a/ntsc-640-err.py b/ntsc-640-err.py new file mode 100755 index 0000000..347fe47 --- /dev/null +++ b/ntsc-640-err.py @@ -0,0 +1,168 @@ +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(y, u, v): + r = max(0.0, y + v) + g = max(0.0, y - 0.707 * u - 0.707 * v) + b = max(0.0, y + 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() diff --git a/ntsc-640.py b/ntsc-640.py new file mode 100644 index 0000000..951e1bb --- /dev/null +++ b/ntsc-640.py @@ -0,0 +1,156 @@ +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)] +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(y, u, v): + r = max(0.0, y + v) + g = max(0.0, y - 0.707 * u - 0.707 * v) + b = max(0.0, y + 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): + shr = [] + for p in xrange(len(rgb)): + prev = ntscPrev() + red = max(0, rgb[p][0] - prev[0]) + grn = max(0, rgb[p][1] - prev[1]) + blu = max(0, rgb[p][2] - prev[2]) + shr.append(ntscBest(ntscRGB[p], (red, grn, blu))) + return shr + +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 + for x in xrange(0, WIDTH, len(ntscRGB)): + rgb = [] + for p in xrange(len(ntscRGB)): + rgb.append(image.getpixel((x + p, y))) + shr = ntscMapRGB(rgb) + # + # 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()