瀏覽代碼

First attempt at a super-compressor for the purpose of sending images to

Twitter, rendering this service slightly more useful. It's still full of
crap, of course.

git-svn-id: file:///srv/caca.zoy.org/var/lib/svn/libpipi/trunk@3518 92316355-f0b4-4df1-b90c-862c8a59935f
master
sam 15 年之前
父節點
當前提交
4dd30b4ad4
共有 3 個檔案被更改,包括 474 行新增1 行删除
  1. +2
    -0
      examples/.gitignore
  2. +11
    -1
      examples/Makefile.am
  3. +461
    -0
      examples/img2twit.cpp

+ 2
- 0
examples/.gitignore 查看文件

@@ -4,7 +4,9 @@ edd
floodfill
histogram
img2rubik
img2twit
line
makemovie
sharpen
storyboard
*.exe

+ 11
- 1
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


+ 461
- 0
examples/img2twit.cpp 查看文件

@@ -0,0 +1,461 @@
#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Delaunay_triangulation_2.h>
#include <CGAL/natural_neighbor_coordinates_2.h>

#include <pipi.h>

#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<K> Delaunay_triangulation;
typedef std::vector<std::pair<K::Point_2, K::FT> > 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<Point_coordinate_vector>,
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;
}


Loading…
取消
儲存