Commit Graph

206 Commits

Author SHA1 Message Date
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
8694ab364e Perform conversions in linear RGB space 2021-11-16 12:38:53 +00:00
kris
7ad560247b Clean up 2021-11-16 12:24:43 +00:00
kris
10c829906b Checkpoint
- Repeatedly refit palettes since k-means is only a local
  optimization.  This can produce incremental improvements in image
  quality but may also overfit, especially on complex images.
- use pygame to render incremental images
- Fix off-by-one in palette striping
- When fitting palettes, first cluster a 16-colour palette for the
  entire image and use this to initialize the centroids for individual
  palettes.  This improves quality when fitting images with large
  blocks of colour, since they will otherwise be fit separately and
  may have slight differences.  With a global initializer these will
  tend to be the same.  This also improves performance.
2021-11-16 11:21:53 +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
21058084e2 Tidy 2021-11-09 16:14:37 +00:00
kris
01b19a4a06 Use 4-bit RGB values instead of 8-bit 2021-11-09 15:35:44 +00:00
kris
a92c9cd7b5 Work in CAM16-UCS colour space and cythonize 2021-11-09 15:13:07 +00:00
kris
173c283369 First implementation of using k-means clustering in RGB space to dither a 320x200 SHR image. 2021-11-09 11:23:25 +00:00
kris
0630db2bf1 More relnotes 2021-11-04 14:55:23 +00:00
kris
f097945b2e Add version history 2021-11-04 14:46:53 +00:00
kris
aebb21263e Update docs 2021-11-04 14:35:14 +00:00
kris
387500a9b4 Remove stray file 2021-11-04 14:34:44 +00:00
kris
bd19d27bd3 Add a script to regenerate image conversions for example images, and add a .po disk image 2021-11-04 14:32:58 +00:00
kris
7e68847bfe nit 2021-11-04 14:31:33 +00:00
kris
1010b64272 Fix shift_pixel_window when shift_right_by > window_width 2021-11-03 15:19:29 +00:00
kris
34ae40ac2d Fix a bug with output for non-ntsc palette 2021-11-03 15:17:57 +00:00
kris
b7174778e6 Oops, no that was wrong. I forgot to cross-check against OpenEmulator <o> 2021-11-03 12:40:22 +00:00
kris
bf76271d75 NTSC conversion should be using YIQ space instead of YUV, which seems
to explain several fudge factors I needed to include to match colours.
2021-11-02 23:28:58 +00:00
kris
df0adec8aa Fix typo 2021-11-02 22:24:47 +00:00
kris
d35cdbc877 Switch remaining palettes to be indexed by (n-bit pixel value, NTSC
phase) and update the comments to explain the encoding scheme.
2021-11-02 22:15:13 +00:00
kris
52bd35e875 Update comment 2021-11-02 22:15:09 +00:00
kris
3544bd74c8 Update comment 2021-11-02 22:14:59 +00:00
kris
1ed565ff8d Fix a bug with palette depths < 8 2021-11-02 22:14:42 +00:00
kris
0f1ec6f6f2 Optimize memory a bit 2021-11-02 21:25:00 +00:00
kris
4fbb0faf84 Fixes 2021-11-02 15:47:37 +00:00
kris
9dbf413733 Tidy 2021-11-02 15:45:20 +00:00
kris
e84dfb59f9 Tidy a bit 2021-11-02 15:26:43 +00:00
kris
b63fd81c07 Unify DHGRScreen implementations 2021-11-02 15:23:23 +00:00
kris
75a3c6bc48 Simplify image_to_rgb and remove the need for DOTS and DOTS_TO_INDEX 2021-11-02 15:14:22 +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
cf69dc9cf2 Bounds check lookahead 2021-11-02 13:42:23 +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
d442baf1f1 Tidy a bit 2021-11-02 12:30:43 +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
e5c49b3518 Floyd-Steinberg is working well now, and gives better detail 2021-07-19 18:39:38 +01:00
kris
feefdb5dc6 Use .npy format for RGB to CAM16UCS conversion matrix, and get rid of precomputed CIE2000 distances 2021-07-19 18:35:44 +01:00
kris
e979df03bc Nope, don't need XYZ 2021-07-19 18:13:43 +01:00