2 * GStreamer gstreamer-lcms
3 * Copyright (C) 2016 Andreas Frisch <fraxinas@dreambox.guru>
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.
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.
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.
24 * SECTION:element-lcms
25 * @short_description: Uses LittleCMS 2 to perform ICC profile correction
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.
31 * ## Example launch line
33 * (write everything in one line, without the backslash characters)
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
48 #include <gst/video/video.h>
52 GST_DEBUG_CATEGORY_STATIC (lcms_debug);
53 #define GST_CAT_DEFAULT lcms_debug
55 /* GstLcms properties */
68 gst_lcms_intent_get_type (void)
70 static volatile gsize intent_type = 0;
71 static const GEnumValue intent[] = {
72 {GST_LCMS_INTENT_PERCEPTUAL, "Perceptual",
74 {GST_LCMS_INTENT_RELATIVE_COLORIMETRIC, "Relative Colorimetric",
76 {GST_LCMS_INTENT_SATURATION, "Saturation",
78 {GST_LCMS_INTENT_ABSOLUTE_COLORIMETRIC, "Absolute Colorimetric",
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);
87 return (GType) intent_type;
91 gst_lcms_lookup_method_get_type (void)
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)",
98 {GST_LCMS_LOOKUP_METHOD_PRECALCULATED,
99 "Precalculate lookup table (takes a long time getting READY)",
101 {GST_LCMS_LOOKUP_METHOD_CACHED,
102 "Calculate and cache color replacement values on first occurrence",
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);
111 return (GType) lookup_method_type;
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
119 static GstStaticPadTemplate gst_lcms_src_template =
120 GST_STATIC_PAD_TEMPLATE ("src",
123 GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{ "
124 "ARGB, BGRA, ABGR, RGBA, xRGB," "RGBx, xBGR, BGRx, RGB, BGR }"))
127 static GstStaticPadTemplate gst_lcms_sink_template =
128 GST_STATIC_PAD_TEMPLATE ("sink",
131 GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{ "
132 "ARGB, BGRA, ABGR, RGBA, xRGB," "RGBx, xBGR, BGRx, RGB, BGR }"))
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,
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);
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);
160 G_DEFINE_TYPE (GstLcms, gst_lcms, GST_TYPE_VIDEO_FILTER);
163 gst_lcms_class_init (GstLcmsClass * klass)
165 GObjectClass *gobject_class = (GObjectClass *) klass;
166 GstElementClass *element_class = (GstElementClass *) klass;
167 GstBaseTransformClass *trans_class = (GstBaseTransformClass *) klass;
168 GstVideoFilterClass *vfilter_class = (GstVideoFilterClass *) klass;
170 GST_DEBUG_CATEGORY_INIT (lcms_debug, "lcms", 0, "lcms");
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;
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)));
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)));
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)));
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)));
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)));
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)));
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>");
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));
220 element_class->change_state = GST_DEBUG_FUNCPTR (gst_lcms_change_state);
222 trans_class->sink_event = GST_DEBUG_FUNCPTR (gst_lcms_sink_event);
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);
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);
234 gst_lcms_init (GstLcms * lcms)
236 lcms->color_lut = NULL;
237 lcms->cms_inp_profile = NULL;
238 lcms->cms_dst_profile = NULL;
239 lcms->cms_transform = NULL;
243 gst_lcms_finalize (GObject * object)
245 GstLcms *lcms = GST_LCMS (object);
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);
254 gst_lcms_set_intent (GstLcms * lcms, GstLcmsIntent intent)
257 g_enum_get_value (G_ENUM_CLASS (g_type_class_ref
258 (GST_TYPE_LCMS_INTENT)), intent);
259 const gchar *value_nick;
261 g_return_if_fail (GST_IS_LCMS (lcms));
263 GST_ERROR_OBJECT (lcms, "no such rendering intent %i!", intent);
266 value_nick = val->value_nick;
268 GST_OBJECT_LOCK (lcms);
269 lcms->intent = intent;
270 GST_OBJECT_UNLOCK (lcms);
272 GST_DEBUG_OBJECT (lcms, "successfully set rendering intent to %s (%i)",
278 gst_lcms_get_intent (GstLcms * lcms)
280 g_return_val_if_fail (GST_IS_LCMS (lcms), (GstLcmsIntent) - 1);
285 gst_lcms_set_lookup_method (GstLcms * lcms, GstLcmsLookupMethod method)
288 g_enum_get_value (G_ENUM_CLASS (g_type_class_ref
289 (GST_TYPE_LCMS_LOOKUP_METHOD)), method);
290 const gchar *value_nick;
292 g_return_if_fail (GST_IS_LCMS (lcms));
294 GST_ERROR_OBJECT (lcms, "no such lookup method %i!", method);
297 value_nick = val->value_nick;
299 GST_OBJECT_LOCK (lcms);
300 lcms->lookup_method = method;
301 GST_OBJECT_UNLOCK (lcms);
303 GST_DEBUG_OBJECT (lcms, "successfully set lookup method to %s (%i)",
308 static GstLcmsLookupMethod
309 gst_lcms_get_lookup_method (GstLcms * lcms)
311 g_return_val_if_fail (GST_IS_LCMS (lcms), (GstLcmsLookupMethod) - 1);
312 return lcms->lookup_method;
316 gst_lcms_set_property (GObject * object, guint prop_id, const GValue * value,
319 const gchar *filename;
320 GstLcms *lcms = GST_LCMS (object);
325 GST_OBJECT_LOCK (lcms);
326 filename = g_value_get_string (value);
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);
334 GST_WARNING_OBJECT (lcms, "Input profile file '%s' not found!",
337 GST_OBJECT_UNLOCK (lcms);
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);
350 GST_WARNING_OBJECT (lcms, "Destination profile file '%s' not found!",
353 GST_OBJECT_UNLOCK (lcms);
357 gst_lcms_set_intent (lcms, (GstLcmsIntent) g_value_get_enum (value));
359 case PROP_LOOKUP_METHOD:
360 gst_lcms_set_lookup_method (lcms,
361 (GstLcmsLookupMethod) g_value_get_enum (value));
363 case PROP_PRESERVE_BLACK:
364 lcms->preserve_black = g_value_get_boolean (value);
366 case PROP_EMBEDDED_PROFILE:
367 lcms->embeddedprofiles = g_value_get_boolean (value);
370 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
376 gst_lcms_get_property (GObject * object, guint prop_id, GValue * value,
379 GstLcms *lcms = GST_LCMS (object);
383 g_value_set_string (value, lcms->inp_profile_filename);
386 g_value_set_string (value, lcms->dst_profile_filename);
389 g_value_set_enum (value, gst_lcms_get_intent (lcms));
391 case PROP_LOOKUP_METHOD:
392 g_value_set_enum (value, gst_lcms_get_lookup_method (lcms));
394 case PROP_PRESERVE_BLACK:
395 g_value_set_boolean (value, lcms->preserve_black);
397 case PROP_EMBEDDED_PROFILE:
398 g_value_set_boolean (value, lcms->embeddedprofiles);
401 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
406 static GstStateChangeReturn
407 gst_lcms_change_state (GstElement * element, GstStateChange transition)
409 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
410 GstLcms *lcms = GST_LCMS (element);
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);
417 case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
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;
428 lcms->cms_inp_profile = cmsCreate_sRGBProfile ();
429 GST_INFO_OBJECT (lcms,
430 "No input profile specified, falling back to sRGB");
438 if (ret == GST_STATE_CHANGE_SUCCESS)
440 GST_ELEMENT_CLASS (gst_lcms_parent_class)->change_state (element,
443 switch (transition) {
444 case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
445 case GST_STATE_CHANGE_PAUSED_TO_READY:
447 case GST_STATE_CHANGE_READY_TO_NULL:
448 gst_lcms_cleanup_cms (lcms);
456 gst_lcms_get_ready (GstLcms * lcms)
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);
465 GST_DEBUG_OBJECT (lcms, "Successfully opened input ICC profile '%s'",
466 lcms->inp_profile_filename);
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);
477 GST_DEBUG_OBJECT (lcms, "Successfully opened output ICC profile '%s'",
478 lcms->dst_profile_filename);
481 if (lcms->lookup_method != GST_LCMS_LOOKUP_METHOD_UNCACHED) {
482 gst_lcms_init_lookup_table (lcms);
487 gst_lcms_cleanup_cms (GstLcms * lcms)
489 if (lcms->cms_inp_profile) {
490 cmsCloseProfile (lcms->cms_inp_profile);
491 lcms->cms_inp_profile = NULL;
493 if (lcms->cms_dst_profile) {
494 cmsCloseProfile (lcms->cms_dst_profile);
495 lcms->cms_dst_profile = NULL;
497 if (lcms->cms_transform) {
498 cmsDeleteTransform (lcms->cms_transform);
499 lcms->cms_transform = NULL;
504 gst_lcms_init_lookup_table (GstLcms * lcms)
507 const guint32 color_max = 0x01000000;
510 g_free (lcms->color_lut);
512 lcms->color_lut = g_new (guint32, color_max);
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!"));
520 if (lcms->lookup_method == GST_LCMS_LOOKUP_METHOD_PRECALCULATED) {
521 cmsHTRANSFORM 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");
535 if (lcms->preserve_black)
536 lcms->color_lut[0] = 0x000000;
539 static cmsUInt32Number
540 gst_lcms_cms_format_from_gst (GstVideoFormat gst_format)
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;
548 case GST_VIDEO_FORMAT_xBGR:
549 case GST_VIDEO_FORMAT_ABGR:
550 cms_format = TYPE_ABGR_8;
552 case GST_VIDEO_FORMAT_BGRA:
553 case GST_VIDEO_FORMAT_BGRx:
554 cms_format = TYPE_BGRA_8;
556 case GST_VIDEO_FORMAT_BGR:
557 cms_format = TYPE_BGR_8;
559 case GST_VIDEO_FORMAT_RGBA:
560 case GST_VIDEO_FORMAT_RGBx:
561 cms_format = TYPE_RGBA_8;
563 case GST_VIDEO_FORMAT_RGB:
564 cms_format = TYPE_RGB_8;
573 gst_lcms_set_info (GstVideoFilter * vfilter, GstCaps * incaps,
574 GstVideoInfo * in_info, GstCaps * outcaps, GstVideoInfo * out_info)
576 GstLcms *lcms = GST_LCMS (vfilter);
578 GST_DEBUG_OBJECT (lcms,
579 "setting caps: in %" GST_PTR_FORMAT " out %" GST_PTR_FORMAT, incaps,
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));
587 if (gst_base_transform_is_passthrough (GST_BASE_TRANSFORM (lcms)))
590 if (!lcms->cms_inp_format || !lcms->cms_dst_format)
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);
597 gst_base_transform_set_in_place (GST_BASE_TRANSFORM (lcms), FALSE);
599 gst_lcms_create_transform (lcms);
600 lcms->process = gst_lcms_process_rgb;
606 GST_ERROR_OBJECT (lcms, "Invalid caps: %" GST_PTR_FORMAT, incaps);
612 gst_lcms_create_transform (GstLcms * lcms)
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");
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);
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);
633 gst_lcms_sink_event (GstBaseTransform * trans, GstEvent * event)
635 gboolean ret = FALSE;
636 GstLcms *lcms = GST_LCMS (trans);
638 switch (GST_EVENT_TYPE (event)) {
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);
653 GST_BASE_TRANSFORM_CLASS (gst_lcms_parent_class)->sink_event (trans,
659 gst_lcms_handle_tag_sample (GstLcms * lcms, GstSample * sample)
662 const GstStructure *structure;
664 buf = gst_sample_get_buffer (sample);
665 structure = gst_sample_get_info (sample);
667 if (!buf || !structure)
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) {
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);
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);
691 GST_DEBUG_OBJECT (lcms,
692 "disregarding embedded ICC profile because input profile file was explicitly specified");
695 GST_DEBUG_OBJECT (lcms, "attachment is not an ICC profile");
699 gst_lcms_handle_tags (GstLcms * lcms, GstTagList * taglist)
706 tag_size = gst_tag_list_get_tag_size (taglist, GST_TAG_ATTACHMENT);
710 for (index = 0; index < tag_size; index++) {
711 if (gst_tag_list_get_sample_index (taglist, GST_TAG_ATTACHMENT, index,
713 gst_lcms_handle_tag_sample (lcms, sample);
714 gst_sample_unref (sample);
721 gst_lcms_transform_frame_ip (GstVideoFilter * vfilter, GstVideoFrame * inframe)
723 GstLcms *lcms = GST_LCMS (vfilter);
724 if (!gst_base_transform_is_passthrough (GST_BASE_TRANSFORM (lcms)))
725 lcms->process (lcms, inframe, NULL);
730 gst_lcms_transform_frame (GstVideoFilter * vfilter, GstVideoFrame * inframe,
731 GstVideoFrame * outframe)
733 GstLcms *lcms = GST_LCMS (vfilter);
734 if (!gst_base_transform_is_passthrough (GST_BASE_TRANSFORM (lcms)))
735 lcms->process (lcms, inframe, outframe);
740 gst_lcms_process_rgb (GstLcms * lcms, GstVideoFrame * inframe,
741 GstVideoFrame * outframe)
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;
749 gint in_row_wrap, out_row_wrap;
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);
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);
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!");
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));
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];
796 in_row_wrap = in_stride - in_pixel_stride * width;
797 out_row_wrap = out_stride - out_pixel_stride * width;
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);
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;
819 cmsDoTransformStride (lcms->cms_transform, in_data, out_data, 1,
822 out_data[in_offsets[3]] = alpha;
823 in_data += in_pixel_stride;
824 out_data += out_pixel_stride;
826 in_data += in_row_wrap;
827 out_data += out_row_wrap;
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++) {
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,
845 if (GST_VIDEO_FORMAT_INFO_HAS_ALPHA (inframe->info.finfo)) {
846 out_data[in_offsets[3]] = in_data[out_offsets[3]];
848 in_data += in_pixel_stride;
849 out_data += out_pixel_stride;
851 in_data += in_row_wrap;
852 out_data += out_row_wrap;
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]];
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);
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,
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;
882 out_data[in_offsets[3]] = alpha;
884 in_data += in_pixel_stride;
885 out_data += out_pixel_stride;
887 in_data += in_row_wrap;
888 out_data += out_row_wrap;