mirror of
https://github.com/KrisKennaway/ii-pix.git
synced 2025-02-20 17:29:03 +00:00
Small tweaks and cleanups
This commit is contained in:
parent
02393381df
commit
348752dc35
143
dither.py
143
dither.py
@ -137,14 +137,6 @@ class CIE2000Distance(ColourDistance):
|
||||
def __init__(self):
|
||||
self._distances = np.memmap("distances.npy", mode="r+",
|
||||
dtype=np.uint8, shape=(16777216, 16))
|
||||
#
|
||||
# @staticmethod
|
||||
# def _flatten_rgb(rgb):
|
||||
# return (rgb[..., 0] << 16) + (rgb[..., 1] << 8) + (rgb[..., 2])
|
||||
#
|
||||
# def distance(self, rgb: np.ndarray, bit4: np.ndarray) -> np.ndarray:
|
||||
# rgb24 = self._flatten_rgb(rgb)
|
||||
# return self._distances[rgb24, bit4].astype(np.int)
|
||||
|
||||
|
||||
class Screen:
|
||||
@ -232,7 +224,7 @@ class DHGR560Screen(Screen):
|
||||
bitmap = np.zeros((self.Y_RES, self.X_RES), dtype=np.bool)
|
||||
for y in range(self.Y_RES):
|
||||
for x in range(self.X_RES):
|
||||
pixel = image_4bit[y, x].item()
|
||||
pixel = image_4bit[y, x]
|
||||
dots = DOTS[pixel]
|
||||
phase = x % 4
|
||||
bitmap[y, x] = dots[phase]
|
||||
@ -254,56 +246,6 @@ class Dither:
|
||||
PATTERN = None
|
||||
ORIGIN = None
|
||||
|
||||
@functools.lru_cache(None)
|
||||
def x_dither_bounds(self, screen: Screen, x: int):
|
||||
pshape = self.PATTERN.shape
|
||||
el = max(self.ORIGIN[1] - x, 0)
|
||||
er = min(pshape[1], screen.X_RES - 1 - x)
|
||||
|
||||
xl = x - self.ORIGIN[1] + el
|
||||
xr = x - self.ORIGIN[1] + er
|
||||
|
||||
return el, er, xl, xr
|
||||
|
||||
@functools.lru_cache(None)
|
||||
def y_dither_bounds(self, screen: Screen, y: int, one_line=False):
|
||||
pshape = self.PATTERN.shape
|
||||
et = max(self.ORIGIN[0] - y, 0)
|
||||
eb = min(pshape[0], screen.Y_RES - 1 - y)
|
||||
|
||||
yt = y - self.ORIGIN[0] + et
|
||||
yb = y - self.ORIGIN[0] + eb
|
||||
|
||||
if one_line:
|
||||
yb = yt + 1
|
||||
eb = et + 1
|
||||
|
||||
return et, eb, yt, yb
|
||||
|
||||
def apply(self, screen: Screen, image: np.ndarray, x: int, y: int,
|
||||
quant_error: np.ndarray, one_line=False):
|
||||
# el, er, xl, xr = self.x_dither_bounds(screen, x)
|
||||
# et, eb, yt, yb = self.y_dither_bounds(screen, y, one_line)
|
||||
return dither_apply.apply(self, screen, x, y, image, quant_error)
|
||||
# error = self.PATTERN * quant_error.reshape((1, 1, 3))
|
||||
#
|
||||
# # We could avoid clipping here, i.e. allow RGB values to extend beyond
|
||||
# # 0..255 to capture a larger range of residual error. This is faster
|
||||
# # but seems to reduce image quality.
|
||||
# # XXX extend image region to avoid need for boundary box clipping
|
||||
# image[yt:yb, xl:xr, :] = np.clip(
|
||||
# image[yt:yb, xl:xr, :] + error[et:eb, el:er, :], 0, 255)
|
||||
|
||||
def apply_one_line(self, screen: Screen, image: np.ndarray, x: int, y: int,
|
||||
quant_error: np.ndarray):
|
||||
el, er, xl, xr = self.x_dither_bounds(screen, x)
|
||||
return dither_apply.apply_one_line(self.PATTERN, el, er, xl, xr, y,
|
||||
image, quant_error)
|
||||
# error = self.PATTERN[0, :] * quant_error.reshape(1, 3)
|
||||
#
|
||||
# image[y, xl:xr, :] = np.clip(
|
||||
# image[y, xl:xr, :] + error[el:er, :], 0, 255)
|
||||
|
||||
|
||||
class FloydSteinbergDither(Dither):
|
||||
# 0 * 7
|
||||
@ -341,88 +283,11 @@ def open_image(screen: Screen, filename: str) -> np.ndarray:
|
||||
|
||||
# Convert to linear RGB before rescaling so that colour interpolation is
|
||||
# in linear space
|
||||
# XXX opt?
|
||||
linear = srgb_to_linear(np.array(im, dtype=np.float32))
|
||||
rescaled = Image.fromarray(
|
||||
linear.astype(np.uint8)).resize(
|
||||
linear = srgb_to_linear(np.asarray(im)).astype(np.uint8)
|
||||
rescaled = Image.fromarray(linear).resize(
|
||||
(screen.X_RES, screen.Y_RES), Image.LANCZOS)
|
||||
# XXX work with malloc'ed array?
|
||||
return np.array(rescaled, dtype=np.float32)
|
||||
|
||||
|
||||
def dither_lookahead(
|
||||
screen: Screen, image_rgb: np.ndarray, dither: Dither, differ:
|
||||
ColourDistance, x, y, last_pixel_4bit, lookahead
|
||||
) -> Tuple[np.ndarray, np.ndarray]:
|
||||
el, er, xl, xr = dither.x_dither_bounds(screen, x)
|
||||
|
||||
# X coord value of larger of dither bounding box or lookahead horizon
|
||||
xxr = min(max(x + lookahead, xr), screen.X_RES)
|
||||
|
||||
# copies of input pixels so we can dither in bulk
|
||||
# Leave enough space so we can dither the last of our lookahead pixels
|
||||
lah_image_rgb = np.zeros(
|
||||
(2 ** lookahead, lookahead + xr - xl, 3), dtype=np.float32)
|
||||
lah_image_rgb[:, 0:xxr - x, :] = np.copy(image_rgb[y, x:xxr, :])
|
||||
|
||||
options_4bit, options_rgb = lookahead_options(
|
||||
screen, lookahead, last_pixel_4bit, x % 4)
|
||||
for i in range(xxr - x):
|
||||
# options_rgb choices are fixed, but we can still distribute
|
||||
# quantization error from having made these choices, in order to compute
|
||||
# the total error
|
||||
input_pixels = np.copy(lah_image_rgb[:, i, :])
|
||||
output_pixels = options_rgb[:, i, :]
|
||||
quant_error = input_pixels - output_pixels
|
||||
# Don't update the input at position x (since we've already chosen
|
||||
# fixed outputs), but do propagate quantization errors to positions >x
|
||||
# so we can compensate for how good/bad these choices were
|
||||
# XXX vectorize
|
||||
for j in range(2 ** lookahead):
|
||||
# print(quant_error[j])
|
||||
dither.apply_one_line(screen,
|
||||
lah_image_rgb[j, :, :].reshape(1, -1, 3),
|
||||
i, 0, quant_error[j])
|
||||
|
||||
error = differ.distance(np.clip(
|
||||
lah_image_rgb[:, 0:lookahead, :], 0, 255), options_4bit)
|
||||
# print(error.dtype)
|
||||
# print(lah_image_lab)
|
||||
# print("error=", error)
|
||||
# print(error.shape)
|
||||
total_error = np.sum(np.power(error, 2), axis=1)
|
||||
# print("total_error=", total_error)
|
||||
best = np.argmin(total_error)
|
||||
# print("best=", best)
|
||||
# print("best 4bit=", options_4bit[best, 0].item(), options_rgb[best, 0, :])
|
||||
return options_4bit[best, 0].item(), options_rgb[best, 0, :]
|
||||
|
||||
|
||||
def dither_image(
|
||||
screen: Screen, image_rgb: np.ndarray, dither: Dither, differ:
|
||||
ColourDistance, lookahead) -> Tuple[np.ndarray, np.ndarray]:
|
||||
image_4bit = np.empty(
|
||||
(image_rgb.shape[0], image_rgb.shape[1]), dtype=np.uint8)
|
||||
|
||||
for y in range(screen.Y_RES):
|
||||
print(y)
|
||||
output_pixel_4bit = np.uint8(0)
|
||||
for x in range(screen.X_RES):
|
||||
input_pixel_rgb = image_rgb[y, x, :]
|
||||
options_4bit, options_rgb = lookahead_options(
|
||||
screen, lookahead, output_pixel_4bit, x % 4)
|
||||
|
||||
output_pixel_4bit, output_pixel_rgb = \
|
||||
dither_apply.dither_lookahead(
|
||||
screen, image_rgb, dither, differ, x, y, options_4bit,
|
||||
options_rgb,
|
||||
lookahead)
|
||||
quant_error = input_pixel_rgb - output_pixel_rgb
|
||||
image_4bit[y, x] = output_pixel_4bit
|
||||
image_rgb[y, x, :] = output_pixel_rgb
|
||||
dither_apply.apply(dither, screen, x, y, image_rgb, quant_error)
|
||||
|
||||
return image_4bit, image_rgb
|
||||
return np.array(rescaled).astype(np.float32)
|
||||
|
||||
|
||||
def main():
|
||||
|
@ -174,8 +174,8 @@ def lookahead_options(screen, lookahead, last_pixel_4bit, x):
|
||||
@cython.boundscheck(False)
|
||||
@cython.wraparound(False)
|
||||
def dither_image(
|
||||
screen, float [:, :, ::1] image_rgb, dither, differ, int lookahead):
|
||||
image_4bit = np.empty(
|
||||
screen, float[:, :, ::1] image_rgb, dither, differ, int lookahead):
|
||||
cdef (unsigned char)[:, ::1] image_4bit = np.empty(
|
||||
(image_rgb.shape[0], image_rgb.shape[1]), dtype=np.uint8)
|
||||
|
||||
cdef int yres = screen.Y_RES
|
||||
@ -185,10 +185,12 @@ def dither_image(
|
||||
cdef float[3] quant_error
|
||||
cdef (unsigned char)[:, ::1] options_4bit
|
||||
cdef float[:, :, ::1] options_rgb
|
||||
cdef unsigned char output_pixel_4bit
|
||||
cdef float[::1] input_pixel_rgb
|
||||
|
||||
for y in range(yres):
|
||||
# print(y)
|
||||
output_pixel_4bit = np.uint8(0)
|
||||
output_pixel_4bit = 0
|
||||
for x in range(xres):
|
||||
input_pixel_rgb = image_rgb[y, x, :]
|
||||
options_4bit, options_rgb = lookahead_options(
|
||||
@ -204,5 +206,4 @@ def dither_image(
|
||||
image_4bit[y, x] = output_pixel_4bit
|
||||
apply(dither, screen, x, y, image_rgb, quant_error)
|
||||
|
||||
return image_4bit, np.array(image_rgb)
|
||||
|
||||
return image_4bit, np.array(image_rgb)
|
Loading…
x
Reference in New Issue
Block a user