Working SHR version. Still just uses a single palette

This commit is contained in:
kris 2021-11-09 22:26:34 +00:00
parent 21058084e2
commit 80885aabf9
3 changed files with 136 additions and 18 deletions

View File

@ -34,11 +34,9 @@ def cluster_palette(image: Image):
with colour.utilities.suppress_warnings(colour_usage_warnings=True):
palette_rgb = colour.convert(palette_cam, "CAM16UCS", "RGB")
# SHR colour palette only uses 4-bit values
# TODO: do this more carefully
palette_rgb = np.clip(np.round(palette_rgb * 16).astype(np.uint32) *
16, 0, 255)
palette_rgb = palette_rgb.astype(np.float32) / 255
return palette_rgb
palette_rgb = np.round(palette_rgb * 15) / 15
# palette_rgb = palette_rgb.astype(np.float32) / 255
return palette_rgb.astype(np.float32)
@ -96,13 +94,21 @@ def main():
image = image_py.open(args.input)
if args.show_input:
image_py.resize(image, screen.X_RES, screen.Y_RES,
srgb_output=True).show()
srgb_output=False).show()
rgb = np.array(
image_py.resize(image, screen.X_RES, screen.Y_RES,
gamma=args.gamma_correct)).astype(np.float32) / 255
gamma=args.gamma_correct, srgb_output=True)).astype(
np.float32) / 255
palette_rgb = cluster_palette(rgb)
output_rgb = dither_pyx.dither_shr(rgb, palette_rgb, rgb_to_cam16)
# print(palette_rgb)
# screen.set_palette(0, (image_py.linear_to_srgb_array(palette_rgb) *
# 15).astype(np.uint8))
screen.set_palette(0, (np.round(palette_rgb * 15)).astype(np.uint8))
output_4bit = dither_pyx.dither_shr(rgb, palette_rgb, rgb_to_cam16)
screen.set_pixels(output_4bit)
output_rgb = (palette_rgb[output_4bit] * 255).astype(np.uint8)
output_srgb = image_py.linear_to_srgb(output_rgb).astype(np.uint8)
# dither = dither_pattern.PATTERNS[args.dither]()
@ -120,18 +126,20 @@ def main():
# output_screen.bitmap_to_image_rgb(bitmap)).astype(np.uint8)
out_image = image_py.resize(
Image.fromarray(output_srgb), screen.X_RES, screen.Y_RES,
srgb_output=True)
srgb_output=False) # XXX true
if args.show_output:
out_image.show()
# Save Double hi-res image
# outfile = os.path.join(os.path.splitext(args.output)[0] + "-preview.png")
# out_image.save(outfile, "PNG")
# screen.pack(bitmap)
outfile = os.path.join(os.path.splitext(args.output)[0] + "-preview.png")
out_image.save(outfile, "PNG")
screen.pack()
# with open(args.output, "wb") as f:
# f.write(bytes(screen.aux))
# f.write(bytes(screen.main))
with open(args.output, "wb") as f:
f.write(bytes(screen.memory))
if __name__ == "__main__":

View File

