forked from hkc/cc-stuff
1
0
Fork 0

Compare commits

...

3 Commits

Author SHA1 Message Date
Casey c3b5d1a198
Added verbosity and a progress bar 2024-11-16 14:35:05 +03:00
Casey 743435200d
Added missing random things
Mostly sample images and tiny scripts for weight calculation.
Nothing too of importance
2024-10-05 14:27:07 +03:00
Casey c45e9f88bc
OpenMP, CPIv1 writer and proper size for converter
Also moved *_varint into commons, writers are now separate functions,
maybe I could move them into commons sometime and add readers in case I
would use them at some point.
2024-10-05 12:44:16 +03:00
14 changed files with 212 additions and 58 deletions

View File

@ -18,4 +18,7 @@ dependencies/stb/%.o: dependencies/stb/%.h
wsvpn: wsvpn.o dependencies/mongoose/mongoose.o wsvpn: wsvpn.o dependencies/mongoose/mongoose.o
$(CC) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o "$@" $(CC) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o "$@"
clean:
$(RM) -v img2cpi cpi2png wsvpn wsvpn.o cc-common.o dependencies/stb/*.o
.PHONY: all .PHONY: all

View File

@ -1,5 +1,36 @@
#include "cc-common.h" #include "cc-common.h"
int read_varint(FILE *fp, unsigned int *out) {
int position = 0;
while (true) {
unsigned char curr = fgetc(fp);
*out |= (curr & 0x7F) << position;
if ((curr & 0x80) == 0) break;
position += 7;
if (position >= 32) return -position / 7;
}
return (position + 7) / 7;
}
int write_varint(FILE *fp, unsigned int in) {
unsigned mask = 0xFFFFFF80;
int written = 0;
while (true) {
if ((in & mask) == 0) {
fputc(in & 0xff, fp);
return written + 1;
}
fputc((in & 0x7F) | 0x80, fp);
written++;
in >>= 7;
}
}
const struct palette cc_default_palette = PALETTE( const struct palette cc_default_palette = PALETTE(
{ { 0xf0, 0xf0, 0xf0, 0xff } }, { { 0xf0, 0xf0, 0xf0, 0xff } },
{ { 0xf2, 0xb2, 0x33, 0xff } }, { { 0xf2, 0xb2, 0x33, 0xff } },

View File

@ -2,6 +2,8 @@
#define _CC_COMMON_H_ #define _CC_COMMON_H_
#include <stdint.h> #include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
typedef uint8_t GlyphBitmap[11]; typedef uint8_t GlyphBitmap[11];
@ -22,4 +24,8 @@ struct palette {
const extern GlyphBitmap cc_font_atlas[256]; const extern GlyphBitmap cc_font_atlas[256];
const extern struct palette cc_default_palette, cc_default_gray_palette; const extern struct palette cc_default_palette, cc_default_gray_palette;
int read_varint(FILE *fp, unsigned int *out);
int write_varint(FILE *fp, unsigned int in);
#endif #endif

BIN
cpi-images/casey.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

BIN
cpi-images/cute.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

BIN
cpi-images/n25.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

BIN
cpi-images/rando.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

BIN
cpi-images/rat.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

BIN
cpi-images/wp.cpi Normal file

Binary file not shown.

View File

@ -10,8 +10,6 @@
#include "cc-common.h" #include "cc-common.h"
bool read_varint(FILE *fp, unsigned int *out);
int main(int argc, char **argv) { int main(int argc, char **argv) {
if (argc < 3) { if (argc < 3) {
fprintf(stderr, "Usage: %s [input.cpi] [output.png]\n", argv[0]); fprintf(stderr, "Usage: %s [input.cpi] [output.png]\n", argv[0]);
@ -43,8 +41,8 @@ int main(int argc, char **argv) {
height = fgetc(fp_in); height = fgetc(fp_in);
(void)fgetc(fp_in); // XXX: ignore scale (void)fgetc(fp_in); // XXX: ignore scale
} else if (version == 1) { } else if (version == 1) {
assert(read_varint(fp_in, &width) && "Failed to read width varint"); assert(read_varint(fp_in, &width) > 0 && "Failed to read width varint");
assert(read_varint(fp_in, &height) && "Failed to read height varint"); assert(read_varint(fp_in, &height) > 0 && "Failed to read height varint");
} else { } else {
assert(false && "Failed to read size: unsupported version"); assert(false && "Failed to read size: unsupported version");
} }
@ -55,7 +53,7 @@ int main(int argc, char **argv) {
// XXX: may change in future when we introduce variable-size palettes // XXX: may change in future when we introduce variable-size palettes
// though, it may never change, if I'm being honest. Why would I choose // though, it may never change, if I'm being honest. Why would I choose
// worse image quality with less colors when I can use all of them? // worse image quality with less colors when I can use all of them?
union color *colors = calloc(16, sizeof(union color)); union color colors[16] = { 0 };
// NOTE: our `union color` type is 4 bytes long, while palette stored in the // NOTE: our `union color` type is 4 bytes long, while palette stored in the
// file itself uses 3 bytes per color, so we can't just `fread` them at once, // file itself uses 3 bytes per color, so we can't just `fread` them at once,
@ -67,10 +65,13 @@ int main(int argc, char **argv) {
colors[i].rgba.a = 0xff; colors[i].rgba.a = 0xff;
} }
unsigned char *buffer = calloc(width * height, 2);
fread(buffer, 2, width * height, fp_in);
for (int y = 0; y < height; y++) { for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) { for (int x = 0; x < width; x++) {
unsigned char sym = fgetc(fp_in); unsigned char sym = buffer[(x + y * width) * 2];
unsigned char color = fgetc(fp_in); unsigned char color = buffer[(x + y * width) * 2 + 1];
union color background = colors[color & 0xF]; union color background = colors[color & 0xF];
union color foreground = colors[color >> 4]; union color foreground = colors[color >> 4];
for (int oy = 0; oy < 9; oy++) { for (int oy = 0; oy < 9; oy++) {
@ -84,25 +85,7 @@ int main(int argc, char **argv) {
stbi_write_png(argv[2], width * 6, height * 9, 4, canvas, 0); stbi_write_png(argv[2], width * 6, height * 9, 4, canvas, 0);
free(colors);
free(canvas); free(canvas);
fclose(fp_in); fclose(fp_in);
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
bool read_varint(FILE *fp, unsigned int *out) {
int position = 0;
while (true) {
unsigned char curr = fgetc(fp);
*out |= (curr & 0x7F) << position;
if ((curr & 0x80) == 0) break;
position += 7;
if (position >= 32) return false;
}
return true;
}

172
img2cpi.c
View File

@ -13,9 +13,16 @@
#include <strings.h> #include <strings.h>
#include <stdbool.h> #include <stdbool.h>
#include "cc-common.h" #include "cc-common.h"
#ifdef USE_OPENMP
#include <omp.h>
#endif
#define MAX_COLOR_DIFFERENCE 768 #define MAX_COLOR_DIFFERENCE 768
#define K_MEANS_ITERATIONS 4 #define K_MEANS_ITERATIONS 4
#define PROGRESS_BAR_WIDTH 24
#define TOSTRNAME(M) #M
#define TOSTR(M) TOSTRNAME(M)
struct cc_char { struct cc_char {
unsigned char character; unsigned char character;
@ -24,6 +31,7 @@ struct cc_char {
struct arguments { struct arguments {
bool fast_mode; bool fast_mode;
bool verbose;
int width, height; int width, height;
enum cpi_version { enum cpi_version {
CPI_VERSION_AUTO, CPI_VERSION_AUTO,
@ -52,6 +60,7 @@ struct arguments {
char *output_path; char *output_path;
} args = { } args = {
.fast_mode = false, .fast_mode = false,
.verbose = false,
.width = 4 * 8 - 1, // 4x3 blocks screen .width = 4 * 8 - 1, // 4x3 blocks screen
.height = 3 * 6 - 2, .height = 3 * 6 - 2,
.cpi_version = CPI_VERSION_AUTO, .cpi_version = CPI_VERSION_AUTO,
@ -59,8 +68,7 @@ struct arguments {
.input_path = NULL, .input_path = NULL,
.output_path = NULL, .output_path = NULL,
.palette = NULL, .palette = NULL,
.palette_type = PALETTE_DEFAULT // TODO(kc): change to PALETTE_AUTO when .palette_type = PALETTE_AUTO
// k-means is implemented
}; };
struct image { struct image {
@ -91,17 +99,23 @@ struct k_means_state {
bool parse_cmdline(int argc, char **argv); bool parse_cmdline(int argc, char **argv);
void show_help(const char *progname, bool show_all, FILE *fp); void show_help(const char *progname, bool show_all, FILE *fp);
struct image *image_load(const char *fp); struct image *image_load(const char *fp);
struct image *image_new(int w, int h); struct image *image_new(int w, int h);
struct image *image_resize(struct image *original, int new_w, int new_h); struct image *image_resize(struct image *original, int new_w, int new_h);
struct image_pal *image_quantize(struct image *original, const struct palette *palette); struct image_pal *image_quantize(struct image *original, const struct palette *palette);
void image_unload(struct image *img);
float get_color_difference(union color a, union color b); float get_color_difference(union color a, union color b);
float get_color_brightness(union color clr); float get_color_brightness(union color clr);
void image_unload(struct image *img);
void get_size_keep_aspect(int w, int h, int dw, int dh, int *ow, int *oh); void get_size_keep_aspect(int w, int h, int dw, int dh, int *ow, int *oh);
void convert_2x3(const struct image_pal *img, struct cc_char *characters); void convert_2x3(const struct image_pal *img, struct cc_char *characters);
void convert_8x11(const struct image_pal *img, struct cc_char *characters); void convert_6x9(const struct image_pal *img, struct cc_char *characters);
int save_cpi_0(FILE *fp, const struct palette *pal, const struct cc_char *chars, int w, int h);
int save_cpi_1(FILE *fp, const struct palette *pal, const struct cc_char *chars, int w, int h);
// Only one global custom palette is maintained // Only one global custom palette is maintained
struct palette *custom_palette_resize(uint8_t size); struct palette *custom_palette_resize(uint8_t size);
@ -126,6 +140,7 @@ static const struct optiondocs {
} optiondocs[] = { } optiondocs[] = {
{ 'h', "help", 0, "Show help", 0 }, { 'h', "help", 0, "Show help", 0 },
{ 'f', "fast", 0, "Use fast (old) method for picking characters and colors", 0 }, { 'f', "fast", 0, "Use fast (old) method for picking characters and colors", 0 },
{ 'v', "verbose", 0, "Increase verbosity", 0 },
{ 'W', "width", "width", "Width in characters", 0 }, { 'W', "width", "width", "Width in characters", 0 },
{ 'h', "height", "height", "Height in characters", 0 }, { 'h', "height", "height", "Height in characters", 0 },
{ 'P', "palette", "palette", "Use specific palette.\n" { 'P', "palette", "palette", "Use specific palette.\n"
@ -156,6 +171,8 @@ static const struct optiondocs {
{ 0 } { 0 }
}; };
static char progress_bar[PROGRESS_BAR_WIDTH];
int main(int argc, char **argv) { int main(int argc, char **argv) {
if (!parse_cmdline(argc, argv)) { if (!parse_cmdline(argc, argv)) {
show_help(argv[0], false, stderr); show_help(argv[0], false, stderr);
@ -168,11 +185,16 @@ int main(int argc, char **argv) {
return EXIT_FAILURE; return EXIT_FAILURE;
} }
if (args.verbose) {
memset(progress_bar, '#', PROGRESS_BAR_WIDTH);
printf("Input image: %dx%d\n", src_image->w, src_image->h);
}
struct image *canvas; struct image *canvas;
if (args.fast_mode) { if (args.fast_mode) {
canvas = image_new(args.width * 2, args.height * 3); canvas = image_new(args.width * 2, args.height * 3);
} else { } else {
canvas = image_new(args.width * 8, args.height * 11); canvas = image_new(args.width * 6, args.height * 9);
} }
if (!canvas) { if (!canvas) {
@ -180,14 +202,18 @@ int main(int argc, char **argv) {
return EXIT_FAILURE; return EXIT_FAILURE;
} }
if (args.verbose) {
printf("Output image canvas: %dx%d\n", canvas->w, canvas->h);
}
// TODO: load palette, maybe calculate it too? k-means? // TODO: load palette, maybe calculate it too? k-means?
const struct palette *palette = &cc_default_palette; const struct palette *palette = &cc_default_palette;
switch (args.palette_type) { switch (args.palette_type) {
case PALETTE_DEFAULT: palette = &cc_default_palette; break; case PALETTE_DEFAULT: palette = &cc_default_palette; break;
case PALETTE_DEFAULT_GRAY: palette = &cc_default_gray_palette; break; case PALETTE_DEFAULT_GRAY: palette = &cc_default_gray_palette; break;
case PALETTE_AUTO: palette = palette_k_means(src_image, &cc_default_palette); break; case PALETTE_AUTO: palette = palette_k_means(src_image, &cc_default_palette); break;
case PALETTE_LIST: assert(0 && "Not implemented"); break; case PALETTE_LIST: assert(0 && "Not implemented"); break; // TODO
case PALETTE_PATH: assert(0 && "Not implemented"); break; case PALETTE_PATH: assert(0 && "Not implemented"); break; // TODO
default: assert(0 && "Unreachable"); default: assert(0 && "Unreachable");
} }
@ -197,6 +223,10 @@ int main(int argc, char **argv) {
int new_w, new_h; int new_w, new_h;
get_size_keep_aspect(src_image->w, src_image->h, canvas->w, canvas->h, &new_w, &new_h); get_size_keep_aspect(src_image->w, src_image->h, canvas->w, canvas->h, &new_w, &new_h);
if (args.verbose) {
printf("Scaling down to: %dx%d\n", new_w, new_h);
}
scaled_image = image_resize(src_image, new_w, new_h); scaled_image = image_resize(src_image, new_w, new_h);
if (!scaled_image) { if (!scaled_image) {
fprintf(stderr, "Error: failed to open the file\n"); fprintf(stderr, "Error: failed to open the file\n");
@ -222,26 +252,33 @@ int main(int argc, char **argv) {
return EXIT_FAILURE; return EXIT_FAILURE;
} }
if (args.fast_mode) { if (args.verbose) {
convert_2x3(quantized_image, characters); printf("Converting image ");
} else {
convert_8x11(quantized_image, characters);
} }
// TODO: implement something other than CPIv0 if (args.fast_mode) {
FILE *fp = fopen(args.output_path, "wb"); if (args.verbose) {
fwrite("CCPI", 1, 4, fp); printf(" using fast method\n");
fputc(args.width, fp);
fputc(args.height, fp);
fputc(0x00, fp);
for (int i = 0; i < 16; i++) {
fputc(palette->colors[i].rgba.r, fp);
fputc(palette->colors[i].rgba.g, fp);
fputc(palette->colors[i].rgba.b, fp);
} }
for (int i = 0; i < args.width * args.height; i++) { convert_2x3(quantized_image, characters);
fputc(characters[i].character, fp); } else {
fputc(characters[i].bg | (characters[i].fg << 4), fp); if (args.verbose) {
printf(" using slow method\n");
}
convert_6x9(quantized_image, characters);
}
if (args.verbose) {
printf("Conversion done, saving image ");
}
FILE *fp = fopen(args.output_path, "wb");
if (args.width < 256 && args.height < 256) {
printf(" using cpiv0\n");
save_cpi_0(fp, palette, characters, args.width, args.height);
} else {
printf(" using cpiv1\n");
save_cpi_1(fp, palette, characters, args.width, args.height);
} }
fclose(fp); fclose(fp);
@ -250,6 +287,47 @@ int main(int argc, char **argv) {
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
int _write_palette_full(FILE *fp, const struct palette *pal) {
int written = 0;
assert(pal->count == 16 && "Invalid palette size");
for (int i = 0; i < 16; i++) {
written += fputc(pal->colors[i].rgba.r, fp);
written += fputc(pal->colors[i].rgba.g, fp);
written += fputc(pal->colors[i].rgba.b, fp);
}
return written;
}
int _write_pixeldata_v0(FILE *fp, const struct cc_char *chars, int w, int h) {
int written = 0;
for (int i = 0; i < w * h; i++) {
written += fputc(chars[i].character, fp);
written += fputc(chars[i].bg | (chars[i].fg << 4), fp);
}
return written;
}
int save_cpi_0(FILE *fp, const struct palette *pal, const struct cc_char *chars, int w, int h) {
int written = 0;
written += fwrite("CCPI", 1, 4, fp);
written += fputc(w, fp);
written += fputc(h, fp);
written += fputc(0x00, fp);
written += _write_palette_full(fp, pal);
written += _write_pixeldata_v0(fp, chars, w, h);
return written;
}
int save_cpi_1(FILE *fp, const struct palette *pal, const struct cc_char *chars, int w, int h) {
int written = 0;
written += fwrite("CPI\x01", 1, 4, fp);
written += write_varint(fp, w);
written += write_varint(fp, h);
written += _write_palette_full(fp, pal);
written += _write_pixeldata_v0(fp, chars, w, h);
return written;
}
bool parse_cmdline(int argc, char **argv) { bool parse_cmdline(int argc, char **argv) {
static struct option options[] = { static struct option options[] = {
{ "help", no_argument, 0, 'h' }, { "help", no_argument, 0, 'h' },
@ -264,7 +342,7 @@ bool parse_cmdline(int argc, char **argv) {
while (true) { while (true) {
int option_index = 0; int option_index = 0;
int c = getopt_long(argc, argv, "hfW:H:V:p:P:", options, &option_index); int c = getopt_long(argc, argv, "hvfW:H:V:p:P:", options, &option_index);
if (c == -1) break; if (c == -1) break;
if (c == 0) c = options[option_index].val; if (c == 0) c = options[option_index].val;
if (c == '?') break; if (c == '?') break;
@ -280,6 +358,9 @@ bool parse_cmdline(int argc, char **argv) {
fprintf(stderr, "Warning: text mode ignores version\n"); fprintf(stderr, "Warning: text mode ignores version\n");
} }
break; break;
case 'v': // --verbose
args.verbose = true;
break;
case 'W': // --width case 'W': // --width
args.width = atoi(optarg); args.width = atoi(optarg);
break; break;
@ -541,6 +622,14 @@ float get_color_brightness(union color clr) {
void convert_2x3(const struct image_pal *img, struct cc_char *characters) { void convert_2x3(const struct image_pal *img, struct cc_char *characters) {
int w = img->w / 2, h = img->h / 3; int w = img->w / 2, h = img->h / 3;
for (int y = 0; y < h; y++) { for (int y = 0; y < h; y++) {
if (args.verbose) {
int sz = PROGRESS_BAR_WIDTH - (y * PROGRESS_BAR_WIDTH / h);
printf("\r[%-" TOSTR(PROGRESS_BAR_WIDTH) ".*s|%7.3f%%|%4d/%4d]",
PROGRESS_BAR_WIDTH - sz, progress_bar + sz,
100.0 * (y + 1) / h,
y + 1, h);
fflush(stdout);
}
for (int x = 0; x < w; x++) { for (int x = 0; x < w; x++) {
unsigned char darkest_i = 0, brightest_i = 0; unsigned char darkest_i = 0, brightest_i = 0;
float darkest_diff = 0xffffff, brightest_diff = 0; float darkest_diff = 0xffffff, brightest_diff = 0;
@ -591,10 +680,13 @@ void convert_2x3(const struct image_pal *img, struct cc_char *characters) {
characters[x + y * w].fg = brightest_i; characters[x + y * w].fg = brightest_i;
} }
} }
if (args.verbose) {
printf("\n");
}
} }
void convert_8x11(const struct image_pal *img, struct cc_char *characters) { void convert_6x9(const struct image_pal *img, struct cc_char *characters) {
int w = img->w / 8, h = img->h / 11; int w = img->w / 6, h = img->h / 9;
float palette_self_diffs[0x100][0x10] = {{(float) 0xffffff}}; float palette_self_diffs[0x100][0x10] = {{(float) 0xffffff}};
for (int input_color = 0x0; input_color < 0x100 && input_color < img->palette->count; input_color++) { for (int input_color = 0x0; input_color < 0x100 && input_color < img->palette->count; input_color++) {
for (int output_color = 0x0; output_color < 0x10 && output_color < img->palette->count; output_color++) { for (int output_color = 0x0; output_color < 0x10 && output_color < img->palette->count; output_color++) {
@ -603,12 +695,23 @@ void convert_8x11(const struct image_pal *img, struct cc_char *characters) {
} }
for (int y = 0; y < h; y++) { for (int y = 0; y < h; y++) {
if (args.verbose) {
int sz = PROGRESS_BAR_WIDTH - (y * PROGRESS_BAR_WIDTH / h);
printf("\r[%-" TOSTR(PROGRESS_BAR_WIDTH) ".*s|%7.3f%%|%4d/%4d]",
PROGRESS_BAR_WIDTH - sz, progress_bar + sz,
100.0 * (y + 1) / h,
y + 1, h);
fflush(stdout);
}
#ifdef USE_OPENMP
#pragma omp parallel for
#endif
for (int x = 0; x < w; x++) { for (int x = 0; x < w; x++) {
float chunk_palette_diffs[8][11][0x10] = {{{(float) 0xffffff}}}; float chunk_palette_diffs[6][9][0x10] = {{{(float) 0xffffff}}};
for (int ox = 0; ox < 8; ox++) { for (int ox = 0; ox < 6; ox++) {
for (int oy = 0; oy < 11; oy++) { for (int oy = 0; oy < 9; oy++) {
uint8_t pixel_unresolved = img->pixels[ uint8_t pixel_unresolved = img->pixels[
ox + (x + (y * 11 + oy) * w) * 8 ox + (x + (y * 9 + oy) * w) * 6
]; ];
for (int color = 0x0; color < 0x10 && color < img->palette->count; color++) { for (int color = 0x0; color < 0x10 && color < img->palette->count; color++) {
chunk_palette_diffs[ox][oy][color] = palette_self_diffs[pixel_unresolved][color]; chunk_palette_diffs[ox][oy][color] = palette_self_diffs[pixel_unresolved][color];
@ -624,9 +727,9 @@ void convert_8x11(const struct image_pal *img, struct cc_char *characters) {
} }
for (int color = 0x00; color <= 0xff; color++) { for (int color = 0x00; color <= 0xff; color++) {
float difference = 0; float difference = 0;
for (int oy = 0; oy < 11; oy++) { for (int oy = 0; oy < 9; oy++) {
unsigned char sym_line = cc_font_atlas[sym][oy]; unsigned char sym_line = cc_font_atlas[sym][oy];
for (int ox = 0; ox < 8; ox++) { for (int ox = 0; ox < 6; ox++) {
bool lit = sym_line & (0x80 >> ox); bool lit = sym_line & (0x80 >> ox);
difference += chunk_palette_diffs[ox][oy][lit ? color >> 4 : color & 0xF]; difference += chunk_palette_diffs[ox][oy][lit ? color >> 4 : color & 0xF];
} }
@ -643,6 +746,9 @@ void convert_8x11(const struct image_pal *img, struct cc_char *characters) {
characters[x + y * w].fg = closest_color >> 4; characters[x + y * w].fg = closest_color >> 4;
} }
} }
if (args.verbose) {
printf("\n");
}
} }
struct { struct {

BIN
mess/avg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 B

BIN
mess/cc_font.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

25
mess/gen-weights.py Normal file
View File

@ -0,0 +1,25 @@
from PIL import Image
from collections import Counter
with Image.open("./cc_font.png") as im:
pixels = im.load()
weights = [0 for _ in range(6 * 9)]
for char in range(256):
ctx, cty = (char % 16) * 8, (char // 16) * 11
for oy in range(9):
for ox in range(6):
pix = int(pixels[ctx + ox + 1, cty + oy + 1][0]) # type: ignore
weights[ox + 6 * oy] += 1 if pix else 0
with Image.new("L", (6, 9), 0) as im_out:
for y in range(9):
for x in range(6):
print("%3d" % weights[x + 6 * y], end="\t")
im_out.putpixel((x, y), weights[x + 6 * y])
print()
im_out.save("avg.png")
print(dict(enumerate([
iv[0] for iv in sorted(enumerate(weights), key=lambda iv: iv[1])
])))