#define VERSION "v0.073" // With fix contributed by Lachesis // Compile with: // gcc -o ccv ccv.c -Wall -std=c99 -O3 #include #include #include #include #include // This software requires uthash to compile. Download it from // http://troydhanson.github.com/uthash/ #include "uthash.h" #define MAX_PATH 65535 void Usage() { fprintf(stderr, "ccv " VERSION "\n"); fprintf(stderr, "Usage: ccv [options] input_file [...]\n"); } // Usage: // ccv input.bmp // .. converts input.bmp to input.chr. The conversion will convert the image to a charset or partial charset containing a quantised representation of the image. No char reuse will be done. // ccv input.bmp -mzm // .. converts input.bmp to input.chr and input.mzm. If the same character would have been used multiple times, it will be reused. Hence while the .chr itself may not match the input image, the combination of the .chr and .mzm will. // ccv input.bmp -reuse // .. converts input.bmp to input.chr, reusing chars where possible. This option is implied with -mzm, but can be used for simple charset output with this. // ccv input.bmp -mzm -noreuse // .. converts input.bmp to input.chr and input.mzm without reusing characters. // ccv input.bmp -o output // .. converts input.bmp to output.chr // ccv input.bmp -mzm -o output // .. converts input.bmp to output.chr and output.mzm // ccv input1.bmp input2.bmp -mzm // .. converts input1.bmp and input2.bmp to input1.chr, input2.chr, input1.mzm and input2.mzm // ccv input.bmp -mzm -blank 32 // .. converts input.bmp to input.chr and input.mzm. Char 32 will be blank and blank parts of the image will be assigned char #32 instead of some other char. // ccv -c 30 input.bmp -mzm // .. converts input.bmp to input.chr and input.mzm, with no more than 30 characters used. If necessary, close characters will be reused. -c implies -reuse. // ccv -offset 128 input.bmp -mzm // .. converts input.bmp to input.chr and input.mzm with the char indices in input.mzm being offset by 128 places. Hence loading the charset with load char set "@128input.chr" and placing the MZM should result in accurate graphics. // ccv -exclude 32 input.bmp -mzm // .. converts input.bmp to input.chr and input.mzm. If char #32 would otherwise be used in the conversion, it will be replaced with a blank char instead and will not be referenced in the output -mzm. // ccv -exclude 32-63 input.bmp -mzm // .. converts input.bmp to input.chr and input.mzm. If chars #32 to #63 would otherwise be used in the conversion, they will be replaced with blank chars instead and will not be referenced in the output -mzm. // ccv -exclude 0,32 input.bmp -mzm // .. converts input.bmp to input.chr and input.mzm. If chars #0 or #32 would otherwise be used in the conversion, they will be replaced with blank chars instead and will not be referenced in the output -mzm. // ccv input.raw -w 256 -h 112 // .. converts input.raw to input.chr. input.raw is assumed to be an 8-bit headerless image of the dimensions given, where zero is black and nonzero is white. // ccv input.bmp -threshold 160 // .. converts input.bmp to input.chr turning pixels >= 160 brightness white and other pixels black. If not supplied, the default is 127. // ccv input.bmp -dither floyd-steinberg // ccv input.bmp -dither stucki // ccv input.bmp -dither jarvis-judice-ninke // ccv input.bmp -dither burkes // ccv input.bmp -dither sierra // ccv input.bmp -dither stevenson-arce // .. converts input.bmp to input.chr using the given error-diffusion kernel. Note that you can abbreviate it (i.e. 'floyd', 'jarvis') as it only has to match part of the name. Note that the -threshold value, if supplied, is still used here. void Error(const char *string, ...) { va_list vaList; va_start(vaList, string); fprintf(stderr, "Error: "); vfprintf(stderr, string, vaList); fprintf(stderr, "\n"); va_end(vaList); exit(1); } inline static int lc_strcmp(const char *A, const char *B) { if (A == NULL) return -1; if (B == NULL) return 1; const char *a = A; const char *b = B; for (;;) { if (tolower(*a) < tolower(*b)) return -1; if (tolower(*a) > tolower(*b)) return 1; if (*a == '\0') return 0; a++; b++; } } inline static int lc_strncmp(const char *A, const char *B, int n) { if (A == NULL) return -1; if (B == NULL) return 1; const char *a = A; const char *b = B; int i = 0; for (;;) { if (i >= n) return 0; if (tolower(*a) < tolower(*b)) return -1; if (tolower(*a) > tolower(*b)) return 1; if (*a == '\0') return 0; a++; b++; i++; } } inline static void file_write32(int val, FILE *fp) { fputc((val >> 0) & 0xFF, fp); fputc((val >> 8) & 0xFF, fp); fputc((val >> 16) & 0xFF, fp); fputc((val >> 24) & 0xFF, fp); } inline static int file_read32(FILE *fp) { unsigned int r = 0; r |= fgetc(fp); r |= fgetc(fp) << 8; r |= fgetc(fp) << 16; r |= fgetc(fp) << 24; return r; } inline static void file_write16(int val, FILE *fp) { fputc((val >> 0) & 0xFF, fp); fputc((val >> 8) & 0xFF, fp); } inline static int file_read16(FILE *fp) { unsigned int r = 0; r |= fgetc(fp); r |= fgetc(fp) << 8; return r; } typedef struct { char path[MAX_PATH+1]; UT_hash_handle hh; } InputFile; typedef struct { InputFile *files; int w; int h; int mzm; int reuse; int noreuse; int quiet; int c; int threshold; int offset; int exclude_on; char exclude[256]; int blank; char output[MAX_PATH - 4 + 1]; char dither[256]; } Config; Config *DefaultConfig() { Config *cfg = malloc(sizeof(Config)); cfg->files = NULL; cfg->w = 0; cfg->h = 0; cfg->mzm = 0; cfg->reuse = 0; cfg->noreuse = 0; cfg->quiet = 0; cfg->offset = 0; cfg->exclude_on = 0; for (int i = 0; i < 256; i++) { cfg->exclude[i] = 0; } cfg->blank = -1; cfg->c = 0; cfg->threshold = 127; cfg->output[0] = '\0'; cfg->dither[0] = '\0'; return cfg; } int Option(const char *option_name, int arguments, char *argument, int args_left) { if (strcmp(argument, option_name) != 0) return 0; if (args_left < arguments) { Error("Insufficient arguments for %s", option_name); } return 1; } Config *LoadConfig(int argc, char **argv) { Config *cfg = DefaultConfig(); char arg_used[argc]; memset(arg_used, 0, argc); for (int i = 0; i < argc; i++) { if (arg_used[i]) continue; if (argv[i][0] == '-') { // Argument int args_left = argc - i - 1; if (Option("-w", 1, argv[i], args_left)) { cfg->w = atoi(argv[i+1]); arg_used[i+1] = 1; continue; } if (Option("-h", 1, argv[i], args_left)) { cfg->h = atoi(argv[i+1]); arg_used[i+1] = 1; continue; } if (Option("-mzm", 0, argv[i], args_left)) { cfg->mzm = 1; cfg->reuse = 1; continue; } if (Option("-reuse", 0, argv[i], args_left)) { cfg->reuse = 1; continue; } if (Option("-noreuse", 0, argv[i], args_left)) { cfg->noreuse = 1; continue; } if (Option("-output", 1, argv[i], args_left)) { strncpy(cfg->output, argv[i+1], MAX_PATH-4+1); cfg->output[MAX_PATH-4] = '\0'; arg_used[i+1] = 1; continue; } if (Option("-c", 1, argv[i], args_left)) { cfg->c = atoi(argv[i+1]); arg_used[i+1] = 1; continue; } if (Option("-dither", 1, argv[i], args_left)) { strncpy(cfg->dither, argv[i+1], 256); cfg->output[255] = '\0'; arg_used[i+1] = 1; continue; } if (Option("-threshold", 1, argv[i], args_left)) { cfg->threshold = atoi(argv[i+1]); arg_used[i+1] = 1; continue; } if (Option("-offset", 1, argv[i], args_left)) { cfg->offset = atoi(argv[i+1]); arg_used[i+1] = 1; continue; } if (Option("-blank", 1, argv[i], args_left)) { cfg->blank = atoi(argv[i+1]); arg_used[i+1] = 1; continue; } if (Option("-exclude", 1, argv[i], args_left)) { cfg->exclude_on = 1; char *s = argv[i+1]; char tok[256]; char op; int curr = 0; int range_st = -1; int range_ed = -1; while (*s) { if (sscanf(s, "%[0-9]", tok) == 0) break; s += strlen(tok); cfg->exclude[atoi(tok)] = 1; if (curr == 1) { curr = 0; range_ed = atoi(tok); for (int ch = range_st; range_st <= range_ed ? ch <= range_ed : ch >= range_ed; range_st <= range_ed ? ch++ : ch--) { cfg->exclude[ch] = 1; } } op = s[0]; if (op) s++; if (op == '-') { range_st = atoi(tok); curr = 1; } } arg_used[i+1] = 1; continue; } } else { // Filename InputFile *file = malloc(sizeof(InputFile)); strncpy(file->path, argv[i], MAX_PATH+1); HASH_ADD_STR(cfg->files, path, file); } } if (cfg->files == NULL) { Usage(); exit(1); } return cfg; } typedef struct { int w, h; int channels; unsigned char *pixels; } Image; Image *CreateImage(int w, int h, int channels) { Image *image = malloc(sizeof(Image)); image->pixels = malloc(w * h * channels); image->w = w; image->h = h; image->channels = channels; return image; } Image *LoadImage_RAW(Config *cfg, const char *path) { if ((cfg->w == 0) || (cfg->h == 0)) Error("Nonzero -w # and -h # values must be specified when loading raw images."); Image *img = CreateImage(cfg->w, cfg->h, 1); FILE *fp = fopen(path, "rb"); if (!fp) Error("Failed to open %s", path); for (int i = 0; i < cfg->w * cfg->h; i++) { img->pixels[i] = fgetc(fp) != 0 ? 255 : 0; } fclose(fp); return img; } Image *LoadImage_BMP(Config *cfg, const char *path) { FILE *fp = fopen(path, "rb"); if (!fp) Error("Failed to open %s", path); char sig_a = fgetc(fp); char sig_b = fgetc(fp); if ((sig_a != 'B') || (sig_b != 'M')) Error("BMP header corrupt (%s)", path); file_read32(fp); file_read32(fp); int pixelarray = file_read32(fp); int dibheadersize = file_read32(fp); int w = file_read32(fp); int h = file_read32(fp); file_read16(fp); // planes int bpp = file_read16(fp); int compression = file_read32(fp); if (compression != 0) Error("Compressed BMPs are not supported"); file_read32(fp); // image size file_read32(fp); // h res file_read32(fp); // v res int palette_size = file_read32(fp); // colours Image *img = NULL; if (bpp <= 8) { // Paletted image if (palette_size == 0) palette_size = 1<= 0; y--) { int byte_offset = -1; int bit_offset = 8; unsigned char byte_val = -1; for (int x = 0; x < w; x++) { int v = 0; for (int b = 0; b < bpp; b++) { if (bit_offset == 8) { bit_offset = 0; byte_offset++; byte_val = fgetc(fp); } v |= byte_val & (128 >> bit_offset) ? 1 << b : 0; bit_offset++; } img->pixels[(y*w+x)*3+0] = palette[v].r; img->pixels[(y*w+x)*3+1] = palette[v].g; img->pixels[(y*w+x)*3+2] = palette[v].b; } byte_offset++; while (byte_offset % 4) { fgetc(fp); byte_offset++; } } } else { // Truecolour image int channels = bpp / 8; int output_channels = channels <= 3 ? channels : 3; fseek(fp, pixelarray, SEEK_SET); img = CreateImage(w, h, output_channels); int padding = (w * channels + 3) / 4 * 4 - (w * channels); for (int y = 0; y < h; y++) { for (int i = 0; i < w; i++) { img->pixels[(h - y - 1) * w * output_channels + (i * output_channels) + output_channels - 1] = fgetc(fp); // b if (channels >= 2) { img->pixels[(h - y - 1) * w * output_channels + (i * output_channels) + output_channels - 2] = fgetc(fp); // g if (channels >= 3) { img->pixels[(h - y - 1) * w * output_channels + (i * output_channels) + output_channels - 3] = fgetc(fp); // r if (channels >= 4) { fgetc(fp); // a } } } } if (padding) fseek(fp, padding, SEEK_CUR); } } fclose(fp); return img; } Image *LoadImage(Config *cfg, const char *path) { const char *ext = strrchr(path, '.'); if (ext) { if (lc_strcmp(ext, ".bmp") == 0) return LoadImage_BMP(cfg, path); } return LoadImage_RAW(cfg, path); } void FreeImage(Image *img) { free(img->pixels); free(img); } int Brightness(unsigned char *pixels, int channels) { if (channels == 1) return pixels[0]; if (channels == 3) return ((pixels[0] * 30 + pixels[1] * 59 + pixels[2] * 11) + 50) / 100; int sum = 0; for (int i = 0; i < channels; i++) { sum += pixels[i]; } return (sum + (channels / 2)) / channels; } typedef struct { char method[256]; int diffuse[1024]; int w, h, div; } Diffuse; Diffuse diffmethods[] = { {"Floyd-Steinberg", {0, -1, 7, 3, 5, 1}, 3, 2, 16}, {"Jarvis-Judice-Ninke", {0, 0, -1, 7, 5, 3, 5, 7, 5, 3, 1, 3, 5, 3, 1}, 5, 3, 48}, {"Stucki", {0, 0, -1, 7, 5, 2, 4, 8, 4, 2, 1, 2, 4, 2, 1}, 5, 3, 42}, {"Burkes", {0, 0, -1, 8, 4, 2, 4, 8, 4, 2}, 5, 2, 32}, {"Sierra", {0, 0, -1, 5, 3, 2, 4, 5, 4, 2, 0, 2, 3, 2, 0}, 5, 3, 32}, {"Stevenson-Arce", {0, 0, 0, -1, 0, 32, 0, 12, 0, 26, 0, 30, 0, 16, 0, 12, 0, 26, 0, 12, 0, 5, 0, 12, 0, 12, 0, 5}, 7, 4, 200} }; void QuantiseDiffuse(Image *img, Image *qimg, int threshold, const char *method) { int *err = malloc(img->w * img->h * sizeof(int)); memset(err, 0, img->w * img->h * sizeof(int)); int method_i = -1; for (int i = 0; i < sizeof(diffmethods) / sizeof(diffmethods[0]); i++) { if (lc_strncmp(method, diffmethods[i].method, strlen(method)) == 0) method_i = i; } if (method_i == -1) { Error("-dither method '%s' not found. Try another setting", method); } Diffuse D = diffmethods[method_i]; int mx = 0; int my = 0; for (int i = 0; i < D.w * D.h; i++) { int x = i % D.w; int y = i / D.w; if (D.diffuse[i] == -1) { mx = x; my = y; D.diffuse[i] = 0; } } for (int y = 0; y < img->h; y++) { for (int x = 0; x < img->w; x++) { int v_noerr = Brightness(img->pixels + (y * img->w + x) * img->channels, img->channels); int v = v_noerr + err[y * img->w + x] / D.div; if (v > 255) v = 255; if (v < 0) v = 0; int wr = v >= threshold ? 255 : 0; qimg->pixels[y * img->w + x] = v >= threshold ? 1 : 0; if ((v_noerr - wr) != 0) { for (int i = 0; i < D.w * D.h; i++) { int ox = (i % D.w) - mx + x; int oy = (i / D.w) - my + y; if ((D.diffuse[i] > 0) && (ox >= 0) && (oy >= 0) && (ox < img->w) && (oy < img->h)) { err[oy * img->w + ox] += D.diffuse[i] * (v - wr); } } } } } free(err); } void QuantiseTheshold(Image *img, Image *qimg, int threshold) { for (int i = 0; i < img->w * img->h; i++) { int v = Brightness(img->pixels + i * img->channels, img->channels); qimg->pixels[i] = v >= threshold ? 1 : 0; } } Image *Quantise(Config *cfg, Image *img) { Image *qimg = CreateImage(img->w, img->h, 1); if (cfg->dither[0] == '\0') { QuantiseTheshold(img, qimg, cfg->threshold); } else { QuantiseDiffuse(img, qimg, cfg->threshold, cfg->dither); } return qimg; } typedef struct { int chars; unsigned char *data; } Charset; Charset *CreateCharset(Image *img) { Charset *cset = malloc(sizeof(Charset)); int cset_size = (img->w / 8) * (img->h / 14); cset->data = malloc(cset_size * 14); memset(cset->data, 0, cset_size * 14); cset->chars = cset_size; int chr = 0; for (int cy = 0; cy < img->h / 14; cy++) { for (int cx = 0; cx < img->w / 8; cx++) { for (int py = 0; py < 14; py++) { for (int px = 0; px < 8; px++) { cset->data[chr * 14 + py] |= img->pixels[(cy * 14 + py) * img->w + (cx * 8 + px)] ? 128 >> px : 0; } } chr++; } } return cset; } typedef struct { int w, h; int *data; } Mzm; Mzm *CreateMzm(int w, int h) { Mzm *mzm = malloc(sizeof(Mzm)); mzm->w = w; mzm->h = h; mzm->data = malloc(w * h * sizeof(int)); for (int i = 0; i < w * h; i++) { mzm->data[i] = i; } return mzm; } typedef struct { unsigned char data[14]; int i; UT_hash_handle hh; } Char; void Reuse(Charset *cset, Mzm *mzm) { Char *clist = NULL; int out_chars = 0; for (int i = 0; i < cset->chars; i++) { unsigned char *cdata = cset->data + i * 14; Char *c; HASH_FIND(hh, clist, cdata, sizeof(clist->data), c); if (!c) { c = malloc(sizeof(Char)); memcpy(c->data, cdata, 14); if (mzm) { c->i = mzm->data[i] = out_chars; } memmove(cset->data + out_chars * 14, cset->data + i * 14, 14); HASH_ADD(hh, clist, data, sizeof(clist->data), c); out_chars++; } else { if (mzm) { mzm->data[i] = c->i; } } } cset->chars = out_chars; cset->data = realloc(cset->data, cset->chars * 14); } char *OutputPath(const char *input, Config *cfg, const char *ext) { char *output_path = NULL; if (cfg->output[0] == '\0') { char buf[MAX_PATH+1]; strcpy(buf, input); char *buf_e = strrchr(buf, '.'); if (buf_e) *buf_e = '\0'; output_path = malloc(strlen(buf) + strlen(ext) + 1); strcpy(output_path, buf); strcat(output_path, ext); } else { output_path = malloc(strlen(cfg->output) + strlen(ext) + 1); strcpy(output_path, cfg->output); strcat(output_path, ext); } return output_path; } void WriteCharset(const char *path, Charset *cset) { FILE *fp = fopen(path, "wb"); fwrite(cset->data, 14, cset->chars, fp); fclose(fp); } void WriteMzm(const char *path, Mzm *mzm) { FILE *fp = fopen(path, "wb"); fwrite("MZM2", 1, 4, fp); file_write16(mzm->w, fp); file_write16(mzm->h, fp); file_write32(0, fp); fputc(0, fp); // # of robots fputc(1, fp); // storage mode fputc(0, fp); // robot storage mode fputc(0, fp); // reserved for (int i = 0; i < mzm->w * mzm->h; i++) { fputc(mzm->data[i] & 0xFF, fp); fputc(15, fp); } fclose(fp); } static inline int CountBits(const unsigned char *A) { if (sizeof(unsigned int) == 4) { int c = 0; const unsigned int *a = (const unsigned int*)A; unsigned int v; for (int i = 0; i < 3; i++) { v = *a; v = v - ((v >> 1) & 0x55555555); v = (v & 0x33333333) + ((v >> 2) & 0x33333333); c += (((v + (v >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24; a++; } v = (A[13] << 8 | A[12]); v = v - ((v >> 1) & 0x55555555); v = (v & 0x33333333) + ((v >> 2) & 0x33333333); c += (((v + (v >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24; return c; } else { unsigned int c = 0; for (int i = 0; i < 14; i++) { unsigned char v = A[i]; for (; v; c++) { v &= v - 1; // clear the least significant bit set } } return c; } return -1; } static inline int CharDist(const unsigned char *A, const unsigned char *B) { unsigned char v[14]; for (int i = 0; i < 14; i++) { v[i] = A[i] ^ B[i]; } return CountBits(v); } // Simple quantisation- combine the two closest pairs of chars until the right number of chars are available void Reduce_Simple(Charset *cset, int chars) { while (cset->chars > chars) { int pair_i = -1; int pair_j = -1; int pair_dist = INT_MAX; for (int i = 0; i < cset->chars; i++) { unsigned char *data_i = cset->data + i * 14; for (int j = i + 1; j < cset->chars; j++) { unsigned char *data_j = cset->data + j * 14; int data_dist = CharDist(data_i, data_j); if (data_dist < pair_dist) { pair_dist = data_dist; pair_i = i; pair_j = j; } } } unsigned char *data_i = cset->data + pair_i * 14; unsigned char *data_j = cset->data + pair_j * 14; int bits = CountBits(data_i) + CountBits(data_j); if (bits >= 112) { for (int i = 0; i < 14; i++) { //data_i[i] = data_i[i] > data_j[i] ? data_i[i] : data_j[i]; data_i[i] = data_i[i] | data_j[i]; } } else { for (int i = 0; i < 14; i++) { data_i[i] = ~(~data_i[i] | ~data_j[i]); } } memmove(data_j, data_j + 14, (cset->chars - pair_j - 1) * 14); cset->chars--; } cset->data = realloc(cset->data, cset->chars * 14); } int ChrImageDist(const unsigned char *cdata, Image *img, int xoff, int yoff) { int tdist = 0; for (int y = 0; y < 14; y++) { for (int x = 0; x < 8; x++) { int px = xoff + x; int py = yoff + y; int chr_lightness = cdata[y] & (128 >> x) ? 1 : 0; int img_lightness = img->pixels[((py * img->w) + px)] ? 1 : 0; tdist += abs(chr_lightness - img_lightness); } } return tdist; } void RemapMzm(Charset *cset, Mzm *mzm, Image *qimage) { // Remap the MZM to the charset to create the closest approximation of the quantised image. for (int y = 0; y < mzm->h; y++) { for (int x = 0; x < mzm->w; x++) { int closest_chr = -1; int closest_dist = INT_MAX; for (int c = 0; c < cset->chars; c++) { int chr_dist = ChrImageDist(cset->data + c * 14, qimage, x * 8, y * 14); if (chr_dist < closest_dist) { closest_dist = chr_dist; closest_chr = c; } } mzm->data[y * mzm->w + x] = closest_chr; } } } void Reduce(Charset *cset, Mzm *mzm, Image *original, int chars, Config *cfg) { if (cset->chars <= chars) return; Reduce_Simple(cset, chars); RemapMzm(cset, mzm, original); } void OffsetMzm(Mzm *mzm, int offset) { if (mzm) { for (int i = 0; i < mzm->w * mzm->h; i++) { mzm->data[i] += offset; } } } void ExcludeChars(Charset *cset, Mzm *mzm, char *exclude, int offset) { int remap[256]; int remap_offset = 0; for (int i = 0; i < cset->chars && i < 256; i++) { remap[i] = i; int ci = i + remap_offset + offset; if ((ci >= 0) && (ci < 256) && (exclude[ci])) { cset->data = realloc(cset->data, (cset->chars + 1) * 14); remap[i] = cset->chars; memmove(cset->data + (remap[i])*14, cset->data + i * 14, 14); memset(cset->data + i * 14, 0, 14); cset->chars++; } } if (mzm) { for (int i = 0; i < mzm->w * mzm->h; i++) { while ((mzm->data[i] != remap[mzm->data[i]])) { mzm->data[i] = remap[mzm->data[i]]; } } } } void SwapBlank(Charset *cset, Mzm *mzm, int blank) { int current_blank = -1; for (int i = 0; i < cset->chars; i++) { int isblank = 1; for (int j = 0; j < 14; j++) { if (cset->data[i * 14 + j] != 0) { isblank = 0; break; } } if (isblank) current_blank = i; break; } unsigned char tmp[14]; if (current_blank != -1) { memcpy(tmp, cset->data + current_blank*14, 14); memcpy(cset->data + current_blank*14, cset->data + blank*14, 14); memcpy(cset->data + blank*14, tmp, 14); if (mzm) { for (int i = 0; i < mzm->w * mzm->h; i++) { if (mzm->data[i] == current_blank) { mzm->data[i] = blank; } else if (mzm->data[i] == blank) { mzm->data[i] = current_blank; } } } } } int main(int argc, char **argv) { Config *cfg = LoadConfig(argc - 1, argv + 1); for(InputFile *file = cfg->files; file != NULL; file = file->hh.next) { Image *image = LoadImage(cfg, file->path); if (((image->w % 8) != 0) || ((image->h % 14) != 0)) Error("Image dimensions are not divisible by 8x14"); Image *qimage = Quantise(cfg, image); Charset *cset = CreateCharset(qimage); Mzm *mzm = NULL; if (cfg->mzm) { mzm = CreateMzm(qimage->w / 8, qimage->h / 14); } if (cfg->reuse && !cfg->noreuse) { Reuse(cset, mzm); } if (cfg->c) { Reduce(cset, mzm, qimage, cfg->c, cfg); } if (cfg->blank != -1) { SwapBlank(cset, mzm, cfg->blank); } if (cfg->exclude_on) { ExcludeChars(cset, mzm, cfg->exclude, cfg->offset); } if (cfg->offset) { OffsetMzm(mzm, cfg->offset); } { // Output charset char *filename_chr = OutputPath(file->path, cfg, ".chr"); WriteCharset(filename_chr, cset); if (!cfg->quiet) { printf("%s -> %s (%d chars)", file->path, filename_chr, cset->chars); } free(filename_chr); } if (cfg->mzm) { // Output MZM char *filename_mzm = OutputPath(file->path, cfg, ".mzm"); WriteMzm(filename_mzm, mzm); if (!cfg->quiet) { printf(", %s", filename_mzm); } free(filename_mzm); } if (!cfg->quiet) printf("\n"); } return 0; }