diff --git a/ntsc_colours.py b/ntsc_colours.py index e2afcf0..1602e38 100644 --- a/ntsc_colours.py +++ b/ntsc_colours.py @@ -17,15 +17,19 @@ def main(): # For each sequence of 8 pixels, compute the RGB colour of the right-most # pixel, using NTSC emulation. for bits in range(256): + bits8 = np.empty((8,), dtype=np.bool) for i in range(8): - bitmap[0, i] = bits & (1 << i) + bits8[i] = bits & (1 << i) + # Double Hi-Res has a timing shift that rotates the displayed bits one + # position with respect to NTSC phase. + bitmap[0, :] = bits8[[3, 0, 1, 2, 7, 4, 5, 6]] ntsc = s.bitmap_to_ntsc(bitmap) last_colour = ntsc[0, -1, :] colours[bits] = last_colour unique.add(tuple(last_colour)) print("%d: np.array((%d, %d, %d))," % ( - bits, last_colour[0], last_colour[1], last_colour[2])) + bits, last_colour[0], last_colour[1], last_colour[2])) print("}") print("# %d unique colours" % len(unique)) @@ -34,6 +38,7 @@ def main(): for x, hsv in enumerate(sorted([tuple(colour.RGB_to_HSV(c / 256)) for c in colours.values()])): im[0:128, x * 16: (x + 1) * 16, :] = colour.HSV_to_RGB(hsv) * 256 + Image.fromarray(im).show() diff --git a/palette.py b/palette.py index b489acc..d9a94cb 100644 --- a/palette.py +++ b/palette.py @@ -121,263 +121,263 @@ class NTSCPalette(Palette): # Computed using ntsc_colours.py SRGB = { 0: np.array((0, 0, 0)), - 1: np.array((0, 0, 62)), - 2: np.array((0, 18, 0)), - 3: np.array((0, 3, 28)), - 4: np.array((44, 14, 0)), + 1: np.array((0, 0, 115)), + 2: np.array((0, 37, 0)), + 3: np.array((0, 23, 88)), + 4: np.array((14, 14, 0)), 5: np.array((0, 0, 0)), - 6: np.array((0, 32, 0)), - 7: np.array((0, 18, 0)), - 8: np.array((67, 0, 34)), - 9: np.array((22, 0, 96)), + 6: np.array((0, 52, 0)), + 7: np.array((0, 37, 0)), + 8: np.array((64, 0, 26)), + 9: np.array((49, 0, 141)), 10: np.array((0, 0, 0)), - 11: np.array((0, 0, 62)), - 12: np.array((112, 0, 0)), - 13: np.array((67, 0, 34)), - 14: np.array((44, 14, 0)), + 11: np.array((0, 0, 115)), + 12: np.array((79, 0, 0)), + 13: np.array((64, 0, 26)), + 14: np.array((14, 14, 0)), 15: np.array((0, 0, 0)), - 16: np.array((24, 54, 131)), - 17: np.array((0, 40, 193)), - 18: np.array((0, 73, 97)), - 19: np.array((0, 58, 159)), - 20: np.array((69, 69, 69)), - 21: np.array((24, 54, 131)), - 22: np.array((1, 87, 35)), - 23: np.array((0, 73, 97)), - 24: np.array((91, 36, 165)), - 25: np.array((47, 22, 227)), - 26: np.array((24, 54, 131)), - 27: np.array((0, 40, 193)), - 28: np.array((136, 50, 103)), - 29: np.array((91, 36, 165)), - 30: np.array((69, 69, 69)), - 31: np.array((24, 54, 131)), - 32: np.array((1, 87, 35)), - 33: np.array((0, 73, 97)), - 34: np.array((0, 105, 1)), - 35: np.array((0, 91, 63)), - 36: np.array((46, 101, 0)), - 37: np.array((1, 87, 35)), - 38: np.array((0, 120, 0)), - 39: np.array((0, 105, 1)), - 40: np.array((69, 69, 69)), - 41: np.array((24, 54, 131)), - 42: np.array((1, 87, 35)), - 43: np.array((0, 73, 97)), - 44: np.array((113, 83, 7)), - 45: np.array((69, 69, 69)), - 46: np.array((46, 101, 0)), - 47: np.array((1, 87, 35)), - 48: np.array((26, 142, 166)), - 49: np.array((0, 127, 228)), - 50: np.array((0, 160, 132)), - 51: np.array((0, 146, 194)), - 52: np.array((70, 156, 104)), - 53: np.array((26, 142, 166)), - 54: np.array((3, 174, 70)), - 55: np.array((0, 160, 132)), - 56: np.array((93, 124, 200)), - 57: np.array((48, 109, 255)), - 58: np.array((26, 142, 166)), - 59: np.array((0, 127, 228)), - 60: np.array((138, 138, 138)), - 61: np.array((93, 124, 200)), - 62: np.array((70, 156, 104)), - 63: np.array((26, 142, 166)), - 64: np.array((113, 83, 7)), - 65: np.array((69, 69, 69)), - 66: np.array((46, 101, 0)), - 67: np.array((1, 87, 35)), - 68: np.array((158, 97, 0)), - 69: np.array((113, 83, 7)), - 70: np.array((91, 116, 0)), - 71: np.array((46, 101, 0)), - 72: np.array((181, 65, 41)), - 73: np.array((136, 50, 103)), - 74: np.array((113, 83, 7)), - 75: np.array((69, 69, 69)), - 76: np.array((226, 79, 0)), - 77: np.array((181, 65, 41)), - 78: np.array((158, 97, 0)), - 79: np.array((113, 83, 7)), - 80: np.array((138, 138, 138)), - 81: np.array((93, 124, 200)), - 82: np.array((70, 156, 104)), - 83: np.array((26, 142, 166)), - 84: np.array((183, 152, 76)), - 85: np.array((138, 138, 138)), - 86: np.array((115, 171, 42)), - 87: np.array((70, 156, 104)), - 88: np.array((205, 120, 172)), - 89: np.array((161, 105, 234)), - 90: np.array((138, 138, 138)), - 91: np.array((93, 124, 200)), - 92: np.array((250, 134, 110)), - 93: np.array((205, 120, 172)), - 94: np.array((183, 152, 76)), - 95: np.array((138, 138, 138)), - 96: np.array((115, 171, 42)), - 97: np.array((70, 156, 104)), - 98: np.array((48, 189, 8)), - 99: np.array((3, 174, 70)), - 100: np.array((160, 185, 0)), - 101: np.array((115, 171, 42)), - 102: np.array((93, 203, 0)), - 103: np.array((48, 189, 8)), - 104: np.array((183, 152, 76)), - 105: np.array((138, 138, 138)), - 106: np.array((115, 171, 42)), - 107: np.array((70, 156, 104)), - 108: np.array((227, 167, 14)), - 109: np.array((183, 152, 76)), - 110: np.array((160, 185, 0)), - 111: np.array((115, 171, 42)), - 112: np.array((140, 225, 173)), - 113: np.array((95, 211, 235)), - 114: np.array((72, 244, 139)), - 115: np.array((28, 229, 201)), - 116: np.array((184, 240, 111)), - 117: np.array((140, 225, 173)), - 118: np.array((117, 255, 77)), - 119: np.array((72, 244, 139)), - 120: np.array((207, 207, 207)), - 121: np.array((162, 193, 255)), - 122: np.array((140, 225, 173)), - 123: np.array((95, 211, 235)), - 124: np.array((252, 221, 145)), - 125: np.array((207, 207, 207)), - 126: np.array((184, 240, 111)), - 127: np.array((140, 225, 173)), - 128: np.array((136, 50, 103)), - 129: np.array((91, 36, 165)), - 130: np.array((69, 69, 69)), - 131: np.array((24, 54, 131)), - 132: np.array((181, 65, 41)), - 133: np.array((136, 50, 103)), - 134: np.array((113, 83, 7)), - 135: np.array((69, 69, 69)), - 136: np.array((203, 32, 137)), - 137: np.array((159, 18, 199)), - 138: np.array((136, 50, 103)), - 139: np.array((91, 36, 165)), - 140: np.array((248, 47, 75)), - 141: np.array((203, 32, 137)), - 142: np.array((181, 65, 41)), - 143: np.array((136, 50, 103)), - 144: np.array((161, 105, 234)), - 145: np.array((116, 91, 255)), - 146: np.array((93, 124, 200)), - 147: np.array((48, 109, 255)), - 148: np.array((205, 120, 172)), - 149: np.array((161, 105, 234)), - 150: np.array((138, 138, 138)), - 151: np.array((93, 124, 200)), - 152: np.array((228, 87, 255)), - 153: np.array((183, 73, 255)), - 154: np.array((161, 105, 234)), - 155: np.array((116, 91, 255)), - 156: np.array((255, 101, 206)), - 157: np.array((228, 87, 255)), - 158: np.array((205, 120, 172)), - 159: np.array((161, 105, 234)), - 160: np.array((138, 138, 138)), - 161: np.array((93, 124, 200)), - 162: np.array((70, 156, 104)), - 163: np.array((26, 142, 166)), - 164: np.array((183, 152, 76)), - 165: np.array((138, 138, 138)), - 166: np.array((115, 171, 42)), - 167: np.array((70, 156, 104)), - 168: np.array((205, 120, 172)), - 169: np.array((161, 105, 234)), - 170: np.array((138, 138, 138)), - 171: np.array((93, 124, 200)), - 172: np.array((250, 134, 110)), - 173: np.array((205, 120, 172)), - 174: np.array((183, 152, 76)), - 175: np.array((138, 138, 138)), - 176: np.array((162, 193, 255)), - 177: np.array((118, 178, 255)), - 178: np.array((95, 211, 235)), - 179: np.array((50, 197, 255)), - 180: np.array((207, 207, 207)), - 181: np.array((162, 193, 255)), - 182: np.array((140, 225, 173)), - 183: np.array((95, 211, 235)), - 184: np.array((230, 174, 255)), - 185: np.array((185, 160, 255)), - 186: np.array((162, 193, 255)), - 187: np.array((118, 178, 255)), - 188: np.array((255, 189, 241)), - 189: np.array((230, 174, 255)), - 190: np.array((207, 207, 207)), - 191: np.array((162, 193, 255)), - 192: np.array((250, 134, 110)), - 193: np.array((205, 120, 172)), - 194: np.array((183, 152, 76)), - 195: np.array((138, 138, 138)), - 196: np.array((255, 148, 48)), - 197: np.array((250, 134, 110)), - 198: np.array((227, 167, 14)), - 199: np.array((183, 152, 76)), - 200: np.array((255, 116, 144)), - 201: np.array((255, 101, 206)), - 202: np.array((250, 134, 110)), - 203: np.array((205, 120, 172)), - 204: np.array((255, 130, 82)), - 205: np.array((255, 116, 144)), - 206: np.array((255, 148, 48)), - 207: np.array((250, 134, 110)), - 208: np.array((255, 189, 241)), - 209: np.array((230, 174, 255)), - 210: np.array((207, 207, 207)), - 211: np.array((162, 193, 255)), - 212: np.array((255, 203, 179)), - 213: np.array((255, 189, 241)), - 214: np.array((252, 221, 145)), - 215: np.array((207, 207, 207)), - 216: np.array((255, 171, 255)), - 217: np.array((255, 156, 255)), - 218: np.array((255, 189, 241)), - 219: np.array((230, 174, 255)), - 220: np.array((255, 185, 213)), - 221: np.array((255, 171, 255)), - 222: np.array((255, 203, 179)), - 223: np.array((255, 189, 241)), - 224: np.array((252, 221, 145)), - 225: np.array((207, 207, 207)), - 226: np.array((184, 240, 111)), - 227: np.array((140, 225, 173)), - 228: np.array((255, 236, 83)), - 229: np.array((252, 221, 145)), - 230: np.array((229, 254, 49)), - 231: np.array((184, 240, 111)), - 232: np.array((255, 203, 179)), - 233: np.array((255, 189, 241)), - 234: np.array((252, 221, 145)), - 235: np.array((207, 207, 207)), - 236: np.array((255, 218, 117)), - 237: np.array((255, 203, 179)), - 238: np.array((255, 236, 83)), - 239: np.array((252, 221, 145)), - 240: np.array((255, 255, 255)), - 241: np.array((232, 255, 255)), - 242: np.array((209, 255, 242)), - 243: np.array((164, 255, 255)), - 244: np.array((255, 255, 214)), - 245: np.array((255, 255, 255)), - 246: np.array((254, 255, 180)), - 247: np.array((209, 255, 242)), - 248: np.array((255, 255, 255)), - 249: np.array((255, 244, 255)), - 250: np.array((255, 255, 255)), - 251: np.array((232, 255, 255)), - 252: np.array((255, 255, 248)), - 253: np.array((255, 255, 255)), - 254: np.array((255, 255, 214)), - 255: np.array((255, 255, 255)), + 16: np.array((49, 48, 178)), + 17: np.array((34, 34, 255)), + 18: np.array((0, 86, 152)), + 19: np.array((0, 71, 255)), + 20: np.array((63, 63, 63)), + 21: np.array((49, 48, 178)), + 22: np.array((0, 101, 37)), + 23: np.array((0, 86, 152)), + 24: np.array((113, 10, 204)), + 25: np.array((98, 0, 255)), + 26: np.array((49, 48, 178)), + 27: np.array((34, 34, 255)), + 28: np.array((128, 25, 89)), + 29: np.array((113, 10, 204)), + 30: np.array((63, 63, 63)), + 31: np.array((49, 48, 178)), + 32: np.array((0, 101, 37)), + 33: np.array((0, 86, 152)), + 34: np.array((0, 139, 11)), + 35: np.array((0, 124, 126)), + 36: np.array((13, 116, 0)), + 37: np.array((0, 101, 37)), + 38: np.array((0, 154, 0)), + 39: np.array((0, 139, 11)), + 40: np.array((63, 63, 63)), + 41: np.array((49, 48, 178)), + 42: np.array((0, 101, 37)), + 43: np.array((0, 86, 152)), + 44: np.array((78, 78, 0)), + 45: np.array((63, 63, 63)), + 46: np.array((13, 116, 0)), + 47: np.array((0, 101, 37)), + 48: np.array((48, 150, 216)), + 49: np.array((33, 135, 255)), + 50: np.array((0, 188, 190)), + 51: np.array((0, 173, 255)), + 52: np.array((62, 165, 101)), + 53: np.array((48, 150, 216)), + 54: np.array((0, 203, 75)), + 55: np.array((0, 188, 190)), + 56: np.array((112, 112, 242)), + 57: np.array((98, 97, 255)), + 58: np.array((48, 150, 216)), + 59: np.array((33, 135, 255)), + 60: np.array((127, 127, 127)), + 61: np.array((112, 112, 242)), + 62: np.array((62, 165, 101)), + 63: np.array((48, 150, 216)), + 64: np.array((78, 78, 0)), + 65: np.array((63, 63, 63)), + 66: np.array((13, 116, 0)), + 67: np.array((0, 101, 37)), + 68: np.array((93, 93, 0)), + 69: np.array((78, 78, 0)), + 70: np.array((28, 131, 0)), + 71: np.array((13, 116, 0)), + 72: np.array((142, 40, 0)), + 73: np.array((128, 25, 89)), + 74: np.array((78, 78, 0)), + 75: np.array((63, 63, 63)), + 76: np.array((157, 55, 0)), + 77: np.array((142, 40, 0)), + 78: np.array((93, 93, 0)), + 79: np.array((78, 78, 0)), + 80: np.array((127, 127, 127)), + 81: np.array((112, 112, 242)), + 82: np.array((62, 165, 101)), + 83: np.array((48, 150, 216)), + 84: np.array((142, 142, 12)), + 85: np.array((127, 127, 127)), + 86: np.array((77, 180, 0)), + 87: np.array((62, 165, 101)), + 88: np.array((192, 89, 153)), + 89: np.array((177, 74, 255)), + 90: np.array((127, 127, 127)), + 91: np.array((112, 112, 242)), + 92: np.array((206, 104, 38)), + 93: np.array((192, 89, 153)), + 94: np.array((142, 142, 12)), + 95: np.array((127, 127, 127)), + 96: np.array((77, 180, 0)), + 97: np.array((62, 165, 101)), + 98: np.array((13, 218, 0)), + 99: np.array((0, 203, 75)), + 100: np.array((92, 195, 0)), + 101: np.array((77, 180, 0)), + 102: np.array((27, 233, 0)), + 103: np.array((13, 218, 0)), + 104: np.array((142, 142, 12)), + 105: np.array((127, 127, 127)), + 106: np.array((77, 180, 0)), + 107: np.array((62, 165, 101)), + 108: np.array((156, 157, 0)), + 109: np.array((142, 142, 12)), + 110: np.array((92, 195, 0)), + 111: np.array((77, 180, 0)), + 112: np.array((126, 229, 165)), + 113: np.array((112, 214, 255)), + 114: np.array((62, 255, 138)), + 115: np.array((47, 252, 253)), + 116: np.array((141, 244, 50)), + 117: np.array((126, 229, 165)), + 118: np.array((76, 255, 23)), + 119: np.array((62, 255, 138)), + 120: np.array((191, 191, 191)), + 121: np.array((176, 176, 255)), + 122: np.array((126, 229, 165)), + 123: np.array((112, 214, 255)), + 124: np.array((205, 206, 76)), + 125: np.array((191, 191, 191)), + 126: np.array((141, 244, 50)), + 127: np.array((126, 229, 165)), + 128: np.array((128, 25, 89)), + 129: np.array((113, 10, 204)), + 130: np.array((63, 63, 63)), + 131: np.array((49, 48, 178)), + 132: np.array((142, 40, 0)), + 133: np.array((128, 25, 89)), + 134: np.array((78, 78, 0)), + 135: np.array((63, 63, 63)), + 136: np.array((192, 0, 116)), + 137: np.array((178, 0, 231)), + 138: np.array((128, 25, 89)), + 139: np.array((113, 10, 204)), + 140: np.array((207, 2, 1)), + 141: np.array((192, 0, 116)), + 142: np.array((142, 40, 0)), + 143: np.array((128, 25, 89)), + 144: np.array((177, 74, 255)), + 145: np.array((162, 59, 255)), + 146: np.array((112, 112, 242)), + 147: np.array((98, 97, 255)), + 148: np.array((192, 89, 153)), + 149: np.array((177, 74, 255)), + 150: np.array((127, 127, 127)), + 151: np.array((112, 112, 242)), + 152: np.array((241, 36, 255)), + 153: np.array((227, 21, 255)), + 154: np.array((177, 74, 255)), + 155: np.array((162, 59, 255)), + 156: np.array((255, 51, 179)), + 157: np.array((241, 36, 255)), + 158: np.array((192, 89, 153)), + 159: np.array((177, 74, 255)), + 160: np.array((127, 127, 127)), + 161: np.array((112, 112, 242)), + 162: np.array((62, 165, 101)), + 163: np.array((48, 150, 216)), + 164: np.array((142, 142, 12)), + 165: np.array((127, 127, 127)), + 166: np.array((77, 180, 0)), + 167: np.array((62, 165, 101)), + 168: np.array((192, 89, 153)), + 169: np.array((177, 74, 255)), + 170: np.array((127, 127, 127)), + 171: np.array((112, 112, 242)), + 172: np.array((206, 104, 38)), + 173: np.array((192, 89, 153)), + 174: np.array((142, 142, 12)), + 175: np.array((127, 127, 127)), + 176: np.array((176, 176, 255)), + 177: np.array((161, 161, 255)), + 178: np.array((112, 214, 255)), + 179: np.array((97, 199, 255)), + 180: np.array((191, 191, 191)), + 181: np.array((176, 176, 255)), + 182: np.array((126, 229, 165)), + 183: np.array((112, 214, 255)), + 184: np.array((241, 138, 255)), + 185: np.array((226, 123, 255)), + 186: np.array((176, 176, 255)), + 187: np.array((161, 161, 255)), + 188: np.array((255, 153, 217)), + 189: np.array((241, 138, 255)), + 190: np.array((191, 191, 191)), + 191: np.array((176, 176, 255)), + 192: np.array((206, 104, 38)), + 193: np.array((192, 89, 153)), + 194: np.array((142, 142, 12)), + 195: np.array((127, 127, 127)), + 196: np.array((221, 119, 0)), + 197: np.array((206, 104, 38)), + 198: np.array((156, 157, 0)), + 199: np.array((142, 142, 12)), + 200: np.array((255, 66, 64)), + 201: np.array((255, 51, 179)), + 202: np.array((206, 104, 38)), + 203: np.array((192, 89, 153)), + 204: np.array((255, 81, 0)), + 205: np.array((255, 66, 64)), + 206: np.array((221, 119, 0)), + 207: np.array((206, 104, 38)), + 208: np.array((255, 153, 217)), + 209: np.array((241, 138, 255)), + 210: np.array((191, 191, 191)), + 211: np.array((176, 176, 255)), + 212: np.array((255, 168, 102)), + 213: np.array((255, 153, 217)), + 214: np.array((205, 206, 76)), + 215: np.array((191, 191, 191)), + 216: np.array((255, 115, 243)), + 217: np.array((255, 100, 255)), + 218: np.array((255, 153, 217)), + 219: np.array((241, 138, 255)), + 220: np.array((255, 130, 128)), + 221: np.array((255, 115, 243)), + 222: np.array((255, 168, 102)), + 223: np.array((255, 153, 217)), + 224: np.array((205, 206, 76)), + 225: np.array((191, 191, 191)), + 226: np.array((141, 244, 50)), + 227: np.array((126, 229, 165)), + 228: np.array((220, 220, 0)), + 229: np.array((205, 206, 76)), + 230: np.array((156, 255, 0)), + 231: np.array((141, 244, 50)), + 232: np.array((255, 168, 102)), + 233: np.array((255, 153, 217)), + 234: np.array((205, 206, 76)), + 235: np.array((191, 191, 191)), + 236: np.array((255, 183, 0)), + 237: np.array((255, 168, 102)), + 238: np.array((220, 220, 0)), + 239: np.array((205, 206, 76)), + 240: np.array((254, 255, 255)), + 241: np.array((240, 240, 255)), + 242: np.array((190, 255, 228)), + 243: np.array((175, 255, 255)), + 244: np.array((255, 255, 139)), + 245: np.array((254, 255, 255)), + 246: np.array((205, 255, 113)), + 247: np.array((190, 255, 228)), + 248: np.array((255, 217, 255)), + 249: np.array((255, 202, 255)), + 250: np.array((254, 255, 255)), + 251: np.array((240, 240, 255)), + 252: np.array((255, 231, 166)), + 253: np.array((255, 217, 255)), + 254: np.array((255, 255, 139)), + 255: np.array((254, 255, 255)), } - # 84 unique colours + # 85 unique colours PALETTES = { diff --git a/precompute_distance.py b/precompute_distance.py index 85b3688..c1cbf58 100644 --- a/precompute_distance.py +++ b/precompute_distance.py @@ -34,7 +34,7 @@ def all_lab_colours(): def nearest_colours(palette, all_lab, diffs): - palette_size = len(palette) + palette_size = len(palette.RGB) palette_labs = np.empty((palette_size, 3), dtype=np.float) for i, palette_rgb in palette.RGB.items(): palette_labs[i, :] = rgb_to_lab(palette_rgb) @@ -78,7 +78,8 @@ def main(): except FileExistsError: pass out = np.memmap(filename=palette.DISTANCES_PATH, mode="w+", - dtype=np.uint8, shape=(RGB_LEVELS ** 3, len(palette))) + dtype=np.uint8, shape=(RGB_LEVELS ** 3, + len(palette.RGB))) nearest_colours(palette, all_lab, out) diff --git a/screen.py b/screen.py index 5a83d04..0e76e19 100644 --- a/screen.py +++ b/screen.py @@ -3,6 +3,7 @@ import numpy as np import palette as palette_py + # TODO: rename "4bit" variable naming now that we also have palettes with 8 bit # depth. @@ -83,77 +84,71 @@ class Screen: raise NotImplementedError @staticmethod - def _sin(pos, phase0=4): + def _sin(pos, phase0=0): x = pos % 12 + phase0 - return 8 * np.sin(x * 2 * np.pi / 12) + return np.sin(x * 2 * np.pi / 12) @staticmethod - def _cos(pos, phase0=4): + def _cos(pos, phase0=0): x = pos % 12 + phase0 - return 8 * np.cos(x * 2 * np.pi / 12) + return np.cos(x * 2 * np.pi / 12) def _read(self, line, pos): if pos < 0: return 0 - # Sather says black level is 0.36V and white level 1.1V, but this - # doesn't seem to be right (they correspond to values -29 and +33) - # which means that 0101 grey has Y value ~0, i.e. is black. These are - # only mentioned as labels on figure 8.2 though. - # - # _The Apple II Circuit description_ by W. Gayler gives black=0.5V - # and white=2.0V which is much more plausible. - # - # Conversion is given by floor((voltage-0.518)*1000/12)-15 - return 108 if line[pos] else 0 # -16 + return 1 if line[pos] else 0 def bitmap_to_ntsc(self, bitmap: np.ndarray) -> np.ndarray: - """ - See http://forums.nesdev.com/viewtopic.php?p=172329#p172329 - """ y_width = 12 - i_width = 24 - q_width = 24 + u_width = 24 + v_width = 24 - contrast = 167941 - saturation = 144044 + contrast = 1 + # TODO: where does this come from? OpenEmulator looks like it should + # use a value of 1.0 by default. + saturation = 2 + # Fudge factor to make colours line up with OpenEmulator + # TODO: where does this come from - is it due to the band-pass + # filtering they do? + hue = -0.3 - yr = contrast / y_width - ir = contrast * 1.994681e-6 * saturation / i_width - qr = contrast * 9.915742e-7 * saturation / q_width - - yg = contrast / y_width - ig = contrast * 9.151351e-8 * saturation / i_width - qg = contrast * -6.334805e-7 * saturation / q_width - - yb = contrast / y_width - ib = contrast * -1.012984e-6 * saturation / i_width - qb = contrast * 1.667217e-6 * saturation / q_width + # Apply effect of saturation + yuv_to_rgb = np.array( + ((1, 0, 0), (0, saturation, 0), (0, 0, saturation)), dtype=np.float) + # Apply hue phase rotation + yuv_to_rgb = np.matmul(np.array( + ((1, 0, 0), (0, np.cos(hue), np.sin(hue)), (0, -np.sin(hue), + np.cos(hue)))), + yuv_to_rgb) + # Y'UV to R'G'B' conversion + yuv_to_rgb = np.matmul(np.array( + ((1, 0, 1.13983), (1, -0.39465, -.58060), (1, 2.03211, 0))), + yuv_to_rgb) + # Apply effect of contrast + yuv_to_rgb *= contrast out_rgb = np.empty((bitmap.shape[0], bitmap.shape[1] * 3, 3), dtype=np.uint8) for y in range(bitmap.shape[0]): ysum = 0 - isum = 0 - qsum = 0 + usum = 0 + vsum = 0 line = np.repeat(bitmap[y], 3) - # color = y // (192//16) - # line = np.repeat(np.tile((color & 1, color & 2, color & 4, - # color & 8), 140), 3) for x in range(bitmap.shape[1] * 3): ysum += self._read(line, x) - self._read(line, x - y_width) - isum += self._read(line, x) * self._cos(x) - self._read( - line, x - i_width) * self._cos((x - i_width)) - qsum += self._read(line, x) * self._sin(x) - self._read( - line, x - q_width) * self._sin((x - q_width)) - - r = min(255, max(0, ysum * yr + isum * ir + qsum * qr) / - 65536) - g = min(255, - max(0, (ysum * yg + isum * ig + qsum * qg) / 65536)) - b = min(255, - max(0, (ysum * yb + isum * ib + qsum * qb) / 65536)) + usum += self._read(line, x) * self._sin(x) - self._read( + line, x - u_width) * self._sin((x - u_width)) + vsum += self._read(line, x) * self._cos(x) - self._read( + line, x - v_width) * self._cos((x - v_width)) + rgb = np.matmul( + yuv_to_rgb, np.array( + (ysum / y_width, usum / u_width, + vsum / v_width)).reshape((3, 1))).reshape(3) + r = min(255, max(0, rgb[0] * 255)) + g = min(255, max(0, rgb[1] * 255)) + b = min(255, max(0, rgb[2] * 255)) out_rgb[y, x, :] = (r, g, b) return out_rgb