mirror of
https://github.com/KrisKennaway/ii-pix.git
synced 2025-02-11 19:30:49 +00:00
Try to fix how we compute the next pixel palette for 8-bit mode.
Still not sure this is correct.
This commit is contained in:
parent
70074a2942
commit
508ce134aa
41
dither.pyx
41
dither.pyx
@ -1,6 +1,7 @@
|
||||
# cython: infer_types=True
|
||||
# cython: profile=True
|
||||
|
||||
import math
|
||||
cimport cython
|
||||
import functools
|
||||
import numpy as np
|
||||
@ -82,8 +83,12 @@ def lookahead_options(object screen, int lookahead, unsigned char last_pixel_nbi
|
||||
# Two possible n-bit palette choices at position xx, given state of n pixels to left.
|
||||
# TODO: port screen.py to pyx
|
||||
palette_choices_nbit = screen.pixel_palette_options(output_pixel_nbit, xx)
|
||||
output_pixel_nbit = palette_choices_nbit[(i & (1 << j)) >> j]
|
||||
options_nbit[i, j] = output_pixel_nbit
|
||||
next_bit_choice = (i & (1 << j)) >> j
|
||||
options_nbit[i, j] = palette_choices_nbit[next_bit_choice]
|
||||
output_pixel_nbit >>= 1
|
||||
output_pixel_nbit |= (next_bit_choice << 7)
|
||||
# print(bin(i),j,bin(last_pixel_nbit), bin(output_pixel_nbit), bin(options_nbit[i, j]))
|
||||
#print("Picking %s" % ((i & (1 << j)) >> j))
|
||||
|
||||
return options_nbit
|
||||
|
||||
@ -123,6 +128,8 @@ cdef int dither_lookahead(Dither* dither, float[:, ::1] palette_rgb,
|
||||
cdef int lah_shape2 = 3
|
||||
cdef float *lah_image_rgb = <float *> malloc(lah_shape1 * lah_shape2 * sizeof(float))
|
||||
|
||||
cdef unsigned char lookahead_bits
|
||||
|
||||
# For each 2**lookahead possibilities for the on/off state of the next lookahead pixels, apply error diffusion
|
||||
# and compute the total squared error to the source image. Since we only have two possible colours for each
|
||||
# given pixel (dependent on the state already chosen for pixels to the left), we need to look beyond local minima.
|
||||
@ -153,13 +160,27 @@ cdef int dither_lookahead(Dither* dither, float[:, ::1] palette_rgb,
|
||||
#flat = (r << 16) + (g << 8) + b
|
||||
# bit4 = options_nbit[i, j]
|
||||
|
||||
## XXX parametrize number of palette bits
|
||||
#if j < 8:
|
||||
# lookahead_bits = (last_bits >> (j+1))
|
||||
#else:
|
||||
# lookahead_bits = 0
|
||||
#lookahead_bits |= (options_nbit[i, j]) << (7-j)
|
||||
#lookahead_bits &= (1<<8)-1
|
||||
|
||||
total_error += colour_distance_squared(lah_image_rgb[j*lah_shape2], lah_image_rgb[j*lah_shape2+1], lah_image_rgb[j*lah_shape2+2], palette_rgb[options_nbit[i,j]])
|
||||
|
||||
#if x > (560*3/4) and y == 180:
|
||||
# print(x, bin(i), j, bin(options_nbit[i, j]), bin(best), best_error, total_error)
|
||||
|
||||
if total_error >= best_error:
|
||||
break
|
||||
|
||||
|
||||
if total_error < best_error:
|
||||
best_error = total_error
|
||||
best = i
|
||||
|
||||
free(lah_image_rgb)
|
||||
return best
|
||||
|
||||
@ -193,7 +214,7 @@ cdef void apply_one_line(Dither* dither, int xl, int xr, int x, float[] image, i
|
||||
for i in range(xl, xr):
|
||||
error_fraction = dither.pattern[i - x + dither.x_origin]
|
||||
for j in range(3):
|
||||
image[i * image_shape1 + j] = clip(image[i * image_shape1 + j] + error_fraction * quant_error[j], -100, 100)
|
||||
image[i * image_shape1 + j] = clip(image[i * image_shape1 + j] + error_fraction * quant_error[j], 0 if j == 0 else -1, 1)
|
||||
|
||||
|
||||
# Perform error diffusion across multiple image rows.
|
||||
@ -227,7 +248,7 @@ cdef void apply(Dither* dither, int x_res, int y_res, int x, int y, float[:,:,::
|
||||
for j in range(xl, xr):
|
||||
error_fraction = dither.pattern[(i - y) * dither.x_shape + j - x + dither.x_origin]
|
||||
for k in range(3):
|
||||
image[i,j,k] = clip(image[i,j,k] + error_fraction * quant_error[k], -100, 100)
|
||||
image[i,j,k] = clip(image[i,j,k] + error_fraction * quant_error[k], 0 if k == 0 else -1, 1)
|
||||
|
||||
# Compute closest colour from array of candidate n-bit colour palette values.
|
||||
#
|
||||
@ -311,6 +332,8 @@ def dither_image(screen, float[:, :, ::1] image_rgb, dither, int lookahead, unsi
|
||||
cdef (unsigned char)[:, ::1] image_nbit = np.empty(
|
||||
(image_rgb.shape[0], image_rgb.shape[1]), dtype=np.uint8)
|
||||
|
||||
# print(lookahead_options(screen, lookahead, 0b11111111, 0))
|
||||
|
||||
for y in range(yres):
|
||||
if verbose:
|
||||
print("%d/%d" % (y, yres))
|
||||
@ -327,17 +350,21 @@ def dither_image(screen, float[:, :, ::1] image_rgb, dither, int lookahead, unsi
|
||||
best_idx = dither_lookahead(
|
||||
&cdither, palette_rgb, image_rgb, x, y, lookahead_palette_choices_nbit, lookahead,
|
||||
xres)
|
||||
output_pixel_nbit = lookahead_palette_choices_nbit[best_idx, 0]
|
||||
output_pixel_nbit >>= 1
|
||||
output_pixel_nbit |= (best_idx & 0b1) << 7 # XXX n bit shift
|
||||
# print("Picked %d" % (best_idx & 0b1))
|
||||
# lookahead_palette_choices_nbit[best_idx, 0]
|
||||
#else:
|
||||
# # Choose the closest colour among the available n-bit palette options
|
||||
# palette_choices_nbit = screen.pixel_palette_options(output_pixel_nbit, x)
|
||||
# output_pixel_nbit = find_nearest_colour(input_pixel_rgb, palette_choices_nbit, distances)
|
||||
|
||||
# Apply error diffusion from chosen output pixel value
|
||||
output_pixel_rgb = palette_rgb[output_pixel_nbit]
|
||||
# print("picked %d, %s" % (lookahead_palette_choices_nbit[best_idx, 0], bin(lookahead_palette_choices_nbit[best_idx, 0])))
|
||||
output_pixel_rgb = palette_rgb[lookahead_palette_choices_nbit[best_idx, 0]]
|
||||
for i in range(3):
|
||||
quant_error[i] = input_pixel_rgb[i] - output_pixel_rgb[i]
|
||||
image_nbit[y, x] = output_pixel_nbit
|
||||
image_nbit[y, x] = lookahead_palette_choices_nbit[best_idx, 0] # output_pixel_nbit
|
||||
apply(&cdither, xres, yres, x, y, image_rgb, quant_error)
|
||||
|
||||
for i in range(3):
|
||||
|
75
screen.py
75
screen.py
@ -140,15 +140,25 @@ class DHGR560Screen(Screen):
|
||||
return bitmap
|
||||
|
||||
def pixel_palette_options(self, last_pixel_nbit, x: int):
|
||||
# The two available colours for position x are given by the 4-bit
|
||||
# value of position x-1, and the 4-bit value produced by toggling the
|
||||
# value of the x % 4 bit (the current value of NTSC phase)
|
||||
last_dots = self.palette.DOTS[last_pixel_nbit]
|
||||
other_dots = list(last_dots)
|
||||
other_dots[x % 4] = not other_dots[x % 4]
|
||||
other_dots = tuple(other_dots)
|
||||
other_pixel_nbit = self.palette.DOTS_TO_INDEX[other_dots]
|
||||
return np.array([last_pixel_nbit, other_pixel_nbit], dtype=np.uint8)
|
||||
last_dots = self.palette.DOTS[last_pixel_nbit][1:] + [None]
|
||||
|
||||
# rearrange into palette order
|
||||
next_dots = [None] * 8
|
||||
for i in range(4):
|
||||
next_dots[(i - x) % 4] = last_dots[i]
|
||||
next_dots[(i - x) % 4 + 4] = last_dots[i + 4]
|
||||
|
||||
# XXX wrong
|
||||
|
||||
assert next_dots[(3 - x) % 4 + 4] is None
|
||||
#print(x, last_dots, next_dots)
|
||||
|
||||
next_dots[(3 - x) % 4 + 4] = False
|
||||
next_pixel_nbit_0 = self.palette.DOTS_TO_INDEX[next_dots]
|
||||
|
||||
next_dots[(3 - x) % 4 + 4] = True
|
||||
next_pixel_nbit_1 = self.palette.DOTS_TO_INDEX[next_dots]
|
||||
return np.array([next_pixel_nbit_0, next_pixel_nbit_1], dtype=np.uint8)
|
||||
|
||||
|
||||
# TODO: refactor to share implementation with DHGR560Screen
|
||||
@ -175,7 +185,8 @@ class DHGR560NTSCScreen(Screen):
|
||||
window indexed by x % 4, which gives the index into our 256-colour RGB
|
||||
palette.
|
||||
"""
|
||||
image_rgb = np.empty((self.NATIVE_Y_RES, self.NATIVE_X_RES, 3), dtype=np.uint8)
|
||||
image_rgb = np.empty((self.NATIVE_Y_RES, self.NATIVE_X_RES, 3),
|
||||
dtype=np.uint8)
|
||||
for y in range(self.Y_RES):
|
||||
pixel = [False, False, False, False, False, False, False, False]
|
||||
for x in range(self.NATIVE_X_RES):
|
||||
@ -186,19 +197,37 @@ class DHGR560NTSCScreen(Screen):
|
||||
return image_rgb
|
||||
|
||||
def pixel_palette_options(self, last_pixel_nbit, x: int):
|
||||
# The two available 8-bit pixel colour choices are given by:
|
||||
# - Rotating the pixel value from the current x % 4 + 4 position to
|
||||
# x % 4
|
||||
# - choosing 0 and 1 for the new values of x % 4 + 4
|
||||
next_dots0 = list(self.palette.DOTS[last_pixel_nbit])
|
||||
next_dots1 = list(next_dots0)
|
||||
next_dots0[x % 4] = next_dots0[x % 4 + 4]
|
||||
next_dots0[x % 4 + 4] = False
|
||||
next_dots1[x % 4] = next_dots1[x % 4 + 4]
|
||||
next_dots1[x % 4 + 4] = True
|
||||
pixel_nbit_0 = self.palette.DOTS_TO_INDEX[tuple(next_dots0)]
|
||||
pixel_nbit_1 = self.palette.DOTS_TO_INDEX[tuple(next_dots1)]
|
||||
return np.array([pixel_nbit_0, pixel_nbit_1], dtype=np.uint8)
|
||||
# # The two available 8-bit pixel colour choices are given by:
|
||||
# # - Rotating the pixel value from the current x % 4 + 4 position to
|
||||
# # x % 4
|
||||
# # - choosing 0 and 1 for the new values of x % 4 + 4
|
||||
# next_dots0 = list(self.palette.DOTS[last_pixel_nbit])
|
||||
# next_dots1 = list(next_dots0)
|
||||
# next_dots0[x % 4] = next_dots0[x % 4 + 4]
|
||||
# next_dots0[x % 4 + 4] = False
|
||||
# next_dots1[x % 4] = next_dots1[x % 4 + 4]
|
||||
# next_dots1[x % 4 + 4] = True
|
||||
# pixel_nbit_0 = self.palette.DOTS_TO_INDEX[tuple(next_dots0)]
|
||||
# pixel_nbit_1 = self.palette.DOTS_TO_INDEX[tuple(next_dots1)]
|
||||
# return np.array([pixel_nbit_0, pixel_nbit_1], dtype=np.uint8)
|
||||
last_dots = list(self.palette.DOTS[last_pixel_nbit][1:]) + [None]
|
||||
|
||||
# rearrange into palette order
|
||||
next_dots = [None] * 8
|
||||
for i in range(4):
|
||||
next_dots[i] = last_dots[(i - x) % 4]
|
||||
next_dots[i + 4] = last_dots[(i - x) % 4 + 4]
|
||||
|
||||
assert next_dots[(3 + x) % 4 + 4] is None
|
||||
# print(x, last_dots, next_dots)
|
||||
|
||||
next_dots[(3 + x) % 4 + 4] = False
|
||||
next_pixel_nbit_0 = self.palette.DOTS_TO_INDEX[tuple(next_dots)]
|
||||
|
||||
next_dots[(3 + x) % 4 + 4] = True
|
||||
next_pixel_nbit_1 = self.palette.DOTS_TO_INDEX[tuple(next_dots)]
|
||||
return np.array([next_pixel_nbit_0, next_pixel_nbit_1],
|
||||
dtype=np.uint8)
|
||||
|
||||
def bitmap_to_ntsc(self, bitmap: np.ndarray) -> np.ndarray:
|
||||
y_width = 12
|
||||
|
Loading…
x
Reference in New Issue
Block a user