You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

img2rubik.c 14 KiB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. #include "config.h"
  2. #include "common.h"
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. #include <string.h>
  6. #include <math.h>
  7. #include <pipi.h>
  8. #define R 0
  9. #define G 1
  10. #define B 2
  11. #define X 3
  12. #define Y 4
  13. #define A 5
  14. //#define debug printf
  15. #define debug(...) /* */
  16. #define BRIGHT(x) (0.299*(x)[0] + 0.587*(x)[1] + 0.114*(x)[2])
  17. #define MAXCOLORS 16
  18. #define STEPS 25
  19. #define EPSILON (0.000001)
  20. typedef struct
  21. {
  22. double pts[STEPS + 1][MAXCOLORS * (MAXCOLORS - 1) / 2][6];
  23. int hullsize[STEPS + 1];
  24. double bary[STEPS + 1][3];
  25. }
  26. hull_t;
  27. static double const y[3] = { .299, .587, .114 };
  28. static double u[3], v[3];
  29. static int ylen;
  30. /*
  31. * Find two base vectors for the chrominance planes.
  32. */
  33. static void init_uv(void)
  34. {
  35. double tmp;
  36. ylen = sqrt(y[R] * y[R] + y[G] * y[G] + y[B] * y[B]);
  37. u[R] = y[1];
  38. u[G] = -y[0];
  39. u[B] = 0;
  40. tmp = sqrt(u[R] * u[R] + u[G] * u[G] + u[B] * u[B]);
  41. u[R] /= tmp; u[G] /= tmp; u[B] /= tmp;
  42. v[R] = y[G] * u[B] - y[B] * u[G];
  43. v[G] = y[B] * u[R] - y[R] * u[B];
  44. v[B] = y[R] * u[G] - y[G] * u[R];
  45. tmp = sqrt(v[R] * v[R] + v[G] * v[G] + v[B] * v[B]);
  46. v[R] /= tmp; v[G] /= tmp; v[B] /= tmp;
  47. }
  48. /*
  49. * Compute the convex hull of a given palette.
  50. */
  51. static hull_t *compute_hull(int ncolors, double const *palette)
  52. {
  53. hull_t *ret = malloc(sizeof(hull_t));
  54. double tmp;
  55. int i, j;
  56. debug("\n### NEW HULL ###\n\n");
  57. debug("Analysing %i colors\n", ncolors);
  58. double pal[ncolors][3];
  59. for(i = 0; i < ncolors; i++)
  60. {
  61. pal[i][R] = palette[i * 3];
  62. pal[i][G] = palette[i * 3 + 1];
  63. pal[i][B] = palette[i * 3 + 2];
  64. debug(" [%i] (%g,%g,%g)\n", i, pal[i][R], pal[i][G], pal[i][B]);
  65. }
  66. /*
  67. * 1. Find the darkest and lightest colours
  68. */
  69. double *dark = NULL, *light = NULL;
  70. double min = 1.0, max = 0.0;
  71. for(i = 0; i < ncolors; i++)
  72. {
  73. double p = BRIGHT(pal[i]);
  74. if(p < min)
  75. {
  76. dark = pal[i];
  77. min = p;
  78. }
  79. if(p > max)
  80. {
  81. light = pal[i];
  82. max = p;
  83. }
  84. }
  85. double gray[3];
  86. gray[R] = light[R] - dark[R];
  87. gray[G] = light[G] - dark[G];
  88. gray[B] = light[B] - dark[B];
  89. debug(" gray axis (%g,%g,%g) - (%g,%g,%g)\n",
  90. dark[R], dark[G], dark[B], light[R], light[G], light[B]);
  91. /*
  92. * 3. Browse the grey axis and do stuff
  93. */
  94. int n;
  95. for(n = 0; n <= STEPS; n++)
  96. {
  97. double pts[ncolors * (ncolors - 1) / 2][5];
  98. double ptmp[5];
  99. #define SWAP(p1,p2) do { memcpy(ptmp, p1, sizeof(ptmp)); \
  100. memcpy(p1, p2, sizeof(ptmp)); \
  101. memcpy(p2, ptmp, sizeof(ptmp)); } while(0)
  102. double t = n * 1.0 / STEPS;
  103. int npts = 0;
  104. debug("Slice %i/%i\n", n, STEPS);
  105. double p0[3];
  106. p0[R] = dark[R] + t * gray[R];
  107. p0[G] = dark[G] + t * gray[G];
  108. p0[B] = dark[B] + t * gray[B];
  109. debug(" 3D gray (%g,%g,%g)\n", p0[R], p0[G], p0[B]);
  110. /*
  111. * 3.1. Find all edges that intersect the t.y + (u,v) plane
  112. */
  113. for(i = 0; i < ncolors; i++)
  114. {
  115. double k1[3];
  116. k1[R] = pal[i][R] - p0[R];
  117. k1[G] = pal[i][G] - p0[G];
  118. k1[B] = pal[i][B] - p0[B];
  119. tmp = sqrt(k1[R] * k1[R] + k1[G] * k1[G] + k1[B] * k1[B]);
  120. /* If k1.y > t.y.y, we don't want this point */
  121. double yk1 = y[R] * k1[R] + y[G] * k1[G] + y[B] * k1[B];
  122. if(yk1 > t * ylen * ylen + EPSILON)
  123. continue;
  124. for(j = 0; j < ncolors; j++)
  125. {
  126. if(i == j)
  127. continue;
  128. double k2[3];
  129. k2[R] = pal[j][R] - p0[R];
  130. k2[G] = pal[j][G] - p0[G];
  131. k2[B] = pal[j][B] - p0[B];
  132. tmp = sqrt(k2[R] * k2[R] + k2[G] * k2[G] + k2[B] * k2[B]);
  133. /* If k2.y < t.y.y, we don't want this point */
  134. double yk2 = y[R] * k2[R] + y[G] * k2[G] + y[B] * k2[B];
  135. if(yk2 < t * ylen * ylen - EPSILON)
  136. continue;
  137. if(yk2 < yk1)
  138. continue;
  139. double s = yk1 == yk2 ?
  140. 0.5 : (t * ylen * ylen - yk1) / (yk2 - yk1);
  141. pts[npts][R] = p0[R] + k1[R] + s * (k2[R] - k1[R]);
  142. pts[npts][G] = p0[G] + k1[G] + s * (k2[G] - k1[G]);
  143. pts[npts][B] = p0[B] + k1[B] + s * (k2[B] - k1[B]);
  144. npts++;
  145. }
  146. }
  147. /*
  148. * 3.2. Find the barycentre of these points' convex hull. We use
  149. * the Graham Scan technique.
  150. */
  151. /* Make our problem a 2-D problem. */
  152. for(i = 0; i < npts; i++)
  153. {
  154. pts[i][X] = (pts[i][R] - p0[R]) * u[R]
  155. + (pts[i][G] - p0[G]) * u[G]
  156. + (pts[i][B] - p0[B]) * u[B];
  157. pts[i][Y] = (pts[i][R] - p0[R]) * v[R]
  158. + (pts[i][G] - p0[G]) * v[G]
  159. + (pts[i][B] - p0[B]) * v[B];
  160. }
  161. /* Find the leftmost point */
  162. int left = -1;
  163. tmp = 10.;
  164. for(i = 0; i < npts; i++)
  165. if(pts[i][X] < tmp)
  166. {
  167. left = i;
  168. tmp = pts[i][X];
  169. }
  170. SWAP(pts[0], pts[left]);
  171. /* Sort the remaining points radially around pts[0]. Bubble sort
  172. * is okay for small sizes, I don't care. */
  173. for(i = 1; i < npts; i++)
  174. for(j = 1; j < npts - i; j++)
  175. {
  176. double k1 = (pts[j][X] - pts[0][X])
  177. * (pts[j + 1][Y] - pts[0][Y]);
  178. double k2 = (pts[j + 1][X] - pts[0][X])
  179. * (pts[j][Y] - pts[0][Y]);
  180. if(k1 < k2)
  181. SWAP(pts[j], pts[j + 1]);
  182. else if(k1 == k2)
  183. {
  184. /* Aligned! keep the farthest point */
  185. double ax = pts[j][X] - pts[0][X];
  186. double ay = pts[j][Y] - pts[0][Y];
  187. double bx = pts[j + 1][X] - pts[0][X];
  188. double by = pts[j + 1][Y] - pts[0][Y];
  189. if(ax * ax + ay * ay > bx * bx + by * by)
  190. SWAP(pts[j], pts[j + 1]);
  191. }
  192. }
  193. /* Remove points not in the convex hull */
  194. for(i = 2; i < npts; /* */)
  195. {
  196. if(i < 2)
  197. {
  198. i++;
  199. continue;
  200. }
  201. double k1 = (pts[i - 1][X] - pts[i - 2][X])
  202. * (pts[i][Y] - pts[i - 2][Y]);
  203. double k2 = (pts[i][X] - pts[i - 2][X])
  204. * (pts[i - 1][Y] - pts[i - 2][Y]);
  205. if(k1 <= k2 + EPSILON)
  206. {
  207. for(j = i - 1; j < npts - 1; j++)
  208. SWAP(pts[j], pts[j + 1]);
  209. npts--;
  210. }
  211. else
  212. i++;
  213. }
  214. for(i = 0; i < npts; i++)
  215. debug(" 2D pt[%i] (%g,%g)\n", i, pts[i][X], pts[i][Y]);
  216. /* Compute the barycentre coordinates */
  217. double ctx = 0., cty = 0., weight = 0.;
  218. for(i = 2; i < npts; i++)
  219. {
  220. double abx = pts[i - 1][X] - pts[0][X];
  221. double aby = pts[i - 1][Y] - pts[0][Y];
  222. double acx = pts[i][X] - pts[0][X];
  223. double acy = pts[i][Y] - pts[0][Y];
  224. double sqarea = (abx * abx + aby * aby) * (acx * acx + acy * acy)
  225. - (abx * acx + aby * acy) * (abx * acx + aby * acy);
  226. if(sqarea <= 0.)
  227. continue;
  228. double area = sqrt(sqarea);
  229. ctx += area * (abx + acx) / 3;
  230. cty += area * (aby + acy) / 3;
  231. weight += area;
  232. }
  233. if(weight > EPSILON)
  234. {
  235. ctx = pts[0][X] + ctx / weight;
  236. cty = pts[0][Y] + cty / weight;
  237. }
  238. else
  239. {
  240. int right = -1;
  241. tmp = -10.;
  242. for(i = 0; i < npts; i++)
  243. if(pts[i][X] > tmp)
  244. {
  245. right = i;
  246. tmp = pts[i][X];
  247. }
  248. ctx = 0.5 * (pts[0][X] + pts[right][X]);
  249. cty = 0.5 * (pts[0][Y] + pts[right][Y]);
  250. }
  251. debug(" 2D bary (%g,%g)\n", ctx, cty);
  252. /*
  253. * 3.3. Store the barycentre and convex hull information.
  254. */
  255. ret->bary[n][R] = p0[R] + ctx * u[R] + cty * v[R];
  256. ret->bary[n][G] = p0[G] + ctx * u[G] + cty * v[G];
  257. ret->bary[n][B] = p0[B] + ctx * u[B] + cty * v[B];
  258. for(i = 0; i < npts; i++)
  259. {
  260. ret->pts[n][i][R] = pts[i][R];
  261. ret->pts[n][i][G] = pts[i][G];
  262. ret->pts[n][i][B] = pts[i][B];
  263. ret->pts[n][i][X] = pts[i][X] - ctx;
  264. ret->pts[n][i][Y] = pts[i][Y] - cty;
  265. ret->pts[n][i][A] = atan2(pts[i][Y] - cty, pts[i][X] - ctx);
  266. debug(" 3D pt[%i] (%g,%g,%g) angle %g\n",
  267. i, pts[i][R], pts[i][G], pts[i][B], ret->pts[n][i][A]);
  268. }
  269. ret->hullsize[n] = npts;
  270. debug(" 3D bary (%g,%g,%g)\n",
  271. ret->bary[n][R], ret->bary[n][G], ret->bary[n][B]);
  272. }
  273. return ret;
  274. }
  275. int main(int argc, char *argv[])
  276. {
  277. static double const rgbpal[] =
  278. {
  279. 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1,
  280. 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1,
  281. };
  282. static double const mypal[] =
  283. {
  284. 0.900, 0.001, 0.001, /* red */
  285. 0.001, 0.800, 0.001, /* green */
  286. 0.005, 0.001, 0.500, /* blue */
  287. 0.900, 0.900, 0.900, /* white */
  288. 0.900, 0.900, 0.001, /* yellow */
  289. 0.800, 0.400, 0.001, /* orange */
  290. };
  291. int i, j;
  292. init_uv();
  293. hull_t *rgbhull = compute_hull(8, rgbpal);
  294. hull_t *myhull = compute_hull(6, mypal);
  295. /*
  296. * 4. Load image and change its palette.
  297. */
  298. debug("\n### PROCESSING IMAGE ###\n\n");
  299. pipi_image_t *src = pipi_load(argv[1]);
  300. pipi_pixels_t *srcp = pipi_getpixels(src, PIPI_PIXELS_RGBA_F);
  301. float *srcdata = (float *)srcp->pixels;
  302. int w = srcp->w, h = srcp->h;
  303. pipi_image_t *dst = pipi_new(w, h);
  304. pipi_pixels_t *dstp = pipi_getpixels(dst, PIPI_PIXELS_RGBA_F);
  305. float *dstdata = (float *)dstp->pixels;
  306. for(j = 0; j < h; j++)
  307. for(i = 0; i < w; i++)
  308. {
  309. double p[3];
  310. /* FIXME: Imlib fucks up the RGB order. */
  311. p[B] = srcdata[4 * (j * w + i)];
  312. p[G] = srcdata[4 * (j * w + i) + 1];
  313. p[R] = srcdata[4 * (j * w + i) + 2];
  314. debug("Pixel +%i+%i (%g,%g,%g)\n", i, j, p[R], p[G], p[B]);
  315. int slice = (int)(BRIGHT(p) * STEPS + 0.5);
  316. debug(" slice %i\n", slice);
  317. /* Convert to 2D. The origin is the slice's barycentre. */
  318. double xp = (p[R] - rgbhull->bary[slice][R]) * u[R]
  319. + (p[G] - rgbhull->bary[slice][G]) * u[G]
  320. + (p[B] - rgbhull->bary[slice][B]) * u[B];
  321. double yp = (p[R] - rgbhull->bary[slice][R]) * v[R]
  322. + (p[G] - rgbhull->bary[slice][G]) * v[G]
  323. + (p[B] - rgbhull->bary[slice][B]) * v[B];
  324. debug(" 2D pt (%g,%g)\n", xp, yp);
  325. /* 1. find the excentricity in RGB space. There is an easier
  326. * way to do this, which is to find the intersection of our
  327. * line with the RGB cube itself, but we'd lose the possibility
  328. * of having an original colour space other than RGB. */
  329. /* First, find the relevant triangle. */
  330. int n, count = rgbhull->hullsize[slice];
  331. double angle = atan2(yp, xp);
  332. for(n = 0; n < count; n++)
  333. {
  334. double a1 = rgbhull->pts[slice][n][A];
  335. double a2 = rgbhull->pts[slice][(n + 1) % count][A];
  336. if(a1 > a2)
  337. {
  338. if(angle >= a1)
  339. a2 += 2 * M_PI;
  340. else
  341. a1 -= 2 * M_PI;
  342. }
  343. if(angle >= a1 && angle <= a2)
  344. break;
  345. }
  346. /* Now compute the distance to the triangle's edge. If the edge
  347. * intersection is M, then t is such as P = t.M (can be zero) */
  348. double xa = rgbhull->pts[slice][n % count][X];
  349. double ya = rgbhull->pts[slice][n % count][Y];
  350. double xb = rgbhull->pts[slice][(n + 1) % count][X];
  351. double yb = rgbhull->pts[slice][(n + 1) % count][Y];
  352. double t = (xp * (yb - ya) - yp * (xb - xa)) / (xa * yb - xb * ya);
  353. debug(" best RGB %g (%g,%g) (%g,%g)\n", t, xa, ya, xb, yb);
  354. /* 2. apply the excentricity in reduced space. */
  355. count = myhull->hullsize[slice];
  356. for(n = 0; n < count; n++)
  357. {
  358. double a1 = myhull->pts[slice][n][A];
  359. double a2 = myhull->pts[slice][(n + 1) % count][A];
  360. if(a1 > a2)
  361. {
  362. if(angle >= a1)
  363. a2 += 2 * M_PI;
  364. else
  365. a1 -= 2 * M_PI;
  366. }
  367. if(angle >= a1 && angle <= a2)
  368. break;
  369. }
  370. /* If the edge intersection is M', s is such as P = s.M'. We
  371. * want P' = t.M' = t.P/s */
  372. xa = myhull->pts[slice][n % count][X];
  373. ya = myhull->pts[slice][n % count][Y];
  374. xb = myhull->pts[slice][(n + 1) % count][X];
  375. yb = myhull->pts[slice][(n + 1) % count][Y];
  376. double s = (xp * (yb - ya) - yp * (xb - xa)) / (xa * yb - xb * ya);
  377. debug(" best custom %g (%g,%g) (%g,%g)\n", s, xa, ya, xb, yb);
  378. if(s > 0)
  379. {
  380. xp *= t / s;
  381. yp *= t / s;
  382. }
  383. p[R] = myhull->bary[slice][R] + xp * u[R] + yp * v[R];
  384. p[G] = myhull->bary[slice][G] + xp * u[G] + yp * v[G];
  385. p[B] = myhull->bary[slice][B] + xp * u[B] + yp * v[B];
  386. /* Clipping should not be necessary, but the above code
  387. * is unfortunately not perfect. */
  388. if(p[R] < 0.0) p[R] = 0.0; else if(p[R] > 1.0) p[R] = 1.0;
  389. if(p[G] < 0.0) p[G] = 0.0; else if(p[G] > 1.0) p[G] = 1.0;
  390. if(p[B] < 0.0) p[B] = 0.0; else if(p[B] > 1.0) p[B] = 1.0;
  391. dstdata[4 * (j * w + i)] = p[B];
  392. dstdata[4 * (j * w + i) + 1] = p[G];
  393. dstdata[4 * (j * w + i) + 2] = p[R];
  394. }
  395. free(rgbhull);
  396. free(myhull);
  397. pipi_save(dst, argv[2]);
  398. return 0;
  399. }