@ -329,11 +329,13 @@ import colour
@cython.boundscheck(False)
@cython.wraparound(False)
def dither_shr(float[:, :, ::1] working_image, float[:, ::1] palette_rgb, float[:,::1] rgb_to_cam16ucs):
cdef int y, x, idx
cdef int y, x, idx, best_colour_idx
cdef float best_distance, distance
cdef float[::1] best_colour_rgb, pixel_cam, colour_rgb, colour_cam
cdef float[3] quant_error
cdef (unsigned char)[:, ::1] output_4bit = np.zeros((200, 320), dtype=np.uint8)
for y in range(200):
print(y)
for x in range(320):
@ -341,12 +343,15 @@ def dither_shr(float[:, :, ::1] working_image, float[:, ::1] palette_rgb, float[
rgb_to_cam16ucs, working_image[y, x, 0], working_image[y, x, 1], working_image[y, x, 2])
best_distance = 1e9
best_colour_idx = 0
for idx, colour_rgb in enumerate(palette_rgb):
colour_cam = convert_rgb_to_cam16ucs(rgb_to_cam16ucs, colour_rgb[0], colour_rgb[1], colour_rgb[2])
distance = colour_distance_squared(pixel_cam, colour_cam)
if distance < best_distance:
best_distance = distance
best_colour_rgb = colour_rgb
best_colour_idx = idx
output_4bit[y, x] = best_colour_idx
for i in range(3):
quant_error[i] = working_image[y, x, i] - best_colour_rgb[i]
@ -359,14 +364,63 @@ def dither_shr(float[:, :, ::1] working_image, float[:, ::1] palette_rgb, float[
working_image[y, x + 1, i] = clip(
working_image[y, x + 1, i] + quant_error[i] * (7 / 16), 0, 1)
if y < 199:
if x > 0:
working_image[y + 1, x - 1, i] = clip(
working_image[y + 1, x - 1, i] + quant_error[i] * (3 / 16), 0,
1)
working_image[y + 1, x, i] = clip(
working_image[y + 1, x, i] + quant_error[i] * (5 / 16), 0, 1)
if x < 319:
working_image[y + 1, x + 1, i] = clip(
working_image[y + 1, x + 1, i] + quant_error[i] * (1 / 16),
0, 1)
if x > 0:
working_image[y + 1, x - 1, i] = clip(
working_image[y + 1, x - 1, i] + quant_error[i] * (3 / 16), 0,
1)
return np.array(working_image).astype(np.float32) * 255
# # 0 0 X 7 5
# # 3 5 7 5 3
# # 1 3 5 3 1
# if x < 319:
# working_image[y, x + 1, i] = clip(
# working_image[y, x + 1, i] + quant_error[i] * (7 / 48), 0, 1)
# if x < 318:
# working_image[y, x + 2, i] = clip(
# working_image[y, x + 2, i] + quant_error[i] * (5 / 48), 0, 1)
# if y < 199:
# if x > 1:
# working_image[y + 1, x - 2, i] = clip(
# working_image[y + 1, x - 2, i] + quant_error[i] * (3 / 48), 0,
# 1)
# if x > 0:
# working_image[y + 1, x - 1, i] = clip(
# working_image[y + 1, x - 1, i] + quant_error[i] * (5 / 48), 0,
# 1)
# working_image[y + 1, x, i] = clip(
# working_image[y + 1, x, i] + quant_error[i] * (7 / 48), 0, 1)
# if x < 319:
# working_image[y + 1, x + 1, i] = clip(
# working_image[y + 1, x + 1, i] + quant_error[i] * (5 / 48),
# 0, 1)
# if x < 318:
# working_image[y + 1, x + 2, i] = clip(
# working_image[y + 1, x + 2, i] + quant_error[i] * (3 / 48),
# 0, 1)
# if y < 198:
# if x > 1:
# working_image[y + 2, x - 2, i] = clip(
# working_image[y + 2, x - 2, i] + quant_error[i] * (1 / 48), 0,
# 1)
# if x > 0:
# working_image[y + 2, x - 1, i] = clip(
# working_image[y + 2, x - 1, i] + quant_error[i] * (3 / 48), 0,
# 1)
# working_image[y + 2, x, i] = clip(
# working_image[y + 2, x, i] + quant_error[i] * (5 / 48), 0, 1)
# if x < 319:
# working_image[y + 2, x + 1, i] = clip(
# working_image[y + 2, x + 1, i] + quant_error[i] * (3 / 48),
# 0, 1)
# if x < 318:
# working_image[y + 2, x + 2, i] = clip(
# working_image[y + 2, x + 2, i] + quant_error[i] * (1 / 48),
# 0, 1)
return np.array(output_4bit, dtype=np.uint8)

View File

@ -9,6 +9,62 @@ class SHR320Screen:
X_RES = 320
Y_RES = 200
def __init__(self):
self.palettes = {k: np.zeros((16, 3), dtype=np.uint8) for k in
range(16)}
# Really 4-bit values, indexing into palette
self.pixels = np.array((self.Y_RES, self.X_RES), dtype=np.uint8)
# Choice of palette per scan-line
self.line_palette = np.zeros(self.Y_RES, dtype=np.uint8)
self.memory = None
def set_palette(self, idx: int, palette: np.array):
if idx < 0 or idx > 15:
raise ValueError("Palette index %s must be in range 0 .. 15" % idx)
if palette.shape != (16, 3):
raise ValueError("Palette size %s != (16, 3)" % palette.shape)
# XXX check element range
if palette.dtype != np.uint8:
raise ValueError("Palette must be of type np.uint8")
print(palette)
self.palettes[idx] = np.array(palette)
def set_pixels(self, pixels):
self.pixels = np.array(pixels)
def pack(self):
dump = np.zeros(32768, dtype=np.uint8)
for y in range(self.Y_RES):
pixel_pair = 0
for x in range(self.X_RES):
if x % 2 == 0:
pixel_pair |= (self.pixels[y, x] << 4)
else:
pixel_pair |= self.pixels[y, x]
# print(pixel_pair)
dump[y * 160 + (x - 1) // 2] = pixel_pair
pixel_pair = 0
scan_control_offset = 320 * 200 // 2
for y in range(self.Y_RES):
dump[scan_control_offset + y] = self.line_palette[y]
palette_offset = scan_control_offset + 256
for palette_idx, palette in self.palettes.items():
for rgb_idx, rgb in enumerate(palette):
r, g, b = rgb
# print(r, g, b)
rgb_low = (g << 4) | b
rgb_hi = r
print(hex(rgb_hi), hex(rgb_low))
palette_idx_offset = palette_offset + (32 * palette_idx)
dump[palette_idx_offset + (2 * rgb_idx)] = rgb_low
dump[palette_idx_offset + (2 * rgb_idx + 1)] = rgb_hi
self.memory = dump
class DHGRScreen:
X_RES = 560