mirror of
https://github.com/KrisKennaway/ii-vision.git
synced 2025-02-08 18:30:42 +00:00
opcodes until the cycle budget for the frame is exhausted. Output stream is also now aware of TCP framing, and schedules an ACK opcode every 2048 output bytes to instruct the client to perform TCP ACK and buffer management. Fixes several serious bugs in RLE encoding, including: - we were emitting the RLE opcode with the next content byte after the run completed! - we were looking at the wrong field for the start offset! - handle the case where the entire page is a single run - stop trying to allow accumulating error when RLE -- this does not respect the Apple II colour encoding, i.e. may introduce colour fringing. - also because of this we're unlikely to actually be able to find many runs because odd and even columns are encoded differently. In a followup we should start encoding odd and even columns separately Optimize after profiling -- encoder is now about 2x faster Add tests.
169 lines
4.8 KiB
Python
169 lines
4.8 KiB
Python
import unittest
|
|
|
|
import numpy as np
|
|
|
|
import opcodes
|
|
import screen
|
|
import video
|
|
|
|
|
|
class TestHammingWeight(unittest.TestCase):
|
|
def testHammingWeight(self):
|
|
self.assertEqual(0, video.hamming_weight(0))
|
|
self.assertEqual(1, video.hamming_weight(0b1))
|
|
self.assertEqual(1, video.hamming_weight(0b100))
|
|
self.assertEqual(3, video.hamming_weight(0b101100))
|
|
self.assertEqual(7, video.hamming_weight(0b11111110))
|
|
|
|
|
|
class TestVideo(unittest.TestCase):
|
|
def testEncodeEmptyFrame(self):
|
|
f = screen.HGR140Bitmap()
|
|
v = video.Video()
|
|
|
|
self.assertEqual([], list(v.encode_frame(f)))
|
|
|
|
def testEncodeOnePixel(self):
|
|
f = screen.HGR140Bitmap()
|
|
a = np.zeros((f.YMAX, f.XMAX), dtype=bool)
|
|
a[0, 0] = True
|
|
|
|
f = screen.HGR140Bitmap(a)
|
|
|
|
v = video.Video()
|
|
|
|
want = [
|
|
opcodes.SetPage(0x20),
|
|
opcodes.SetContent(0x03),
|
|
opcodes.Store(0x00),
|
|
]
|
|
got = list(v.encode_frame(f))
|
|
self.assertListEqual(want, got)
|
|
|
|
|
|
class TestIndexPage(unittest.TestCase):
|
|
def testFullPageSameValue(self):
|
|
"""Constant data with nonzero weights should return single run"""
|
|
v = video.Video()
|
|
|
|
data = np.ones((256,), dtype=np.uint8)
|
|
|
|
# total_xor_difference, start_offset, content, run_length
|
|
want = [(256, 0, 1, 256)]
|
|
got = list(v._index_page(video.hamming_weight(data), data))
|
|
|
|
self.assertEqual(want, got)
|
|
|
|
def testFullPageZeroValue(self):
|
|
"""Zero data with 0 weights should return nothing"""
|
|
v = video.Video()
|
|
|
|
data = np.zeros((256,), dtype=np.uint8)
|
|
|
|
# total_xor_difference, start_offset, content, run_length
|
|
want = []
|
|
got = list(v._index_page(video.hamming_weight(data), data))
|
|
|
|
self.assertEqual(want, got)
|
|
|
|
def testFullPageZeroValueWithDiff(self):
|
|
"""Zero data with nonzero weights should return single run"""
|
|
v = video.Video()
|
|
|
|
old_data = np.ones((256,), dtype=np.uint8)
|
|
|
|
data = np.zeros((256,), dtype=np.uint8)
|
|
|
|
# total_xor_difference, start_offset, content, run_length
|
|
want = [(256, 0, 0, 256)]
|
|
got = list(v._index_page(video.hamming_weight(old_data), data))
|
|
|
|
self.assertEqual(want, got)
|
|
|
|
def testSingleRun(self):
|
|
"""Single run of nonzero data"""
|
|
v = video.Video()
|
|
|
|
data = np.zeros((256,), dtype=np.uint8)
|
|
for i in range(5):
|
|
data[i] = 1
|
|
|
|
# total_xor_difference, start_offset, content, run_length
|
|
want = [(5, 0, 1, 5)]
|
|
got = list(v._index_page(video.hamming_weight(data), data))
|
|
|
|
self.assertEqual(want, got)
|
|
|
|
def testTwoRuns(self):
|
|
"""Two consecutive runs of nonzero data"""
|
|
v = video.Video()
|
|
|
|
data = np.zeros((256,), dtype=np.uint8)
|
|
for i in range(5):
|
|
data[i] = 1
|
|
for i in range(5, 10):
|
|
data[i] = 2
|
|
|
|
# total_xor_difference, start_offset, content, run_length
|
|
want = [(5, 0, 1, 5), (5, 5, 2, 5)]
|
|
got = list(v._index_page(video.hamming_weight(data), data))
|
|
|
|
self.assertEqual(want, got)
|
|
|
|
def testShortRun(self):
|
|
"""Run that is too short to encode as RLE opcode"""
|
|
v = video.Video()
|
|
|
|
data = np.zeros((256,), dtype=np.uint8)
|
|
for i in range(2):
|
|
data[i] = 1
|
|
|
|
# total_xor_difference, start_offset, content, run_length
|
|
want = [(1, 0, 1, 1), (1, 1, 1, 1)]
|
|
got = list(v._index_page(video.hamming_weight(data), data))
|
|
|
|
self.assertEqual(want, got)
|
|
|
|
|
|
class TestEncodeDecode(unittest.TestCase):
|
|
def testEncodeDecode(self):
|
|
for _ in range(10):
|
|
s = video.Video(frame_rate=1)
|
|
screen_cls = screen.HGR140Bitmap
|
|
|
|
im = np.random.randint(
|
|
0, 2, (screen_cls.YMAX, screen_cls.XMAX), dtype=np.bool)
|
|
f = screen_cls(im)
|
|
|
|
_ = bytes(s.emit_stream(s.encode_frame(f)))
|
|
|
|
# assert that the screen decodes to the original bitmap
|
|
bm = screen_cls.from_bytemap(s.screen).bitmap
|
|
|
|
self.assertTrue(np.array_equal(bm, im))
|
|
|
|
def testEncodeDecodeTwoFrames(self):
|
|
|
|
for _ in range(10):
|
|
s = video.Video(frame_rate=1)
|
|
screen_cls = screen.HGR140Bitmap
|
|
|
|
im = np.random.randint(
|
|
0, 2, (screen_cls.YMAX, screen_cls.XMAX), dtype=np.bool)
|
|
f = screen_cls(im)
|
|
_ = bytes(s.emit_stream(s.encode_frame(f)))
|
|
|
|
im2 = np.random.randint(
|
|
0, 2, (screen_cls.YMAX, screen_cls.XMAX), dtype=np.bool)
|
|
f = screen_cls(im2)
|
|
_ = bytes(s.emit_stream(s.encode_frame(f)))
|
|
|
|
# assert that the screen decodes to the original bitmap
|
|
bm = screen_cls.from_bytemap(s.screen).bitmap
|
|
|
|
self.assertTrue(np.array_equal(bm, im2))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|