Working SHR version. Still just uses a single palette
This commit is contained in:
parent
21058084e2
commit
80885aabf9
32
convert.py
32
convert.py
|
@ -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__":
|
||||
|
|
66
dither.pyx
66
dither.pyx
|
@ -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)
|
56
screen.py
56
screen.py
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue