From 1075ff0136c536f6094c09d4b636d1ded3d3152c Mon Sep 17 00:00:00 2001 From: kris Date: Fri, 26 Nov 2021 10:36:39 +0000 Subject: [PATCH] Tidy a bit and remove support for tunable parameters that are no longer needed --- convert.py | 13 +++++-------- dither.pyx | 35 +++++++++++------------------------ 2 files changed, 16 insertions(+), 32 deletions(-) diff --git a/convert.py b/convert.py index e78d17e..fb2c248 100644 --- a/convert.py +++ b/convert.py @@ -135,7 +135,7 @@ class ClusterPalette: # print("Perfect image error:", total_image_error) return image_rgb - def _dither_image(self, palettes_cam, penalty): + def _dither_image(self, palettes_cam): # Suppress divide by zero warning, # https://github.com/colour-science/colour/issues/900 with colour.utilities.suppress_warnings(python_warnings=True): @@ -145,7 +145,7 @@ class ClusterPalette: output_4bit, line_to_palette, total_image_error, palette_line_errors = \ dither_pyx.dither_shr( self._image_rgb, palettes_cam, palettes_linear_rgb, - self._rgb24_to_cam16ucs, float(penalty)) + self._rgb24_to_cam16ucs) # Update map of palettes to image lines for which the palette was the # best match @@ -159,7 +159,7 @@ class ClusterPalette: return (output_4bit, line_to_palette, palettes_linear_rgb, total_image_error) - def iterate(self, penalty: float, max_inner_iterations: int, + def iterate(self, max_inner_iterations: int, max_outer_iterations: int): total_image_error = 1e9 @@ -176,10 +176,8 @@ class ClusterPalette: # Recompute image with proposed palettes and check whether it # has lower total image error than our previous best. (output_4bit, line_to_palette, palettes_linear_rgb, - new_total_image_error) = self._dither_image( - new_palettes_cam, penalty) + new_total_image_error) = self._dither_image(new_palettes_cam) - # TODO: check for unused colours within a palette self._reassign_unused_palettes( line_to_palette, new_palettes_rgb12_iigs) @@ -444,7 +442,6 @@ def main(): gamma=args.gamma_correct)).astype(np.float32) / 255 # TODO: flags - penalty = 1 # 1e18 # TODO: is this needed any more? inner_iterations = 10 outer_iterations = 20 @@ -466,7 +463,7 @@ def main(): seq = 0 for (new_total_image_error, output_4bit, line_to_palette, palettes_rgb12_iigs, palettes_linear_rgb) in cluster_palette.iterate( - penalty, inner_iterations, outer_iterations): + inner_iterations, outer_iterations): if args.verbose and total_image_error is not None: print("Improved quality +%f%% (%f)" % ( diff --git a/dither.pyx b/dither.pyx index e4be0d4..66db0c9 100644 --- a/dither.pyx +++ b/dither.pyx @@ -163,6 +163,7 @@ cdef inline float[::1] convert_rgb_to_cam16ucs(float[:, ::1] rgb_to_cam16ucs, fl cdef unsigned int rgb_24bit = ((r*255) << 16) + ((g*255) << 8) + (b*255) return rgb_to_cam16ucs[rgb_24bit] + @cython.boundscheck(False) @cython.wraparound(False) cdef inline float fabs(float value) nogil: @@ -175,12 +176,6 @@ cdef inline double colour_distance_squared(float[::1] colour1, float[::1] colour return (colour1[0] - colour2[0]) ** 2 + (colour1[1] - colour2[1]) ** 2 + (colour1[2] - colour2[2]) ** 2 -@cython.boundscheck(False) -@cython.wraparound(False) -cdef inline float colour_distance(float[::1] colour1, float[::1] colour2) nogil: - return fabs(colour1[0] - colour2[0]) + fabs(colour1[1] - colour2[1]) + fabs(colour1[2] - colour2[2]) - - # Perform error diffusion to a single image row. # # Args: @@ -353,7 +348,6 @@ def dither_shr_perfect( cdef int palette_size = full_palette_rgb.shape[0] cdef float decay = 0.5 - cdef float min_quant_error = 0.0 # 0.02 cdef int floyd_steinberg = 1 total_image_error = 0.0 @@ -373,13 +367,11 @@ def dither_shr_perfect( if distance < best_distance: best_distance = distance best_colour_idx = idx - best_colour_rgb = full_palette_rgb[best_colour_idx] + best_colour_rgb = full_palette_rgb[best_colour_idx, :] total_image_error += best_distance for i in range(3): quant_error = working_image[y, x, i] - best_colour_rgb[i] - if abs(quant_error) <= min_quant_error: - quant_error = 0 working_image[y, x, i] = best_colour_rgb[i] if floyd_steinberg: @@ -455,7 +447,7 @@ def dither_shr_perfect( @cython.wraparound(False) def dither_shr( float[:, :, ::1] input_rgb, float[:, :, ::1] palettes_cam, float[:, :, ::1] palettes_rgb, - float[:,::1] rgb_to_cam16ucs, float penalty): + float[:,::1] rgb_to_cam16ucs): cdef int y, x, idx, best_colour_idx, best_palette, i cdef double best_distance, distance, total_image_error cdef float[::1] best_colour_rgb, pixel_cam @@ -471,7 +463,6 @@ def dither_shr( cdef PaletteSelection palette_line cdef float decay = 0.5 - cdef float min_quant_error = 0.0 # 0.02 cdef int floyd_steinberg = 1 best_palette = -1 @@ -481,7 +472,7 @@ def dither_shr( line_cam[x, :] = convert_rgb_to_cam16ucs( rgb_to_cam16ucs, working_image[y,x,0], working_image[y,x,1], working_image[y,x,2]) - palette_line = best_palette_for_line(line_cam, palettes_cam, best_palette, penalty) + palette_line = best_palette_for_line(line_cam, palettes_cam, best_palette) best_palette = palette_line.palette_idx palette_line_errors[y] = palette_line.total_error @@ -506,8 +497,6 @@ def dither_shr( for i in range(3): quant_error = working_image[y, x, i] - best_colour_rgb[i] - if abs(quant_error) <= min_quant_error: - quant_error = 0 working_image[y, x, i] = best_colour_rgb[i] if floyd_steinberg: @@ -586,7 +575,7 @@ cdef struct PaletteSelection: @cython.boundscheck(False) @cython.wraparound(False) -cdef PaletteSelection best_palette_for_line(float [:, ::1] line_cam, float[:, :, ::1] palettes_cam, int last_palette_idx, float last_penalty) nogil: +cdef PaletteSelection best_palette_for_line(float [:, ::1] line_cam, float[:, :, ::1] palettes_cam, int last_palette_idx) nogil: cdef int palette_idx, best_palette_idx, palette_entry_idx, pixel_idx cdef double best_total_dist, total_dist, best_pixel_dist, pixel_dist cdef float[:, ::1] palette_cam @@ -594,17 +583,15 @@ cdef PaletteSelection best_palette_for_line(float [:, ::1] line_cam, float[:, :, best_total_dist = 1e9 best_palette_idx = -1 - cdef float penalty cdef int line_size = line_cam.shape[0] for palette_idx in range(16): palette_cam = palettes_cam[palette_idx, :, :] - penalty = last_penalty if palette_idx == last_palette_idx else 1.0 total_dist = 0 for pixel_idx in range(line_size): pixel_cam = line_cam[pixel_idx] best_pixel_dist = 1e9 for palette_entry_idx in range(16): - pixel_dist = colour_distance_squared(pixel_cam, palette_cam[palette_entry_idx, :]) * penalty + pixel_dist = colour_distance_squared(pixel_cam, palette_cam[palette_entry_idx, :]) if pixel_dist < best_pixel_dist: best_pixel_dist = pixel_dist total_dist += best_pixel_dist @@ -650,13 +637,12 @@ cdef (unsigned char)[:, ::1] _convert_cam16ucs_to_rgb12_iigs(float[:, ::1] point cdef float[:, ::1] rgb cdef (float)[:, ::1] rgb12_iigs - # Convert CAM16UCS input to RGB - # TODO: this dynamically constructs a path on the graph of colour conversions every time, which is - # presumably not very efficient. However, colour.convert doesn't provide a way to cache the composed conversion - # function so we'd have to build it ourselves (https://github.com/colour-science/colour/issues/905) + # Convert CAM16UCS input to RGB. Even though this dynamically constructs a path on the graph of colour conversions + # every time, in practise this seems to have a negligible overhead compared to the actual conversion functions. with colour.utilities.suppress_warnings(python_warnings=True): rgb = colour.convert(point_cam, "CAM16UCS", "RGB").astype(np.float32) + # TODO: precompute this conversion matrix since it's static. This accounts for about 10% of the CPU time here. rgb12_iigs = np.clip( # Convert to Rec.601 R'G'B' colour.YCbCr_to_RGB( @@ -675,7 +661,8 @@ def convert_cam16ucs_to_rgb12_iigs(float[:, ::1] point_cam): @cython.wraparound(False) @cython.cdivision(True) def k_means_with_fixed_centroids( - int n_clusters, int n_fixed, float[:, ::1] samples, (unsigned char)[:, ::1] initial_centroids, int max_iterations, float [:, ::1] rgb12_iigs_to_cam16ucs): + int n_clusters, int n_fixed, float[:, ::1] samples, (unsigned char)[:, ::1] initial_centroids, int max_iterations, + float [:, ::1] rgb12_iigs_to_cam16ucs): cdef double error, best_error, total_error, last_total_error cdef int centroid_idx, closest_centroid_idx, i, point_idx