2
* * Copyright (C) 2006-2011 Anders Brander <anders@brander.dk>,
3
* * Anders Kvist <akv@lnxbx.dk> and Klaus Post <klauspost@gmail.com>
5
* This program is free software; you can redistribute it and/or
6
* modify it under the terms of the GNU General Public License
7
* as published by the Free Software Foundation; either version 2
8
* of the License, or (at your option) any later version.
10
* This program is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
* GNU General Public License for more details.
15
* You should have received a copy of the GNU General Public License
16
* along with this program; if not, write to the Free Software
17
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20
/* Plugin tmpl version 5 */
22
#include <rawstudio.h>
25
#include "colorspace_transform.h"
28
struct _RSColorspaceTransform {
36
struct _RSColorspaceTransformClass {
37
RSFilterClass parent_class;
40
RS_DEFINE_FILTER(rs_colorspace_transform, RSColorspaceTransform)
47
static RSFilterResponse *get_image(RSFilter *filter, const RSFilterRequest *request);
48
static RSFilterResponse *get_image8(RSFilter *filter, const RSFilterRequest *request);
49
static gboolean convert_colorspace16(RSColorspaceTransform *colorspace_transform, RS_IMAGE16 *input_image, RS_IMAGE16 *output_image, RSColorSpace *input_space, RSColorSpace *output_space);
50
static void convert_colorspace8(RSColorspaceTransform *colorspace_transform, RS_IMAGE16 *input_image, GdkPixbuf *output_image, RSColorSpace *input_space, RSColorSpace *output_space, GdkRectangle *roi);
52
static RSFilterClass *rs_colorspace_transform_parent_class = NULL;
54
/* SSE2 optimized functions */
55
extern void transform8_srgb_sse2(ThreadInfo* t);
56
extern void transform8_otherrgb_sse2(ThreadInfo* t);
57
extern gboolean cst_has_sse2(void);
60
rs_plugin_load(RSPlugin *plugin)
62
rs_colorspace_transform_get_type(G_TYPE_MODULE(plugin));
66
rs_colorspace_transform_class_init(RSColorspaceTransformClass *klass)
68
RSFilterClass *filter_class = RS_FILTER_CLASS (klass);
70
rs_colorspace_transform_parent_class = g_type_class_peek_parent (klass);
72
filter_class->name = "ColorspaceTransform filter";
73
filter_class->get_image = get_image;
74
filter_class->get_image8 = get_image8;
78
rs_colorspace_transform_init(RSColorspaceTransform *colorspace_transform)
80
/* FIXME: unref this at some point */
81
colorspace_transform->cmm = rs_cmm_new();
82
rs_cmm_set_num_threads(colorspace_transform->cmm, rs_get_number_of_processor_cores());
85
static RSFilterResponse *
86
get_image(RSFilter *filter, const RSFilterRequest *request)
88
RSColorspaceTransform *colorspace_transform = RS_COLORSPACE_TRANSFORM(filter);
89
RSFilterResponse *previous_response;
90
RSFilterResponse *response;
92
RS_IMAGE16 *output = NULL;
95
previous_response = rs_filter_get_image(filter->previous, request);
96
input = rs_filter_response_get_image(previous_response);
97
if (!RS_IS_IMAGE16(input))
98
return previous_response;
100
RSColorSpace *input_space = rs_filter_param_get_object_with_type(RS_FILTER_PARAM(previous_response), "colorspace", RS_TYPE_COLOR_SPACE);
101
RSColorSpace *output_space = rs_filter_param_get_object_with_type(RS_FILTER_PARAM(request), "colorspace", RS_TYPE_COLOR_SPACE);
103
for( i = 0; i < 4; i++)
104
colorspace_transform->premul[i] = 1.0f;
106
if (input_space && output_space && (input_space != output_space))
108
gboolean is_premultiplied = FALSE;
109
rs_filter_param_get_boolean(RS_FILTER_PARAM(previous_response), "is-premultiplied", &is_premultiplied);
111
if (!is_premultiplied)
112
colorspace_transform->has_premul = rs_filter_param_get_float4(RS_FILTER_PARAM(request), "premul", colorspace_transform->premul);
113
rs_cmm_set_premul(colorspace_transform->cmm, colorspace_transform->premul);
115
output = rs_image16_copy(input, FALSE);
117
if (convert_colorspace16(colorspace_transform, input, output, input_space, output_space))
119
/* Image was converted */
120
response = rs_filter_response_clone(previous_response);
121
g_object_unref(previous_response);
122
if (colorspace_transform->has_premul)
123
rs_filter_param_set_boolean(RS_FILTER_PARAM(response), "is-premultiplied", TRUE);
124
rs_filter_param_set_object(RS_FILTER_PARAM(response), "colorspace", output_space);
125
rs_filter_response_set_image(response, output);
126
g_object_unref(output);
127
g_object_unref(input);
131
/* No conversion was needed */
132
g_object_unref(input);
133
g_object_unref(output);
134
return previous_response;
139
g_object_unref(input);
140
return previous_response;
145
static RSFilterResponse *
146
get_image8(RSFilter *filter, const RSFilterRequest *request)
148
RSColorspaceTransform *colorspace_transform = RS_COLORSPACE_TRANSFORM(filter);
149
RSFilterResponse *previous_response;
150
RSFilterResponse *response;
152
GdkPixbuf *output = NULL;
156
previous_response = rs_filter_get_image(filter->previous, request);
157
input = rs_filter_response_get_image(previous_response);
158
if (!RS_IS_IMAGE16(input))
159
return previous_response;
161
roi = rs_filter_request_get_roi(request);
162
RSColorSpace *input_space = rs_filter_param_get_object_with_type(RS_FILTER_PARAM(previous_response), "colorspace", RS_TYPE_COLOR_SPACE);
163
RSColorSpace *output_space = rs_filter_param_get_object_with_type(RS_FILTER_PARAM(request), "colorspace", RS_TYPE_COLOR_SPACE);
165
response = rs_filter_response_clone(previous_response);
166
g_object_unref(previous_response);
168
for( i = 0; i < 4; i++)
169
colorspace_transform->premul[i] = 1.0f;
171
gboolean is_premultiplied = FALSE;
172
rs_filter_param_get_boolean(RS_FILTER_PARAM(response), "is-premultiplied", &is_premultiplied);
174
if (!is_premultiplied)
175
if ((colorspace_transform->has_premul = rs_filter_param_get_float4(RS_FILTER_PARAM(request), "premul", colorspace_transform->premul)))
176
rs_cmm_set_premul(colorspace_transform->cmm, colorspace_transform->premul);
178
if (colorspace_transform->has_premul)
179
rs_filter_param_set_boolean(RS_FILTER_PARAM(response), "is-premultiplied", TRUE);
182
printf("\033[33m8 input_space: %s\033[0m\n", (input_space) ? G_OBJECT_TYPE_NAME(input_space) : "none");
183
printf("\033[33m8 output_space: %s\n\033[0m", (output_space) ? G_OBJECT_TYPE_NAME(output_space) : "none");
186
output = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, input->w, input->h);
189
convert_colorspace8(colorspace_transform, input, output, input_space, output_space, roi);
191
rs_filter_response_set_image8(response, output);
192
rs_filter_param_set_object(RS_FILTER_PARAM(response), "colorspace", output_space);
193
g_object_unref(output);
194
g_object_unref(input);
199
transform8_c(ThreadInfo* t)
205
RS_IMAGE16 *input = t->input;
206
GdkPixbuf *output = (GdkPixbuf *)t->output;
207
guchar *table8 = t->table8;
209
g_assert(RS_IS_IMAGE16(input));
210
g_assert(GDK_IS_PIXBUF(output));
211
gint o_channels = gdk_pixbuf_get_n_channels(output);
213
matrix3_to_matrix3int(t->matrix, &mati);
215
for(row=t->start_y ; row<t->end_y ; row++)
217
gushort *i = GET_PIXEL(input, t->start_x, row);
218
guchar *o = GET_PIXBUF_PIXEL(output, t->start_x, row);
220
width = t->end_x - t->start_x;
225
( i[R] * mati.coeff[0][0]
226
+ i[G] * mati.coeff[0][1]
227
+ i[B] * mati.coeff[0][2]
228
+ MATRIX_RESOLUTION_ROUNDER ) >> MATRIX_RESOLUTION;
230
( i[R] * mati.coeff[1][0]
231
+ i[G] * mati.coeff[1][1]
232
+ i[B] * mati.coeff[1][2]
233
+ MATRIX_RESOLUTION_ROUNDER ) >> MATRIX_RESOLUTION;
235
( i[R] * mati.coeff[2][0]
236
+ i[G] * mati.coeff[2][1]
237
+ i[B] * mati.coeff[2][2]
238
+ MATRIX_RESOLUTION_ROUNDER ) >> MATRIX_RESOLUTION;
240
r = CLAMP(r, 0, 65535);
241
g = CLAMP(g, 0, 65535);
242
b = CLAMP(b, 0, 65535);
249
i += input->pixelsize;
257
transform16_c(gushort* __restrict input, gushort* __restrict output, gint num_pixels, const gint pixelsize, RS_MATRIX3 *matrix)
262
matrix3_to_matrix3int(matrix, &mati);
267
( input[R] * mati.coeff[0][0]
268
+ input[G] * mati.coeff[0][1]
269
+ input[B] * mati.coeff[0][2]
270
+ MATRIX_RESOLUTION_ROUNDER ) >> MATRIX_RESOLUTION;
272
( input[R] * mati.coeff[1][0]
273
+ input[G] * mati.coeff[1][1]
274
+ input[B] * mati.coeff[1][2]
275
+ MATRIX_RESOLUTION_ROUNDER ) >> MATRIX_RESOLUTION;
277
( input[R] * mati.coeff[2][0]
278
+ input[G] * mati.coeff[2][1]
279
+ input[B] * mati.coeff[2][2]
280
+ MATRIX_RESOLUTION_ROUNDER ) >> MATRIX_RESOLUTION;
282
r = CLAMP(r, 0, 65535);
283
g = CLAMP(g, 0, 65535);
284
b = CLAMP(b, 0, 65535);
296
convert_colorspace16(RSColorspaceTransform *colorspace_transform, RS_IMAGE16 *input_image, RS_IMAGE16 *output_image, RSColorSpace *input_space, RSColorSpace *output_space)
298
g_assert(RS_IS_IMAGE16(input_image));
299
g_assert(RS_IS_IMAGE16(output_image));
300
g_assert(RS_IS_COLOR_SPACE(input_space));
301
g_assert(RS_IS_COLOR_SPACE(output_space));
303
/* If input/output-image doesn't differ, return no transformation needed */
304
if (input_space == output_space && !colorspace_transform->has_premul)
307
/* A few sanity checks */
308
g_assert(input_image->w == output_image->w);
309
g_assert(input_image->h == output_image->h);
311
/* If a CMS is needed, do the transformation using LCMS */
312
if (RS_COLOR_SPACE_REQUIRES_CMS(input_space) || RS_COLOR_SPACE_REQUIRES_CMS(output_space))
314
const RSIccProfile *i, *o;
316
i = rs_color_space_get_icc_profile(input_space, TRUE);
317
o = rs_color_space_get_icc_profile(output_space, TRUE);
319
rs_cmm_set_input_profile(colorspace_transform->cmm, i);
320
rs_cmm_set_output_profile(colorspace_transform->cmm, o);
322
rs_cmm_transform16(colorspace_transform->cmm, input_image, output_image);
325
/* If we get here, we can transform using simple vector math */
328
RS_VECTOR3 vec = {{colorspace_transform->premul[0]},{colorspace_transform->premul[1]},{colorspace_transform->premul[2]}};
329
const RS_MATRIX3 mul_vec = vector3_as_diagonal(&vec);
330
const RS_MATRIX3 a = rs_color_space_get_matrix_from_pcs(input_space);
332
matrix3_multiply(&a, &mul_vec, &a_premul);
333
const RS_MATRIX3 b = rs_color_space_get_matrix_to_pcs(output_space);
335
matrix3_multiply(&b, &a_premul, &mat);
338
GET_PIXEL(input_image, 0, 0),
339
GET_PIXEL(output_image, 0, 0),
340
input_image->h * input_image->pitch,
341
input_image->pixelsize,
348
start_single_cs8_transform_thread(gpointer _thread_info)
350
ThreadInfo* t = _thread_info;
351
RS_IMAGE16 *input_image = t->input;
352
GdkPixbuf *output = (GdkPixbuf*) t->output;
353
RSColorSpace *input_space = t->input_space;
354
RSColorSpace *output_space = t->output_space;
356
g_assert(RS_IS_IMAGE16(input_image));
357
g_assert(GDK_IS_PIXBUF(output));
358
g_assert(RS_IS_COLOR_SPACE(input_space));
359
g_assert(RS_IS_COLOR_SPACE(output_space));
361
gboolean sse2_available = (!!(rs_detect_cpu_features() & RS_CPU_FLAG_SSE2)) && cst_has_sse2();
363
if (sse2_available && rs_color_space_new_singleton("RSSrgb") == output_space)
365
transform8_srgb_sse2(t);
368
if (sse2_available && rs_color_space_new_singleton("RSAdobeRGB") == output_space)
370
t->output_gamma = 1.0 / 2.19921875;
371
transform8_otherrgb_sse2(t);
374
if (sse2_available && rs_color_space_new_singleton("RSProphoto") == output_space)
376
t->output_gamma = 1.0 / 1.8;
377
transform8_otherrgb_sse2(t);
381
/* Fall back to C-functions */
382
/* Calculate our gamma table */
383
const RS1dFunction *input_gamma = rs_color_space_get_gamma_function(input_space);
384
const RS1dFunction *output_gamma = rs_color_space_get_gamma_function(output_space);
385
guchar table8[65536];
389
gdouble nd = ((gdouble) i) * (1.0/65535.0);
391
nd = rs_1d_function_evaluate_inverse(input_gamma, nd);
392
nd = rs_1d_function_evaluate(output_gamma, nd);
395
gint res = (gint) (nd*255.0 + 0.5f);
405
convert_colorspace8(RSColorspaceTransform *colorspace_transform, RS_IMAGE16 *input_image, GdkPixbuf *output_image, RSColorSpace *input_space, RSColorSpace *output_space, GdkRectangle *_roi)
407
g_assert(RS_IS_IMAGE16(input_image));
408
g_assert(GDK_IS_PIXBUF(output_image));
409
g_assert(RS_IS_COLOR_SPACE(input_space));
410
g_assert(RS_IS_COLOR_SPACE(output_space));
412
/* A few sanity checks */
413
g_assert(input_image->w == gdk_pixbuf_get_width(output_image));
414
g_assert(input_image->h == gdk_pixbuf_get_height(output_image));
416
GdkRectangle *roi = _roi;
419
roi = g_new(GdkRectangle, 1);
422
roi->width = input_image->w;
423
roi->height = input_image->h;
426
/* If a CMS is needed, do the transformation using LCMS */
427
if (RS_COLOR_SPACE_REQUIRES_CMS(input_space) || RS_COLOR_SPACE_REQUIRES_CMS(output_space))
429
const RSIccProfile *i, *o;
431
i = rs_color_space_get_icc_profile(input_space, TRUE);
432
o = rs_color_space_get_icc_profile(output_space, FALSE);
434
rs_cmm_set_input_profile(colorspace_transform->cmm, i);
435
rs_cmm_set_output_profile(colorspace_transform->cmm, o);
437
rs_cmm_transform8(colorspace_transform->cmm, input_image, output_image);
440
/* If we get here, we can transform using simple vector math and a lookup table */
443
const RS_VECTOR3 vec = {{colorspace_transform->premul[0]},{colorspace_transform->premul[1]},{colorspace_transform->premul[2]}};
444
const RS_MATRIX3 mul_vec = vector3_as_diagonal(&vec);
445
const RS_MATRIX3 a = rs_color_space_get_matrix_from_pcs(input_space);
447
matrix3_multiply(&a, &mul_vec, &a_premul);
448
const RS_MATRIX3 b = rs_color_space_get_matrix_to_pcs(output_space);
450
matrix3_multiply(&b, &a_premul, &mat);
454
guint y_offset, y_per_thread, threaded_h;
455
const guint threads = rs_get_number_of_processor_cores();
456
ThreadInfo *t = g_new(ThreadInfo, threads);
458
threaded_h = roi->height;
459
y_per_thread = (threaded_h + threads-1)/threads;
462
for (i = 0; i < threads; i++)
464
t[i].input = input_image;
465
t[i].output = output_image;
466
t[i].start_y = y_offset;
467
t[i].start_x = roi->x;
468
t[i].end_x = roi->x + roi->width;
469
t[i].cst = colorspace_transform;
470
t[i].input_space = input_space;
471
t[i].output_space = output_space;
472
y_offset += y_per_thread;
473
y_offset = MIN(input_image->h, y_offset);
474
t[i].end_y = y_offset;
478
t[i].threadid = g_thread_create(start_single_cs8_transform_thread, &t[i], TRUE, NULL);
481
/* Wait for threads to finish */
482
for(i = 0; i < threads; i++)
483
g_thread_join(t[i].threadid);
487
/* If we created the ROI here, free it */