1 /*
2  * This file is part of Siril, an astronomy image processor.
3  * Copyright (C) 2005-2011 Francois Meyer (dulle at free.fr)
4  * Copyright (C) 2012-2021 team free-astro (see more in AUTHORS file)
5  * Reference site is https://free-astro.org/index.php/Siril
6  *
7  * Siril is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * Siril is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with Siril. If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include <stdlib.h>
22 #include <stdio.h>
23 #include <math.h>
24 #include <assert.h>
25 #include <float.h>
26 
27 #include "core/siril.h"
28 #include "core/proto.h"
29 #include "core/processing.h"
30 #include "core/undo.h"
31 #include "core/OS_utils.h"
32 #include "gui/progress_and_log.h"
33 #include "gui/utils.h"
34 #include "gui/image_display.h"
35 #include "gui/image_interactions.h"
36 #include "gui/message_dialog.h"
37 #include "gui/histogram.h"
38 #include "gui/dialogs.h"
39 #include "io/single_image.h"
40 #include "io/image_format_fits.h"
41 #include "algos/colors.h"
42 #include "algos/statistics.h"
43 
44 /*
45  * A Fast HSL-to-RGB Transform
46  * by Ken Fishkin
47  * from "Graphics Gems", Academic Press, 1990
48  * */
49 /*
50  *  * given h,s,l on [0..1],
51  *   * return r,g,b on [0..1]
52  *    */
hsl_to_rgb_float_sat(float h,float sl,float l,float * r,float * g,float * b)53 void hsl_to_rgb_float_sat(float h, float sl, float l, float * r, float * g,
54 		float * b) {
55 	float v;
56 
57 	h = h >= 6.f ? h - 6.f : h;
58 
59 	v = (l <= 0.5f) ? (l * (1.f + sl)) : (l + sl - l * sl);
60 	if (v <= 0.f) {
61 		*r = *g = *b = 0.f;
62 	} else {
63 		float m;
64 		float sv;
65 		int sextant;
66 		float fract, vsf, mid1, mid2;
67 
68 		m = l + l - v;
69 		sv = (v - m) / v;
70 		sextant = h;
71 		fract = h - sextant;
72 		vsf = v * sv * fract;
73 		mid1 = m + vsf;
74 		mid2 = v - vsf;
75 		switch (sextant) {
76 			case 0:
77 				*r = v;
78 				*g = mid1;
79 				*b = m;
80 				break;
81 			case 1:
82 				*r = mid2;
83 				*g = v;
84 				*b = m;
85 				break;
86 			case 2:
87 				*r = m;
88 				*g = v;
89 				*b = mid1;
90 				break;
91 			case 3:
92 				*r = m;
93 				*g = mid2;
94 				*b = v;
95 				break;
96 			case 4:
97 				*r = mid1;
98 				*g = m;
99 				*b = v;
100 				break;
101 			case 5:
102 				*r = v;
103 				*g = m;
104 				*b = mid2;
105 				break;
106 		}
107 	}
108 }
109 /*
110  *  * RGB-HSL transforms.
111  *   * Ken Fishkin, Pixar Inc., January 1989.
112  *    */
113 
114 /*
115  *  * given r,g,b on [0 ... 1],
116  *   * return (h,s,l) on [0 ... 1]
117  *    */
rgb_to_hsl_float_sat(float r,float g,float b,float low,float * h,float * s,float * l)118 void rgb_to_hsl_float_sat(float r, float g, float b, float low, float *h, float *s, float *l) {
119 	float v;
120 	float m;
121 	float vm;
122 	float r2, g2, b2;
123 
124 	v = max(r, g);
125 	v = max(v, b);
126 	m = min(r, g);
127 	m = min(m, b);
128 
129 	if (m + v < low + low) {
130 		*l = 0.f;
131 		return;
132 	}
133 	*l = (m + v) / 2.f;
134 	*h = 0.f;
135 	*s = 0.f;	// init values
136 
137 	if ((*s = vm = v - m) > 0.f) {
138 		*s /= (*l <= 0.5f) ? (v + m) : (2.f - v - m);
139 	} else
140 		return;
141 
142 	if (r == v) {
143 		g2 = (v - g) / vm;
144 		b2 = (v - b) / vm;
145 		*h = (g == m ? 5.f + b2 : 1.f - g2);
146 	}else if (g == v) {
147 		r2 = (v - r) / vm;
148 		b2 = (v - b) / vm;
149 		*h = (b == m ? 1.f + r2 : 3.f - b2);
150 	} else {
151 		r2 = (v - r) / vm;
152 		g2 = (v - g) / vm;
153 		*h = (r == m ? 3.f + g2 : 5.f - r2);
154 	}
155 
156 }
157 
158 /*
159  * A Fast HSL-to-RGB Transform
160  * by Ken Fishkin
161  * from "Graphics Gems", Academic Press, 1990
162  * */
163 /*
164  *  * given h,s,l on [0..1],
165  *   * return r,g,b on [0..1]
166  *    */
hsl_to_rgb(double h,double sl,double l,double * r,double * g,double * b)167 void hsl_to_rgb(double h, double sl, double l, double * r, double * g,
168 		double * b) {
169 	double v;
170 
171 	assert(h >= 0.0 && h <= 1.0);
172 	if (h >= 1.0) h -= 1.0;		// this code doesn't work for h = 1
173 	v = (l <= 0.5) ? (l * (1.0 + sl)) : (l + sl - l * sl);
174 	if (v <= 0) {
175 		*r = *g = *b = 0.0;
176 	} else {
177 		double m;
178 		double sv;
179 		int sextant;
180 		double fract, vsf, mid1, mid2;
181 
182 		m = l + l - v;
183 		sv = (v - m) / v;
184 		h *= 6.0;
185 		sextant = h;
186 		fract = h - sextant;
187 		vsf = v * sv * fract;
188 		mid1 = m + vsf;
189 		mid2 = v - vsf;
190 		switch (sextant) {
191 			case 0:
192 				*r = v;
193 				*g = mid1;
194 				*b = m;
195 				break;
196 			case 1:
197 				*r = mid2;
198 				*g = v;
199 				*b = m;
200 				break;
201 			case 2:
202 				*r = m;
203 				*g = v;
204 				*b = mid1;
205 				break;
206 			case 3:
207 				*r = m;
208 				*g = mid2;
209 				*b = v;
210 				break;
211 			case 4:
212 				*r = mid1;
213 				*g = m;
214 				*b = v;
215 				break;
216 			case 5:
217 				*r = v;
218 				*g = m;
219 				*b = mid2;
220 				break;
221 		}
222 	}
223 }
224 
225 /* RGB-HSL transforms.
226  * Ken Fishkin, Pixar Inc., January 1989.
227  *
228  * given r,g,b on [0 ... 1],
229  * return (h,s,l) on [0 ... 1]
230  */
rgb_to_hsl(double r,double g,double b,double * h,double * s,double * l)231 void rgb_to_hsl(double r, double g, double b, double *h, double *s, double *l) {
232 	double v;
233 	double m;
234 	double vm;
235 	double r2, g2, b2;
236 
237 	v = max(r, g);
238 	v = max(v, b);
239 	m = min(r, g);
240 	m = min(m, b);
241 	*h = 0.0;
242 	*s = 0.0;	// init values
243 
244 	if ((*l = (m + v) / 2.0) <= 0.0) {
245 		*l = 0.0;
246 		return;
247 	}
248 	if ((*s = vm = v - m) > 0.0) {
249 		*s /= (*l <= 0.5) ? (v + m) : (2.0 - v - m);
250 	} else
251 		return;
252 
253 	r2 = (v - r) / vm;
254 	g2 = (v - g) / vm;
255 	b2 = (v - b) / vm;
256 
257 	if (r == v)
258 		*h = (g == m ? 5.0 + b2 : 1.0 - g2);
259 	else if (g == v)
260 		*h = (b == m ? 1.0 + r2 : 3.0 - b2);
261 	else
262 		*h = (r == m ? 3.0 + g2 : 5.0 - r2);
263 
264 	*h /= 6;
265 }
266 
267 /* all variables are between 0 and 1. h takes 0 for grey */
rgb_to_hsv(double r,double g,double b,double * h,double * s,double * v)268 void rgb_to_hsv(double r, double g, double b, double *h, double *s, double *v) {
269 	double cmax, cmin, delta;
270 
271 	cmax = max(r, g);
272 	cmax = max(cmax, b);
273 	cmin = min(r, g);
274 	cmin = min(cmin, b);
275 	delta = cmax - cmin;
276 	*v = cmax;
277 	if (delta == 0.0) {
278 		*s = 0.0;
279 		*h = 0.0;
280 		return;
281 	}
282 	*s = delta / cmax;
283 
284 	if (cmax == r)
285 		*h = (((g - b) / delta)) / 6.0;
286 	else if (cmax == g)
287 		*h = (((b - r) / delta) + 2.0) / 6.0;
288 	else
289 		*h = (((r - g) / delta) + 4.0) / 6.0;
290 
291 	if (*h < 0.0)
292 		*h += 1.0;
293 }
294 
hsv_to_rgb(double h,double s,double v,double * r,double * g,double * b)295 void hsv_to_rgb(double h, double s, double v, double *r, double *g, double *b) {
296 	double p, q, t, f;
297 	int i;
298 
299 	if (h >= 1.0)
300 		h -= 1.0;
301 	h *= 6.0;
302 	i = (int)h;
303 	f = h - (double)i;
304 	p = v * (1.0 - s);
305 	q = v * (1.0 - (s * f));
306 	t = v * (1.0 - (s * (1.0 - f)));
307 
308 	switch (i) {
309 		case 0:
310 			*r = v;
311 			*g = t;
312 			*b = p;
313 			break;
314 		case 1:
315 			*r = q;
316 			*g = v;
317 			*b = p;
318 			break;
319 		case 2:
320 			*r = p;
321 			*g = v;
322 			*b = t;
323 			break;
324 		case 3:
325 			*r = p;
326 			*g = q;
327 			*b = v;
328 			break;
329 		case 4:
330 			*r = t;
331 			*g = p;
332 			*b = v;
333 			break;
334 		case 5:
335 		default:
336 			*r = v;
337 			*g = p;
338 			*b = q;
339 			break;
340 	}
341 }
342 
rgb_to_xyz(double r,double g,double b,double * x,double * y,double * z)343 void rgb_to_xyz(double r, double g, double b, double *x, double *y, double *z) {
344 	r = (r <= 0.04045) ? r / 12.92 : pow(((r + 0.055) / 1.055), 2.4);
345 	g = (g <= 0.04045) ? g / 12.92 : pow(((g + 0.055) / 1.055), 2.4);
346 	b = (b <= 0.04045) ? b / 12.92 : pow(((b + 0.055) / 1.055), 2.4);
347 
348 	r *= 100;
349 	g *= 100;
350 	b *= 100;
351 
352 	*x = 0.412453 * r + 0.357580 * g + 0.180423 * b;
353 	*y = 0.212671 * r + 0.715160 * g + 0.072169 * b;
354 	*z = 0.019334 * r + 0.119193 * g + 0.950227 * b;
355 }
356 
xyz_to_LAB(double x,double y,double z,double * L,double * a,double * b)357 void xyz_to_LAB(double x, double y, double z, double *L, double *a, double *b) {
358 	x /= 95.047;
359 	y /= 100.000;
360 	z /= 108.883;
361 
362 	x = (x > 0.008856452) ? pow(x, 1 / 3.0) : (7.787037037 * x) + (16. / 116.);
363 	y = (y > 0.008856452) ? pow(y, 1 / 3.0) : (7.787037037 * y) + (16. / 116.);
364 	z = (z > 0.008856452) ? pow(z, 1 / 3.0) : (7.787037037 * z) + (16. / 116.);
365 
366 	*L = (116.0 * y) - 16.0;
367 	*a = 500.0 * (x - y);
368 	*b = 200.0 * (y - z);
369 }
370 
LAB_to_xyz(double L,double a,double b,double * x,double * y,double * z)371 void LAB_to_xyz(double L, double a, double b, double *x, double *y, double *z) {
372 	*y = (L + 16.0) / 116.0;
373 	*x = a / 500.0 + (*y);
374 	*z = *y - b / 200.0;
375 
376 	double x3, y3, z3;
377 	x3 = (*x) * (*x) * (*x);
378 	y3 = (*y) * (*y) * (*y);
379 	z3 = (*z) * (*z) * (*z);
380 
381 	*x = (x3 > 0.008856452) ? x3 : (*x - 16. / 116.) / 7.787037037;
382 	*y = (y3 > 0.008856452) ? y3 : (*y - 16. / 116.) / 7.787037037;
383 	*z = (z3 > 0.008856452) ? z3 : (*z - 16. / 116.) / 7.787037037;
384 
385 	*x *= 95.047;
386 	*y *= 100.000;
387 	*z *= 108.883;
388 }
389 
xyz_to_rgb(double x,double y,double z,double * r,double * g,double * b)390 void xyz_to_rgb(double x, double y, double z, double *r, double *g, double *b) {
391 	x /= 100.0;
392 	y /= 100.0;
393 	z /= 100.0;
394 
395 	*r =  3.240479 * x - 1.537150 * y - 0.498535 * z;
396 	*g = -0.969256 * x + 1.875992 * y + 0.041556 * z;
397 	*b =  0.055648 * x - 0.204043 * y + 1.057311 * z;
398 
399 	*r = (*r > 0.0031308) ? 1.055 * (pow(*r, (1 / 2.4))) - 0.055 : 12.92 * (*r);
400 	*g = (*g > 0.0031308) ? 1.055 * (pow(*g, (1 / 2.4))) - 0.055 : 12.92 * (*g);
401 	*b = (*b > 0.0031308) ? 1.055 * (pow(*b, (1 / 2.4))) - 0.055 : 12.92 * (*b);
402 }
403 
404 // color index to temperature in kelvin
BV_to_T(double BV)405 double BV_to_T(double BV) {
406 	double T;
407 
408 	// make sure BV is within its bounds [-0.4, 2] otherwise the math doesnt work
409 	if (BV < -0.4) {
410 		BV = -0.4;
411 	} else if (BV > 2) {
412 		BV = 2;
413 	}
414 
415 	// http://www.wikiwand.com/en/Color_index
416 	T = 4600 * ((1 / ((0.92 * BV) + 1.7)) + (1 / ((0.92 * BV) + 0.62)));
417 
418 	return T;
419 }
420 
equalize_cfa_fit_with_coeffs(fits * fit,float coeff1,float coeff2,int config)421 int equalize_cfa_fit_with_coeffs(fits *fit, float coeff1, float coeff2, int config) {
422 	unsigned int row, col;
423 	float tmp1, tmp2;
424 	if (fit->type == DATA_USHORT) {
425 		WORD *data = fit->data;
426 		for (row = 0; row < fit->ry - 1; row += 2) {
427 			for (col = 0; col < fit->rx - 1; col += 2) {
428 				if (config == 0) {
429 					tmp1 = (float)data[1 + col + row * fit->rx] / coeff1;
430 					data[1 + col + row * fit->rx] = round_to_WORD(tmp1);
431 
432 					tmp2 = (float)data[col + (1 + row) * fit->rx] / coeff2;
433 					data[col + (1 + row) * fit->rx] = round_to_WORD(tmp2);
434 
435 				} else {
436 					tmp1 = (float)data[col + row * fit->rx] / coeff1;
437 					data[col + row * fit->rx] = round_to_WORD(tmp1);
438 
439 					tmp2 = (float)data[1 + col + (1 + row) * fit->rx] / coeff2;
440 					data[1 + col + (1 + row) * fit->rx] = round_to_WORD(tmp2);
441 
442 				}
443 			}
444 		}
445 	}
446 	else if (fit->type == DATA_FLOAT) {
447 		float *data = fit->fdata;
448 		for (row = 0; row < fit->ry - 1; row += 2) {
449 			for (col = 0; col < fit->rx - 1; col += 2) {
450 				if (config == 0) {
451 					tmp1 = data[1 + col + row * fit->rx] / coeff1;
452 					data[1 + col + row * fit->rx] = tmp1;
453 
454 					tmp2 = data[col + (1 + row) * fit->rx] / coeff2;
455 					data[col + (1 + row) * fit->rx] = tmp2;
456 
457 				} else {
458 					tmp1 = data[col + row * fit->rx] / coeff1;
459 					data[col + row * fit->rx] = tmp1;
460 
461 					tmp2 = data[1 + col + (1 + row) * fit->rx] / coeff2;
462 					data[1 + col + (1 + row) * fit->rx] = tmp2;
463 
464 				}
465 			}
466 		}
467 	}
468 	else return 1;
469 	return 0;
470 }
471 
472 // idle function executed at the end of the extract_channels processing
end_extract_channels(gpointer p)473 static gboolean end_extract_channels(gpointer p) {
474 	struct extract_channels_data *args = (struct extract_channels_data *) p;
475 	stop_processing_thread();
476 	free(args->channel[0]);
477 	free(args->channel[1]);
478 	free(args->channel[2]);
479 	free(args);
480 	set_cursor_waiting(FALSE);
481 
482 	return FALSE;
483 }
484 
extract_channels_ushort(gpointer p)485 static gpointer extract_channels_ushort(gpointer p) {
486 	struct extract_channels_data *args = (struct extract_channels_data *) p;
487 	WORD *buf[3] = { args->fit->pdata[RLAYER], args->fit->pdata[GLAYER],
488 		args->fit->pdata[BLAYER] };
489 	size_t n = args->fit->naxes[0] * args->fit->naxes[1];
490 	struct timeval t_start, t_end;
491 	args->process = TRUE;
492 
493 	if (args->fit->naxes[2] != 3) {
494 		siril_log_message(
495 				_("Siril cannot extract layers. Make sure your image is in RGB mode.\n"));
496 		args->process = FALSE;
497 		clearfits(args->fit);
498 		siril_add_idle(end_extract_channels, args);
499 		return GINT_TO_POINTER(1);
500 	}
501 
502 	siril_log_color_message(_("%s channel extraction: processing...\n"), "green",
503 			args->str_type);
504 	gettimeofday(&t_start, NULL);
505 
506 	switch (args->type) {
507 		/* RGB space: nothing to do */
508 		case 0:
509 			break;
510 			/* HSL space */
511 		case 1:
512 #ifdef _OPENMP
513 #pragma omp parallel for num_threads(com.max_thread) schedule(static)
514 #endif
515 		for (size_t i = 0; i < n; i++) {
516 			double h, s, l;
517 			double r = (double) buf[RLAYER][i] / USHRT_MAX_DOUBLE;
518 			double g = (double) buf[GLAYER][i] / USHRT_MAX_DOUBLE;
519 			double b = (double) buf[BLAYER][i] / USHRT_MAX_DOUBLE;
520 			rgb_to_hsl(r, g, b, &h, &s, &l);
521 			buf[RLAYER][i] = round_to_WORD(h * 360.0);	// TODO: what's that?
522 			buf[GLAYER][i] = round_to_WORD(s * USHRT_MAX_DOUBLE);
523 			buf[BLAYER][i] = round_to_WORD(l * USHRT_MAX_DOUBLE);
524 		}
525 		break;
526 		/* HSV space */
527 	case 2:
528 #ifdef _OPENMP
529 #pragma omp parallel for num_threads(com.max_thread) schedule(static)
530 #endif
531 		for (size_t i = 0; i < n; i++) {
532 			double h, s, v;
533 			double r = (double) buf[RLAYER][i] / USHRT_MAX_DOUBLE;
534 			double g = (double) buf[GLAYER][i] / USHRT_MAX_DOUBLE;
535 			double b = (double) buf[BLAYER][i] / USHRT_MAX_DOUBLE;
536 			rgb_to_hsv(r, g, b, &h, &s, &v);
537 			buf[RLAYER][i] = round_to_WORD(h * 360.0);
538 			buf[GLAYER][i] = round_to_WORD(s * USHRT_MAX_DOUBLE);
539 			buf[BLAYER][i] = round_to_WORD(v * USHRT_MAX_DOUBLE);
540 		}
541 		break;
542 		/* CIE L*a*b */
543 	case 3:
544 #ifdef _OPENMP
545 #pragma omp parallel for num_threads(com.max_thread) schedule(static)
546 #endif
547 		for (size_t i = 0; i < n; i++) {
548 			double x, y, z, L, a, b;
549 			double red = (double) buf[RLAYER][i] / USHRT_MAX_DOUBLE;
550 			double green = (double) buf[GLAYER][i] / USHRT_MAX_DOUBLE;
551 			double blue = (double) buf[BLAYER][i] / USHRT_MAX_DOUBLE;
552 			rgb_to_xyz(red, green, blue, &x, &y, &z);
553 			xyz_to_LAB(x, y, z, &L, &a, &b);
554 			buf[RLAYER][i] = round_to_WORD(L / 100. * USHRT_MAX_DOUBLE);// 0 < L < 100
555 			buf[GLAYER][i] = round_to_WORD(
556 					((a + 128) / 255.) * USHRT_MAX_DOUBLE);	// -128 < a < 127
557 			buf[BLAYER][i] = round_to_WORD(
558 					((b + 128) / 255.) * USHRT_MAX_DOUBLE);	// -128 < b < 127
559 		}
560 
561 	}
562 	for (int i = 0; i < 3; i++)
563 		save1fits16(args->channel[i], args->fit, i);
564 	clearfits(args->fit);
565 	gettimeofday(&t_end, NULL);
566 	show_time(t_start, t_end);
567 	siril_add_idle(end_extract_channels, args);
568 
569 	return GINT_TO_POINTER(0);
570 }
571 
extract_channels_float(gpointer p)572 static gpointer extract_channels_float(gpointer p) {
573 	struct extract_channels_data *args = (struct extract_channels_data *) p;
574 	float *buf[3] = { args->fit->fpdata[RLAYER], args->fit->fpdata[GLAYER],
575 		args->fit->fpdata[BLAYER] };
576 	struct timeval t_start, t_end;
577 	size_t n = args->fit->naxes[0] * args->fit->naxes[1];
578 	args->process = TRUE;
579 
580 	if (args->fit->naxes[2] != 3) {
581 		siril_log_message(
582 				_("Siril cannot extract layers. Make sure your image is in RGB mode.\n"));
583 		args->process = FALSE;
584 		clearfits(args->fit);
585 		siril_add_idle(end_extract_channels, args);
586 		return GINT_TO_POINTER(1);
587 	}
588 
589 	siril_log_color_message(_("%s channel extraction: processing...\n"), "green",
590 			args->str_type);
591 	gettimeofday(&t_start, NULL);
592 
593 	switch (args->type) {
594 	/* RGB space: nothing to do */
595 	case 0:
596 		break;
597 		/* HSL space */
598 	case 1:
599 #ifdef _OPENMP
600 #pragma omp parallel for num_threads(com.max_thread) schedule(static)
601 #endif
602 		for (size_t i = 0; i < n; i++) {
603 			double h, s, l;
604 			double r = (double) buf[RLAYER][i];
605 			double g = (double) buf[GLAYER][i];
606 			double b = (double) buf[BLAYER][i];
607 			rgb_to_hsl(r, g, b, &h, &s, &l);
608 			buf[RLAYER][i] = (float) h;
609 			buf[GLAYER][i] = (float) s;
610 			buf[BLAYER][i] = (float) l;
611 		}
612 		break;
613 		/* HSV space */
614 	case 2:
615 #ifdef _OPENMP
616 #pragma omp parallel for num_threads(com.max_thread) schedule(static)
617 #endif
618 		for (size_t i = 0; i < n; i++) {
619 			double h, s, v;
620 			double r = (double) buf[RLAYER][i];
621 			double g = (double) buf[GLAYER][i];
622 			double b = (double) buf[BLAYER][i];
623 			rgb_to_hsv(r, g, b, &h, &s, &v);
624 			buf[RLAYER][i] = (float) h;
625 			buf[GLAYER][i] = (float) s;
626 			buf[BLAYER][i] = (float) v;
627 		}
628 		break;
629 		/* CIE L*a*b */
630 	case 3:
631 #ifdef _OPENMP
632 #pragma omp parallel for num_threads(com.max_thread) schedule(static)
633 #endif
634 		for (size_t i = 0; i < n; i++) {
635 			double x, y, z, L, a, b;
636 			double red = (double) buf[RLAYER][i];
637 			double green = (double) buf[GLAYER][i];
638 			double blue = (double) buf[BLAYER][i];
639 			rgb_to_xyz(red, green, blue, &x, &y, &z);
640 			xyz_to_LAB(x, y, z, &L, &a, &b);
641 			buf[RLAYER][i] = (float) (L / 100.);		// 0 < L < 100
642 			buf[GLAYER][i] = (float) ((a + 128.) / 255.);	// -128 < a < 127
643 			buf[BLAYER][i] = (float) ((b + 128.) / 255.);	// -128 < b < 127
644 		}
645 
646 	}
647 	for (int i = 0; i < 3; i++)
648 		save1fits32(args->channel[i], args->fit, i);
649 	clearfits(args->fit);
650 	gettimeofday(&t_end, NULL);
651 	show_time(t_start, t_end);
652 	siril_add_idle(end_extract_channels, args);
653 
654 	return GINT_TO_POINTER(0);
655 }
656 
extract_channels(gpointer p)657 gpointer extract_channels(gpointer p) {
658 	struct extract_channels_data *args = (struct extract_channels_data *)p;
659 	if (args->fit->type == DATA_USHORT)
660 		return extract_channels_ushort(p);
661 	if (args->fit->type == DATA_FLOAT)
662 		return extract_channels_float(p);
663 	siril_add_idle(end_extract_channels, args);
664 	return GINT_TO_POINTER(1);
665 }
666 
667 /****************** Color calibration ************************/
on_button_bkg_selection_clicked(GtkButton * button,gpointer user_data)668 void on_button_bkg_selection_clicked(GtkButton *button, gpointer user_data) {
669 	static GtkSpinButton *selection_black_value[4] = { NULL, NULL, NULL, NULL };
670 
671 	if ((!com.selection.h) || (!com.selection.w)) {
672 		siril_message_dialog(GTK_MESSAGE_WARNING, _("There is no selection"),
673 				_("Make a selection of the background area"));
674 		return;
675 	}
676 
677 	if (!selection_black_value[0]) {
678 		selection_black_value[0] = GTK_SPIN_BUTTON(
679 				gtk_builder_get_object(builder, "spin_bkg_x"));
680 		selection_black_value[1] = GTK_SPIN_BUTTON(
681 				gtk_builder_get_object(builder, "spin_bkg_y"));
682 		selection_black_value[2] = GTK_SPIN_BUTTON(
683 				gtk_builder_get_object(builder, "spin_bkg_w"));
684 		selection_black_value[3] = GTK_SPIN_BUTTON(
685 				gtk_builder_get_object(builder, "spin_bkg_h"));
686 	}
687 
688 	gtk_spin_button_set_value(selection_black_value[0], com.selection.x);
689 	gtk_spin_button_set_value(selection_black_value[1], com.selection.y);
690 	gtk_spin_button_set_value(selection_black_value[2], com.selection.w);
691 	gtk_spin_button_set_value(selection_black_value[3], com.selection.h);
692 }
693 
initialize_calibration_interface()694 void initialize_calibration_interface() {
695 	static GtkAdjustment *selection_black_adjustment[4] = { NULL, NULL, NULL,
696 		NULL };
697 	static GtkAdjustment *selection_white_adjustment[4] = { NULL, NULL, NULL,
698 		NULL };
699 
700 	if (!selection_black_adjustment[0]) {
701 		selection_black_adjustment[0] = GTK_ADJUSTMENT(
702 				gtk_builder_get_object(builder, "adjustment_bkg_x"));
703 		selection_black_adjustment[1] = GTK_ADJUSTMENT(
704 				gtk_builder_get_object(builder, "adjustment_bkg_y"));
705 		selection_black_adjustment[2] = GTK_ADJUSTMENT(
706 				gtk_builder_get_object(builder, "adjustment_bkg_w"));
707 		selection_black_adjustment[3] = GTK_ADJUSTMENT(
708 				gtk_builder_get_object(builder, "adjustment_bkg_h"));
709 	}
710 	if (!selection_white_adjustment[0]) {
711 		selection_white_adjustment[0] = GTK_ADJUSTMENT(
712 				gtk_builder_get_object(builder, "adjustment_white_x"));
713 		selection_white_adjustment[1] = GTK_ADJUSTMENT(
714 				gtk_builder_get_object(builder, "adjustment_white_y"));
715 		selection_white_adjustment[2] = GTK_ADJUSTMENT(
716 				gtk_builder_get_object(builder, "adjustment_white_w"));
717 		selection_white_adjustment[3] = GTK_ADJUSTMENT(
718 				gtk_builder_get_object(builder, "adjustment_white_h"));
719 	}
720 	gtk_adjustment_set_upper(selection_black_adjustment[0], gfit.rx);
721 	gtk_adjustment_set_upper(selection_black_adjustment[1], gfit.ry);
722 	gtk_adjustment_set_upper(selection_black_adjustment[2], gfit.rx);
723 	gtk_adjustment_set_upper(selection_black_adjustment[3], gfit.ry);
724 	gtk_adjustment_set_value(selection_black_adjustment[0], 0);
725 	gtk_adjustment_set_value(selection_black_adjustment[1], 0);
726 	gtk_adjustment_set_value(selection_black_adjustment[2], 0);
727 	gtk_adjustment_set_value(selection_black_adjustment[3], 0);
728 
729 	gtk_adjustment_set_upper(selection_white_adjustment[0], gfit.rx);
730 	gtk_adjustment_set_upper(selection_white_adjustment[1], gfit.ry);
731 	gtk_adjustment_set_upper(selection_white_adjustment[2], gfit.rx);
732 	gtk_adjustment_set_upper(selection_white_adjustment[3], gfit.ry);
733 	gtk_adjustment_set_value(selection_white_adjustment[0], 0);
734 	gtk_adjustment_set_value(selection_white_adjustment[1], 0);
735 	gtk_adjustment_set_value(selection_white_adjustment[2], 0);
736 	gtk_adjustment_set_value(selection_white_adjustment[3], 0);
737 }
738 
739 /* This function equalize the background by giving equal value for all layers */
background_neutralize(fits * fit,rectangle black_selection)740 static void background_neutralize(fits* fit, rectangle black_selection) {
741 	int chan;
742 	size_t i, n = fit->naxes[0] * fit->naxes[1];
743 	imstats* stats[3];
744 	double ref = 0;
745 
746 	assert(fit->naxes[2] == 3);
747 
748 	for (chan = 0; chan < 3; chan++) {
749 		stats[chan] = statistics(NULL, -1, fit, chan, &black_selection, STATS_BASIC, TRUE);
750 		if (!stats[chan]) {
751 			siril_log_message(_("Error: statistics computation failed.\n"));
752 			return;
753 		}
754 		ref += stats[chan]->median;
755 	}
756 	ref /= 3.0;
757 
758 	if (fit->type == DATA_USHORT) {
759 		for (chan = 0; chan < 3; chan++) {
760 			double offset = stats[chan]->mean - ref;
761 			WORD *buf = fit->pdata[chan];
762 			for (i = 0; i < n; i++) {
763 				buf[i] = round_to_WORD((double)buf[i] - offset);
764 			}
765 			free_stats(stats[chan]);
766 		}
767 	}
768 	else if (fit->type == DATA_FLOAT) {
769 		for (chan = 0; chan < 3; chan++) {
770 			float offset = stats[chan]->mean - ref;
771 			float *buf = fit->fpdata[chan];
772 			for (i = 0; i < n; i++) {
773 				buf[i] = buf[i] - offset;
774 			}
775 			free_stats(stats[chan]);
776 		}
777 	}
778 
779 	invalidate_stats_from_fit(fit);
780 }
781 
on_button_bkg_neutralization_clicked(GtkButton * button,gpointer user_data)782 void on_button_bkg_neutralization_clicked(GtkButton *button, gpointer user_data) {
783 	static GtkSpinButton *selection_black_value[4] = { NULL, NULL, NULL, NULL };
784 	rectangle black_selection;
785 	int width, height;
786 
787 	if (!selection_black_value[0]) {
788 		selection_black_value[0] = GTK_SPIN_BUTTON(
789 				gtk_builder_get_object(builder, "spin_bkg_x"));
790 		selection_black_value[1] = GTK_SPIN_BUTTON(
791 				gtk_builder_get_object(builder, "spin_bkg_y"));
792 		selection_black_value[2] = GTK_SPIN_BUTTON(
793 				gtk_builder_get_object(builder, "spin_bkg_w"));
794 		selection_black_value[3] = GTK_SPIN_BUTTON(
795 				gtk_builder_get_object(builder, "spin_bkg_h"));
796 	}
797 	width = (int) gtk_spin_button_get_value(selection_black_value[2]);
798 	height = (int) gtk_spin_button_get_value(selection_black_value[3]);
799 
800 	if ((!width) || (!height)) {
801 		siril_message_dialog( GTK_MESSAGE_WARNING, _("There is no selection"),
802 				_("Make a selection of the background area"));
803 		return;
804 	}
805 	black_selection.x = gtk_spin_button_get_value(selection_black_value[0]);
806 	black_selection.y = gtk_spin_button_get_value(selection_black_value[1]);
807 	black_selection.w = gtk_spin_button_get_value(selection_black_value[2]);
808 	black_selection.h = gtk_spin_button_get_value(selection_black_value[3]);
809 
810 	undo_save_state(&gfit, _("Background neutralization"));
811 
812 	set_cursor_waiting(TRUE);
813 	background_neutralize(&gfit, black_selection);
814 	delete_selected_area();
815 
816 	redraw(com.cvport, REMAP_ALL);
817 	redraw_previews();
818 	update_gfit_histogram_if_needed();
819 	set_cursor_waiting(FALSE);
820 }
821 
on_button_white_selection_clicked(GtkButton * button,gpointer user_data)822 void on_button_white_selection_clicked(GtkButton *button, gpointer user_data) {
823 	static GtkSpinButton *selection_white_value[4] = { NULL, NULL, NULL, NULL };
824 
825 	if (!selection_white_value[0]) {
826 		selection_white_value[0] = GTK_SPIN_BUTTON(
827 				gtk_builder_get_object(builder, "spin_white_x"));
828 		selection_white_value[1] = GTK_SPIN_BUTTON(
829 				gtk_builder_get_object(builder, "spin_white_y"));
830 		selection_white_value[2] = GTK_SPIN_BUTTON(
831 				gtk_builder_get_object(builder, "spin_white_w"));
832 		selection_white_value[3] = GTK_SPIN_BUTTON(
833 				gtk_builder_get_object(builder, "spin_white_h"));
834 	}
835 
836 	if ((!com.selection.h) || (!com.selection.w)) {
837 		siril_message_dialog( GTK_MESSAGE_WARNING, _("There is no selection"),
838 				_("Make a selection of the white reference area"));
839 		return;
840 	}
841 
842 	gtk_spin_button_set_value(selection_white_value[0], com.selection.x);
843 	gtk_spin_button_set_value(selection_white_value[1], com.selection.y);
844 	gtk_spin_button_set_value(selection_white_value[2], com.selection.w);
845 	gtk_spin_button_set_value(selection_white_value[3], com.selection.h);
846 }
847 
get_coeff_for_wb(fits * fit,rectangle white,rectangle black,double kw[],double bg[],double norm,double low,double high)848 static void get_coeff_for_wb(fits *fit, rectangle white, rectangle black,
849 		double kw[], double bg[], double norm, double low, double high) {
850 	int chan, i, j, n;
851 	double tmp[3] = { 0.0, 0.0, 0.0 };
852 
853 	assert(fit->naxes[2] == 3);
854 
855 	if (fit->type == DATA_USHORT) {
856 		WORD lo = round_to_WORD(low * (norm));
857 		WORD hi = round_to_WORD(high * (norm));
858 
859 		for (chan = 0; chan < 3; chan++) {
860 			n = 0;
861 			WORD *from = fit->pdata[chan] + (fit->ry - white.y - white.h) * fit->rx
862 				+ white.x;
863 			int stridefrom = fit->rx - white.w;
864 
865 			for (i = 0; i < white.h; i++) {
866 				for (j = 0; j < white.w; j++) {
867 					if (*from > lo && *from < hi ) {
868 						kw[chan] += (double)*from / norm;
869 						n++;
870 					}
871 					from++;
872 				}
873 				from += stridefrom;
874 			}
875 			if (n > 0)
876 				kw[chan] /= (double)n;
877 		}
878 	}
879 	else if (fit->type == DATA_FLOAT) {
880 		for (chan = 0; chan < 3; chan++) {
881 			n = 0;
882 			float *from = fit->fpdata[chan] + (fit->ry - white.y - white.h) * fit->rx
883 				+ white.x;
884 			int stridefrom = fit->rx - white.w;
885 
886 			for (i = 0; i < white.h; i++) {
887 				for (j = 0; j < white.w; j++) {
888 					double f = (double)*from;
889 					if (f > low && f < high) {
890 						kw[chan] += f;
891 						n++;
892 					}
893 					from++;
894 				}
895 				from += stridefrom;
896 			}
897 			if (n > 0)
898 				kw[chan] /= (double)n;
899 		}
900 	}
901 	else return;
902 
903 	siril_log_message(_("Background reference:\n"));
904 	for (chan = 0; chan < 3; chan++) {
905 		imstats *stat = statistics(NULL, -1, fit, chan, &black, STATS_BASIC, TRUE);
906 		if (!stat) {
907 			siril_log_message(_("Error: statistics computation failed.\n"));
908 			return;
909 		}
910 		bg[chan] = stat->median / stat->normValue;
911 		siril_log_message("B%d: %.5e\n", chan, bg[chan]);
912 		free_stats(stat);
913 
914 	}
915 
916 	siril_log_message(_("White reference:\n"));
917 	for (chan = 0; chan < 3; chan++) {
918 		siril_log_message("W%d: %.5e\n", chan, kw[chan]);
919 		kw[chan] = fabs(kw[chan] - bg[chan]);
920 	}
921 
922 	int rc = (kw[0] > kw[1]) ? ((kw[0] > kw[2]) ? 0 : 2) :
923 		((kw[1] > kw[2]) ? 1 : 2);
924 	for (chan = 0; chan < 3; chan++) {
925 		if (chan == rc)
926 			tmp[chan] = 1.0;
927 		else
928 			tmp[chan] = kw[rc] / kw[chan];
929 	}
930 
931 	siril_log_message(_("Color calibration factors:\n"));
932 	for (chan = 0; chan < 3; chan++) {
933 		kw[chan] = tmp[chan];
934 		siril_log_message("K%d: %5.3lf\n", chan, kw[chan]);
935 	}
936 }
937 
calibrate(fits * fit,int layer,double kw,double bg,double norm)938 static int calibrate(fits *fit, int layer, double kw, double bg, double norm) {
939 	size_t i, n = fit->naxes[0] * fit->naxes[1];
940 	if (fit->type == DATA_USHORT) {
941 		double bgNorm = bg * norm;
942 		WORD *buf = fit->pdata[layer];
943 		for (i = 0; i < n; ++i) {
944 			buf[i] = round_to_WORD((buf[i] - bgNorm) * kw + bgNorm);
945 		}
946 	}
947 	else if (fit->type == DATA_FLOAT) {
948 		float *buf = fit->fpdata[layer];
949 		for (i = 0; i < n; ++i) {
950 			buf[i] = (float)(((double)buf[i] - bg) * kw + bg);
951 		}
952 	}
953 	else return 1;
954 	return 0;
955 }
956 
white_balance(fits * fit,gboolean is_manual,rectangle white_selection,rectangle black_selection)957 static void white_balance(fits *fit, gboolean is_manual, rectangle white_selection,
958 		rectangle black_selection) {
959 	int chan;
960 	double norm, low, high;
961 	double kw[3] = { 0.0, 0.0, 0.0 };
962 	double bg[3] = { 0.0, 0.0, 0.0 };
963 	static GtkRange *scale_white_balance[3] = { NULL, NULL, NULL };
964 	static GtkRange *scaleLimit[2] = { NULL, NULL };
965 
966 	if (scale_white_balance[RLAYER] == NULL) {
967 		scale_white_balance[RLAYER] = GTK_RANGE(lookup_widget("scale_r"));
968 		scale_white_balance[GLAYER] = GTK_RANGE(lookup_widget("scale_g"));
969 		scale_white_balance[BLAYER] = GTK_RANGE(lookup_widget("scale_b"));
970 
971 		scaleLimit[0] = GTK_RANGE(lookup_widget("lowWhiteColorCalibScale"));
972 		scaleLimit[1] = GTK_RANGE(lookup_widget("upWhiteColorCalibScale"));
973 	}
974 
975 	assert(fit->naxes[2] == 3);
976 	norm = get_normalized_value(fit);
977 
978 	if (is_manual) {
979 		kw[RLAYER] = gtk_range_get_value(scale_white_balance[RLAYER]);
980 		kw[GLAYER] = gtk_range_get_value(scale_white_balance[GLAYER]);
981 		kw[BLAYER] = gtk_range_get_value(scale_white_balance[BLAYER]);
982 
983 	} else {
984 		low = gtk_range_get_value(scaleLimit[0]);
985 		high = gtk_range_get_value(scaleLimit[1]);
986 		get_coeff_for_wb(fit, white_selection, black_selection, kw, bg, norm, low, high);
987 	}
988 #ifdef _OPENMP
989 #pragma omp parallel for num_threads(com.max_thread) private(chan) schedule(static)
990 #endif
991 	for (chan = 0; chan < 3; chan++) {
992 		if (kw[chan] == 1.0) continue;
993 		calibrate(fit, chan, kw[chan], bg[chan], norm);
994 	}
995 
996 	invalidate_stats_from_fit(fit);
997 }
998 
on_calibration_apply_button_clicked(GtkButton * button,gpointer user_data)999 void on_calibration_apply_button_clicked(GtkButton *button, gpointer user_data) {
1000 	rectangle black_selection, white_selection;
1001 	static GtkSpinButton *selection_black_value[4] = { NULL, NULL, NULL, NULL };
1002 	static GtkSpinButton *selection_white_value[4] = { NULL, NULL, NULL, NULL };
1003 	struct timeval t_start, t_end;
1004 
1005 	siril_log_color_message(_("Color Calibration: processing...\n"), "green");
1006 	gettimeofday(&t_start, NULL);
1007 
1008 	GtkToggleButton *manual = GTK_TOGGLE_BUTTON(lookup_widget("checkbutton_manual_calibration"));
1009 	gboolean is_manual = gtk_toggle_button_get_active(manual);
1010 
1011 	if (!selection_black_value[0]) {
1012 		selection_black_value[0] = GTK_SPIN_BUTTON(lookup_widget("spin_bkg_x"));
1013 		selection_black_value[1] = GTK_SPIN_BUTTON(lookup_widget("spin_bkg_y"));
1014 		selection_black_value[2] = GTK_SPIN_BUTTON(lookup_widget("spin_bkg_w"));
1015 		selection_black_value[3] = GTK_SPIN_BUTTON(lookup_widget("spin_bkg_h"));
1016 	}
1017 
1018 	if (!selection_white_value[0]) {
1019 		selection_white_value[0] = GTK_SPIN_BUTTON(lookup_widget("spin_white_x"));
1020 		selection_white_value[1] = GTK_SPIN_BUTTON(lookup_widget("spin_white_y"));
1021 		selection_white_value[2] = GTK_SPIN_BUTTON(lookup_widget("spin_white_w"));
1022 		selection_white_value[3] = GTK_SPIN_BUTTON(lookup_widget("spin_white_h"));
1023 	}
1024 
1025 	black_selection.x = gtk_spin_button_get_value(selection_black_value[0]);
1026 	black_selection.y = gtk_spin_button_get_value(selection_black_value[1]);
1027 	black_selection.w = gtk_spin_button_get_value(selection_black_value[2]);
1028 	black_selection.h = gtk_spin_button_get_value(selection_black_value[3]);
1029 
1030 	if ((!black_selection.w || !black_selection.h) && !is_manual) {
1031 		siril_message_dialog( GTK_MESSAGE_WARNING, _("There is no selection"),
1032 				_("Make a selection of the background area"));
1033 		return;
1034 	}
1035 
1036 	white_selection.x = gtk_spin_button_get_value(selection_white_value[0]);
1037 	white_selection.y = gtk_spin_button_get_value(selection_white_value[1]);
1038 	white_selection.w = gtk_spin_button_get_value(selection_white_value[2]);
1039 	white_selection.h = gtk_spin_button_get_value(selection_white_value[3]);
1040 
1041 	if ((!white_selection.w || !white_selection.h) && !is_manual) {
1042 		siril_message_dialog( GTK_MESSAGE_WARNING, _("There is no selection"),
1043 				_("Make a selection of the white reference area"));
1044 		return;
1045 	}
1046 
1047 	set_cursor_waiting(TRUE);
1048 	undo_save_state(&gfit, _("Color Calibration"));
1049 	white_balance(&gfit, is_manual, white_selection, black_selection);
1050 
1051 	gettimeofday(&t_end, NULL);
1052 
1053 	show_time(t_start, t_end);
1054 
1055 	delete_selected_area();
1056 
1057 	redraw(com.cvport, REMAP_ALL);
1058 	redraw_previews();
1059 	update_gfit_histogram_if_needed();
1060 	set_cursor_waiting(FALSE);
1061 }
1062 
on_calibration_close_button_clicked(GtkButton * button,gpointer user_data)1063 void on_calibration_close_button_clicked(GtkButton *button, gpointer user_data) {
1064 	siril_close_dialog("color_calibration");
1065 }
1066 
on_checkbutton_manual_calibration_toggled(GtkToggleButton * togglebutton,gpointer user_data)1067 void on_checkbutton_manual_calibration_toggled(GtkToggleButton *togglebutton,
1068 		gpointer user_data) {
1069 	GtkWidget *cc_box_red = lookup_widget("cc_box_red");
1070 	GtkWidget *scale_r = lookup_widget("scale_r");
1071 	GtkWidget *cc_box_green = lookup_widget("cc_box_green");
1072 	GtkWidget *scale_g = lookup_widget("scale_g");
1073 	GtkWidget *cc_box_blue = lookup_widget("cc_box_blue");
1074 	GtkWidget *scale_b = lookup_widget("scale_b");
1075 	gtk_widget_set_sensitive(cc_box_red, gtk_toggle_button_get_active(togglebutton));
1076 	gtk_widget_set_sensitive(scale_r, gtk_toggle_button_get_active(togglebutton));
1077 	gtk_widget_set_sensitive(cc_box_green, gtk_toggle_button_get_active(togglebutton));
1078 	gtk_widget_set_sensitive(scale_g, gtk_toggle_button_get_active(togglebutton));
1079 	gtk_widget_set_sensitive(cc_box_blue, gtk_toggle_button_get_active(togglebutton));
1080 	gtk_widget_set_sensitive(scale_b, gtk_toggle_button_get_active(togglebutton));
1081 }
1082 
pos_to_neg(fits * fit)1083 int pos_to_neg(fits *fit) {
1084 	size_t i, n = fit->naxes[0] * fit->naxes[1] * fit->naxes[2];
1085 	if (fit->type == DATA_USHORT) {
1086 		WORD norm = (WORD)get_normalized_value(fit);
1087 #ifdef _OPENMP
1088 #pragma omp parallel for num_threads(com.max_thread)
1089 #endif
1090 		for (i = 0; i < n; i++) {
1091 			fit->data[i] = norm - fit->data[i];
1092 		}
1093 	}
1094 	else if (fit->type == DATA_FLOAT) {
1095 #ifdef _OPENMP
1096 #pragma omp parallel for num_threads(com.max_thread)
1097 #endif
1098 		for (i = 0; i < n; i++) {
1099 			fit->fdata[i] = 1.0f - fit->fdata[i];
1100 		}
1101 	}
1102 	else return 1;
1103 
1104 	return 0;
1105 }
1106 
negative_processing()1107 void negative_processing() {
1108 	set_cursor_waiting(TRUE);
1109 	undo_save_state(&gfit, _("Negative Transformation"));
1110 	pos_to_neg(&gfit);
1111 	invalidate_stats_from_fit(&gfit);
1112 	invalidate_gfit_histogram();
1113 	update_gfit_histogram_if_needed();
1114 	redraw(com.cvport, REMAP_ALL);
1115 	redraw_previews();
1116 	set_cursor_waiting(FALSE);
1117 }
1118 
1119 /**********************************************************************/
1120 
on_extract_channel_button_close_clicked(GtkButton * button,gpointer user_data)1121 void on_extract_channel_button_close_clicked(GtkButton *button,
1122 		gpointer user_data) {
1123 	siril_close_dialog("extract_channel_dialog");
1124 }
1125 
on_combo_extract_colors_changed(GtkComboBox * box,gpointer user_data)1126 void on_combo_extract_colors_changed(GtkComboBox *box, gpointer user_data) {
1127 	switch(gtk_combo_box_get_active(box)) {
1128 		default:
1129 		case 0: // RGB
1130 			gtk_label_set_text(GTK_LABEL(lookup_widget("label_extract_c1")), _("Red: "));
1131 			gtk_label_set_text(GTK_LABEL(lookup_widget("label_extract_c2")), _("Green: "));
1132 			gtk_label_set_text(GTK_LABEL(lookup_widget("label_extract_c3")), _("Blue: "));
1133 			break;
1134 		case 1: // HSL
1135 			gtk_label_set_text(GTK_LABEL(lookup_widget("label_extract_c1")), _("Hue: "));
1136 			gtk_label_set_text(GTK_LABEL(lookup_widget("label_extract_c2")), _("Saturation: "));
1137 			gtk_label_set_text(GTK_LABEL(lookup_widget("label_extract_c3")), _("Lightness: "));
1138 			break;
1139 		case 2: // HSV
1140 			gtk_label_set_text(GTK_LABEL(lookup_widget("label_extract_c1")), _("Hue: "));
1141 			gtk_label_set_text(GTK_LABEL(lookup_widget("label_extract_c2")), _("Saturation: "));
1142 			gtk_label_set_text(GTK_LABEL(lookup_widget("label_extract_c3")), _("Value: "));
1143 			break;
1144 		case 3: // CIE L*a*b*
1145 			gtk_label_set_text(GTK_LABEL(lookup_widget("label_extract_c1")), "L*: ");
1146 			gtk_label_set_text(GTK_LABEL(lookup_widget("label_extract_c2")), "a*: ");
1147 			gtk_label_set_text(GTK_LABEL(lookup_widget("label_extract_c3")), "b*: ");
1148 	}
1149 }
1150 
on_extract_channel_button_ok_clicked(GtkButton * button,gpointer user_data)1151 void on_extract_channel_button_ok_clicked(GtkButton *button, gpointer user_data) {
1152 	static GtkEntry *channel_extract_entry[3] = { NULL, NULL, NULL };
1153 	static GtkComboBox *combo_extract_channel = NULL;
1154 
1155 	if (get_thread_run()) {
1156 		PRINT_ANOTHER_THREAD_RUNNING;
1157 		return;
1158 	}
1159 
1160 	struct extract_channels_data *args = malloc(sizeof(struct extract_channels_data));
1161 	if (!args) {
1162 		PRINT_ALLOC_ERR;
1163 		return;
1164 	}
1165 
1166 	if (combo_extract_channel == NULL) {
1167 		combo_extract_channel = GTK_COMBO_BOX(
1168 				lookup_widget("combo_extract_colors"));
1169 		channel_extract_entry[0] = GTK_ENTRY(
1170 				lookup_widget("Ch1_extract_channel_entry"));
1171 		channel_extract_entry[1] = GTK_ENTRY(
1172 				lookup_widget("Ch2_extract_channel_entry"));
1173 		channel_extract_entry[2] = GTK_ENTRY(
1174 				lookup_widget("Ch3_extract_channel_entry"));
1175 	}
1176 
1177 	args->type = gtk_combo_box_get_active(combo_extract_channel);
1178 	args->str_type = gtk_combo_box_get_active_id(combo_extract_channel);
1179 
1180 	args->channel[0] = g_strdup_printf("%s%s", gtk_entry_get_text(channel_extract_entry[0]), com.pref.ext);
1181 	args->channel[1] = g_strdup_printf("%s%s", gtk_entry_get_text(channel_extract_entry[1]), com.pref.ext);
1182 	args->channel[2] = g_strdup_printf("%s%s", gtk_entry_get_text(channel_extract_entry[2]), com.pref.ext);
1183 
1184 	if ((args->channel[0][0] != '\0') && (args->channel[1][0] != '\0')
1185 			&& (args->channel[2][0] != '\0')) {
1186 		args->fit = calloc(1, sizeof(fits));
1187 		set_cursor_waiting(TRUE);
1188 		if (copyfits(&gfit, args->fit, CP_ALLOC | CP_COPYA | CP_FORMAT, -1)) {
1189 			siril_log_message(_("Could not copy the input image, aborting.\n"));
1190 			free(args->fit);
1191 			free(args->channel[0]);
1192 			free(args->channel[1]);
1193 			free(args->channel[2]);
1194 			free(args);
1195 		} else {
1196 			copy_fits_metadata(&gfit, args->fit);
1197 			start_in_new_thread(extract_channels, args);
1198 		}
1199 	}
1200 	else {
1201 		free(args->channel[0]);
1202 		free(args->channel[1]);
1203 		free(args->channel[2]);
1204 		free(args);
1205 	}
1206 }
1207