From 9d2ab87f85590888effc2fa7075192bc7a42e4a8 Mon Sep 17 00:00:00 2001 From: sam Date: Mon, 25 May 2009 00:16:50 +0000 Subject: [PATCH] Add proper Unicode handling to img2twit. This includes an UTF-8 encoder/decoder and a special bit stack that can handle arbitrary bases within its stream. The image encoder and decoder are now separate code paths. git-svn-id: file:///srv/caca.zoy.org/var/lib/svn/libpipi/trunk@3521 92316355-f0b4-4df1-b90c-862c8a59935f --- examples/img2twit.cpp | 686 +++++++++++++++++++++++++++++++++--------- 1 file changed, 544 insertions(+), 142 deletions(-) diff --git a/examples/img2twit.cpp b/examples/img2twit.cpp index c38fd7b..522ff08 100644 --- a/examples/img2twit.cpp +++ b/examples/img2twit.cpp @@ -15,15 +15,46 @@ * User-definable settings. */ +/* Debug mode */ +#define DEBUG 1 + +/* Encoding iterations: 1000 gives a fast answer, 10000 gives good quality */ +#define MAX_ITERATIONS 10000 + /* The maximum message length */ #define MAX_MSG_LEN 140 -/* The number of characters at disposal */ -//#define NUM_CHARACTERS 0x7fffffff // The sky's the limit -//#define NUM_CHARACTERS 1111998 // Full valid Unicode set -//#define NUM_CHARACTERS 100507 // Full graphic Unicode -#define NUM_CHARACTERS 32768 // Chinese characters -//#define NUM_CHARACTERS 127 // ASCII +/* The Unicode characters at disposal - XXX: must be _ordered_ */ +static const uint32_t unichars[] = +{ + /* Printable ASCII (except space) */ + 0x0021, 0x007f, + + /* Stupid symbols and Dingbats shit */ + //0x25a0, 0x2600, /* Geometric Shapes */ + //0x2600, 0x269e, 0x26a0, 0x26bd, 0x26c0, 0x26c4, /* Misc. Symbols */ + //0x2701, 0x2705, 0x2706, 0x270a, 0x270c, 0x2728, 0x2729, 0x274c, + // 0x274d, 0x274e, 0x274f, 0x2753, 0x2756, 0x2757, 0x2758, 0x275f, + // 0x2761, 0x2795, 0x2798, 0x27b0, 0x27b1, 0x27bf, /* Dingbats */ + + /* Chinese-looking stuff */ + //0x2e80, 0x2e9a, 0x2e9b, 0x2ef4, /* CJK Radicals Supplement */ + //0x2f00, 0x2fd6, /* Kangxi Radicals */ + //0x3400, 0x4db6, /* CJK Unified Ideographs Extension A */ + //0x4e00, 0x9fa6, /* CJK Unified Ideographs */ + + /* Korean - most people don't know the difference anyway */ + 0xac00, 0xd7a4, /* Hangul Syllables */ + + /* More Chinese */ + //0xf900, 0xfa2e, 0xfa30, 0xfa6b, 0xfa70, 0xfada, /* CJK Compat. Idgphs. */ + + /* TODO: there's also the U+20000 and U+2f800 planes, but they're + * not supported by the Twitter Javascript filter (yet?). */ + + /* End of list marker - XXX: don't remove! */ + 0x0000, 0x0000 +}; /* The maximum image size we want to support */ #define MAX_W 4000 @@ -53,8 +84,9 @@ static unsigned int RANGE_S = 1; static float TOTAL_BITS; static float HEADER_BITS; static float DATA_BITS; -static float POINT_BITS; +static float CELL_BITS; +static int NUM_CHARACTERS; static unsigned int TOTAL_CELLS; #define RANGE_SY (RANGE_S*RANGE_Y) @@ -77,6 +109,274 @@ static int npoints = 0; /* Global triangulation */ static Delaunay_triangulation dt; +/* + * Unicode stuff handling + */ + +/* Return the number of chars in the unichars table */ +static int count_unichars(void) +{ + int ret = 0; + + for(int u = 0; unichars[u] != unichars[u + 1]; u += 2) + ret += unichars[u + 1] - unichars[u]; + + return ret; +} + +/* Get the ith Unicode character in our list */ +static uint32_t index2uni(uint32_t i) +{ + for(int u = 0; unichars[u] != unichars[u + 1]; u += 2) + if(i < unichars[u + 1] - unichars[u]) + return unichars[u] + i; + else + i -= unichars[u + 1] - unichars[u]; + + return 0; /* Should not happen! */ +} + +/* Convert a Unicode character to its position in the compact list */ +static uint32_t uni2index(uint32_t x) +{ + uint32_t ret = 0; + + for(int u = 0; unichars[u] != unichars[u + 1]; u += 2) + if(x < unichars[u + 1]) + return ret + x - unichars[u]; + else + ret += unichars[u + 1] - unichars[u]; + + return ret; /* Should not happen! */ +} + +static uint8_t const utf8_trailing[256] = +{ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 +}; + +static uint32_t const utf8_offsets[6] = +{ + 0x00000000UL, 0x00003080UL, 0x000E2080UL, + 0x03C82080UL, 0xFA082080UL, 0x82082080UL +}; + +static uint32_t fread_utf8(FILE *f) +{ + int ch, i = 0, todo = -1; + uint32_t ret = 0; + + for(;;) + { + ch = fgetc(f); + if(!ch) + return 0; + if(todo == -1) + todo = utf8_trailing[ch]; + ret += ((uint32_t)ch) << (6 * (todo - i)); + if(todo == i++) + return ret - utf8_offsets[todo]; + } +} + +static void fwrite_utf8(FILE *f, uint32_t x) +{ + static const uint8_t mark[7] = + { + 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC + }; + + char buf[8]; + char *parser = buf; + size_t bytes; + + if(x < 0x80) + { + fprintf(f, "%c", x); + return; + } + + bytes = (x < 0x800) ? 2 : (x < 0x10000) ? 3 : 4; + parser += bytes; + *parser = '\0'; + + switch(bytes) + { + case 4: *--parser = (x | 0x80) & 0xbf; x >>= 6; + case 3: *--parser = (x | 0x80) & 0xbf; x >>= 6; + case 2: *--parser = (x | 0x80) & 0xbf; x >>= 6; + } + *--parser = x | mark[bytes]; + + fprintf(f, "%s", buf); +} + +/* + * Our nifty non-power-of-two bitstack handling + */ + +class bitstack +{ +public: + bitstack() { init(0); } + + char const *tostring() + { + int pos = sprintf(str, "0x%x", digits[msb]); + + for(int i = msb - 1; i >= 0; i--) + pos += sprintf(str + pos, "%08x", digits[i]); + + return str; + } + + void push(uint32_t val, uint32_t range) + { + if(!range) + return; + + mul(range); + add(val % range); + } + + uint32_t pop(uint32_t range) + { + if(!range) + return 0; + + return div(range); + } + + bool isempty() + { + for(int i = msb; i >= 0; i--) + if(digits[i]) + return false; + + return true; + } + +private: + bitstack(uint32_t i) { init(i); } + + void init(uint32_t i) + { + memset(digits, 0, sizeof(digits)); + digits[0] = i; + msb = 0; + } + + /* Could be done much faster, but we don't care! */ + void add(uint32_t x) { add(bitstack(x)); } + void sub(uint32_t x) { sub(bitstack(x)); } + + void add(bitstack const &_b) + { + /* Copy the operand in case we get added to ourselves */ + bitstack b = _b; + uint64_t x = 0; + + if(msb < b.msb) + msb = b.msb; + + for(int i = 0; i <= msb; i++) + { + uint64_t tmp = (uint64_t)digits[i] + (uint64_t)b.digits[i] + x; + digits[i] = tmp; + if((uint64_t)digits[i] == tmp) + x = 0; + else + { + x = 1; + if(i == msb) + msb++; + } + } + } + + void sub(bitstack const &_b) + { + /* Copy the operand in case we get substracted from ourselves */ + bitstack b = _b; + uint64_t x = 0; + + /* We cannot substract a larger number! */ + if(msb < b.msb) + { + init(0); + return; + } + + for(int i = 0; i <= msb; i++) + { + uint64_t tmp = (uint64_t)digits[i] - (uint64_t)b.digits[i] - x; + digits[i] = tmp; + if((uint64_t)digits[i] == tmp) + x = 0; + else + { + x = 1; + if(i == msb) + { + /* Error: carry into MSB! */ + init(0); + return; + } + } + } + + while(msb > 0 && digits[msb] == 0) msb--; + } + + void mul(uint32_t x) + { + bitstack b = *this; + init(0); + + while(x) + { + if(x & 1) + add(b); + x /= 2; + b.add(b); + } + } + + uint32_t div(uint32_t x) + { + bitstack b = *this; + + for(int i = msb; i >= 0; i--) + { + uint64_t tmp = b.digits[i] + (((uint64_t)b.digits[i + 1]) << 32); + uint32_t res = tmp / x; + uint32_t rem = tmp % x; + digits[i]= res; + b.digits[i + 1] = 0; + b.digits[i] = rem; + } + + while(msb > 0 && digits[msb] == 0) msb--; + + return b.digits[0]; + } + + int msb; + uint32_t digits[MAX_MSG_LEN + 1]; /* This is a safe max value */ + char str[(MAX_MSG_LEN + 1) * 8 + 1]; +}; + +/* + * Point handling + */ + static unsigned int det_rand(unsigned int mod) { static unsigned long next = 1; @@ -156,11 +456,13 @@ static void add_point(float x, float y, float r, float g, float b, float s) npoints++; } +#if 0 static void add_random_point() { points[npoints] = det_rand(RANGE_SYXRGB); npoints++; } +#endif #define NB_OPS 20 @@ -438,37 +740,68 @@ add_random_point(); int main(int argc, char *argv[]) { int opstats[2 * NB_OPS]; + bitstack b; pipi_image_t *src, *tmp, *dst; double error = 1.0; int width, height, ret = 0; + bool decode = (argc >= 2 && !strcmp(argv[1], "-o")); - /* Compute bit allocation */ - fprintf(stderr, "Available characters: %i\n", NUM_CHARACTERS); - fprintf(stderr, "Maximum message size: %i\n", MAX_MSG_LEN); + if(!((argc == 2 && !decode) || (argc == 3 && decode))) + { + fprintf(stderr, "img2twit: wrong argument count\n"); + fprintf(stderr, "Usage: img2twit Encode image and print result to stdout\n"); + fprintf(stderr, " img2twit -o Read data from stdin and decode image to file\n"); + return EXIT_FAILURE; + } + + pipi_set_gamma(1.0); + + /* Precompute bit allocation */ + NUM_CHARACTERS = count_unichars(); TOTAL_BITS = MAX_MSG_LEN * logf(NUM_CHARACTERS) / logf(2); - fprintf(stderr, "Available bits: %f\n", TOTAL_BITS); - fprintf(stderr, "Maximum image resolution: %ix%i\n", MAX_W, MAX_H); HEADER_BITS = logf(MAX_W * MAX_H) / logf(2); - fprintf(stderr, "Header bits: %f\n", HEADER_BITS); DATA_BITS = TOTAL_BITS - HEADER_BITS; - fprintf(stderr, "Bits available for data: %f\n", DATA_BITS); #if POINTS_PER_CELL == 1 - POINT_BITS = logf(RANGE_SYXRGB) / logf(2); + CELL_BITS = logf(RANGE_SYXRGB) / logf(2); #else - float coord_bits = logf((RANGE_Y * RANGE_X) * (RANGE_Y * RANGE_X + 1) / 2); - float other_bits = logf(RANGE_R * RANGE_G * RANGE_B * RANGE_S); - POINT_BITS = (coord_bits + 2 * other_bits) / logf(2); + // TODO: implement the following shit + //float coord_bits = logf((RANGE_Y * RANGE_X) * (RANGE_Y * RANGE_X + 1) / 2); + //float other_bits = logf(RANGE_R * RANGE_G * RANGE_B * RANGE_S); + //CELL_BITS = (coord_bits + 2 * other_bits) / logf(2); + CELL_BITS = 2 * logf(RANGE_SYXRGB) / logf(2); #endif - fprintf(stderr, "Cell bits: %f\n", POINT_BITS); - TOTAL_CELLS = (int)(DATA_BITS / POINT_BITS); - fprintf(stderr, "Available cells: %i\n", TOTAL_CELLS); - fprintf(stderr, "Wasted bits: %f\n", DATA_BITS - POINT_BITS * TOTAL_CELLS); + TOTAL_CELLS = (int)(DATA_BITS / CELL_BITS); - /* Load image */ - pipi_set_gamma(1.0); - src = pipi_load(argv[1]); - width = pipi_get_image_width(src); - height = pipi_get_image_height(src); + if(decode) + { + /* Decoding mode: read UTF-8 text from stdin, find each + * character's index in our character list, and push it to our + * wonderful custom bitstream. */ + uint32_t data[MAX_MSG_LEN]; + for(int i = 0; i < MAX_MSG_LEN; i++) + data[i] = uni2index(fread_utf8(stdin)); + for(int i = MAX_MSG_LEN; i--; ) + b.push(data[i], NUM_CHARACTERS); + + /* Read width and height from bitstream */ + src = NULL; + width = b.pop(MAX_W); + height = b.pop(MAX_H); + } + else + { + /* Argument given: open image for encoding */ + src = pipi_load(argv[1]); + + if(!src) + { + fprintf(stderr, "Error loading %s\n", argv[1]); + return EXIT_FAILURE; + } + + width = pipi_get_image_width(src); + height = pipi_get_image_height(src); + } /* Compute best w/h ratio */ dw = 1; dh = TOTAL_CELLS; @@ -488,145 +821,214 @@ int main(int argc, char *argv[]) } while((dh + 1) * dw <= TOTAL_CELLS) dh++; while(dw * (dh + 1) <= TOTAL_CELLS) dw++; + + /* Print debug information */ +#if DEBUG + fprintf(stderr, "Maximum message size: %i\n", MAX_MSG_LEN); + fprintf(stderr, "Available characters: %i\n", NUM_CHARACTERS); + fprintf(stderr, "Available bits: %f\n", TOTAL_BITS); + fprintf(stderr, "Maximum image resolution: %ix%i\n", MAX_W, MAX_H); + fprintf(stderr, "Image resolution: %ix%i\n", width, height); + fprintf(stderr, "Header bits: %f\n", HEADER_BITS); + fprintf(stderr, "Bits available for data: %f\n", DATA_BITS); + fprintf(stderr, "Cell bits: %f\n", CELL_BITS); + fprintf(stderr, "Available cells: %i\n", TOTAL_CELLS); + fprintf(stderr, "Wasted bits: %f\n", DATA_BITS - CELL_BITS * TOTAL_CELLS); + fprintf(stderr, "Chosen image ratio: %i:%i (wasting %i point cells)\n", dw, dh, TOTAL_CELLS - dw * dh); fprintf(stderr, "Total wasted bits: %f\n", - DATA_BITS - POINT_BITS * dw * dh); - - /* Resize and filter image to better state */ - tmp = pipi_resize(src, dw * RANGE_X, dh * RANGE_Y); - pipi_free(src); - src = pipi_median_ext(tmp, 1, 1); - pipi_free(tmp); - - /* Analyse image */ - analyse(src); - - /* Render what we just computed */ - tmp = pipi_new(dw * RANGE_X, dh * RANGE_Y); - render(tmp, 0, 0, dw * RANGE_X, dh * RANGE_Y); - error = pipi_measure_rmsd(src, tmp); - - fprintf(stderr, "Distance: %2.10g\n", error); + DATA_BITS - CELL_BITS * dw * dh); +#endif - memset(opstats, 0, sizeof(opstats)); - for(int iter = 0, stuck = 0, failures = 0, success = 0; - /*stuck < 5 && */iter < 10000; - iter++) + if(src) { - if(failures > 500) + /* Resize and filter image to better state */ + tmp = pipi_resize(src, dw * RANGE_X, dh * RANGE_Y); + pipi_free(src); + src = pipi_median_ext(tmp, 1, 1); + pipi_free(tmp); + + /* Analyse image */ + analyse(src); + + /* Render what we just computed */ + tmp = pipi_new(dw * RANGE_X, dh * RANGE_Y); + render(tmp, 0, 0, dw * RANGE_X, dh * RANGE_Y); + error = pipi_measure_rmsd(src, tmp); + +#if DEBUG + fprintf(stderr, "Distance: %2.10g\n", error); +#endif + + memset(opstats, 0, sizeof(opstats)); + for(int iter = 0, stuck = 0, failures = 0, success = 0; + iter < MAX_ITERATIONS /* && stuck < 5 && */; + iter++) { - stuck++; - failures = 0; - } + if(failures > 500) + { + stuck++; + failures = 0; + } - pipi_image_t *scrap = pipi_copy(tmp); +#if !DEBUG + if(!(iter % 16)) + fprintf(stderr, "\rEncoding... %i%%", + iter * 100 / MAX_ITERATIONS); +#endif + + pipi_image_t *scrap = pipi_copy(tmp); + + /* Choose a point at random */ + int pt = det_rand(npoints); + uint32_t oldval = points[pt]; + + /* Compute the affected image zone */ + float fx, fy, fr, fg, fb, fs; + get_point(pt, &fx, &fy, &fr, &fg, &fb, &fs); + int zonex = (int)fx / RANGE_X - 1; + int zoney = (int)fy / RANGE_Y - 1; + int zonew = 3; + int zoneh = 3; + if(zonex < 0) { zonex = 0; zonew--; } + if(zoney < 0) { zoney = 0; zoneh--; } + if(zonex + zonew >= (int)dw) { zonew--; } + if(zoney + zoneh >= (int)dh) { zoneh--; } + + /* Choose random operations and measure their effect */ + uint8_t op1 = rand_op(); + //uint8_t op2 = rand_op(); + + uint32_t candidates[3]; + double besterr = error + 1.0; + int bestop = -1; + candidates[0] = apply_op(op1, oldval); + //candidates[1] = apply_op(op2, oldval); + //candidates[2] = apply_op(op1, apply_op(op2, oldval)); + + for(int i = 0; i < 1; i++) + //for(int i = 0; i < 3; i++) + { + if(oldval == candidates[i]) + continue; - /* Choose a point at random */ - int pt = det_rand(npoints); - uint32_t oldval = points[pt]; + points[pt] = candidates[i]; - /* Compute the affected image zone */ - float fx, fy, fr, fg, fb, fs; - get_point(pt, &fx, &fy, &fr, &fg, &fb, &fs); - int zonex = (int)fx / RANGE_X - 1; - int zoney = (int)fy / RANGE_Y - 1; - int zonew = 3; - int zoneh = 3; - if(zonex < 0) { zonex = 0; zonew--; } - if(zoney < 0) { zoney = 0; zoneh--; } - if(zonex + zonew >= (int)dw) { zonew--; } - if(zoney + zoneh >= (int)dh) { zoneh--; } - - /* Choose random operations and measure their effect */ - uint8_t op1 = rand_op(); - //uint8_t op2 = rand_op(); - - uint32_t candidates[3]; - double besterr = error + 1.0; - int bestop = -1; - candidates[0] = apply_op(op1, oldval); - //candidates[1] = apply_op(op2, oldval); - //candidates[2] = apply_op(op1, apply_op(op2, oldval)); - - for(int i = 0; i < 1; i++) - //for(int i = 0; i < 3; i++) - { - if(oldval == candidates[i]) - continue; + render(scrap, zonex * RANGE_X, zoney * RANGE_Y, + zonew * RANGE_X, zoneh * RANGE_Y); - points[pt] = candidates[i]; + double newerr = pipi_measure_rmsd(src, scrap); + if(newerr < besterr) + { + besterr = newerr; + bestop = i; + } + } - render(scrap, zonex * RANGE_X, zoney * RANGE_Y, - zonew * RANGE_X, zoneh * RANGE_Y); + opstats[op1 * 2]++; + //opstats[op2 * 2]++; - double newerr = pipi_measure_rmsd(src, scrap); - if(newerr < besterr) + if(besterr < error) { - besterr = newerr; - bestop = i; + points[pt] = candidates[bestop]; + /* Redraw image if the last check wasn't the best one */ + if(bestop != 2) + render(scrap, zonex * RANGE_X, zoney * RANGE_Y, + zonew * RANGE_X, zoneh * RANGE_Y); + + pipi_free(tmp); + tmp = scrap; +#if DEBUG + fprintf(stderr, "%08i -.%08i %2.010g after op%i(%i)\n", iter, + (int)((error - besterr) * 100000000), error, op1, pt); +#endif + error = besterr; + opstats[op1 * 2 + 1]++; + //opstats[op2 * 2 + 1]++; + failures = 0; + success++; + + /* Save image! */ + //char buf[128]; + //sprintf(buf, "twit%08i.bmp", success); + //if((success % 10) == 0) + // pipi_save(tmp, buf); + } + else + { + pipi_free(scrap); + points[pt] = oldval; + failures++; } } - opstats[op1 * 2]++; - //opstats[op2 * 2]++; - - if(besterr < error) - { - points[pt] = candidates[bestop]; - /* Redraw image if the last check wasn't the best one */ - if(bestop != 2) - render(scrap, zonex * RANGE_X, zoney * RANGE_Y, - zonew * RANGE_X, zoneh * RANGE_Y); + fprintf(stderr, "\r \r"); - pipi_free(tmp); - tmp = scrap; - //fprintf(stderr, "%08i %2.010g %2.010g after op%i(%i)\n", - // iter, besterr - error, error, op1, pt); - fprintf(stderr, "%08i -.%08i %2.010g after op%i(%i)\n", iter, - (int)((error - besterr) * 100000000), error, op1, pt); - error = besterr; - opstats[op1 * 2 + 1]++; - //opstats[op2 * 2 + 1]++; - failures = 0; - success++; - - /* Save image! */ - //char buf[128]; - //sprintf(buf, "twit%08i.bmp", success); - //if((success % 10) == 0) - // pipi_save(tmp, buf); - } - else +#if DEBUG + for(int j = 0; j < 2; j++) { - pipi_free(scrap); - points[pt] = oldval; - failures++; + fprintf(stderr, "operation: "); + for(int i = NB_OPS / 2 * j; i < NB_OPS / 2 * (j + 1); i++) + fprintf(stderr, "%4i ", i); + fprintf(stderr, "\nattempts: "); + for(int i = NB_OPS / 2 * j; i < NB_OPS / 2 * (j + 1); i++) + fprintf(stderr, "%4i ", opstats[i * 2]); + fprintf(stderr, "\nsuccesses: "); + for(int i = NB_OPS / 2 * j; i < NB_OPS / 2 * (j + 1); i++) + fprintf(stderr, "%4i ", opstats[i * 2 + 1]); + fprintf(stderr, "\n"); } - } - for(int j = 0; j < 2; j++) - { - fprintf(stderr, "operation: "); - for(int i = NB_OPS / 2 * j; i < NB_OPS / 2 * (j + 1); i++) - fprintf(stderr, "%4i ", i); - fprintf(stderr, "\nattempts: "); - for(int i = NB_OPS / 2 * j; i < NB_OPS / 2 * (j + 1); i++) - fprintf(stderr, "%4i ", opstats[i * 2]); - fprintf(stderr, "\nsuccesses: "); - for(int i = NB_OPS / 2 * j; i < NB_OPS / 2 * (j + 1); i++) - fprintf(stderr, "%4i ", opstats[i * 2 + 1]); - fprintf(stderr, "\n"); + fprintf(stderr, "Distance: %2.10g\n", error); +#endif + +#if 0 + dst = pipi_resize(tmp, width, height); + pipi_free(tmp); + + /* Save image and bail out */ + pipi_save(dst, "lol.bmp"); + pipi_free(dst); +#endif + + /* Push our points to the bitstream */ + for(int i = 0; i < npoints; i++) + b.push(points[i], RANGE_SYXRGB); + b.push(height, MAX_H); + b.push(width, MAX_W); + + /* Pop Unicode characters from the bitstream and print them */ + for(int i = 0; i < MAX_MSG_LEN; i++) + fwrite_utf8(stdout, index2uni(b.pop(NUM_CHARACTERS))); + fprintf(stdout, "\n"); } + else + { + /* Pop points from the bitstream */ + for(int i = dw * dh; i--; ) + { +#if POINTS_PER_CELL == 2 + points[i * 2 + 1] = b.pop(RANGE_SYXRGB); + points[i * 2] = b.pop(RANGE_SYXRGB); +#else + points[i] = b.pop(RANGE_SYXRGB); +#endif + } + npoints = dw * dh * POINTS_PER_CELL; - fprintf(stderr, "Distance: %2.10g\n", error); + /* Render these points to a new image */ + tmp = pipi_new(dw * RANGE_X, dh * RANGE_Y); + render(tmp, 0, 0, dw * RANGE_X, dh * RANGE_Y); - dst = pipi_resize(tmp, width, height); - pipi_free(tmp); + /* TODO: render directly to the final image; scaling sucks */ + dst = pipi_resize(tmp, width, height); + pipi_free(tmp); - /* Save image and bail out */ - pipi_save(dst, "lol.bmp"); - pipi_free(dst); + /* Save image and bail out */ + pipi_save(dst, argv[2]); + pipi_free(dst); + } return ret; }