Commit Graph

61 Commits

Author SHA1 Message Date
kris
61b4cbb184 Tweak k-means convergence criterion to return once the total centroid position error stops decreasing. 2021-11-25 21:33:12 +00:00
kris
ad50ed103d Improvements to image quality:
- Preprocess the source image by dithering with the full 12-bit //gs
  colour palette, ignoring SHR palette restrictions (i.e. each pixel
  chosen independently from 4096 colours)

- Using this as the ground truth allows much better handling of
  e.g. solid colours, which were being dithered inconsistently with
  the previous approach

- Also when fitting an SHR palette, fix any colours that comprise more
  than 10% of source pixels.  This also encourages more uniformity in
  regions of solid colour.
2021-11-25 11:46:42 +00:00
kris
870c008827 Parametrize quantization error decay and minimum value. The latter
helps with images where there are large solid colour fields that
sometimes cause uneven dithering because of colours that cannot be
matched with the //gs palette, but it's not a viable solution in
general since it reduces overall quality (sometimes substantially,
e.g. in case of vertical colour gradients)
2021-11-25 09:09:40 +00:00
kris
de8a303de2 Initial attempt at fitting palettes to arbitrary lines instead of line ranges.
Works OK but isn't converging as well as I hoped.
2021-11-24 10:41:25 +00:00
kris
62f23ff910 Don't mutate initial_centroids 2021-11-24 09:10:03 +00:00
kris
189b4655ad Since fixing the bug in the previous commit there is no longer a need
to limit to neighbouring palettes (which was unaware of the dynamic
line splits anyway)
2021-11-23 12:49:37 +00:00
kris
be55fb859d - Fix a serious bug in best_palette_for_line which was not actually computing the palette with lowest per-row error, rather the lowest per-pixel error!
- Tidy a bit
2021-11-23 12:46:36 +00:00
kris
c608f6b961 Optimize calling _convert_cam16ucs_to_rgb12_iigs since it has
significant overhead
2021-11-18 21:50:39 +00:00
kris
7609297f0d Optimize a bit 2021-11-18 17:34:27 +00:00
kris
d7969f50ba Remove cython checks and obsolete TODO 2021-11-18 17:24:12 +00:00
kris
e53c085a91 Remove debugging prints 2021-11-17 22:55:47 +00:00
kris
ed2082344a Working version! Quantize the k-means centroids in 12-bit //gs RGB
space but continue to use CAM16-UCS for distances and updating
centroid positions, before mapping back to the nearest legal 12-bit
RGB position.

Needs some more work to deal with the fact that now that there are
discrete distances (but no fixed minimum) between allowed centroid
positions, the previous notion of convergence doesn't apply.  Actually
the centroids can oscillate between positions.

There is room for optimization but this is already reasonably
performant, and the image quality is much higher \o/
2021-11-17 22:49:06 +00:00
kris
0009ce8913 - allow reserving a number of colours which are to be shared across
all palettes.  This will be useful for Total Replay which does an
  animation effect when displaying the image (first set palettes, then
  transition in pixels)

- this requires us to go back to computing k-means ourself instead of
  using sklearn, since it can't keep some centroids fixed

- try to be more careful about //gs RGB values, which are in the
  Rec.601 colour space.  This isn't quite right yet - the issue seems
  to be that since we dither in linear RGB space but quantize in the
  nonlinear space, small differences may lead to a +/- 1 in the 4-bit
  //gs RGB value, which is quite noticeable.  Instead we need to be
  clustering and/or dithering with awareness of the quantized palette
  space.
2021-11-17 17:09:42 +00:00
kris
bb70eea7b0 Cleanup 2021-11-16 21:07:13 +00:00
kris
83b047b73f Whoops, fix a major bug with the iterated image fitting: we don't want
to mutate our source image!

Fix another bug introduced in the previous commit: convert from linear
rgb before quantizing //gs RGB palette since //gs RGB values are in
Rec.601 colour space.

Switch to double for colour_squared_distance and related variables,
not sure if it matters though.

When iterating palette clustering, reject the new palettes if they
would increase the total image error.  This prevents accepting changes
that are local improvements to one palette but which would introduce
more net errors elsewhere when this palette is reused.

This now seems to give monotonic improvements in image quality so no need
to write out intermediate images any more.
2021-11-16 15:44:04 +00:00
kris
b363d60754 Checkpoint
- switch to pyclustering for kmedians
- allow choosing the same palette as previous line, with a multiplicative penalty to distance in case it's much better
- iterate kmedians multiple times and choose the best, since it's only a local optimum
2021-11-15 09:19:44 +00:00
kris
643e50349e Optimize more 2021-11-13 17:29:13 +00:00
kris
0596aefe0b Use pyclustering for kmedians instead of hand-rolled
Optimize cython code
2021-11-13 17:18:34 +00:00
kris
52af982159 k-means should be using median with L1 norm, otherwise it may not converge
Also optimize a tiny bit
2021-11-13 16:10:33 +00:00
kris
5cab854269 Fit palettes from overlapping line ranges, and map line to palette
when dithering with two limitations:

