From 1779efba6b8dc661a8243a545920afd798141850 Mon Sep 17 00:00:00 2001 From: sam Date: Mon, 25 May 2009 00:16:44 +0000 Subject: [PATCH] Tremendously improve img2twit.cpp: only compute the new error around the updated pixel, compute bit allocation at runtime, weight operations in order to try usual "winners" more often, increase RGB allocation size. git-svn-id: file:///srv/caca.zoy.org/var/lib/svn/libpipi/trunk@3520 92316355-f0b4-4df1-b90c-862c8a59935f --- examples/img2twit.cpp | 380 ++++++++++++++++++++++++++++++------------ 1 file changed, 276 insertions(+), 104 deletions(-) diff --git a/examples/img2twit.cpp b/examples/img2twit.cpp index 421c0e1..c38fd7b 100644 --- a/examples/img2twit.cpp +++ b/examples/img2twit.cpp @@ -11,15 +11,51 @@ #include -#define TOTAL_POINTS 138 +/* + * User-definable settings. + */ + +/* 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 maximum image size we want to support */ +#define MAX_W 4000 +#define MAX_H 4000 + +/* How does the algorithm work: one point per cell, or two */ #define POINTS_PER_CELL 2 -#define RANGE_X 16 -#define RANGE_Y 16 -#define RANGE_R 5 -#define RANGE_G 5 -#define RANGE_B 5 -#define RANGE_S 1 +/* The range value for point parameters: X Y, red/green/blue, "strength" + * Tested values (on Mona Lisa) are: + * 16 16 5 5 5 2 -> 0.06511725914 + * 16 16 6 7 6 1 -> 0.05731491348 * + * 16 16 7 6 6 1 -> 0.06450513783 + * 14 14 7 7 6 1 -> 0.0637207893 + * 19 19 6 6 5 1 -> 0.06801999094 */ +static unsigned int RANGE_X = 16; +static unsigned int RANGE_Y = 16; +static unsigned int RANGE_R = 6; +static unsigned int RANGE_G = 6; +static unsigned int RANGE_B = 6; +static unsigned int RANGE_S = 1; + +/* + * These values are computed at runtime + */ + +static float TOTAL_BITS; +static float HEADER_BITS; +static float DATA_BITS; +static float POINT_BITS; + +static unsigned int TOTAL_CELLS; #define RANGE_SY (RANGE_S*RANGE_Y) #define RANGE_SYX (RANGE_S*RANGE_Y*RANGE_X) @@ -32,7 +68,7 @@ typedef CGAL::Delaunay_triangulation_2 Delaunay_triangulation; typedef std::vector > Point_coordinate_vector; /* Global aspect ratio */ -static int dw, dh; +static unsigned int dw, dh; /* Global point encoding */ static uint32_t points[1024]; @@ -48,19 +84,24 @@ static unsigned int det_rand(unsigned int mod) return ((unsigned)(next / 65536) % 32768) % mod; } -static inline int float2int(float val, int range) +static inline int range2int(float val, int range) { int ret = (int)(val * ((float)range - 0.0001)); - return ret < 0 ? 0 : ret > range - 1? range - 1 : ret; + return ret < 0 ? 0 : ret > range - 1 ? range - 1 : ret; } -static inline float int2float(int val, int range) +static inline float int2midrange(int val, int range) { return (float)(1 + 2 * val) / (float)(2 * range); } -static inline uint32_t set_point(int index, float x, float y, float r, - float g, float b, float s) +static inline float int2fullrange(int val, int range) +{ + return range > 1 ? (float)val / (float)(range - 1) : 0.0; +} + +static inline void set_point(int index, float x, float y, float r, + float g, float b, float s) { int dx = (index / POINTS_PER_CELL) % dw; int dy = (index / POINTS_PER_CELL) / dw; @@ -68,14 +109,14 @@ static inline uint32_t set_point(int index, float x, float y, float r, float fx = (x - dx * RANGE_X) / RANGE_X; float fy = (y - dy * RANGE_Y) / RANGE_Y; - int is = float2int(s, RANGE_S); + int is = range2int(s, RANGE_S); - int ix = float2int(fx, RANGE_X); - int iy = float2int(fy, RANGE_Y); + int ix = range2int(fx, RANGE_X); + int iy = range2int(fy, RANGE_Y); - int ir = float2int(r, RANGE_R); - int ig = float2int(g, RANGE_G); - int ib = float2int(b, RANGE_B); + int ir = range2int(r, RANGE_R); + int ig = range2int(g, RANGE_G); + int ib = range2int(b, RANGE_B); points[index] = is + RANGE_S * (iy + RANGE_Y * (ix + RANGE_X * (ib + RANGE_B * (ig + (RANGE_R * ir))))); @@ -89,19 +130,17 @@ static inline void get_point(int index, float *x, float *y, float *r, unsigned int dx = (index / POINTS_PER_CELL) % dw; unsigned int dy = (index / POINTS_PER_CELL) / dw; - float fs = int2float(pt % RANGE_S, RANGE_S); pt /= RANGE_S; + *s = int2fullrange(pt % RANGE_S, RANGE_S); pt /= RANGE_S; - *s = fs < 0.5 ? 0.0 : 1.0; - - float fy = int2float(pt % RANGE_Y, RANGE_Y); pt /= RANGE_Y; - float fx = int2float(pt % RANGE_X, RANGE_X); pt /= RANGE_X; + float fy = int2midrange(pt % RANGE_Y, RANGE_Y); pt /= RANGE_Y; + float fx = int2midrange(pt % RANGE_X, RANGE_X); pt /= RANGE_X; *x = (fx + dx) * RANGE_X; *y = (fy + dy) * RANGE_Y; - *b = int2float(pt % RANGE_R, RANGE_R); pt /= RANGE_R; - *g = int2float(pt % RANGE_G, RANGE_G); pt /= RANGE_G; - *r = int2float(pt % RANGE_B, RANGE_B); pt /= RANGE_B; + *b = int2midrange(pt % RANGE_R, RANGE_R); pt /= RANGE_R; + *g = int2midrange(pt % RANGE_G, RANGE_G); pt /= RANGE_G; + *r = int2midrange(pt % RANGE_B, RANGE_B); pt /= RANGE_B; } static inline float clip(float x, int modulo) @@ -114,11 +153,33 @@ static inline float clip(float x, int modulo) static void add_point(float x, float y, float r, float g, float b, float s) { set_point(npoints, x, y, r, g, b, s); - get_point(npoints, &x, &y, &r, &g, &b, &s); npoints++; } -#define MAX_OPS 15 +static void add_random_point() +{ + points[npoints] = det_rand(RANGE_SYXRGB); + npoints++; +} + +#define NB_OPS 20 + +static uint8_t rand_op(void) +{ + uint8_t x = det_rand(NB_OPS); + + /* Randomly ignore statistically less efficient ops */ + if(x == 0) + return rand_op(); + if(x == 1 && (RANGE_S == 1 || det_rand(2))) + return rand_op(); + if(x <= 5 && det_rand(2)) + return rand_op(); + //if((x < 10 || x > 15) && !det_rand(4)) /* Favour colour changes */ + // return rand_op(); + + return x; +} static uint32_t apply_op(uint8_t op, uint32_t val) { @@ -127,61 +188,72 @@ static uint32_t apply_op(uint8_t op, uint32_t val) switch(op) { case 0: /* Flip strength value */ + case 1: + /* Statistics show that this helps often, but does not reduce + * the error significantly. */ return val ^ 1; - case 1: /* Move up; if impossible, down */ + case 2: /* Move up; if impossible, down */ rem = val % RANGE_S; ext = (val / RANGE_S) % RANGE_Y; ext = ext > 0 ? ext - 1 : ext + 1; return (val / RANGE_SY * RANGE_Y + ext) * RANGE_S + rem; - case 2: /* Move down; if impossible, up */ + case 3: /* Move down; if impossible, up */ rem = val % RANGE_S; ext = (val / RANGE_S) % RANGE_Y; ext = ext < RANGE_Y - 1 ? ext + 1 : ext - 1; return (val / RANGE_SY * RANGE_Y + ext) * RANGE_S + rem; - case 3: /* Move left; if impossible, right */ + case 4: /* Move left; if impossible, right */ rem = val % RANGE_SY; ext = (val / RANGE_SY) % RANGE_X; ext = ext > 0 ? ext - 1 : ext + 1; return (val / RANGE_SYX * RANGE_X + ext) * RANGE_SY + rem; - case 4: /* Move left; if impossible, right */ + case 5: /* Move left; if impossible, right */ rem = val % RANGE_SY; ext = (val / RANGE_SY) % RANGE_X; ext = ext < RANGE_X - 1 ? ext + 1 : ext - 1; return (val / RANGE_SYX * RANGE_X + ext) * RANGE_SY + rem; - case 5: /* Corner 1 */ - return apply_op(1, apply_op(3, val)); - case 6: /* Corner 2 */ - return apply_op(1, apply_op(4, val)); - case 7: /* Corner 3 */ + case 6: /* Corner 1 */ return apply_op(2, apply_op(4, val)); - case 8: /* Corner 4 */ - return apply_op(2, apply_op(3, val)); - case 9: /* R-- (or R++) */ + case 7: /* Corner 2 */ + return apply_op(2, apply_op(5, val)); + case 8: /* Corner 3 */ + return apply_op(3, apply_op(5, val)); + case 9: /* Corner 4 */ + return apply_op(3, apply_op(4, val)); + case 16: /* Double up */ + return apply_op(2, apply_op(2, val)); + case 17: /* Double down */ + return apply_op(3, apply_op(3, val)); + case 18: /* Double left */ + return apply_op(4, apply_op(4, val)); + case 19: /* Double right */ + return apply_op(5, apply_op(5, val)); + case 10: /* R-- (or R++) */ rem = val % RANGE_SYX; ext = (val / RANGE_SYX) % RANGE_R; ext = ext > 0 ? ext - 1 : ext + 1; return (val / RANGE_SYXR * RANGE_R + ext) * RANGE_SYX + rem; - case 10: /* R++ (or R--) */ + case 11: /* R++ (or R--) */ rem = val % RANGE_SYX; ext = (val / RANGE_SYX) % RANGE_R; ext = ext < RANGE_R - 1 ? ext + 1 : ext - 1; return (val / RANGE_SYXR * RANGE_R + ext) * RANGE_SYX + rem; - case 11: /* G-- (or G++) */ + case 12: /* G-- (or G++) */ rem = val % RANGE_SYXR; ext = (val / RANGE_SYXR) % RANGE_G; ext = ext > 0 ? ext - 1 : ext + 1; return (val / RANGE_SYXRG * RANGE_G + ext) * RANGE_SYXR + rem; - case 12: /* G++ (or G--) */ + case 13: /* G++ (or G--) */ rem = val % RANGE_SYXR; ext = (val / RANGE_SYXR) % RANGE_G; ext = ext < RANGE_G - 1 ? ext + 1 : ext - 1; return (val / RANGE_SYXRG * RANGE_G + ext) * RANGE_SYXR + rem; - case 13: /* B-- (or B++) */ + case 14: /* B-- (or B++) */ rem = val % RANGE_SYXRG; ext = (val / RANGE_SYXRG) % RANGE_B; ext = ext > 0 ? ext - 1 : ext + 1; return ext * RANGE_SYXRG + rem; - case 14: /* B++ (or B--) */ + case 15: /* B++ (or B--) */ rem = val % RANGE_SYXRG; ext = (val / RANGE_SYXRG) % RANGE_B; ext = ext < RANGE_B - 1 ? ext + 1 : ext - 1; @@ -211,16 +283,16 @@ static uint32_t apply_op(uint8_t op, uint32_t val) static void render(pipi_image_t *dst, int rx, int ry, int rw, int rh) { - uint8_t lookup[TOTAL_POINTS / POINTS_PER_CELL * RANGE_X * RANGE_Y]; + uint8_t lookup[TOTAL_CELLS * RANGE_X * RANGE_Y]; pipi_pixels_t *p = pipi_get_pixels(dst, PIPI_PIXELS_RGBA_F32); float *data = (float *)p->pixels; - float fx, fy, fr, fg, fb, fs; int i, x, y; memset(lookup, 0, sizeof(lookup)); dt.clear(); for(i = 0; i < npoints; i++) { + float fx, fy, fr, fg, fb, fs; get_point(i, &fx, &fy, &fr, &fg, &fb, &fs); lookup[(int)fx + dw * RANGE_X * (int)fy] = i; /* Keep link to point */ dt.insert(K::Point_2(fx, fy)); @@ -236,9 +308,6 @@ static void render(pipi_image_t *dst, int rx, int ry, int rw, int rh) { for(x = rx; x < rx + rw; x++) { - int i1 = 0, i2 = 0, i3 = 0; - float d1 = 1000000, d2 = 0, d3 = 0; - K::Point_2 m(x, y); Point_coordinate_vector coords; CGAL::Triple< @@ -252,8 +321,10 @@ static void render(pipi_image_t *dst, int rx, int ry, int rw, int rh) Point_coordinate_vector::iterator it; for(it = coords.begin(); it != coords.end(); ++it) { - float fx = (*it).first.x(); - float fy = (*it).first.y(); + float fx, fy, fr, fg, fb, fs; + + fx = (*it).first.x(); + fy = (*it).first.y(); if(fx < 0 || fy < 0 || fx > p->w - 1 || fy > p->h - 1) continue; @@ -262,10 +333,10 @@ static void render(pipi_image_t *dst, int rx, int ry, int rw, int rh) get_point(index, &fx, &fy, &fr, &fg, &fb, &fs); - float k = (*it).second; - if(fs > 0.5) k = pow(k, 1.2); - else k = pow(k, 0.8); - //float k = (*it).second * (1.0f + fs); + //float k = pow((*it).second * (1.0 + fs), 1.2); + float k = (*it).second * (1.00f + fs); + //float k = (*it).second * (0.60f + fs); + //float k = pow((*it).second, (1.0f + fs)); r += k * fr; g += k * fg; @@ -287,18 +358,17 @@ static void analyse(pipi_image_t *src) { pipi_pixels_t *p = pipi_get_pixels(src, PIPI_PIXELS_RGBA_F32); float *data = (float *)p->pixels; - int i; - for(int dy = 0; dy < dh; dy++) - for(int dx = 0; dx < dw; dx++) + for(unsigned int dy = 0; dy < dh; dy++) + for(unsigned int dx = 0; dx < dw; dx++) { float min = 1.1f, max = -0.1f; float total = 0.0; int xmin = 0, xmax = 0, ymin = 0, ymax = 0; int npixels = 0; - for(int iy = RANGE_Y * dy; iy < RANGE_Y * (dy + 1); iy++) - for(int ix = RANGE_X * dx; ix < RANGE_X * (dx + 1); ix++) + for(unsigned int iy = RANGE_Y * dy; iy < RANGE_Y * (dy + 1); iy++) + for(unsigned int ix = RANGE_X * dx; ix < RANGE_X * (dx + 1); ix++) { float lum = 0.0f; @@ -330,41 +400,70 @@ static void analyse(pipi_image_t *src) if(total < min + (max - min) / 4) wmin = 1.0, wmax = 0.0; - else if(total < min + (max - min) / 4 * 2) - wmin = 0.0, wmax = 0.0; else if(total < min + (max - min) / 4 * 3) wmin = 0.0, wmax = 0.0; else wmin = 0.0, wmax = 1.0; -//wmin = wmax = 1.0; -//if(total < min + (max - min) /3 ) -//if((dx + dy) & 1) -{ +#if 0 +add_random_point(); +add_random_point(); +#else +#if POINTS_PER_CELL == 1 + if(total < min + (max - min) / 2) + { +#endif add_point(xmin, ymin, data[4 * (xmin + ymin * p->w) + 0], data[4 * (xmin + ymin * p->w) + 1], data[4 * (xmin + ymin * p->w) + 2], wmin); -} -//else -{ +#if POINTS_PER_CELL == 1 + } + else + { +#endif add_point(xmax, ymax, data[4 * (xmax + ymax * p->w) + 0], data[4 * (xmax + ymax * p->w) + 1], data[4 * (xmax + ymax * p->w) + 2], wmax); -} +#if POINTS_PER_CELL == 1 + } +#endif +#endif } } int main(int argc, char *argv[]) { - int opstats[2 * MAX_OPS]; + int opstats[2 * NB_OPS]; pipi_image_t *src, *tmp, *dst; double error = 1.0; int width, height, ret = 0; + /* Compute bit allocation */ + fprintf(stderr, "Available characters: %i\n", NUM_CHARACTERS); + fprintf(stderr, "Maximum message size: %i\n", MAX_MSG_LEN); + 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); +#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); +#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); + /* Load image */ pipi_set_gamma(1.0); src = pipi_load(argv[1]); @@ -372,23 +471,33 @@ int main(int argc, char *argv[]) height = pipi_get_image_height(src); /* Compute best w/h ratio */ - dw = 1; - for(int i = 1; i <= TOTAL_POINTS / POINTS_PER_CELL; i++) + dw = 1; dh = TOTAL_CELLS; + for(unsigned int i = 1; i <= TOTAL_CELLS; i++) { + int j = TOTAL_CELLS / i; + float r = (float)width / (float)height; - float ir = (float)i / (float)(TOTAL_POINTS / POINTS_PER_CELL / i); - float dwr = (float)dw / (float)(TOTAL_POINTS / POINTS_PER_CELL / dw); + float ir = (float)i / (float)j; + float dwr = (float)dw / (float)dh; if(fabs(logf(r / ir)) < fabs(logf(r / dwr))) + { dw = i; + dh = TOTAL_CELLS / dw; + } } - dh = TOTAL_POINTS / POINTS_PER_CELL / dw; - fprintf(stderr, "Chosen image ratio: %i:%i\n", dw, dh); + while((dh + 1) * dw <= TOTAL_CELLS) dh++; + while(dw * (dh + 1) <= TOTAL_CELLS) dw++; + 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, 2, 2); + src = pipi_median_ext(tmp, 1, 1); + pipi_free(tmp); /* Analyse image */ analyse(src); @@ -401,32 +510,92 @@ int main(int argc, char *argv[]) fprintf(stderr, "Distance: %2.10g\n", error); memset(opstats, 0, sizeof(opstats)); - for(int iter = 0, failures = 0; /*failures < 200 &&*/ iter < 3000; iter++) + for(int iter = 0, stuck = 0, failures = 0, success = 0; + /*stuck < 5 && */iter < 10000; + iter++) { - uint32_t oldval; + if(failures > 500) + { + stuck++; + failures = 0; + } + pipi_image_t *scrap = pipi_copy(tmp); /* Choose a point at random */ int pt = det_rand(npoints); - oldval = points[pt]; + 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; - /* Apply a random operation and measure its effect */ - uint8_t op = det_rand(MAX_OPS); - points[pt] = apply_op(op, oldval); - render(scrap, 0, 0, dw * RANGE_X, dh * RANGE_Y); + points[pt] = candidates[i]; - opstats[op * 2]++; + render(scrap, zonex * RANGE_X, zoney * RANGE_Y, + zonew * RANGE_X, zoneh * RANGE_Y); - double newerr = pipi_measure_rmsd(src, scrap); - if(newerr < error) + double newerr = pipi_measure_rmsd(src, scrap); + if(newerr < besterr) + { + besterr = newerr; + bestop = i; + } + } + + 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); + pipi_free(tmp); tmp = scrap; - error = newerr; - fprintf(stderr, "%06i %2.010g after op%i(%i)\n", - iter, error, op, pt); - opstats[op * 2 + 1]++; + //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 { @@ -436,16 +605,19 @@ int main(int argc, char *argv[]) } } - fprintf(stderr, "operation: "); - for(int i = 0; i < MAX_OPS; i++) - fprintf(stderr, "%3i ", i); - fprintf(stderr, "\nattempts: "); - for(int i = 0; i < MAX_OPS; i++) - fprintf(stderr, "%3i ", opstats[i * 2]); - fprintf(stderr, "\nsuccesses: "); - for(int i = 0; i < MAX_OPS; i++) - fprintf(stderr, "%3i ", 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);