PageRenderTime 27ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/src/palette.c

https://git.sr.ht/~coco/imscript/
C | 423 lines | 329 code | 52 blank | 42 comment | 77 complexity | 906932e5faafa5b3675990d2b68ce6f4 MD5 | raw file
  1. // palette: a program to tranform from GRAY FLOATS to RGB BYTES
  2. // SYNOPSIS:
  3. // palette FROM TO PALSPEC [in.gray [out.rgb]]
  4. //
  5. // FROM = real value (-inf=min)
  6. // TO = real value (inf=max)
  7. // PALSPEC = named palette (HOT, GLOBE, RELIEF, etc)j
  8. #include <assert.h>
  9. #include <math.h>
  10. #include <stdbool.h>
  11. #include <stdio.h>
  12. #include <stdint.h>
  13. #include <string.h>
  14. #define PALSAMPLES 1024
  15. //#define PALSAMPLES 256
  16. // Idea: The function
  17. //
  18. // palette : R -> rgb
  19. //
  20. // is described as a composition of two steps
  21. //
  22. // quantization : R -> {0, ..., PALSAMPLES-1}
  23. // lookup : {0, ..., PALSAMPLES} -> rgb
  24. //
  25. // The first step is an affine function followed by a rounding, and the second
  26. // step is the evaluation of a look-up table. The proposed implementation is
  27. // kept simple and the size of the look-up table is fixed.
  28. #include "fail.c"
  29. #include "xmalloc.c"
  30. #include "xfopen.c"
  31. struct palette {
  32. uint8_t t[3*PALSAMPLES];
  33. float m, M;
  34. };
  35. static void fprint_palette(FILE *f, struct palette *p)
  36. {
  37. fprintf(f, "palette %g %g\n", p->m, p->M);
  38. for (int i = 0; i < PALSAMPLES; i++)
  39. fprintf(f, "\tpal[%d] = {%d %d %d}\n",
  40. i, p->t[3*i+0], p->t[3*i+1], p->t[3*i+2]);
  41. }
  42. static void fill_palette_gray(uint8_t *t)
  43. {
  44. float factor = 255.0 / PALSAMPLES;
  45. for (int i = 0; i < PALSAMPLES; i++)
  46. for (int j = 0; j < 3; j++)
  47. t[3*i+j] = factor * i;
  48. }
  49. static void fill_palette_hot(uint8_t *t)
  50. {
  51. float factor = 255.0 / PALSAMPLES;
  52. for (int i = 0; i < PALSAMPLES; i++)
  53. {
  54. t[3*i+0] = 255;
  55. if (i < 85)
  56. t[3*i+0] = 3 * factor * i;
  57. t[3*i+1] = factor * i;
  58. t[3*i+2] = factor * i;
  59. }
  60. }
  61. static void fill_palette_with_nodes(struct palette *p, float *n, int nn)
  62. {
  63. if (nn < 2) fail("at least 2 nodes required");
  64. float factor = (PALSAMPLES-1.0)/(nn-1.0);
  65. //fprintf(stderr, "1pfnnm1 = %g\n", 1 + factor*(nn-1));
  66. assert(PALSAMPLES == round(1+factor*(nn-1)));
  67. for (int i = 0; i < nn-1; i++)
  68. {
  69. float posi = n[4*i];
  70. float posnext = n[4*(i+1)];
  71. //fprintf(stderr, "fwpn node %d : %g %g\n", i, posi, posnext);
  72. if (posi > posnext)
  73. fail("palette nodes must be in increasing order");
  74. int idxi = factor * i;
  75. int idxnext = factor * (i+1);
  76. assert(idxi <= idxnext);
  77. assert(idxnext < PALSAMPLES);
  78. for (int j = 0; j <= (idxnext-idxi); j++)
  79. {
  80. float a = j*1.0/(idxnext - idxi);
  81. p->t[3*(idxi+j)+0] = (1-a)*n[4*i+1] + a*n[4*(i+1)+1];
  82. p->t[3*(idxi+j)+1] = (1-a)*n[4*i+2] + a*n[4*(i+1)+2];
  83. p->t[3*(idxi+j)+2] = (1-a)*n[4*i+3] + a*n[4*(i+1)+3];
  84. }
  85. }
  86. p->m = n[0];
  87. p->M = n[4*(nn-1)];
  88. // dirty hack: copy the penultimate position of the palette to the last
  89. for (int j = 0; j < 3; j++)
  90. p->t[3*(PALSAMPLES-1)+j] = p->t[3*(PALSAMPLES-2)+j];
  91. }
  92. static void set_node_positions_linearly(float *n, int nn, float m, float M)
  93. {
  94. float beta = (M - m)/(nn - 1);
  95. for (int i = 0; i < nn; i++)
  96. n[4*i] = m + beta * i;
  97. }
  98. static float nodes_cocoterrain[] = {
  99. 1, 0, 0, 0, // black
  100. 2, 255, 0, 255, // magenta
  101. 3, 0, 0, 255, // blue
  102. 4, 0, 255, 255, // cyan
  103. 5, 0, 255, 0, // green
  104. 6, 170, 84, 0, // brown
  105. 7, 255, 255, 255, // white
  106. };
  107. static float nodes_dem[] = {
  108. 0 , 0 , 96.9994 , 70.9997 ,
  109. 2.6010 , 16.0012 ,121.9997 , 46.9990 ,
  110. 26.0100 , 231.9990 , 215.0007 , 125.0010,
  111. 62.4495 , 160.9993 , 67.0012 , 0,
  112. 88.4595 , 158.0006 , 0 , 0,
  113. 145.7070, 109.9993, 109.9993, 109.9993,
  114. 208.1565, 255.0000, 255.0000, 255.0000,
  115. 255.0000, 255.0000, 255.0000, 255.0000,
  116. };
  117. static float nodes_nice[] = {
  118. 1, 0, 0, 255, // blue
  119. 2, 255, 255, 255, // white
  120. 3, 255, 0, 0, // red
  121. };
  122. static float nodes_nnice[] = {
  123. 1, 255, 0, 0, // red
  124. 2, 0, 0, 0, // white
  125. 3, 0, 255, 0, // blue
  126. };
  127. static float *get_gpl_nodes(char *filename, int *n)
  128. {
  129. int bufsize = 0xff, nnodes = 0;
  130. char buf[bufsize];
  131. FILE *f = xfopen(filename, "r");
  132. static float nodes[4*PALSAMPLES];
  133. while(fgets(buf, bufsize, f) && nnodes < PALSAMPLES) {
  134. //fprintf(stderr, "s = \"%s\"\n", buf);
  135. float *t = nodes + 4*nnodes;
  136. *t = nnodes;
  137. if (3 == sscanf(buf, "%g %g %g", t+1, t+2, t+3)) {
  138. //fprintf(stderr, "\tnod[%d] = %g %g %g\n", nnodes, t[1], t[2], t[3]);
  139. nnodes += 1;
  140. }
  141. }
  142. xfclose(f);
  143. *n = nnodes;
  144. return nodes;
  145. }
  146. static float *get_gpf_nodes(char *filename, int *n)
  147. {
  148. int bufsize = 0xff, nnodes = 0;
  149. char buf[bufsize];
  150. FILE *f = xfopen(filename, "r");
  151. static float nodes[4*PALSAMPLES];
  152. while(fgets(buf, bufsize, f) && nnodes < PALSAMPLES) {
  153. //fprintf(stderr, "s = \"%s\"\n", buf);
  154. float *t = nodes + 4*nnodes;
  155. *t = nnodes;
  156. if (4 == sscanf(buf, "%g %g %g %g", t, t+1, t+2, t+3)) {
  157. t[1] = round(t[1] * 255);
  158. t[2] = round(t[2] * 255);
  159. t[3] = round(t[3] * 255);
  160. //fprintf(stderr, "\tnod[%d]{%g} = %g %g %g\n", nnodes, t[0], t[1], t[2], t[3]);
  161. nnodes += 1;
  162. }
  163. }
  164. xfclose(f);
  165. *n = nnodes;
  166. return nodes;
  167. }
  168. static bool hassuffix(const char *s, const char *suf)
  169. {
  170. int len_s = strlen(s);
  171. int len_suf = strlen(suf);
  172. if (len_s < len_suf)
  173. return false;
  174. return 0 == strcmp(suf, s + (len_s - len_suf));
  175. }
  176. static void fill_palette(struct palette *p, char *s, float m, float M)
  177. {
  178. p->m = m;
  179. p->M = M;
  180. if (0 == strcmp(s, "gray"))
  181. fill_palette_gray(p->t);
  182. else if (0 == strcmp(s, "hot"))
  183. fill_palette_hot(p->t);
  184. else if (0 == strcmp(s, "cocoterrain")) {
  185. set_node_positions_linearly(nodes_cocoterrain, 7, m, M);
  186. fill_palette_with_nodes(p, nodes_cocoterrain, 7);
  187. } else if (0 == strcmp(s, "dem")) {
  188. set_node_positions_linearly(nodes_dem, 8, m, M);
  189. fill_palette_with_nodes(p, nodes_dem, 8);
  190. } else if (0 == strcmp(s, "nice")) {
  191. set_node_positions_linearly(nodes_nice, 3, m, M);
  192. fill_palette_with_nodes(p, nodes_nice, 3);
  193. } else if (0 == strcmp(s, "nnice")) {
  194. set_node_positions_linearly(nodes_nnice, 3, m, M);
  195. fill_palette_with_nodes(p, nodes_nnice, 3);
  196. } else if (hassuffix(s, ".gpl")) {
  197. int nnodes;
  198. float *nodes = get_gpl_nodes(s, &nnodes);
  199. set_node_positions_linearly(nodes, nnodes, m, M);
  200. fill_palette_with_nodes(p, nodes, nnodes);
  201. } else if (hassuffix(s, ".gpf")) {
  202. int nnodes;
  203. float *nodes = get_gpf_nodes(s, &nnodes);
  204. set_node_positions_linearly(nodes, nnodes, m, M);
  205. fill_palette_with_nodes(p, nodes, nnodes);
  206. }
  207. else fail("unrecognized palette \"%s\"", s);
  208. }
  209. static void get_palette_color(uint8_t *rgb, struct palette *p, float x)
  210. {
  211. if (isnan(x)) {
  212. rgb[0] = rgb[1] = rgb[2] = 0;
  213. return;
  214. }
  215. if (!isfinite(x)) {
  216. rgb[0] = rgb[1] = rgb[2] = 255;
  217. return;
  218. }
  219. int ix = round((PALSAMPLES-1)*(x - p->m)/(p->M - p->m));
  220. if (ix < 0) ix = 0;
  221. if (ix >= PALSAMPLES) ix = PALSAMPLES - 1;
  222. rgb[0] = p->t[3*ix+0];
  223. rgb[1] = p->t[3*ix+1];
  224. rgb[2] = p->t[3*ix+2];
  225. }
  226. #include "smapa.h"
  227. SMART_PARAMETER_SILENT(PALMAXEPS,0)
  228. static void get_min_max(float *min, float *max, float *x, int n)
  229. {
  230. float m = INFINITY, M = -m;
  231. for (int i = 0; i < n; i++)
  232. if (isfinite(x[i]))
  233. {
  234. m = fmin(m, x[i]);
  235. M = fmax(M, x[i]);
  236. }
  237. if (min) *min = m;
  238. if (max) *max = M+PALMAXEPS();
  239. }
  240. void apply_palette(uint8_t *y, float *x, int n, char *s, float *m, float *M)
  241. {
  242. if (!isfinite(*m)) get_min_max(m, 0, x, n);
  243. if (!isfinite(*M)) get_min_max(0, M, x, n);
  244. struct palette p[1];
  245. fill_palette(p, s, *m, *M);
  246. //fprint_palette(stderr, p);
  247. for (int i = 0; i < n; i++)
  248. get_palette_color(y + 3*i, p, x[i]);
  249. }
  250. #define PALETTE_MAIN
  251. #ifdef PALETTE_MAIN
  252. #include "iio.h"
  253. #include "xmalloc.c"
  254. #include "pickopt.c"
  255. #define OMIT_MAIN_FONTU
  256. #include "fontu.c"
  257. #include "fonts/xfonts_all.c"
  258. SMART_PARAMETER_SILENT(PLEGEND_WIDTH,64)
  259. SMART_PARAMETER_SILENT(PLEGEND_HEIGHT,256)
  260. SMART_PARAMETER_SILENT(PLEGEND_MARGIN_LEFT,12)
  261. SMART_PARAMETER_SILENT(PLEGEND_MARGIN_RIGHT,36)
  262. SMART_PARAMETER_SILENT(PLEGEND_MARGIN_TOP,12)
  263. SMART_PARAMETER_SILENT(PLEGEND_MARGIN_BOTTOM,12)
  264. SMART_PARAMETER_SILENT(PLEGEND_TICKWIDTH,3)
  265. SMART_PARAMETER_SILENT(PLEGEND_TEXT_XOFFSET,4)
  266. SMART_PARAMETER_SILENT(PLEGEND_TEXT_YOFFSET,0)
  267. SMART_PARAMETER_SILENT(PLEGEND_NTICKS,3)
  268. void save_legend(char *filename_legend, char *palette_id, float m, float M)
  269. {
  270. // palette and font structs
  271. struct bitmap_font f[1] = {reformat_font(*xfont_7x14B, UNPACKED)};
  272. struct palette p[1]; fill_palette(p, palette_id, m, M);
  273. // sizes, margins and positions
  274. int w = PLEGEND_WIDTH();
  275. int h = PLEGEND_HEIGHT();
  276. int m_l = PLEGEND_MARGIN_LEFT();
  277. int m_r = PLEGEND_MARGIN_RIGHT();
  278. int m_t = PLEGEND_MARGIN_TOP();
  279. int m_b = PLEGEND_MARGIN_BOTTOM();
  280. int p_i = m_l; // left boundary of colored part;
  281. int p_j = m_t; // top boundary of colored part
  282. int q_i = w - m_r; // right boundary of colored part
  283. int q_j = h - m_b; // bottom boundary of colored part
  284. // image with the legend
  285. uint8_t *rgb = malloc(3*w*h);
  286. // fill background
  287. for (int i = 0; i < 3*w*h; i++)
  288. rgb[i] = 255;
  289. // transformation "x -> alpha * j + beta" from positions to values
  290. float alpha = (M - m) / (p_j - q_j);
  291. float beta = m - alpha * q_j;
  292. // fill legend colors
  293. for (int j = p_j; j < q_j; j++)
  294. for (int i = p_i; i < q_i; i++)
  295. {
  296. //float x = m + ((M - m) * (j - p_j)) / (q_j - p_j);
  297. float x = alpha * j + beta;
  298. if (i == 64) fprintf(stderr, "j=%d x=%g\n", j, x);
  299. get_palette_color(rgb + 3*(j*w+i), p, x);
  300. }
  301. // border (1-pix black border)
  302. for (int l = 0; l < 3; l++) {
  303. for (int j = p_j; j < q_j; j++) {
  304. rgb[3*(j*w+p_i-1)+l] = 0;
  305. rgb[3*(j*w+q_i+0)+l] = 0;
  306. }
  307. for (int i = p_i-1; i < q_i; i++) {
  308. rgb[3*((p_j-1)*w+i)+l] = 0;
  309. rgb[3*((q_j+0)*w+i)+l] = 0;
  310. }
  311. }
  312. // ticks and numbers
  313. float ticks[][2] = { // table with tick values and positions
  314. {M, p_j-1},
  315. {m, q_j},
  316. {(m+M)/2, (p_j+q_j)/2},
  317. {(3*m+M)/4, (3*p_j+q_j)/4},
  318. {(m+3*M)/4, (p_j+3*q_j)/4}
  319. // TODO: put more ticks (?)
  320. };
  321. int nticks = PLEGEND_NTICKS();
  322. if (nticks != 2 && nticks != 3 && nticks != 5)
  323. nticks = 2;
  324. for (int k = 0; k < nticks; k++)
  325. {
  326. float x = ticks[k][0]; // tick value
  327. int j = ticks[k][1]; // tick position inside the legend
  328. // draw tick
  329. for (int i = q_i; i < q_i+1+PLEGEND_TICKWIDTH(); i++)
  330. for (int l = 0; l < 3; l++)
  331. rgb[3*(j*w+i)+l] = 0;
  332. // draw number associated to this tick
  333. char buf[0x100];
  334. uint8_t bg[3] = { 255, 255, 255}, fg[3] = {0, 0, 0};
  335. snprintf(buf, sizeof buf, "%g", x);
  336. int pos_i = q_i + PLEGEND_TICKWIDTH() + PLEGEND_TEXT_XOFFSET();
  337. int pos_j = j - f->height/2 + PLEGEND_TEXT_YOFFSET();
  338. put_string_in_rgb_image(rgb,w,h, pos_i, pos_j, fg,bg,0,f,buf);
  339. }
  340. // save legend into file
  341. iio_write_image_uint8_vec(filename_legend, rgb, w, h, 3);
  342. free(rgb);
  343. }
  344. int main_palette(int c, char *v[])
  345. {
  346. char *filename_legend = pick_option(&c, &v, "l", "");
  347. if (c != 4 && c != 5 && c != 6 ) {
  348. fprintf(stderr, "usage:\n\t%s from to pal [in [out]]\n", *v);
  349. // 0 1 2 3 4 5
  350. return 1;
  351. }
  352. float from = atof(v[1]);
  353. float to = atof(v[2]);
  354. char *palette_id = v[3];
  355. char *filename_in = c > 4 ? v[4] : "-";
  356. char *filename_out = c > 5 ? v[5] : "-";
  357. int w, h;
  358. float *x = iio_read_image_float(filename_in, &w, &h);
  359. uint8_t *y = xmalloc(3*w*h);
  360. apply_palette(y, x, w*h, palette_id, &from, &to);
  361. iio_write_image_uint8_vec(filename_out, y, w, h, 3);
  362. if (*filename_legend)
  363. save_legend(filename_legend, palette_id, from, to);
  364. free(x);
  365. free(y);
  366. return 0;
  367. }
  368. #ifndef HIDE_ALL_MAINS
  369. int main(int c, char **v) { return main_palette(c, v); }
  370. #endif
  371. #endif//PALETTE_MAIN