ebcc4438b882d1b9194a4deb8f9b538f41c8799b
[platform/upstream/gstreamer.git] / ext / colormanagement / gstlcms.c
1 /*
2  * GStreamer gstreamer-lcms
3  * Copyright (C) 2016 Andreas Frisch <fraxinas@dreambox.guru>
4  *
5  * gstlcms.c
6  * 
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library 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 GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  */
22
23 /**
24  * SECTION:element-lcms
25  * @short_description: Uses LittleCMS 2 to perform ICC profile correction
26  *
27  * This is a color management plugin that uses LittleCMS 2 to correct
28  * frames using the given ICC (International Color Consortium) profiles.
29  * Falls back to internal sRGB profile if no ICC file is specified in property.
30  *
31  * ## Example launch line
32  *
33  * (write everything in one line, without the backslash characters)
34  * |[
35  * gst-launch-1.0 filesrc location=photo_camera.png ! pngdec ! \
36  * videoconvert ! lcms input-profile=sRGB.icc dest-profile=printer.icc \
37  * pngenc ! filesink location=photo_print.png
38  * ]|
39  */
40
41 #ifdef HAVE_CONFIG_H
42 #include "config.h"
43 #endif
44
45 #include "gstlcms.h"
46
47 #include <gst/gst.h>
48 #include <gst/video/video.h>
49 #include <stdlib.h>
50 #include <string.h>
51
52 GST_DEBUG_CATEGORY_STATIC (lcms_debug);
53 #define GST_CAT_DEFAULT lcms_debug
54
55 /* GstLcms properties */
56 enum
57 {
58   PROP_0,
59   PROP_INTENT,
60   PROP_LOOKUP_METHOD,
61   PROP_SRC_FILE,
62   PROP_DST_FILE,
63   PROP_PRESERVE_BLACK,
64   PROP_EMBEDDED_PROFILE
65 };
66
67 GType
68 gst_lcms_intent_get_type (void)
69 {
70   static volatile gsize intent_type = 0;
71   static const GEnumValue intent[] = {
72     {GST_LCMS_INTENT_PERCEPTUAL, "Perceptual",
73         "perceptual"},
74     {GST_LCMS_INTENT_RELATIVE_COLORIMETRIC, "Relative Colorimetric",
75         "relative"},
76     {GST_LCMS_INTENT_SATURATION, "Saturation",
77         "saturation"},
78     {GST_LCMS_INTENT_ABSOLUTE_COLORIMETRIC, "Absolute Colorimetric",
79         "absolute"},
80     {0, NULL, NULL},
81   };
82
83   if (g_once_init_enter (&intent_type)) {
84     GType tmp = g_enum_register_static ("GstLcmsIntent", intent);
85     g_once_init_leave (&intent_type, tmp);
86   }
87   return (GType) intent_type;
88 }
89
90 static GType
91 gst_lcms_lookup_method_get_type (void)
92 {
93   static volatile gsize lookup_method_type = 0;
94   static const GEnumValue lookup_method[] = {
95     {GST_LCMS_LOOKUP_METHOD_UNCACHED,
96           "Uncached, calculate every pixel on the fly (very slow playback)",
97         "uncached"},
98     {GST_LCMS_LOOKUP_METHOD_PRECALCULATED,
99           "Precalculate lookup table (takes a long time getting READY)",
100         "precalculated"},
101     {GST_LCMS_LOOKUP_METHOD_CACHED,
102           "Calculate and cache color replacement values on first occurrence",
103         "cached"},
104     {0, NULL, NULL},
105   };
106
107   if (g_once_init_enter (&lookup_method_type)) {
108     GType tmp = g_enum_register_static ("GstLcmsLookupMethod", lookup_method);
109     g_once_init_leave (&lookup_method_type, tmp);
110   }
111   return (GType) lookup_method_type;
112 }
113
114 #define DEFAULT_INTENT  GST_LCMS_INTENT_PERCEPTUAL
115 #define DEFAULT_LOOKUP_METHOD     GST_LCMS_LOOKUP_METHOD_CACHED
116 #define DEFAULT_PRESERVE_BLACK    FALSE
117 #define DEFAULT_EMBEDDED_PROFILE  TRUE
118
119 static GstStaticPadTemplate gst_lcms_src_template =
120 GST_STATIC_PAD_TEMPLATE ("src",
121     GST_PAD_SRC,
122     GST_PAD_ALWAYS,
123     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{ "
124             "ARGB, BGRA, ABGR, RGBA, xRGB," "RGBx, xBGR, BGRx, RGB, BGR }"))
125     );
126
127 static GstStaticPadTemplate gst_lcms_sink_template =
128 GST_STATIC_PAD_TEMPLATE ("sink",
129     GST_PAD_SINK,
130     GST_PAD_ALWAYS,
131     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{ "
132             "ARGB, BGRA, ABGR, RGBA, xRGB," "RGBx, xBGR, BGRx, RGB, BGR }"))
133     );
134
135 static void gst_lcms_set_property (GObject * object, guint prop_id,
136     const GValue * value, GParamSpec * pspec);
137 static void gst_lcms_get_property (GObject * object, guint prop_id,
138     GValue * value, GParamSpec * pspec);
139 static void gst_lcms_finalize (GObject * object);
140 static GstStateChangeReturn gst_lcms_change_state (GstElement * element,
141     GstStateChange transition);
142 static gboolean gst_lcms_set_info (GstVideoFilter * vfilter, GstCaps * incaps,
143     GstVideoInfo * in_info, GstCaps * outcaps, GstVideoInfo * out_info);
144 static gboolean gst_lcms_sink_event (GstBaseTransform * trans,
145     GstEvent * event);
146 static void gst_lcms_handle_tags (GstLcms * lcms, GstTagList * taglist);
147 static void gst_lcms_handle_tag_sample (GstLcms * lcms, GstSample * sample);
148 static GstFlowReturn gst_lcms_transform_frame (GstVideoFilter * vfilter,
149     GstVideoFrame * inframe, GstVideoFrame * outframe);
150 static GstFlowReturn gst_lcms_transform_frame_ip (GstVideoFilter * vfilter,
151     GstVideoFrame * frame);
152
153 static void gst_lcms_get_ready (GstLcms * lcms);
154 static void gst_lcms_create_transform (GstLcms * lcms);
155 static void gst_lcms_cleanup_cms (GstLcms * lcms);
156 static void gst_lcms_init_lookup_table (GstLcms * lcms);
157 static void gst_lcms_process_rgb (GstLcms * lcms, GstVideoFrame * inframe,
158     GstVideoFrame * outframe);
159
160 G_DEFINE_TYPE (GstLcms, gst_lcms, GST_TYPE_VIDEO_FILTER);
161
162 static void
163 gst_lcms_class_init (GstLcmsClass * klass)
164 {
165   GObjectClass *gobject_class = (GObjectClass *) klass;
166   GstElementClass *element_class = (GstElementClass *) klass;
167   GstBaseTransformClass *trans_class = (GstBaseTransformClass *) klass;
168   GstVideoFilterClass *vfilter_class = (GstVideoFilterClass *) klass;
169
170   GST_DEBUG_CATEGORY_INIT (lcms_debug, "lcms", 0, "lcms");
171
172   gobject_class->set_property = gst_lcms_set_property;
173   gobject_class->get_property = gst_lcms_get_property;
174   gobject_class->finalize = gst_lcms_finalize;
175
176   g_object_class_install_property (gobject_class, PROP_INTENT,
177       g_param_spec_enum ("intent", "Rendering intent",
178           "Select the rendering intent of the color correction",
179           GST_TYPE_LCMS_INTENT, DEFAULT_INTENT,
180           (G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
181
182   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SRC_FILE,
183       g_param_spec_string ("input-profile", "Input ICC profile file",
184           "Specify the input ICC profile file to apply", NULL,
185           (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
186
187   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DST_FILE,
188       g_param_spec_string ("dest-profile", "Destination ICC profile file",
189           "Specify the destination ICC profile file to apply", NULL,
190           (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
191
192   g_object_class_install_property (gobject_class, PROP_LOOKUP_METHOD,
193       g_param_spec_enum ("lookup", "Lookup method",
194           "Select the caching method for the color compensation calculations",
195           GST_TYPE_LCMS_LOOKUP_METHOD, DEFAULT_LOOKUP_METHOD,
196           (G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
197
198   g_object_class_install_property (gobject_class, PROP_PRESERVE_BLACK,
199       g_param_spec_boolean ("preserve-black", "Preserve black",
200           "Select whether purely black pixels should be preserved",
201           DEFAULT_PRESERVE_BLACK,
202           (G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
203
204   g_object_class_install_property (gobject_class, PROP_EMBEDDED_PROFILE,
205       g_param_spec_boolean ("embedded-profile", "Embedded Profile",
206           "Extract and use source profiles embedded in images",
207           DEFAULT_EMBEDDED_PROFILE,
208           (G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
209
210   gst_element_class_set_static_metadata (element_class,
211       "LCMS2 ICC correction", "Filter/Effect/Video",
212       "Uses LittleCMS 2 to perform ICC profile correction",
213       "Andreas Frisch <fraxinas@opendreambox.org>");
214
215   gst_element_class_add_pad_template (element_class,
216       gst_static_pad_template_get (&gst_lcms_sink_template));
217   gst_element_class_add_pad_template (element_class,
218       gst_static_pad_template_get (&gst_lcms_src_template));
219
220   element_class->change_state = GST_DEBUG_FUNCPTR (gst_lcms_change_state);
221
222   trans_class->sink_event = GST_DEBUG_FUNCPTR (gst_lcms_sink_event);
223
224   vfilter_class->set_info = GST_DEBUG_FUNCPTR (gst_lcms_set_info);
225   vfilter_class->transform_frame_ip =
226       GST_DEBUG_FUNCPTR (gst_lcms_transform_frame_ip);
227   vfilter_class->transform_frame = GST_DEBUG_FUNCPTR (gst_lcms_transform_frame);
228
229   gst_type_mark_as_plugin_api (GST_TYPE_LCMS_INTENT, 0);
230   gst_type_mark_as_plugin_api (GST_TYPE_LCMS_LOOKUP_METHOD, 0);
231 }
232
233 static void
234 gst_lcms_init (GstLcms * lcms)
235 {
236   lcms->color_lut = NULL;
237   lcms->cms_inp_profile = NULL;
238   lcms->cms_dst_profile = NULL;
239   lcms->cms_transform = NULL;
240 }
241
242 static void
243 gst_lcms_finalize (GObject * object)
244 {
245   GstLcms *lcms = GST_LCMS (object);
246   if (lcms->color_lut)
247     g_free (lcms->color_lut);
248   g_free (lcms->inp_profile_filename);
249   g_free (lcms->dst_profile_filename);
250   G_OBJECT_CLASS (gst_lcms_parent_class)->finalize (object);
251 }
252
253 static void
254 gst_lcms_set_intent (GstLcms * lcms, GstLcmsIntent intent)
255 {
256   GEnumValue *val =
257       g_enum_get_value (G_ENUM_CLASS (g_type_class_ref
258           (GST_TYPE_LCMS_INTENT)), intent);
259   const gchar *value_nick;
260
261   g_return_if_fail (GST_IS_LCMS (lcms));
262   if (!val) {
263     GST_ERROR_OBJECT (lcms, "no such rendering intent %i!", intent);
264     return;
265   }
266   value_nick = val->value_nick;
267
268   GST_OBJECT_LOCK (lcms);
269   lcms->intent = intent;
270   GST_OBJECT_UNLOCK (lcms);
271
272   GST_DEBUG_OBJECT (lcms, "successfully set rendering intent to %s (%i)",
273       value_nick, intent);
274   return;
275 }
276
277 static GstLcmsIntent
278 gst_lcms_get_intent (GstLcms * lcms)
279 {
280   g_return_val_if_fail (GST_IS_LCMS (lcms), (GstLcmsIntent) - 1);
281   return lcms->intent;
282 }
283
284 static void
285 gst_lcms_set_lookup_method (GstLcms * lcms, GstLcmsLookupMethod method)
286 {
287   GEnumValue *val =
288       g_enum_get_value (G_ENUM_CLASS (g_type_class_ref
289           (GST_TYPE_LCMS_LOOKUP_METHOD)), method);
290   const gchar *value_nick;
291
292   g_return_if_fail (GST_IS_LCMS (lcms));
293   if (!val) {
294     GST_ERROR_OBJECT (lcms, "no such lookup method %i!", method);
295     return;
296   }
297   value_nick = val->value_nick;
298
299   GST_OBJECT_LOCK (lcms);
300   lcms->lookup_method = method;
301   GST_OBJECT_UNLOCK (lcms);
302
303   GST_DEBUG_OBJECT (lcms, "successfully set lookup method to %s (%i)",
304       value_nick, method);
305   return;
306 }
307
308 static GstLcmsLookupMethod
309 gst_lcms_get_lookup_method (GstLcms * lcms)
310 {
311   g_return_val_if_fail (GST_IS_LCMS (lcms), (GstLcmsLookupMethod) - 1);
312   return lcms->lookup_method;
313 }
314
315 static void
316 gst_lcms_set_property (GObject * object, guint prop_id, const GValue * value,
317     GParamSpec * pspec)
318 {
319   const gchar *filename;
320   GstLcms *lcms = GST_LCMS (object);
321
322   switch (prop_id) {
323     case PROP_SRC_FILE:
324     {
325       GST_OBJECT_LOCK (lcms);
326       filename = g_value_get_string (value);
327       if (filename
328           && g_file_test (filename,
329               (GFileTest) (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR))) {
330         if (lcms->inp_profile_filename)
331           g_free (lcms->inp_profile_filename);
332         lcms->inp_profile_filename = g_strdup (filename);
333       } else {
334         GST_WARNING_OBJECT (lcms, "Input profile file '%s' not found!",
335             filename);
336       }
337       GST_OBJECT_UNLOCK (lcms);
338       break;
339     }
340     case PROP_DST_FILE:
341     {
342       GST_OBJECT_LOCK (lcms);
343       filename = g_value_get_string (value);
344       if (g_file_test (filename,
345               (GFileTest) (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR))) {
346         if (lcms->dst_profile_filename)
347           g_free (lcms->dst_profile_filename);
348         lcms->dst_profile_filename = g_strdup (filename);
349       } else {
350         GST_WARNING_OBJECT (lcms, "Destination profile file '%s' not found!",
351             filename);
352       }
353       GST_OBJECT_UNLOCK (lcms);
354       break;
355     }
356     case PROP_INTENT:
357       gst_lcms_set_intent (lcms, (GstLcmsIntent) g_value_get_enum (value));
358       break;
359     case PROP_LOOKUP_METHOD:
360       gst_lcms_set_lookup_method (lcms,
361           (GstLcmsLookupMethod) g_value_get_enum (value));
362       break;
363     case PROP_PRESERVE_BLACK:
364       lcms->preserve_black = g_value_get_boolean (value);
365       break;
366     case PROP_EMBEDDED_PROFILE:
367       lcms->embeddedprofiles = g_value_get_boolean (value);
368       break;
369     default:
370       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
371       break;
372   }
373 }
374
375 static void
376 gst_lcms_get_property (GObject * object, guint prop_id, GValue * value,
377     GParamSpec * pspec)
378 {
379   GstLcms *lcms = GST_LCMS (object);
380
381   switch (prop_id) {
382     case PROP_SRC_FILE:
383       g_value_set_string (value, lcms->inp_profile_filename);
384       break;
385     case PROP_DST_FILE:
386       g_value_set_string (value, lcms->dst_profile_filename);
387       break;
388     case PROP_INTENT:
389       g_value_set_enum (value, gst_lcms_get_intent (lcms));
390       break;
391     case PROP_LOOKUP_METHOD:
392       g_value_set_enum (value, gst_lcms_get_lookup_method (lcms));
393       break;
394     case PROP_PRESERVE_BLACK:
395       g_value_set_boolean (value, lcms->preserve_black);
396       break;
397     case PROP_EMBEDDED_PROFILE:
398       g_value_set_boolean (value, lcms->embeddedprofiles);
399       break;
400     default:
401       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
402       break;
403   }
404 }
405
406 static GstStateChangeReturn
407 gst_lcms_change_state (GstElement * element, GstStateChange transition)
408 {
409   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
410   GstLcms *lcms = GST_LCMS (element);
411
412   switch (transition) {
413     case GST_STATE_CHANGE_NULL_TO_READY:
414       GST_DEBUG_OBJECT (lcms, "GST_STATE_CHANGE_NULL_TO_READY");
415       gst_lcms_get_ready (lcms);
416       break;
417     case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
418     {
419       if (!lcms->cms_inp_profile) {
420         if (!lcms->cms_dst_profile) {
421           GST_WARNING_OBJECT (lcms,
422               "No input or output ICC profile specified, falling back to passthrough!");
423           gst_base_transform_set_passthrough (GST_BASE_TRANSFORM (lcms), TRUE);
424           GST_BASE_TRANSFORM_CLASS (GST_LCMS_GET_CLASS
425               (lcms))->transform_ip_on_passthrough = lcms->embeddedprofiles;
426           return GST_STATE_CHANGE_SUCCESS;
427         }
428         lcms->cms_inp_profile = cmsCreate_sRGBProfile ();
429         GST_INFO_OBJECT (lcms,
430             "No input profile specified, falling back to sRGB");
431       }
432     }
433
434     default:
435       break;
436   }
437
438   if (ret == GST_STATE_CHANGE_SUCCESS)
439     ret =
440         GST_ELEMENT_CLASS (gst_lcms_parent_class)->change_state (element,
441         transition);
442
443   switch (transition) {
444     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
445     case GST_STATE_CHANGE_PAUSED_TO_READY:
446       break;
447     case GST_STATE_CHANGE_READY_TO_NULL:
448       gst_lcms_cleanup_cms (lcms);
449     default:
450       break;
451   }
452   return ret;
453 }
454
455 static void
456 gst_lcms_get_ready (GstLcms * lcms)
457 {
458   if (lcms->inp_profile_filename) {
459     lcms->cms_inp_profile =
460         cmsOpenProfileFromFile (lcms->inp_profile_filename, "r");
461     if (!lcms->cms_inp_profile)
462       GST_ERROR_OBJECT (lcms, "Couldn't parse input ICC profile '%s'",
463           lcms->inp_profile_filename);
464     else
465       GST_DEBUG_OBJECT (lcms, "Successfully opened input ICC profile '%s'",
466           lcms->inp_profile_filename);
467   }
468
469   if (lcms->dst_profile_filename) {
470     lcms->cms_dst_profile =
471         cmsOpenProfileFromFile (lcms->dst_profile_filename, "r");
472     if (!lcms->cms_dst_profile)
473       GST_ERROR_OBJECT (lcms,
474           "Couldn't parse destination ICC profile '%s'",
475           lcms->dst_profile_filename);
476     else
477       GST_DEBUG_OBJECT (lcms, "Successfully opened output ICC profile '%s'",
478           lcms->dst_profile_filename);
479   }
480
481   if (lcms->lookup_method != GST_LCMS_LOOKUP_METHOD_UNCACHED) {
482     gst_lcms_init_lookup_table (lcms);
483   }
484 }
485
486 static void
487 gst_lcms_cleanup_cms (GstLcms * lcms)
488 {
489   if (lcms->cms_inp_profile) {
490     cmsCloseProfile (lcms->cms_inp_profile);
491     lcms->cms_inp_profile = NULL;
492   }
493   if (lcms->cms_dst_profile) {
494     cmsCloseProfile (lcms->cms_dst_profile);
495     lcms->cms_dst_profile = NULL;
496   }
497   if (lcms->cms_transform) {
498     cmsDeleteTransform (lcms->cms_transform);
499     lcms->cms_transform = NULL;
500   }
501 }
502
503 static void
504 gst_lcms_init_lookup_table (GstLcms * lcms)
505 {
506   guint32 p;
507   const guint32 color_max = 0x01000000;
508
509   if (lcms->color_lut)
510     g_free (lcms->color_lut);
511
512   lcms->color_lut = g_new (guint32, color_max);
513
514   if (lcms->color_lut == NULL) {
515     GST_ELEMENT_ERROR (lcms, RESOURCE, FAILED, ("LUT alloc failed"),
516         ("Unable to open allocate memory for lookup table!"));
517     return;
518   }
519
520   if (lcms->lookup_method == GST_LCMS_LOOKUP_METHOD_PRECALCULATED) {
521     cmsHTRANSFORM hTransform;
522     hTransform =
523         cmsCreateTransform (lcms->cms_inp_profile, TYPE_RGB_8,
524         lcms->cms_dst_profile, TYPE_RGB_8, lcms->intent, 0);
525     /*FIXME use cmsFLAGS_COPY_ALPHA when new lcms2 2.8 release is available */
526     for (p = 0; p < color_max; p++)
527       cmsDoTransform (hTransform, (const cmsUInt32Number *) &p,
528           &lcms->color_lut[p], 1);
529     cmsDeleteTransform (hTransform);
530     GST_DEBUG_OBJECT (lcms, "writing lookup table finished");
531   } else if (lcms->lookup_method == GST_LCMS_LOOKUP_METHOD_CACHED) {
532     memset (lcms->color_lut, 0xAA, color_max * sizeof (guint32));
533     GST_DEBUG_OBJECT (lcms, "initialized empty lookup table for caching");
534   }
535   if (lcms->preserve_black)
536     lcms->color_lut[0] = 0x000000;
537 }
538
539 static cmsUInt32Number
540 gst_lcms_cms_format_from_gst (GstVideoFormat gst_format)
541 {
542   cmsUInt32Number cms_format = 0;
543   switch (gst_format) {
544     case GST_VIDEO_FORMAT_ARGB:
545     case GST_VIDEO_FORMAT_xRGB:
546       cms_format = TYPE_ARGB_8;
547       break;
548     case GST_VIDEO_FORMAT_xBGR:
549     case GST_VIDEO_FORMAT_ABGR:
550       cms_format = TYPE_ABGR_8;
551       break;
552     case GST_VIDEO_FORMAT_BGRA:
553     case GST_VIDEO_FORMAT_BGRx:
554       cms_format = TYPE_BGRA_8;
555       break;
556     case GST_VIDEO_FORMAT_BGR:
557       cms_format = TYPE_BGR_8;
558       break;
559     case GST_VIDEO_FORMAT_RGBA:
560     case GST_VIDEO_FORMAT_RGBx:
561       cms_format = TYPE_RGBA_8;
562       break;
563     case GST_VIDEO_FORMAT_RGB:
564       cms_format = TYPE_RGB_8;
565       break;
566     default:
567       break;
568   }
569   return cms_format;
570 }
571
572 static gboolean
573 gst_lcms_set_info (GstVideoFilter * vfilter, GstCaps * incaps,
574     GstVideoInfo * in_info, GstCaps * outcaps, GstVideoInfo * out_info)
575 {
576   GstLcms *lcms = GST_LCMS (vfilter);
577
578   GST_DEBUG_OBJECT (lcms,
579       "setting caps: in %" GST_PTR_FORMAT " out %" GST_PTR_FORMAT, incaps,
580       outcaps);
581
582   lcms->cms_inp_format =
583       gst_lcms_cms_format_from_gst (GST_VIDEO_INFO_FORMAT (in_info));
584   lcms->cms_dst_format =
585       gst_lcms_cms_format_from_gst (GST_VIDEO_INFO_FORMAT (out_info));
586
587   if (gst_base_transform_is_passthrough (GST_BASE_TRANSFORM (lcms)))
588     return TRUE;
589
590   if (!lcms->cms_inp_format || !lcms->cms_dst_format)
591     goto invalid_caps;
592
593   if (lcms->cms_inp_format == lcms->cms_dst_format
594       && lcms->lookup_method != GST_LCMS_LOOKUP_METHOD_UNCACHED) {
595     gst_base_transform_set_in_place (GST_BASE_TRANSFORM (lcms), TRUE);
596   } else
597     gst_base_transform_set_in_place (GST_BASE_TRANSFORM (lcms), FALSE);
598
599   gst_lcms_create_transform (lcms);
600   lcms->process = gst_lcms_process_rgb;
601
602   return TRUE;
603
604 invalid_caps:
605   {
606     GST_ERROR_OBJECT (lcms, "Invalid caps: %" GST_PTR_FORMAT, incaps);
607     return FALSE;
608   }
609 }
610
611 static void
612 gst_lcms_create_transform (GstLcms * lcms)
613 {
614   if (!lcms->cms_dst_profile) {
615     lcms->cms_dst_profile = cmsCreate_sRGBProfile ();
616     GST_INFO_OBJECT (lcms, "No output profile specified, falling back to sRGB");
617   }
618   lcms->cms_transform =
619       cmsCreateTransform (lcms->cms_inp_profile, lcms->cms_inp_format,
620       lcms->cms_dst_profile, lcms->cms_dst_format, lcms->intent, 0);
621   if (lcms->cms_transform) {
622     GST_DEBUG_OBJECT (lcms, "created transformation format=%i->%i",
623         lcms->cms_inp_format, lcms->cms_dst_format);
624   } else {
625     GST_WARNING_OBJECT (lcms,
626         "couldn't create transformation format=%i->%i, fallback to passthrough!",
627         lcms->cms_inp_format, lcms->cms_dst_format);
628     gst_base_transform_set_passthrough (GST_BASE_TRANSFORM (lcms), TRUE);
629   }
630 }
631
632 static gboolean
633 gst_lcms_sink_event (GstBaseTransform * trans, GstEvent * event)
634 {
635   gboolean ret = FALSE;
636   GstLcms *lcms = GST_LCMS (trans);
637
638   switch (GST_EVENT_TYPE (event)) {
639     case GST_EVENT_TAG:
640     {
641       if (lcms->embeddedprofiles) {
642         GstTagList *taglist = NULL;
643         /* icc profiles might be embedded in attachments */
644         gst_event_parse_tag (event, &taglist);
645         gst_lcms_handle_tags (lcms, taglist);
646       }
647       break;
648     }
649     default:
650       break;
651   }
652   ret =
653       GST_BASE_TRANSFORM_CLASS (gst_lcms_parent_class)->sink_event (trans,
654       event);
655   return ret;
656 }
657
658 static void
659 gst_lcms_handle_tag_sample (GstLcms * lcms, GstSample * sample)
660 {
661   GstBuffer *buf;
662   const GstStructure *structure;
663
664   buf = gst_sample_get_buffer (sample);
665   structure = gst_sample_get_info (sample);
666
667   if (!buf || !structure)
668     return;
669
670   if (gst_structure_has_name (structure, "application/vnd.iccprofile")) {
671     if (!lcms->inp_profile_filename
672         && lcms->lookup_method != GST_LCMS_LOOKUP_METHOD_UNCACHED) {
673       GstMapInfo map;
674       const gchar *icc_name;
675       icc_name = gst_structure_get_string (structure, "icc-name");
676       gst_buffer_map (buf, &map, GST_MAP_READ);
677       lcms->cms_inp_profile = cmsOpenProfileFromMem (map.data, map.size);
678       gst_buffer_unmap (buf, &map);
679       if (!lcms->cms_inp_profile)
680         GST_WARNING_OBJECT (lcms,
681             "Couldn't parse embedded input ICC profile '%s'", icc_name);
682       else {
683         GST_DEBUG_OBJECT (lcms,
684             "Successfully opened embedded input ICC profile '%s'", icc_name);
685         if (lcms->cms_inp_format) {
686           gst_lcms_create_transform (lcms);
687           gst_lcms_init_lookup_table (lcms);
688         }
689       }
690     } else {
691       GST_DEBUG_OBJECT (lcms,
692           "disregarding embedded ICC profile because input profile file was explicitly specified");
693     }
694   } else
695     GST_DEBUG_OBJECT (lcms, "attachment is not an ICC profile");
696 }
697
698 static void
699 gst_lcms_handle_tags (GstLcms * lcms, GstTagList * taglist)
700 {
701   guint tag_size;
702
703   if (!taglist)
704     return;
705
706   tag_size = gst_tag_list_get_tag_size (taglist, GST_TAG_ATTACHMENT);
707   if (tag_size > 0) {
708     guint index;
709     GstSample *sample;
710     for (index = 0; index < tag_size; index++) {
711       if (gst_tag_list_get_sample_index (taglist, GST_TAG_ATTACHMENT, index,
712               &sample)) {
713         gst_lcms_handle_tag_sample (lcms, sample);
714         gst_sample_unref (sample);
715       }
716     }
717   }
718 }
719
720 static GstFlowReturn
721 gst_lcms_transform_frame_ip (GstVideoFilter * vfilter, GstVideoFrame * inframe)
722 {
723   GstLcms *lcms = GST_LCMS (vfilter);
724   if (!gst_base_transform_is_passthrough (GST_BASE_TRANSFORM (lcms)))
725     lcms->process (lcms, inframe, NULL);
726   return GST_FLOW_OK;
727 }
728
729 static GstFlowReturn
730 gst_lcms_transform_frame (GstVideoFilter * vfilter, GstVideoFrame * inframe,
731     GstVideoFrame * outframe)
732 {
733   GstLcms *lcms = GST_LCMS (vfilter);
734   if (!gst_base_transform_is_passthrough (GST_BASE_TRANSFORM (lcms)))
735     lcms->process (lcms, inframe, outframe);
736   return GST_FLOW_OK;
737 }
738
739 static void
740 gst_lcms_process_rgb (GstLcms * lcms, GstVideoFrame * inframe,
741     GstVideoFrame * outframe)
742 {
743   gint height;
744   gint width, in_stride, out_stride;
745   gint in_pixel_stride, out_pixel_stride;
746   gint in_offsets[4], out_offsets[4];
747   guint8 *in_data, *out_data;
748   gint i, j;
749   gint in_row_wrap, out_row_wrap;
750   guint8 alpha = 0;
751
752   in_data = (guint8 *) GST_VIDEO_FRAME_PLANE_DATA (inframe, 0);
753   in_stride = GST_VIDEO_FRAME_PLANE_STRIDE (inframe, 0);
754   width = GST_VIDEO_FRAME_COMP_WIDTH (inframe, 0);
755   height = GST_VIDEO_FRAME_COMP_HEIGHT (inframe, 0);
756   in_pixel_stride = GST_VIDEO_FRAME_COMP_PSTRIDE (inframe, 0);
757
758   in_offsets[0] = GST_VIDEO_FRAME_COMP_OFFSET (inframe, 0);
759   in_offsets[1] = GST_VIDEO_FRAME_COMP_OFFSET (inframe, 1);
760   in_offsets[2] = GST_VIDEO_FRAME_COMP_OFFSET (inframe, 2);
761   in_offsets[3] = GST_VIDEO_FRAME_COMP_OFFSET (inframe, 3);
762
763   if (outframe) {
764     if (width != GST_VIDEO_FRAME_COMP_WIDTH (outframe, 0)
765         || height != GST_VIDEO_FRAME_COMP_HEIGHT (outframe, 0)) {
766       GST_WARNING_OBJECT (lcms,
767           "can't transform, input dimensions != output dimensions!");
768       return;
769     }
770     out_data = (guint8 *) GST_VIDEO_FRAME_PLANE_DATA (outframe, 0);
771     out_stride = GST_VIDEO_FRAME_PLANE_STRIDE (outframe, 0);
772     out_pixel_stride = GST_VIDEO_FRAME_COMP_PSTRIDE (outframe, 0);
773     out_offsets[0] = GST_VIDEO_FRAME_COMP_OFFSET (inframe, 0);
774     out_offsets[1] = GST_VIDEO_FRAME_COMP_OFFSET (inframe, 1);
775     out_offsets[2] = GST_VIDEO_FRAME_COMP_OFFSET (inframe, 2);
776     out_offsets[3] = GST_VIDEO_FRAME_COMP_OFFSET (inframe, 3);
777     GST_LOG_OBJECT (lcms,
778         "transforming frame (%ix%i) stride=%i->%i pixel_stride=%i->%i format=%s->%s",
779         width, height, in_stride, out_stride, in_pixel_stride, out_pixel_stride,
780         gst_video_format_to_string (inframe->info.finfo->format),
781         gst_video_format_to_string (outframe->info.finfo->format));
782   } else {                      /* in-place transformation */
783     GST_LOG_OBJECT (lcms,
784         "transforming frame IN-PLACE (%ix%i) pixel_stride=%i format=%s", width,
785         height, in_pixel_stride,
786         gst_video_format_to_string (inframe->info.finfo->format));
787     out_data = in_data;
788     out_stride = in_stride;
789     out_pixel_stride = in_pixel_stride;
790     out_offsets[0] = in_offsets[0];
791     out_offsets[1] = in_offsets[1];
792     out_offsets[2] = in_offsets[2];
793     out_offsets[3] = in_offsets[3];
794   }
795
796   in_row_wrap = in_stride - in_pixel_stride * width;
797   out_row_wrap = out_stride - out_pixel_stride * width;
798
799   if (lcms->lookup_method == GST_LCMS_LOOKUP_METHOD_UNCACHED) {
800     if (!GST_VIDEO_FORMAT_INFO_HAS_ALPHA (inframe->info.finfo)
801         && !lcms->preserve_black) {
802       GST_DEBUG_OBJECT (lcms,
803           "GST_LCMS_LOOKUP_METHOD_UNCACHED WITHOUT alpha AND WITHOUT preserve-black -> picture-at-once transformation!");
804       cmsDoTransformStride (lcms->cms_transform, in_data, out_data,
805           height * width, out_pixel_stride);
806     } else {
807       GST_DEBUG_OBJECT (lcms,
808           "GST_LCMS_LOOKUP_METHOD_UNCACHED WITH alpha or preserve-black -> pixel-by-pixel transformation!");
809       for (i = 0; i < height; i++) {
810         for (j = 0; j < width; j++) {
811           if (GST_VIDEO_FORMAT_INFO_HAS_ALPHA (inframe->info.finfo))
812             alpha = in_data[in_offsets[3]];
813           if (lcms->preserve_black && (in_data[in_offsets[0]] == 0x00)
814               && (in_data[in_offsets[1]] == 0x00)
815               && (in_data[in_offsets[2]] == 0x0))
816             out_data[out_offsets[0]] = out_data[out_offsets[1]] =
817                 out_data[out_offsets[2]] = 0x00;
818           else
819             cmsDoTransformStride (lcms->cms_transform, in_data, out_data, 1,
820                 in_pixel_stride);
821           if (alpha)
822             out_data[in_offsets[3]] = alpha;
823           in_data += in_pixel_stride;
824           out_data += out_pixel_stride;
825         }
826         in_data += in_row_wrap;
827         out_data += out_row_wrap;
828       }
829     }
830   } else if (lcms->lookup_method == GST_LCMS_LOOKUP_METHOD_PRECALCULATED) {
831     guint32 color, new_color;
832     GST_LOG_OBJECT (lcms, "GST_LCMS_LOOKUP_METHOD_PRECALCULATED");
833     for (i = 0; i < height; i++) {
834       for (j = 0; j < width; j++) {
835         color =
836             in_data[in_offsets[0]] |
837             in_data[in_offsets[1]] << 0x08 | in_data[in_offsets[2]] << 0x10;
838         new_color = lcms->color_lut[color];
839         out_data[out_offsets[0]] = (new_color & 0x0000FF) >> 0x00;
840         out_data[out_offsets[1]] = (new_color & 0x00FF00) >> 0x08;
841         out_data[out_offsets[2]] = (new_color & 0xFF0000) >> 0x10;
842         GST_TRACE_OBJECT (lcms,
843             "(%i:%i)@%p original color 0x%08X (dest was 0x%08X)", i, j, in_data,
844             color, new_color);
845         if (GST_VIDEO_FORMAT_INFO_HAS_ALPHA (inframe->info.finfo)) {
846           out_data[in_offsets[3]] = in_data[out_offsets[3]];
847         }
848         in_data += in_pixel_stride;
849         out_data += out_pixel_stride;
850       }
851       in_data += in_row_wrap;
852       out_data += out_row_wrap;
853     }
854   } else if (lcms->lookup_method == GST_LCMS_LOOKUP_METHOD_CACHED) {
855     guint32 color, new_color;
856     GST_LOG_OBJECT (lcms, "GST_LCMS_LOOKUP_METHOD_CACHED");
857     for (i = 0; i < height; i++) {
858       for (j = 0; j < width; j++) {
859         if (GST_VIDEO_FORMAT_INFO_HAS_ALPHA (inframe->info.finfo))
860           alpha = in_data[in_offsets[3]];
861         color =
862             in_data[in_offsets[0]] |
863             in_data[in_offsets[1]] << 0x08 | in_data[in_offsets[2]] << 0x10;
864         new_color = lcms->color_lut[color];
865         if (new_color == 0xAAAAAAAA) {
866           cmsDoTransform (lcms->cms_transform, in_data, out_data, 1);
867           new_color =
868               out_data[out_offsets[0]] |
869               out_data[out_offsets[1]] << 0x08 |
870               out_data[out_offsets[2]] << 0x10;
871           GST_OBJECT_LOCK (lcms);
872           lcms->color_lut[color] = new_color;
873           GST_OBJECT_UNLOCK (lcms);
874           GST_TRACE_OBJECT (lcms, "cached color 0x%08X -> 0x%08X", color,
875               new_color);
876         } else {
877           out_data[out_offsets[0]] = (new_color & 0x0000FF) >> 0x00;
878           out_data[out_offsets[1]] = (new_color & 0x00FF00) >> 0x08;
879           out_data[out_offsets[2]] = (new_color & 0xFF0000) >> 0x10;
880         }
881         if (alpha) {
882           out_data[in_offsets[3]] = alpha;
883         }
884         in_data += in_pixel_stride;
885         out_data += out_pixel_stride;
886       }
887       in_data += in_row_wrap;
888       out_data += out_row_wrap;
889     }
890   }
891 }