diff --git a/examples/.gitignore b/examples/.gitignore index d317853..720cbad 100644 --- a/examples/.gitignore +++ b/examples/.gitignore @@ -4,7 +4,9 @@ edd floodfill histogram img2rubik +img2twit line +makemovie sharpen storyboard *.exe diff --git a/examples/Makefile.am b/examples/Makefile.am index 3dcd1fc..fc94a24 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -3,7 +3,7 @@ AM_CPPFLAGS = -I$(top_srcdir) -I$(top_srcdir)/pipi -I../pipi noinst_PROGRAMS = edd img2rubik sharpen floodfill line bezier histogram \ - colorstring $(makemovie) $(storyboard) + colorstring img2twit $(makemovie) $(storyboard) $(img2twit) edd_SOURCES = edd.c edd_LDADD = ../pipi/libpipi.la @@ -33,6 +33,7 @@ if USE_FFMPEG makemovie = makemovie storyboard = storyboard endif + makemovie_SOURCES = makemovie.c makemovie_LDADD = ../pipi/libpipi.la makemovie_CFLAGS = $(LIBAVCODEC_CFLAGS) $(LIBAVFORMAT_CFLAGS) $(LIBSWSCALE_CFLAGS) @@ -43,3 +44,12 @@ storyboard_LDADD = ../pipi/libpipi.la storyboard_CFLAGS = $(LIBAVCODEC_CFLAGS) $(LIBAVFORMAT_CFLAGS) $(LIBSWSCALE_CFLAGS) storyboard_LDFLAGS = $(LIBAVCODEC_LIBS) $(LIBAVFORMAT_LIBS) $(LIBSWSCALE_LIBS) +if USE_CGAL +img2twit = img2twit +endif + +img2twit_SOURCES = img2twit.cpp +img2twit_LDADD = ../pipi/libpipi.la +img2twit_CXXFLAGS = -frounding-math +img2twit_LDFLAGS = -lCGAL + diff --git a/examples/img2twit.cpp b/examples/img2twit.cpp new file mode 100644 index 0000000..421c0e1 --- /dev/null +++ b/examples/img2twit.cpp @@ -0,0 +1,461 @@ +#include "config.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#define TOTAL_POINTS 138 +#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 + +#define RANGE_SY (RANGE_S*RANGE_Y) +#define RANGE_SYX (RANGE_S*RANGE_Y*RANGE_X) +#define RANGE_SYXR (RANGE_S*RANGE_Y*RANGE_X*RANGE_R) +#define RANGE_SYXRG (RANGE_S*RANGE_Y*RANGE_X*RANGE_R*RANGE_G) +#define RANGE_SYXRGB (RANGE_S*RANGE_Y*RANGE_X*RANGE_R*RANGE_G*RANGE_B) + +struct K : CGAL::Exact_predicates_inexact_constructions_kernel {}; +typedef CGAL::Delaunay_triangulation_2 Delaunay_triangulation; +typedef std::vector > Point_coordinate_vector; + +/* Global aspect ratio */ +static int dw, dh; + +/* Global point encoding */ +static uint32_t points[1024]; +static int npoints = 0; + +/* Global triangulation */ +static Delaunay_triangulation dt; + +static unsigned int det_rand(unsigned int mod) +{ + static unsigned long next = 1; + next = next * 1103515245 + 12345; + return ((unsigned)(next / 65536) % 32768) % mod; +} + +static inline int float2int(float val, int range) +{ + int ret = (int)(val * ((float)range - 0.0001)); + return ret < 0 ? 0 : ret > range - 1? range - 1 : ret; +} + +static inline float int2float(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) +{ + int dx = (index / POINTS_PER_CELL) % dw; + int dy = (index / POINTS_PER_CELL) / dw; + + float fx = (x - dx * RANGE_X) / RANGE_X; + float fy = (y - dy * RANGE_Y) / RANGE_Y; + + int is = float2int(s, RANGE_S); + + int ix = float2int(fx, RANGE_X); + int iy = float2int(fy, RANGE_Y); + + int ir = float2int(r, RANGE_R); + int ig = float2int(g, RANGE_G); + int ib = float2int(b, RANGE_B); + + points[index] = is + RANGE_S * (iy + RANGE_Y * (ix + RANGE_X * + (ib + RANGE_B * (ig + (RANGE_R * ir))))); +} + +static inline void get_point(int index, float *x, float *y, float *r, + float *g, float *b, float *s) +{ + uint32_t pt = points[index]; + + 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 = 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; + + *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; +} + +static inline float clip(float x, int modulo) +{ + float mul = (float)modulo + 0.9999; + int round = (int)(x * mul); + return (float)round / (float)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 uint32_t apply_op(uint8_t op, uint32_t val) +{ + uint32_t rem, ext; + + switch(op) + { + case 0: /* Flip strength value */ + return val ^ 1; + case 1: /* 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 */ + 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 */ + 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 */ + 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 */ + 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++) */ + 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--) */ + 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++) */ + 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--) */ + 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++) */ + 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--) */ + rem = val % RANGE_SYXRG; + ext = (val / RANGE_SYXRG) % RANGE_B; + ext = ext < RANGE_B - 1 ? ext + 1 : ext - 1; + return ext * RANGE_SYXRG + rem; +#if 0 + case 15: /* Brightness-- */ + return apply_op(9, apply_op(11, apply_op(13, val))); + case 16: /* Brightness++ */ + return apply_op(10, apply_op(12, apply_op(14, val))); + case 17: /* RG-- */ + return apply_op(9, apply_op(11, val)); + case 18: /* RG++ */ + return apply_op(10, apply_op(12, val)); + case 19: /* GB-- */ + return apply_op(11, apply_op(13, val)); + case 20: /* GB++ */ + return apply_op(12, apply_op(14, val)); + case 21: /* RB-- */ + return apply_op(9, apply_op(13, val)); + case 22: /* RB++ */ + return apply_op(10, apply_op(14, val)); +#endif + default: + return 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]; + 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++) + { + 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)); + } + + /* Add fake points to close the triangulation */ + dt.insert(K::Point_2(-p->w, -p->h)); + dt.insert(K::Point_2(2 * p->w, -p->h)); + dt.insert(K::Point_2(-p->w, 2 * p->h)); + dt.insert(K::Point_2(2 * p->w, 2 * p->h)); + + for(y = ry; y < ry + rh; y++) + { + 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< + std::back_insert_iterator, + K::FT, bool> result = + CGAL::natural_neighbor_coordinates_2(dt, m, + std::back_inserter(coords)); + + float r = 0.0f, g = 0.0f, b = 0.0f, norm = 0.0f; + + Point_coordinate_vector::iterator it; + for(it = coords.begin(); it != coords.end(); ++it) + { + float fx = (*it).first.x(); + float fy = (*it).first.y(); + + if(fx < 0 || fy < 0 || fx > p->w - 1 || fy > p->h - 1) + continue; + + int index = lookup[(int)fx + dw * RANGE_X * (int)fy]; + + 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); + + r += k * fr; + g += k * fg; + b += k * fb; + norm += k; + } + + data[4 * (x + y * p->w) + 0] = r / norm; + data[4 * (x + y * p->w) + 1] = g / norm; + data[4 * (x + y * p->w) + 2] = b / norm; + data[4 * (x + y * p->w) + 3] = 0.0; + } + } + + pipi_release_pixels(dst, p); +} + +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++) + { + 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++) + { + float lum = 0.0f; + + lum += data[4 * (ix + iy * p->w) + 0]; + lum += data[4 * (ix + iy * p->w) + 1]; + lum += data[4 * (ix + iy * p->w) + 2]; + + if(lum < min) + { + min = lum; + xmin = ix; + ymin = iy; + } + + if(lum > max) + { + max = lum; + xmax = ix; + ymax = iy; + } + + total += lum; + npixels++; + } + + total /= npixels; + + float wmin, wmax; + + 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) +{ + 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 +{ + 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); +} + } +} + +int main(int argc, char *argv[]) +{ + int opstats[2 * MAX_OPS]; + pipi_image_t *src, *tmp, *dst; + double error = 1.0; + int width, height, ret = 0; + + /* Load image */ + pipi_set_gamma(1.0); + src = pipi_load(argv[1]); + width = pipi_get_image_width(src); + height = pipi_get_image_height(src); + + /* Compute best w/h ratio */ + dw = 1; + for(int i = 1; i <= TOTAL_POINTS / POINTS_PER_CELL; 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); + + if(fabs(logf(r / ir)) < fabs(logf(r / dwr))) + dw = i; + } + dh = TOTAL_POINTS / POINTS_PER_CELL / dw; + fprintf(stderr, "Chosen image ratio: %i:%i\n", 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); + + /* 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); + + memset(opstats, 0, sizeof(opstats)); + for(int iter = 0, failures = 0; /*failures < 200 &&*/ iter < 3000; iter++) + { + uint32_t oldval; + pipi_image_t *scrap = pipi_copy(tmp); + + /* Choose a point at random */ + int pt = det_rand(npoints); + oldval = points[pt]; + + /* 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); + + opstats[op * 2]++; + + double newerr = pipi_measure_rmsd(src, scrap); + if(newerr < error) + { + 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]++; + failures = 0; + } + else + { + pipi_free(scrap); + points[pt] = oldval; + failures++; + } + } + + 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"); + + fprintf(stderr, "Distance: %2.10g\n", error); + + dst = pipi_resize(tmp, width, height); + pipi_free(tmp); + + /* Save image and bail out */ + pipi_save(dst, "lol.bmp"); + pipi_free(dst); + + return ret; +} +