acb9de99e0fda584372a240c24f9d1633360719a
[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 occurence",
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
230 static void
231 gst_lcms_init (GstLcms * lcms)
232 {
233   lcms->color_lut = NULL;
234   lcms->cms_inp_profile = NULL;
235   lcms->cms_dst_profile = NULL;
236   lcms->cms_transform = NULL;
237 }
238
239 static void
240 gst_lcms_finalize (GObject * object)
241 {
242   GstLcms *lcms = GST_LCMS (object);
243   if (lcms->color_lut)
244     g_free (lcms->color_lut);
245   g_free (lcms->inp_profile_filename);
246   g_free (lcms->dst_profile_filename);
247   G_OBJECT_CLASS (gst_lcms_parent_class)->finalize (object);
248 }
249
250 static void
251 gst_lcms_set_intent (GstLcms * lcms, GstLcmsIntent intent)
252 {
253   GEnumValue *val =
254       g_enum_get_value (G_ENUM_CLASS (g_type_class_ref
255           (GST_TYPE_LCMS_INTENT)), intent);
256   const gchar *value_nick;
257
258   g_return_if_fail (GST_IS_LCMS (lcms));
259   if (!val) {
260     GST_ERROR_OBJECT (lcms, "no such rendering intent %i!", intent);
261     return;
262   }
263   value_nick = val->value_nick;
264
265   GST_OBJECT_LOCK (lcms);
266   lcms->intent = intent;
267   GST_OBJECT_UNLOCK (lcms);
268
269   GST_DEBUG_OBJECT (lcms, "successfully set rendering intent to %s (%i)",
270       value_nick, intent);
271   return;
272 }
273
274 static GstLcmsIntent
275 gst_lcms_get_intent (GstLcms * lcms)
276 {
277   g_return_val_if_fail (GST_IS_LCMS (lcms), (GstLcmsIntent) - 1);
278   return lcms->intent;
279 }
280
281 static void
282 gst_lcms_set_lookup_method (GstLcms * lcms, GstLcmsLookupMethod method)
283 {
284   GEnumValue *val =
285       g_enum_get_value (G_ENUM_CLASS (g_type_class_ref
286           (GST_TYPE_LCMS_LOOKUP_METHOD)), method);
287   const gchar *value_nick;
288
289   g_return_if_fail (GST_IS_LCMS (lcms));
290   if (!val) {
291     GST_ERROR_OBJECT (lcms, "no such lookup method %i!", method);
292     return;
293   }
294   value_nick = val->value_nick;
295
296   GST_OBJECT_LOCK (lcms);
297   lcms->lookup_method = method;
298   GST_OBJECT_UNLOCK (lcms);
299
300   GST_DEBUG_OBJECT (lcms, "successfully set lookup method to %s (%i)",
301       value_nick, method);
302   return;
303 }
304
305 static GstLcmsLookupMethod
306 gst_lcms_get_lookup_method (GstLcms * lcms)
307 {
308   g_return_val_if_fail (GST_IS_LCMS (lcms), (GstLcmsLookupMethod) - 1);
309   return lcms->lookup_method;
310 }
311
312 static void
313 gst_lcms_set_property (GObject * object, guint prop_id, const GValue * value,
314     GParamSpec * pspec)
315 {
316   const gchar *filename;
317   GstLcms *lcms = GST_LCMS (object);
318
319   switch (prop_id) {
320     case PROP_SRC_FILE:
321     {
322       GST_OBJECT_LOCK (lcms);
323       filename = g_value_get_string (value);
324       if (filename
325           && g_file_test (filename,
326               (GFileTest) (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR))) {
327         if (lcms->inp_profile_filename)
328           g_free (lcms->inp_profile_filename);
329         lcms->inp_profile_filename = g_strdup (filename);
330       } else {
331         GST_WARNING_OBJECT (lcms, "Input profile file '%s' not found!",
332             filename);
333       }
334       GST_OBJECT_UNLOCK (lcms);
335       break;
336     }
337     case PROP_DST_FILE:
338     {
339       GST_OBJECT_LOCK (lcms);
340       filename = g_value_get_string (value);
341       if (g_file_test (filename,
342               (GFileTest) (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR))) {
343         if (lcms->dst_profile_filename)
344           g_free (lcms->dst_profile_filename);
345         lcms->dst_profile_filename = g_strdup (filename);
346       } else {
347         GST_WARNING_OBJECT (lcms, "Destination profile file '%s' not found!",
348             filename);
349       }
350       GST_OBJECT_UNLOCK (lcms);
351       break;
352     }
353     case PROP_INTENT:
354       gst_lcms_set_intent (lcms, (GstLcmsIntent) g_value_get_enum (value));
355       break;
356     case PROP_LOOKUP_METHOD:
357       gst_lcms_set_lookup_method (lcms,
358           (GstLcmsLookupMethod) g_value_get_enum (value));
359       break;
360     case PROP_PRESERVE_BLACK:
361       lcms->preserve_black = g_value_get_boolean (value);
362       break;
363     case PROP_EMBEDDED_PROFILE:
364       lcms->embeddedprofiles = g_value_get_boolean (value);
365       break;
366     default:
367       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
368       break;
369   }
370 }
371
372 static void
373 gst_lcms_get_property (GObject * object, guint prop_id, GValue * value,
374     GParamSpec * pspec)
375 {
376   GstLcms *lcms = GST_LCMS (object);
377
378   switch (prop_id) {
379     case PROP_SRC_FILE:
380       g_value_set_string (value, lcms->inp_profile_filename);
381       break;
382     case PROP_DST_FILE:
383       g_value_set_string (value, lcms->dst_profile_filename);
384       break;
385     case PROP_INTENT:
386       g_value_set_enum (value, gst_lcms_get_intent (lcms));
387       break;
388     case PROP_LOOKUP_METHOD:
389       g_value_set_enum (value, gst_lcms_get_lookup_method (lcms));
390       break;
391     case PROP_PRESERVE_BLACK:
392       g_value_set_boolean (value, lcms->preserve_black);
393       break;
394     case PROP_EMBEDDED_PROFILE:
395       g_value_set_boolean (value, lcms->embeddedprofiles);
396       break;
397     default:
398       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
399       break;
400   }
401 }
402
403 static GstStateChangeReturn
404 gst_lcms_change_state (GstElement * element, GstStateChange transition)
405 {
406   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
407   GstLcms *lcms = GST_LCMS (element);
408
409   switch (transition) {
410     case GST_STATE_CHANGE_NULL_TO_READY:
411       GST_DEBUG_OBJECT (lcms, "GST_STATE_CHANGE_NULL_TO_READY");
412       gst_lcms_get_ready (lcms);
413       break;
414     case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
415     {
416       if (!lcms->cms_inp_profile) {
417         if (!lcms->cms_dst_profile) {
418           GST_WARNING_OBJECT (lcms,
419               "No input or output ICC profile specified, falling back to passthrough!");
420           gst_base_transform_set_passthrough (GST_BASE_TRANSFORM (lcms), TRUE);
421           GST_BASE_TRANSFORM_CLASS (GST_LCMS_GET_CLASS
422               (lcms))->transform_ip_on_passthrough = lcms->embeddedprofiles;
423           return GST_STATE_CHANGE_SUCCESS;
424         }
425         lcms->cms_inp_profile = cmsCreate_sRGBProfile ();
426         GST_INFO_OBJECT (lcms,
427             "No input profile specified, falling back to sRGB");
428       }
429     }
430
431     default:
432       break;
433   }
434
435   if (ret == GST_STATE_CHANGE_SUCCESS)
436     ret =
437         GST_ELEMENT_CLASS (gst_lcms_parent_class)->change_state (element,
438         transition);
439
440   switch (transition) {
441     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
442     case GST_STATE_CHANGE_PAUSED_TO_READY:
443       break;
444     case GST_STATE_CHANGE_READY_TO_NULL:
445       gst_lcms_cleanup_cms (lcms);
446     default:
447       break;
448   }
449   return ret;
450 }
451
452 static void
453 gst_lcms_get_ready (GstLcms * lcms)
454 {
455   if (lcms->inp_profile_filename) {
456     lcms->cms_inp_profile =
457         cmsOpenProfileFromFile (lcms->inp_profile_filename, "r");
458     if (!lcms->cms_inp_profile)
459       GST_ERROR_OBJECT (lcms, "Couldn't parse input ICC profile '%s'",
460           lcms->inp_profile_filename);
461     else
462       GST_DEBUG_OBJECT (lcms, "Successfully opened input ICC profile '%s'",
463           lcms->inp_profile_filename);
464   }
465
466   if (lcms->dst_profile_filename) {
467     lcms->cms_dst_profile =
468         cmsOpenProfileFromFile (lcms->dst_profile_filename, "r");
469     if (!lcms->cms_dst_profile)
470       GST_ERROR_OBJECT (lcms,
471           "Couldn't parse destination ICC profile '%s'",
472           lcms->dst_profile_filename);
473     else
474       GST_DEBUG_OBJECT (lcms, "Successfully opened output ICC profile '%s'",
475           lcms->dst_profile_filename);
476   }
477
478   if (lcms->lookup_method != GST_LCMS_LOOKUP_METHOD_UNCACHED) {
479     gst_lcms_init_lookup_table (lcms);
480   }
481 }
482
483 static void
484 gst_lcms_cleanup_cms (GstLcms * lcms)
485 {
486   if (lcms->cms_inp_profile) {
487     cmsCloseProfile (lcms->cms_inp_profile);
488     lcms->cms_inp_profile = NULL;
489   }
490   if (lcms->cms_dst_profile) {
491     cmsCloseProfile (lcms->cms_dst_profile);
492     lcms->cms_dst_profile = NULL;
493   }
494   if (lcms->cms_transform) {
495     cmsDeleteTransform (lcms->cms_transform);
496     lcms->cms_transform = NULL;
497   }
498 }
499
500 static void
501 gst_lcms_init_lookup_table (GstLcms * lcms)
502 {
503   guint32 p;
504   const guint32 color_max = 0x01000000;
505
506   if (lcms->color_lut)
507     g_free (lcms->color_lut);
508
509   lcms->color_lut = g_new (guint32, color_max);
510
511   if (lcms->color_lut == NULL) {
512     GST_ELEMENT_ERROR (lcms, RESOURCE, FAILED, ("LUT alloc failed"),
513         ("Unable to open allocate memory for lookup table!"));
514     return;
515   }
516
517   if (lcms->lookup_method == GST_LCMS_LOOKUP_METHOD_PRECALCULATED) {
518     cmsHTRANSFORM hTransform;
519     hTransform =
520         cmsCreateTransform (lcms->cms_inp_profile, TYPE_RGB_8,
521         lcms->cms_dst_profile, TYPE_RGB_8, lcms->intent, 0);
522     /*FIXME use cmsFLAGS_COPY_ALPHA when new lcms2 2.8 release is available */
523     for (p = 0; p < color_max; p++)
524       cmsDoTransform (hTransform, (const cmsUInt32Number *) &p,
525           &lcms->color_lut[p], 1);
526     cmsDeleteTransform (hTransform);
527     GST_DEBUG_OBJECT (lcms, "writing lookup table finished");
528   } else if (lcms->lookup_method == GST_LCMS_LOOKUP_METHOD_CACHED) {
529     memset (lcms->color_lut, 0xAA, color_max * sizeof (guint32));
530     GST_DEBUG_OBJECT (lcms, "initialized empty lookup table for caching");
531   }
532   if (lcms->preserve_black)
533     lcms->color_lut[0] = 0x000000;
534 }
535
536 static cmsUInt32Number
537 gst_lcms_cms_format_from_gst (GstVideoFormat gst_format)
538 {
539   cmsUInt32Number cms_format = 0;
540   switch (gst_format) {
541     case GST_VIDEO_FORMAT_ARGB:
542     case GST_VIDEO_FORMAT_xRGB:
543       cms_format = TYPE_ARGB_8;
544       break;
545     case GST_VIDEO_FORMAT_xBGR:
546     case GST_VIDEO_FORMAT_ABGR:
547       cms_format = TYPE_ABGR_8;
548       break;
549     case GST_VIDEO_FORMAT_BGRA:
550     case GST_VIDEO_FORMAT_BGRx:
551       cms_format = TYPE_BGRA_8;
552       break;
553     case GST_VIDEO_FORMAT_BGR:
554       cms_format = TYPE_BGR_8;
555       break;
556     case GST_VIDEO_FORMAT_RGBA:
557     case GST_VIDEO_FORMAT_RGBx:
558       cms_format = TYPE_RGBA_8;
559       break;
560     case GST_VIDEO_FORMAT_RGB:
561       cms_format = TYPE_RGB_8;
562       break;
563     default:
564       break;
565   }
566   return cms_format;
567 }
568
569 static gboolean
570 gst_lcms_set_info (GstVideoFilter * vfilter, GstCaps * incaps,
571     GstVideoInfo * in_info, GstCaps * outcaps, GstVideoInfo * out_info)
572 {
573   GstLcms *lcms = GST_LCMS (vfilter);
574
575   GST_DEBUG_OBJECT (lcms,
576       "setting caps: in %" GST_PTR_FORMAT " out %" GST_PTR_FORMAT, incaps,
577       outcaps);
578
579   lcms->cms_inp_format =
580       gst_lcms_cms_format_from_gst (GST_VIDEO_INFO_FORMAT (in_info));
581   lcms->cms_dst_format =
582       gst_lcms_cms_format_from_gst (GST_VIDEO_INFO_FORMAT (out_info));
583
584   if (gst_base_transform_is_passthrough (GST_BASE_TRANSFORM (lcms)))
585     return TRUE;
586
587   if (!lcms->cms_inp_format || !lcms->cms_dst_format)
588     goto invalid_caps;
589
590   if (lcms->cms_inp_format == lcms->cms_dst_format
591       && lcms->lookup_method != GST_LCMS_LOOKUP_METHOD_UNCACHED) {
592     gst_base_transform_set_in_place (GST_BASE_TRANSFORM (lcms), TRUE);
593   } else
594     gst_base_transform_set_in_place (GST_BASE_TRANSFORM (lcms), FALSE);
595
596   gst_lcms_create_transform (lcms);
597   lcms->process = gst_lcms_process_rgb;
598
599   return TRUE;
600
601 invalid_caps:
602   {
603     GST_ERROR_OBJECT (lcms, "Invalid caps: %" GST_PTR_FORMAT, incaps);
604     return FALSE;
605   }
606 }
607
608 static void
609 gst_lcms_create_transform (GstLcms * lcms)
610 {
611   if (!lcms->cms_dst_profile) {
612     lcms->cms_dst_profile = cmsCreate_sRGBProfile ();
613     GST_INFO_OBJECT (lcms, "No output profile specified, falling back to sRGB");
614   }
615   lcms->cms_transform =
616       cmsCreateTransform (lcms->cms_inp_profile, lcms->cms_inp_format,
617       lcms->cms_dst_profile, lcms->cms_dst_format, lcms->intent, 0);
618   if (lcms->cms_transform) {
619     GST_DEBUG_OBJECT (lcms, "created transformation format=%i->%i",
620         lcms->cms_inp_format, lcms->cms_dst_format);
621   } else {
622     GST_WARNING_OBJECT (lcms,
623         "couldn't create transformation format=%i->%i, fallback to passthrough!",
624         lcms->cms_inp_format, lcms->cms_dst_format);
625     gst_base_transform_set_passthrough (GST_BASE_TRANSFORM (lcms), TRUE);
626   }
627 }
628
629 static gboolean
630 gst_lcms_sink_event (GstBaseTransform * trans, GstEvent * event)
631 {
632   gboolean ret = FALSE;
633   GstLcms *lcms = GST_LCMS (trans);
634
635   switch (GST_EVENT_TYPE (event)) {
636     case GST_EVENT_TAG:
637     {
638       if (lcms->embeddedprofiles) {
639         GstTagList *taglist = NULL;
640         /* icc profiles might be embedded in attachments */
641         gst_event_parse_tag (event, &taglist);
642         gst_lcms_handle_tags (lcms, taglist);
643       }
644       break;
645     }
646     default:
647       break;
648   }
649   ret =
650       GST_BASE_TRANSFORM_CLASS (gst_lcms_parent_class)->sink_event (trans,
651       event);
652   return ret;
653 }
654
655 static void
656 gst_lcms_handle_tag_sample (GstLcms * lcms, GstSample * sample)
657 {
658   GstBuffer *buf;
659   const GstStructure *structure;
660
661   buf = gst_sample_get_buffer (sample);
662   structure = gst_sample_get_info (sample);
663
664   if (!buf || !structure)
665     return;
666
667   if (gst_structure_has_name (structure, "application/vnd.iccprofile")) {
668     if (!lcms->inp_profile_filename
669         && lcms->lookup_method != GST_LCMS_LOOKUP_METHOD_UNCACHED) {
670       GstMapInfo map;
671       const gchar *icc_name;
672       icc_name = gst_structure_get_string (structure, "icc-name");
673       gst_buffer_map (buf, &map, GST_MAP_READ);
674       lcms->cms_inp_profile = cmsOpenProfileFromMem (map.data, map.size);
675       gst_buffer_unmap (buf, &map);
676       if (!lcms->cms_inp_profile)
677         GST_WARNING_OBJECT (lcms,
678             "Couldn't parse embedded input ICC profile '%s'", icc_name);
679       else {
680         GST_DEBUG_OBJECT (lcms,
681             "Successfully opened embedded input ICC profile '%s'", icc_name);
682         if (lcms->cms_inp_format) {
683           gst_lcms_create_transform (lcms);
684           gst_lcms_init_lookup_table (lcms);
685         }
686       }
687     } else {
688       GST_DEBUG_OBJECT (lcms,
689           "disregarding embedded ICC profile because input profile file was explicitly specified");
690     }
691   } else
692     GST_DEBUG_OBJECT (lcms, "attachment is not an ICC profile");
693 }
694
695 static void
696 gst_lcms_handle_tags (GstLcms * lcms, GstTagList * taglist)
697 {
698   guint tag_size;
699
700   if (!taglist)
701     return;
702
703   tag_size = gst_tag_list_get_tag_size (taglist, GST_TAG_ATTACHMENT);
704   if (tag_size > 0) {
705     guint index;
706     GstSample *sample;
707     for (index = 0; index < tag_size; index++) {
708       if (gst_tag_list_get_sample_index (taglist, GST_TAG_ATTACHMENT, index,
709               &sample)) {
710         gst_lcms_handle_tag_sample (lcms, sample);
711         gst_sample_unref (sample);
712       }
713     }
714   }
715 }
716
717 static GstFlowReturn
718 gst_lcms_transform_frame_ip (GstVideoFilter * vfilter, GstVideoFrame * inframe)
719 {
720   GstLcms *lcms = GST_LCMS (vfilter);
721   if (!gst_base_transform_is_passthrough (GST_BASE_TRANSFORM (lcms)))
722     lcms->process (lcms, inframe, NULL);
723   return GST_FLOW_OK;
724 }
725
726 static GstFlowReturn
727 gst_lcms_transform_frame (GstVideoFilter * vfilter, GstVideoFrame * inframe,
728     GstVideoFrame * outframe)
729 {
730   GstLcms *lcms = GST_LCMS (vfilter);
731   if (!gst_base_transform_is_passthrough (GST_BASE_TRANSFORM (lcms)))
732     lcms->process (lcms, inframe, outframe);
733   return GST_FLOW_OK;
734 }
735
736 static void
737 gst_lcms_process_rgb (GstLcms * lcms, GstVideoFrame * inframe,
738     GstVideoFrame * outframe)
739 {
740   gint height;
741   gint width, in_stride, out_stride;
742   gint in_pixel_stride, out_pixel_stride;
743   gint in_offsets[4], out_offsets[4];
744   guint8 *in_data, *out_data;
745   gint i, j;
746   gint in_row_wrap, out_row_wrap;
747   guint8 alpha = 0;
748
749   in_data = (guint8 *) GST_VIDEO_FRAME_PLANE_DATA (inframe, 0);
750   in_stride = GST_VIDEO_FRAME_PLANE_STRIDE (inframe, 0);
751   width = GST_VIDEO_FRAME_COMP_WIDTH (inframe, 0);
752   height = GST_VIDEO_FRAME_COMP_HEIGHT (inframe, 0);
753   in_pixel_stride = GST_VIDEO_FRAME_COMP_PSTRIDE (inframe, 0);
754
755   in_offsets[0] = GST_VIDEO_FRAME_COMP_OFFSET (inframe, 0);
756   in_offsets[1] = GST_VIDEO_FRAME_COMP_OFFSET (inframe, 1);
757   in_offsets[2] = GST_VIDEO_FRAME_COMP_OFFSET (inframe, 2);
758   in_offsets[3] = GST_VIDEO_FRAME_COMP_OFFSET (inframe, 3);
759
760   if (outframe) {
761     if (width != GST_VIDEO_FRAME_COMP_WIDTH (outframe, 0)
762         || height != GST_VIDEO_FRAME_COMP_HEIGHT (outframe, 0)) {
763       GST_WARNING_OBJECT (lcms,
764           "can't transform, input dimensions != output dimensions!");
765       return;
766     }
767     out_data = (guint8 *) GST_VIDEO_FRAME_PLANE_DATA (outframe, 0);
768     out_stride = GST_VIDEO_FRAME_PLANE_STRIDE (outframe, 0);
769     out_pixel_stride = GST_VIDEO_FRAME_COMP_PSTRIDE (outframe, 0);
770     out_offsets[0] = GST_VIDEO_FRAME_COMP_OFFSET (inframe, 0);
771     out_offsets[1] = GST_VIDEO_FRAME_COMP_OFFSET (inframe, 1);
772     out_offsets[2] = GST_VIDEO_FRAME_COMP_OFFSET (inframe, 2);
773     out_offsets[3] = GST_VIDEO_FRAME_COMP_OFFSET (inframe, 3);
774     GST_LOG_OBJECT (lcms,
775         "transforming frame (%ix%i) stride=%i->%i pixel_stride=%i->%i format=%s->%s",
776         width, height, in_stride, out_stride, in_pixel_stride, out_pixel_stride,
777         gst_video_format_to_string (inframe->info.finfo->format),
778         gst_video_format_to_string (outframe->info.finfo->format));
779   } else {                      /* in-place transformation */
780     GST_LOG_OBJECT (lcms,
781         "transforming frame IN-PLACE (%ix%i) pixel_stride=%i format=%s", width,
782         height, in_pixel_stride,
783         gst_video_format_to_string (inframe->info.finfo->format));
784     out_data = in_data;
785     out_stride = in_stride;
786     out_pixel_stride = in_pixel_stride;
787     out_offsets[0] = in_offsets[0];
788     out_offsets[1] = in_offsets[1];
789     out_offsets[2] = in_offsets[2];
790     out_offsets[3] = in_offsets[3];
791   }
792
793   in_row_wrap = in_stride - in_pixel_stride * width;
794   out_row_wrap = out_stride - out_pixel_stride * width;
795
796   if (lcms->lookup_method == GST_LCMS_LOOKUP_METHOD_UNCACHED) {
797     if (!GST_VIDEO_FORMAT_INFO_HAS_ALPHA (inframe->info.finfo)
798         && !lcms->preserve_black) {
799       GST_DEBUG_OBJECT (lcms,
800           "GST_LCMS_LOOKUP_METHOD_UNCACHED WITHOUT alpha AND WITHOUT preserve-black -> picture-at-once transformation!");
801       cmsDoTransformStride (lcms->cms_transform, in_data, out_data,
802           height * width, out_pixel_stride);
803     } else {
804       GST_DEBUG_OBJECT (lcms,
805           "GST_LCMS_LOOKUP_METHOD_UNCACHED WITH alpha or preserve-black -> pixel-by-pixel transformation!");
806       for (i = 0; i < height; i++) {
807         for (j = 0; j < width; j++) {
808           if (GST_VIDEO_FORMAT_INFO_HAS_ALPHA (inframe->info.finfo))
809             alpha = in_data[in_offsets[3]];
810           if (lcms->preserve_black && (in_data[in_offsets[0]] == 0x00)
811               && (in_data[in_offsets[1]] == 0x00)
812               && (in_data[in_offsets[2]] == 0x0))
813             out_data[out_offsets[0]] = out_data[out_offsets[1]] =
814                 out_data[out_offsets[2]] = 0x00;
815           else
816             cmsDoTransformStride (lcms->cms_transform, in_data, out_data, 1,
817                 in_pixel_stride);
818           if (alpha)
819             out_data[in_offsets[3]] = alpha;
820           in_data += in_pixel_stride;
821           out_data += out_pixel_stride;
822         }
823         in_data += in_row_wrap;
824         out_data += out_row_wrap;
825       }
826     }
827   } else if (lcms->lookup_method == GST_LCMS_LOOKUP_METHOD_PRECALCULATED) {
828     guint32 color, new_color;
829     GST_LOG_OBJECT (lcms, "GST_LCMS_LOOKUP_METHOD_PRECALCULATED");
830     for (i = 0; i < height; i++) {
831       for (j = 0; j < width; j++) {
832         color =
833             in_data[in_offsets[0]] |
834             in_data[in_offsets[1]] << 0x08 | in_data[in_offsets[2]] << 0x10;
835         new_color = lcms->color_lut[color];
836         out_data[out_offsets[0]] = (new_color & 0x0000FF) >> 0x00;
837         out_data[out_offsets[1]] = (new_color & 0x00FF00) >> 0x08;
838         out_data[out_offsets[2]] = (new_color & 0xFF0000) >> 0x10;
839         GST_TRACE_OBJECT (lcms,
840             "(%i:%i)@%p original color 0x%08X (dest was 0x%08X)", i, j, in_data,
841             color, new_color);
842         if (GST_VIDEO_FORMAT_INFO_HAS_ALPHA (inframe->info.finfo)) {
843           out_data[in_offsets[3]] = in_data[out_offsets[3]];
844         }
845         in_data += in_pixel_stride;
846         out_data += out_pixel_stride;
847       }
848       in_data += in_row_wrap;
849       out_data += out_row_wrap;
850     }
851   } else if (lcms->lookup_method == GST_LCMS_LOOKUP_METHOD_CACHED) {
852     guint32 color, new_color;
853     GST_LOG_OBJECT (lcms, "GST_LCMS_LOOKUP_METHOD_CACHED");
854     for (i = 0; i < height; i++) {
855       for (j = 0; j < width; j++) {
856         if (GST_VIDEO_FORMAT_INFO_HAS_ALPHA (inframe->info.finfo))
857           alpha = in_data[in_offsets[3]];
858         color =
859             in_data[in_offsets[0]] |
860             in_data[in_offsets[1]] << 0x08 | in_data[in_offsets[2]] << 0x10;
861         new_color = lcms->color_lut[color];
862         if (new_color == 0xAAAAAAAA) {
863           cmsDoTransform (lcms->cms_transform, in_data, out_data, 1);
864           new_color =
865               out_data[out_offsets[0]] |
866               out_data[out_offsets[1]] << 0x08 |
867               out_data[out_offsets[2]] << 0x10;
868           GST_OBJECT_LOCK (lcms);
869           lcms->color_lut[color] = new_color;
870           GST_OBJECT_UNLOCK (lcms);
871           GST_TRACE_OBJECT (lcms, "cached color 0x%08X -> 0x%08X", color,
872               new_color);
873         } else {
874           out_data[out_offsets[0]] = (new_color & 0x0000FF) >> 0x00;
875           out_data[out_offsets[1]] = (new_color & 0x00FF00) >> 0x08;
876           out_data[out_offsets[2]] = (new_color & 0xFF0000) >> 0x10;
877         }
878         if (alpha) {
879           out_data[in_offsets[3]] = alpha;
880         }
881         in_data += in_pixel_stride;
882         out_data += out_pixel_stride;
883       }
884       in_data += in_row_wrap;
885       out_data += out_row_wrap;
886     }
887   }
888 }