- cannot choose the same palette as the previous line (this avoids banding)
- must be within +/- 1 of the "base" palette for the line number

This gives pretty good results!
2021-11-11 16:10:03 +00:00
kris
ee2229d0ea * Modify Floyd-Steinberg dithering to diffuse less error in the y
direction.  Otherwise, errors can accumulate in an RGB channel if
  there are no palette colours with an extremal value, and then when
  we introduce a new palette the error all suddenly discharges in a
  spurious horizontal line.  This now gives quite good results!

* Switch to using L1-norm for k-means, per suggestion of Lucas
  Scharenbroich: "A k-medians effectively uses an L1 distance metric
  instead of L2 for k-means.  Using a squared distance metric causes
  the fit to "fall off" too quickly and allows too many of the k
  centroids to cluster around areas of high density, which results in
  many similar colors being selected.  A linear cost function forces
  the centroids to spread out since the error influence has a broader
  range."
2021-11-11 11:10:22 +00:00
kris
8c34d87216 WIP - interleave 3 successive palettes for each contiguous row range.
Avoids the banding but not clear if it's overall better

Also implement my own k-means clustering which is able to keep some
centroids fixed, e.g. to be able to retain some fixed palette entries
while swapping out others.  I was hoping this would improve colour
blending across neighbouring palettes but it's also not clear if it
does.
2021-11-10 18:30:39 +00:00
kris
322123522c Assign scan lines randomly to palettes and cluster independently. This doesn't give good results either, since
neighbouring lines end up getting similar but not identical colours, which still results in horizontal striping.
2021-11-10 00:34:17 +00:00
kris
fb52815412 Experiment with striping 16 palettes contiguously across line ranges.
As expected it has clear banding.  A better approach (though still not optimal)
might be to assign lines to palettes randomly.
2021-11-09 22:42:27 +00:00
kris
80885aabf9 Working SHR version. Still just uses a single palette 2021-11-09 22:26:34 +00:00
kris
a92c9cd7b5 Work in CAM16-UCS colour space and cythonize 2021-11-09 15:13:07 +00:00
kris
1010b64272 Fix shift_pixel_window when shift_right_by > window_width 2021-11-03 15:19:29 +00:00
kris
1ed565ff8d Fix a bug with palette depths < 8 2021-11-02 22:14:42 +00:00
kris
809b975e6e Return a bitmap directly from dither_image. This removes the need to
deal with n-bit encodings at all in DHGRScreen
2021-11-02 14:42:00 +00:00
kris
b7e8c69f64 More descriptive variables 2021-11-02 13:48:53 +00:00
kris
8cfee55b1d Get rid of support for 140px mode, it was only useful as a demo of why
other converters have the wrong basic approach.
2021-11-02 13:40:32 +00:00
kris
5675fac40d Clean up a bit and accommodate palette depth in sliding pixel window 2021-11-02 12:29:30 +00:00
kris
24644658cb Tidy a bit 2021-11-01 12:10:02 +00:00
kris
0c588f7489 Tidy 2021-07-19 18:40:16 +01:00
kris
e979df03bc Nope, don't need XYZ 2021-07-19 18:13:43 +01:00
kris
8b500b16cb Dither in XYZ representation but use CAM16UCS for colour differences.
This gives the best of both worlds: dithering in a linear space, with
good (and fast) perceptual error differences

TBD: would linear RGB work as well as XYZ?
2021-07-19 17:54:46 +01:00
kris
4984df7f7a Tidy up and optimize a bit 2021-07-19 13:21:32 +01:00
kris
e08f25e4cc Simplify 2021-07-19 12:55:50 +01:00
kris
508ce134aa Try to fix how we compute the next pixel palette for 8-bit mode.
Still not sure this is correct.
2021-07-19 09:58:22 +01:00
kris
4fcda908bd WIP - use colourspacious to perform image dithering in CAM02_UCS
colour space, which is supposed to be perceptually uniform.  i.e. we
can use Euclidean distance instead of CIEDE2000
2021-07-15 13:58:22 +01:00
kris
527f6504f7 Add comments and tidy up some more 2021-03-15 17:22:14 +00:00
kris
ede063a21b No need to pattern to have third axis of shape 1
Minor optimization to not compute error_fraction 3 times

Tidy a bit
2021-03-15 16:22:55 +00:00
kris
551be3eba7 Get --resolution=140 working again
Back out the pixel_palette_options WIP to make this mergable
2021-03-15 15:41:33 +00:00
kris
fcf83aaf9c Even more fasterer! 2021-03-15 15:11:06 +00:00
kris
f157272327 Checkpoint more type annotations 2021-03-15 15:10:49 +00:00
kris
722e85f645 Combine loops in dither_lookahead and don't bother dithering beyond
lookahead or edge of screen.
2021-03-15 15:10:33 +00:00
kris
8b48455c8f Checkpoint 2021-03-15 15:10:33 +00:00
kris
049c334950 Fixed 2021-03-15 15:10:33 +00:00
kris
f4cc7dac40 Checkpoint - I broke something 2021-03-15 15:10:33 +00:00
kris
a686ef76c3 WIP 2021-03-15 15:10:12 +00:00