No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.
 
 
 
 
 
 

492 líneas
15 KiB

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