PageRenderTime 90ms CodeModel.GetById 27ms app.highlight 40ms RepoModel.GetById 1ms app.codeStats 1ms

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