mirror of
https://github.com/KrisKennaway/ii-pix.git
synced 2024-06-03 05:29:32 +00:00
Work in CAM16-UCS colour space and cythonize
This commit is contained in:
parent
173c283369
commit
a92c9cd7b5
59
convert.py
59
convert.py
|
@ -1,6 +1,7 @@
|
||||||
"""Image converter to Apple II Double Hi-Res format."""
|
"""Image converter to Apple II Double Hi-Res format."""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import array
|
||||||
import os.path
|
import os.path
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
@ -24,49 +25,22 @@ def _to_pixel(float_array):
|
||||||
return tuple(np.clip(float_array.astype(np.uint8), 0, 255))
|
return tuple(np.clip(float_array.astype(np.uint8), 0, 255))
|
||||||
|
|
||||||
|
|
||||||
def cluster_palette(image: Image):
|
def cluster_palette(image: Image, rgb_to_cam16):
|
||||||
# TODO: cluster in CAM16-UCS space
|
# TODO: only 4-bit RGB colour channels
|
||||||
colours = np.asarray(image).reshape((-1, 3))
|
colours_rgb = np.asarray(image).reshape((-1, 3))
|
||||||
|
with colour.utilities.suppress_warnings(colour_usage_warnings=True):
|
||||||
|
colours_cam = colour.convert(colours_rgb / 255, "RGB",
|
||||||
|
"CAM16UCS").astype(np.float32)
|
||||||
|
|
||||||
kmeans = KMeans(n_clusters=16)
|
kmeans = KMeans(n_clusters=16)
|
||||||
kmeans.fit_predict(colours)
|
kmeans.fit_predict(colours_cam)
|
||||||
palette = kmeans.cluster_centers_
|
palette_cam = kmeans.cluster_centers_
|
||||||
|
with colour.utilities.suppress_warnings(colour_usage_warnings=True):
|
||||||
|
palette_rgb = colour.convert(palette_cam, "CAM16UCS", "RGB").astype(
|
||||||
|
np.float32)
|
||||||
|
|
||||||
pal_image = Image.new('P', (1, 1), 0)
|
return dither_pyx.dither_shr(
|
||||||
pal_image.putpalette(palette.reshape(-1).astype(np.uint8))
|
np.asarray(image).astype(np.float32) / 255, palette_rgb, rgb_to_cam16)
|
||||||
|
|
||||||
working_image = np.asarray(image).astype(np.float32)
|
|
||||||
for y in range(200):
|
|
||||||
print(y)
|
|
||||||
for x in range(320):
|
|
||||||
pixel = working_image[y, x]
|
|
||||||
|
|
||||||
best_distance = 1e9
|
|
||||||
best_colour = None
|
|
||||||
for colour in palette:
|
|
||||||
distance = np.sum(np.power(colour - pixel, 2))
|
|
||||||
if distance < best_distance:
|
|
||||||
best_distance = distance
|
|
||||||
best_colour = colour
|
|
||||||
quant_error = pixel - best_colour
|
|
||||||
|
|
||||||
# Floyd-Steinberg dither
|
|
||||||
# 0 * 7
|
|
||||||
# 3 5 1
|
|
||||||
working_image[y, x] = best_colour
|
|
||||||
if x < 319:
|
|
||||||
working_image[y, x + 1] = np.clip(
|
|
||||||
working_image[y, x + 1] + quant_error * (7 / 16), 0, 255)
|
|
||||||
if y < 199:
|
|
||||||
working_image[y + 1, x] = np.clip(
|
|
||||||
working_image[y + 1, x] + quant_error * (5 / 16), 0, 255)
|
|
||||||
if x < 319:
|
|
||||||
working_image[y + 1, x + 1] = np.clip(
|
|
||||||
working_image[y + 1, x + 1] + quant_error * (1 / 16),
|
|
||||||
0, 255)
|
|
||||||
if x > 0:
|
|
||||||
working_image[y + 1, x - 1] = np.clip(
|
|
||||||
working_image[y + 1, x - 1] + quant_error * (3 / 16), 0,
|
|
||||||
255)
|
|
||||||
return working_image
|
return working_image
|
||||||
|
|
||||||
|
|
||||||
|
@ -129,7 +103,8 @@ def main():
|
||||||
image_py.resize(image, screen.X_RES, screen.Y_RES,
|
image_py.resize(image, screen.X_RES, screen.Y_RES,
|
||||||
gamma=args.gamma_correct)).astype(np.float32) / 255
|
gamma=args.gamma_correct)).astype(np.float32) / 255
|
||||||
|
|
||||||
output_rgb = cluster_palette(Image.fromarray((rgb * 255).astype(np.uint8)))
|
output_rgb = cluster_palette(Image.fromarray((rgb * 255).astype(
|
||||||
|
np.uint8)), rgb_to_cam16)
|
||||||
output_srgb = image_py.linear_to_srgb(output_rgb).astype(np.uint8)
|
output_srgb = image_py.linear_to_srgb(output_rgb).astype(np.uint8)
|
||||||
|
|
||||||
# dither = dither_pattern.PATTERNS[args.dither]()
|
# dither = dither_pattern.PATTERNS[args.dither]()
|
||||||
|
|
47
dither.pyx
47
dither.pyx
|
@ -323,3 +323,50 @@ def dither_image(
|
||||||
|
|
||||||
free(cdither.pattern)
|
free(cdither.pattern)
|
||||||
return image_nbit_to_bitmap(image_nbit, xres, yres, palette_depth)
|
return image_nbit_to_bitmap(image_nbit, xres, yres, palette_depth)
|
||||||
|
|
||||||
|
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 float best_distance, distance
|
||||||
|
cdef float[::1] best_colour_rgb, pixel_cam, colour_rgb, colour_cam
|
||||||
|
cdef float[3] quant_error
|
||||||
|
|
||||||
|
for y in range(200):
|
||||||
|
print(y)
|
||||||
|
for x in range(320):
|
||||||
|
pixel_cam = convert_rgb_to_cam16ucs(
|
||||||
|
rgb_to_cam16ucs, working_image[y, x, 0], working_image[y, x, 1], working_image[y, x, 2])
|
||||||
|
|
||||||
|
best_distance = 1e9
|
||||||
|
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
|
||||||
|
|
||||||
|
for i in range(3):
|
||||||
|
quant_error[i] = working_image[y, x, i] - best_colour_rgb[i]
|
||||||
|
|
||||||
|
# Floyd-Steinberg dither
|
||||||
|
# 0 * 7
|
||||||
|
# 3 5 1
|
||||||
|
working_image[y, x, i] = best_colour_rgb[i]
|
||||||
|
if x < 319:
|
||||||
|
working_image[y, x + 1, i] = clip(
|
||||||
|
working_image[y, x + 1, i] + quant_error[i] * (7 / 16), 0, 1)
|
||||||
|
if y < 199:
|
||||||
|
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
|
||||||
|
|
Loading…
Reference in New Issue
Block a user