SHR-NTSC/ntsc-640-err.py
2017-07-20 14:33:07 -07:00

169 lines
5.4 KiB
Python
Executable File

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()