| /* |
| * GStreamer gstreamer-lcms |
| * Copyright (C) 2016 Andreas Frisch <fraxinas@dreambox.guru> |
| * |
| * gstlcms.c |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public |
| * License along with this library; if not, write to the |
| * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| /** |
| * SECTION:element-lcms |
| * @short_description: Uses LittleCMS 2 to perform ICC profile correction |
| * |
| * This is a color management plugin that uses LittleCMS 2 to correct |
| * frames using the given ICC (International Color Consortium) profiles. |
| * Falls back to internal sRGB profile if no ICC file is specified in property. |
| * |
| * <refsect2> |
| * <title>Example launch line</title> |
| * <para>(write everything in one line, without the backslash characters)</para> |
| * |[ |
| * gst-launch-1.0 filesrc location=photo_camera.png ! pngdec ! \ |
| * videoconvert ! lcms input-profile=sRGB.icc dest-profile=printer.icc \ |
| * pngenc ! filesink location=photo_print.png |
| * ]| |
| * </refsect2> |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include "gstlcms.h" |
| |
| #include <gst/gst.h> |
| #include <gst/video/video.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| GST_DEBUG_CATEGORY_STATIC (lcms_debug); |
| #define GST_CAT_DEFAULT lcms_debug |
| |
| /* GstLcms properties */ |
| enum |
| { |
| PROP_0, |
| PROP_INTENT, |
| PROP_LOOKUP_METHOD, |
| PROP_SRC_FILE, |
| PROP_DST_FILE, |
| PROP_PRESERVE_BLACK, |
| PROP_EMBEDDED_PROFILE |
| }; |
| |
| GType |
| gst_lcms_intent_get_type (void) |
| { |
| static volatile gsize intent_type = 0; |
| static const GEnumValue intent[] = { |
| {GST_LCMS_INTENT_PERCEPTUAL, "Perceptual", |
| "perceptual"}, |
| {GST_LCMS_INTENT_RELATIVE_COLORIMETRIC, "Relative Colorimetric", |
| "relative"}, |
| {GST_LCMS_INTENT_SATURATION, "Saturation", |
| "saturation"}, |
| {GST_LCMS_INTENT_ABSOLUTE_COLORIMETRIC, "Absolute Colorimetric", |
| "absolute"}, |
| {0, NULL, NULL}, |
| }; |
| |
| if (g_once_init_enter (&intent_type)) { |
| GType tmp = g_enum_register_static ("GstLcmsIntent", intent); |
| g_once_init_leave (&intent_type, tmp); |
| } |
| return (GType) intent_type; |
| } |
| |
| static GType |
| gst_lcms_lookup_method_get_type (void) |
| { |
| static volatile gsize lookup_method_type = 0; |
| static const GEnumValue lookup_method[] = { |
| {GST_LCMS_LOOKUP_METHOD_UNCACHED, |
| "Uncached, calculate every pixel on the fly (very slow playback)", |
| "uncached"}, |
| {GST_LCMS_LOOKUP_METHOD_PRECALCULATED, |
| "Precalculate lookup table (takes a long time getting READY)", |
| "precalculated"}, |
| {GST_LCMS_LOOKUP_METHOD_CACHED, |
| "Calculate and cache color replacement values on first occurence", |
| "cached"}, |
| {0, NULL, NULL}, |
| }; |
| |
| if (g_once_init_enter (&lookup_method_type)) { |
| GType tmp = g_enum_register_static ("GstLcmsLookupMethod", lookup_method); |
| g_once_init_leave (&lookup_method_type, tmp); |
| } |
| return (GType) lookup_method_type; |
| } |
| |
| #define DEFAULT_INTENT GST_LCMS_INTENT_PERCEPTUAL |
| #define DEFAULT_LOOKUP_METHOD GST_LCMS_LOOKUP_METHOD_CACHED |
| #define DEFAULT_PRESERVE_BLACK FALSE |
| #define DEFAULT_EMBEDDED_PROFILE TRUE |
| |
| static GstStaticPadTemplate gst_lcms_src_template = |
| GST_STATIC_PAD_TEMPLATE ("src", |
| GST_PAD_SRC, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{ " |
| "ARGB, BGRA, ABGR, RGBA, xRGB," "RGBx, xBGR, BGRx, RGB, BGR }")) |
| ); |
| |
| static GstStaticPadTemplate gst_lcms_sink_template = |
| GST_STATIC_PAD_TEMPLATE ("sink", |
| GST_PAD_SINK, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{ " |
| "ARGB, BGRA, ABGR, RGBA, xRGB," "RGBx, xBGR, BGRx, RGB, BGR }")) |
| ); |
| |
| static void gst_lcms_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec); |
| static void gst_lcms_get_property (GObject * object, guint prop_id, |
| GValue * value, GParamSpec * pspec); |
| static void gst_lcms_finalize (GObject * object); |
| static GstStateChangeReturn gst_lcms_change_state (GstElement * element, |
| GstStateChange transition); |
| static gboolean gst_lcms_set_info (GstVideoFilter * vfilter, GstCaps * incaps, |
| GstVideoInfo * in_info, GstCaps * outcaps, GstVideoInfo * out_info); |
| static gboolean gst_lcms_sink_event (GstBaseTransform * trans, |
| GstEvent * event); |
| static void gst_lcms_handle_tags (GstLcms * lcms, GstTagList * taglist); |
| static void gst_lcms_handle_tag_sample (GstLcms * lcms, GstSample * sample); |
| static GstFlowReturn gst_lcms_transform_frame (GstVideoFilter * vfilter, |
| GstVideoFrame * inframe, GstVideoFrame * outframe); |
| static GstFlowReturn gst_lcms_transform_frame_ip (GstVideoFilter * vfilter, |
| GstVideoFrame * frame); |
| |
| static void gst_lcms_get_ready (GstLcms * lcms); |
| static void gst_lcms_create_transform (GstLcms * lcms); |
| static void gst_lcms_cleanup_cms (GstLcms * lcms); |
| static void gst_lcms_init_lookup_table (GstLcms * lcms); |
| static void gst_lcms_process_rgb (GstLcms * lcms, GstVideoFrame * inframe, |
| GstVideoFrame * outframe); |
| |
| G_DEFINE_TYPE (GstLcms, gst_lcms, GST_TYPE_VIDEO_FILTER); |
| |
| static void |
| gst_lcms_class_init (GstLcmsClass * klass) |
| { |
| GObjectClass *gobject_class = (GObjectClass *) klass; |
| GstElementClass *element_class = (GstElementClass *) klass; |
| GstBaseTransformClass *trans_class = (GstBaseTransformClass *) klass; |
| GstVideoFilterClass *vfilter_class = (GstVideoFilterClass *) klass; |
| |
| GST_DEBUG_CATEGORY_INIT (lcms_debug, "lcms", 0, "lcms"); |
| |
| gobject_class->set_property = gst_lcms_set_property; |
| gobject_class->get_property = gst_lcms_get_property; |
| gobject_class->finalize = gst_lcms_finalize; |
| |
| g_object_class_install_property (gobject_class, PROP_INTENT, |
| g_param_spec_enum ("intent", "Rendering intent", |
| "Select the rendering intent of the color correction", |
| GST_TYPE_LCMS_INTENT, DEFAULT_INTENT, |
| (G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); |
| |
| g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SRC_FILE, |
| g_param_spec_string ("input-profile", "Input ICC profile file", |
| "Specify the input ICC profile file to apply", NULL, |
| (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); |
| |
| g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DST_FILE, |
| g_param_spec_string ("dest-profile", "Destination ICC profile file", |
| "Specify the destination ICC profile file to apply", NULL, |
| (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); |
| |
| g_object_class_install_property (gobject_class, PROP_LOOKUP_METHOD, |
| g_param_spec_enum ("lookup", "Lookup method", |
| "Select the caching method for the color compensation calculations", |
| GST_TYPE_LCMS_LOOKUP_METHOD, DEFAULT_LOOKUP_METHOD, |
| (G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); |
| |
| g_object_class_install_property (gobject_class, PROP_PRESERVE_BLACK, |
| g_param_spec_boolean ("preserve-black", "Preserve black", |
| "Select whether purely black pixels should be preserved", |
| DEFAULT_PRESERVE_BLACK, |
| (G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); |
| |
| g_object_class_install_property (gobject_class, PROP_EMBEDDED_PROFILE, |
| g_param_spec_boolean ("embedded-profile", "Embedded Profile", |
| "Extract and use source profiles embedded in images", |
| DEFAULT_EMBEDDED_PROFILE, |
| (G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); |
| |
| gst_element_class_set_static_metadata (element_class, |
| "LCMS2 ICC correction", "Filter/Effect/Video", |
| "Uses LittleCMS 2 to perform ICC profile correction", |
| "Andreas Frisch <fraxinas@opendreambox.org>"); |
| |
| gst_element_class_add_pad_template (element_class, |
| gst_static_pad_template_get (&gst_lcms_sink_template)); |
| gst_element_class_add_pad_template (element_class, |
| gst_static_pad_template_get (&gst_lcms_src_template)); |
| |
| element_class->change_state = GST_DEBUG_FUNCPTR (gst_lcms_change_state); |
| |
| trans_class->sink_event = GST_DEBUG_FUNCPTR (gst_lcms_sink_event); |
| |
| vfilter_class->set_info = GST_DEBUG_FUNCPTR (gst_lcms_set_info); |
| vfilter_class->transform_frame_ip = |
| GST_DEBUG_FUNCPTR (gst_lcms_transform_frame_ip); |
| vfilter_class->transform_frame = GST_DEBUG_FUNCPTR (gst_lcms_transform_frame); |
| } |
| |
| static void |
| gst_lcms_init (GstLcms * lcms) |
| { |
| lcms->color_lut = NULL; |
| lcms->cms_inp_profile = NULL; |
| lcms->cms_dst_profile = NULL; |
| lcms->cms_transform = NULL; |
| } |
| |
| static void |
| gst_lcms_finalize (GObject * object) |
| { |
| GstLcms *lcms = GST_LCMS (object); |
| if (lcms->color_lut) |
| g_free (lcms->color_lut); |
| g_free (lcms->inp_profile_filename); |
| g_free (lcms->dst_profile_filename); |
| G_OBJECT_CLASS (gst_lcms_parent_class)->finalize (object); |
| } |
| |
| static void |
| gst_lcms_set_intent (GstLcms * lcms, GstLcmsIntent intent) |
| { |
| GEnumValue *val = |
| g_enum_get_value (G_ENUM_CLASS (g_type_class_ref |
| (GST_TYPE_LCMS_INTENT)), intent); |
| const gchar *value_nick; |
| |
| g_return_if_fail (GST_IS_LCMS (lcms)); |
| if (!val) { |
| GST_ERROR_OBJECT (lcms, "no such rendering intent %i!", intent); |
| return; |
| } |
| value_nick = val->value_nick; |
| |
| GST_OBJECT_LOCK (lcms); |
| lcms->intent = intent; |
| GST_OBJECT_UNLOCK (lcms); |
| |
| GST_DEBUG_OBJECT (lcms, "successfully set rendering intent to %s (%i)", |
| value_nick, intent); |
| return; |
| } |
| |
| static GstLcmsIntent |
| gst_lcms_get_intent (GstLcms * lcms) |
| { |
| g_return_val_if_fail (GST_IS_LCMS (lcms), (GstLcmsIntent) - 1); |
| return lcms->intent; |
| } |
| |
| static void |
| gst_lcms_set_lookup_method (GstLcms * lcms, GstLcmsLookupMethod method) |
| { |
| GEnumValue *val = |
| g_enum_get_value (G_ENUM_CLASS (g_type_class_ref |
| (GST_TYPE_LCMS_LOOKUP_METHOD)), method); |
| const gchar *value_nick; |
| |
| g_return_if_fail (GST_IS_LCMS (lcms)); |
| if (!val) { |
| GST_ERROR_OBJECT (lcms, "no such lookup method %i!", method); |
| return; |
| } |
| value_nick = val->value_nick; |
| |
| GST_OBJECT_LOCK (lcms); |
| lcms->lookup_method = method; |
| GST_OBJECT_UNLOCK (lcms); |
| |
| GST_DEBUG_OBJECT (lcms, "successfully set lookup method to %s (%i)", |
| value_nick, method); |
| return; |
| } |
| |
| static GstLcmsLookupMethod |
| gst_lcms_get_lookup_method (GstLcms * lcms) |
| { |
| g_return_val_if_fail (GST_IS_LCMS (lcms), (GstLcmsLookupMethod) - 1); |
| return lcms->lookup_method; |
| } |
| |
| static void |
| gst_lcms_set_property (GObject * object, guint prop_id, const GValue * value, |
| GParamSpec * pspec) |
| { |
| const gchar *filename; |
| GstLcms *lcms = GST_LCMS (object); |
| |
| switch (prop_id) { |
| case PROP_SRC_FILE: |
| { |
| GST_OBJECT_LOCK (lcms); |
| filename = g_value_get_string (value); |
| if (filename |
| && g_file_test (filename, |
| (GFileTest) (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR))) { |
| if (lcms->inp_profile_filename) |
| g_free (lcms->inp_profile_filename); |
| lcms->inp_profile_filename = g_strdup (filename); |
| } else { |
| GST_WARNING_OBJECT (lcms, "Input profile file '%s' not found!", |
| filename); |
| } |
| GST_OBJECT_UNLOCK (lcms); |
| break; |
| } |
| case PROP_DST_FILE: |
| { |
| GST_OBJECT_LOCK (lcms); |
| filename = g_value_get_string (value); |
| if (g_file_test (filename, |
| (GFileTest) (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR))) { |
| if (lcms->dst_profile_filename) |
| g_free (lcms->dst_profile_filename); |
| lcms->dst_profile_filename = g_strdup (filename); |
| } else { |
| GST_WARNING_OBJECT (lcms, "Destination profile file '%s' not found!", |
| filename); |
| } |
| GST_OBJECT_UNLOCK (lcms); |
| break; |
| } |
| case PROP_INTENT: |
| gst_lcms_set_intent (lcms, (GstLcmsIntent) g_value_get_enum (value)); |
| break; |
| case PROP_LOOKUP_METHOD: |
| gst_lcms_set_lookup_method (lcms, |
| (GstLcmsLookupMethod) g_value_get_enum (value)); |
| break; |
| case PROP_PRESERVE_BLACK: |
| lcms->preserve_black = g_value_get_boolean (value); |
| break; |
| case PROP_EMBEDDED_PROFILE: |
| lcms->embeddedprofiles = g_value_get_boolean (value); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static void |
| gst_lcms_get_property (GObject * object, guint prop_id, GValue * value, |
| GParamSpec * pspec) |
| { |
| GstLcms *lcms = GST_LCMS (object); |
| |
| switch (prop_id) { |
| case PROP_SRC_FILE: |
| g_value_set_string (value, lcms->inp_profile_filename); |
| break; |
| case PROP_DST_FILE: |
| g_value_set_string (value, lcms->dst_profile_filename); |
| break; |
| case PROP_INTENT: |
| g_value_set_enum (value, gst_lcms_get_intent (lcms)); |
| break; |
| case PROP_LOOKUP_METHOD: |
| g_value_set_enum (value, gst_lcms_get_lookup_method (lcms)); |
| break; |
| case PROP_PRESERVE_BLACK: |
| g_value_set_boolean (value, lcms->preserve_black); |
| break; |
| case PROP_EMBEDDED_PROFILE: |
| g_value_set_boolean (value, lcms->embeddedprofiles); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static GstStateChangeReturn |
| gst_lcms_change_state (GstElement * element, GstStateChange transition) |
| { |
| GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; |
| GstLcms *lcms = GST_LCMS (element); |
| |
| switch (transition) { |
| case GST_STATE_CHANGE_NULL_TO_READY: |
| GST_DEBUG_OBJECT (lcms, "GST_STATE_CHANGE_NULL_TO_READY"); |
| gst_lcms_get_ready (lcms); |
| break; |
| case GST_STATE_CHANGE_PAUSED_TO_PLAYING: |
| { |
| if (!lcms->cms_inp_profile) { |
| if (!lcms->cms_dst_profile) { |
| GST_WARNING_OBJECT (lcms, |
| "No input or output ICC profile specified, falling back to passthrough!"); |
| gst_base_transform_set_passthrough (GST_BASE_TRANSFORM (lcms), TRUE); |
| GST_BASE_TRANSFORM_CLASS (GST_LCMS_GET_CLASS |
| (lcms))->transform_ip_on_passthrough = lcms->embeddedprofiles; |
| return GST_STATE_CHANGE_SUCCESS; |
| } |
| lcms->cms_inp_profile = cmsCreate_sRGBProfile (); |
| GST_INFO_OBJECT (lcms, |
| "No input profile specified, falling back to sRGB"); |
| } |
| } |
| |
| default: |
| break; |
| } |
| |
| if (ret == GST_STATE_CHANGE_SUCCESS) |
| ret = |
| GST_ELEMENT_CLASS (gst_lcms_parent_class)->change_state (element, |
| transition); |
| |
| switch (transition) { |
| case GST_STATE_CHANGE_PLAYING_TO_PAUSED: |
| case GST_STATE_CHANGE_PAUSED_TO_READY: |
| break; |
| case GST_STATE_CHANGE_READY_TO_NULL: |
| gst_lcms_cleanup_cms (lcms); |
| default: |
| break; |
| } |
| return ret; |
| } |
| |
| static void |
| gst_lcms_get_ready (GstLcms * lcms) |
| { |
| if (lcms->inp_profile_filename) { |
| lcms->cms_inp_profile = |
| cmsOpenProfileFromFile (lcms->inp_profile_filename, "r"); |
| if (!lcms->cms_inp_profile) |
| GST_ERROR_OBJECT (lcms, "Couldn't parse input ICC profile '%s'", |
| lcms->inp_profile_filename); |
| else |
| GST_DEBUG_OBJECT (lcms, "Successfully opened input ICC profile '%s'", |
| lcms->inp_profile_filename); |
| } |
| |
| if (lcms->dst_profile_filename) { |
| lcms->cms_dst_profile = |
| cmsOpenProfileFromFile (lcms->dst_profile_filename, "r"); |
| if (!lcms->cms_dst_profile) |
| GST_ERROR_OBJECT (lcms, |
| "Couldn't parse destination ICC profile '%s'", |
| lcms->dst_profile_filename); |
| else |
| GST_DEBUG_OBJECT (lcms, "Successfully opened output ICC profile '%s'", |
| lcms->dst_profile_filename); |
| } |
| |
| if (lcms->lookup_method != GST_LCMS_LOOKUP_METHOD_UNCACHED) { |
| gst_lcms_init_lookup_table (lcms); |
| } |
| } |
| |
| static void |
| gst_lcms_cleanup_cms (GstLcms * lcms) |
| { |
| if (lcms->cms_inp_profile) { |
| cmsCloseProfile (lcms->cms_inp_profile); |
| lcms->cms_inp_profile = NULL; |
| } |
| if (lcms->cms_dst_profile) { |
| cmsCloseProfile (lcms->cms_dst_profile); |
| lcms->cms_dst_profile = NULL; |
| } |
| if (lcms->cms_transform) { |
| cmsDeleteTransform (lcms->cms_transform); |
| lcms->cms_transform = NULL; |
| } |
| } |
| |
| static void |
| gst_lcms_init_lookup_table (GstLcms * lcms) |
| { |
| guint32 p; |
| const guint32 color_max = 0x01000000; |
| |
| if (lcms->color_lut) |
| g_free (lcms->color_lut); |
| |
| lcms->color_lut = g_new (guint32, color_max); |
| |
| if (lcms->color_lut == NULL) { |
| GST_ELEMENT_ERROR (lcms, RESOURCE, FAILED, ("LUT alloc failed"), |
| ("Unable to open allocate memory for lookup table!")); |
| return; |
| } |
| |
| if (lcms->lookup_method == GST_LCMS_LOOKUP_METHOD_PRECALCULATED) { |
| cmsHTRANSFORM hTransform; |
| hTransform = |
| cmsCreateTransform (lcms->cms_inp_profile, TYPE_RGB_8, |
| lcms->cms_dst_profile, TYPE_RGB_8, lcms->intent, 0); |
| /*FIXME use cmsFLAGS_COPY_ALPHA when new lcms2 2.8 release is available */ |
| for (p = 0; p < color_max; p++) |
| cmsDoTransform (hTransform, (const cmsUInt32Number *) &p, |
| &lcms->color_lut[p], 1); |
| cmsDeleteTransform (hTransform); |
| GST_DEBUG_OBJECT (lcms, "writing lookup table finished"); |
| } else if (lcms->lookup_method == GST_LCMS_LOOKUP_METHOD_CACHED) { |
| memset (lcms->color_lut, 0xAA, color_max * sizeof (guint32)); |
| GST_DEBUG_OBJECT (lcms, "initialized empty lookup table for caching"); |
| } |
| if (lcms->preserve_black) |
| lcms->color_lut[0] = 0x000000; |
| } |
| |
| static cmsUInt32Number |
| gst_lcms_cms_format_from_gst (GstVideoFormat gst_format) |
| { |
| cmsUInt32Number cms_format = 0; |
| switch (gst_format) { |
| case GST_VIDEO_FORMAT_ARGB: |
| case GST_VIDEO_FORMAT_xRGB: |
| cms_format = TYPE_ARGB_8; |
| break; |
| case GST_VIDEO_FORMAT_xBGR: |
| case GST_VIDEO_FORMAT_ABGR: |
| cms_format = TYPE_ABGR_8; |
| break; |
| case GST_VIDEO_FORMAT_BGRA: |
| case GST_VIDEO_FORMAT_BGRx: |
| cms_format = TYPE_BGRA_8; |
| break; |
| case GST_VIDEO_FORMAT_BGR: |
| cms_format = TYPE_BGR_8; |
| break; |
| case GST_VIDEO_FORMAT_RGBA: |
| case GST_VIDEO_FORMAT_RGBx: |
| cms_format = TYPE_RGBA_8; |
| break; |
| case GST_VIDEO_FORMAT_RGB: |
| cms_format = TYPE_RGB_8; |
| break; |
| default: |
| break; |
| } |
| return cms_format; |
| } |
| |
| static gboolean |
| gst_lcms_set_info (GstVideoFilter * vfilter, GstCaps * incaps, |
| GstVideoInfo * in_info, GstCaps * outcaps, GstVideoInfo * out_info) |
| { |
| GstLcms *lcms = GST_LCMS (vfilter); |
| |
| GST_DEBUG_OBJECT (lcms, |
| "setting caps: in %" GST_PTR_FORMAT " out %" GST_PTR_FORMAT, incaps, |
| outcaps); |
| |
| lcms->cms_inp_format = |
| gst_lcms_cms_format_from_gst (GST_VIDEO_INFO_FORMAT (in_info)); |
| lcms->cms_dst_format = |
| gst_lcms_cms_format_from_gst (GST_VIDEO_INFO_FORMAT (out_info)); |
| |
| if (gst_base_transform_is_passthrough (GST_BASE_TRANSFORM (lcms))) |
| return TRUE; |
| |
| if (!lcms->cms_inp_format || !lcms->cms_dst_format) |
| goto invalid_caps; |
| |
| if (lcms->cms_inp_format == lcms->cms_dst_format |
| && lcms->lookup_method != GST_LCMS_LOOKUP_METHOD_UNCACHED) { |
| gst_base_transform_set_in_place (GST_BASE_TRANSFORM (lcms), TRUE); |
| } else |
| gst_base_transform_set_in_place (GST_BASE_TRANSFORM (lcms), FALSE); |
| |
| gst_lcms_create_transform (lcms); |
| lcms->process = gst_lcms_process_rgb; |
| |
| return TRUE; |
| |
| invalid_caps: |
| { |
| GST_ERROR_OBJECT (lcms, "Invalid caps: %" GST_PTR_FORMAT, incaps); |
| return FALSE; |
| } |
| } |
| |
| static void |
| gst_lcms_create_transform (GstLcms * lcms) |
| { |
| if (!lcms->cms_dst_profile) { |
| lcms->cms_dst_profile = cmsCreate_sRGBProfile (); |
| GST_INFO_OBJECT (lcms, "No output profile specified, falling back to sRGB"); |
| } |
| lcms->cms_transform = |
| cmsCreateTransform (lcms->cms_inp_profile, lcms->cms_inp_format, |
| lcms->cms_dst_profile, lcms->cms_dst_format, lcms->intent, 0); |
| if (lcms->cms_transform) { |
| GST_DEBUG_OBJECT (lcms, "created transformation format=%i->%i", |
| lcms->cms_inp_format, lcms->cms_dst_format); |
| } else { |
| GST_WARNING_OBJECT (lcms, |
| "couldn't create transformation format=%i->%i, fallback to passthrough!", |
| lcms->cms_inp_format, lcms->cms_dst_format); |
| gst_base_transform_set_passthrough (GST_BASE_TRANSFORM (lcms), TRUE); |
| } |
| } |
| |
| static gboolean |
| gst_lcms_sink_event (GstBaseTransform * trans, GstEvent * event) |
| { |
| gboolean ret = FALSE; |
| GstLcms *lcms = GST_LCMS (trans); |
| |
| switch (GST_EVENT_TYPE (event)) { |
| case GST_EVENT_TAG: |
| { |
| if (lcms->embeddedprofiles) { |
| GstTagList *taglist = NULL; |
| /* icc profiles might be embedded in attachments */ |
| gst_event_parse_tag (event, &taglist); |
| gst_lcms_handle_tags (lcms, taglist); |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| ret = |
| GST_BASE_TRANSFORM_CLASS (gst_lcms_parent_class)->sink_event (trans, |
| event); |
| return ret; |
| } |
| |
| static void |
| gst_lcms_handle_tag_sample (GstLcms * lcms, GstSample * sample) |
| { |
| GstBuffer *buf; |
| const GstStructure *structure; |
| |
| buf = gst_sample_get_buffer (sample); |
| structure = gst_sample_get_info (sample); |
| |
| if (!buf || !structure) |
| return; |
| |
| if (gst_structure_has_name (structure, "application/vnd.iccprofile")) { |
| if (!lcms->inp_profile_filename |
| && lcms->lookup_method != GST_LCMS_LOOKUP_METHOD_UNCACHED) { |
| GstMapInfo map; |
| const gchar *icc_name; |
| icc_name = gst_structure_get_string (structure, "icc-name"); |
| gst_buffer_map (buf, &map, GST_MAP_READ); |
| lcms->cms_inp_profile = cmsOpenProfileFromMem (map.data, map.size); |
| gst_buffer_unmap (buf, &map); |
| if (!lcms->cms_inp_profile) |
| GST_WARNING_OBJECT (lcms, |
| "Couldn't parse embedded input ICC profile '%s'", icc_name); |
| else { |
| GST_DEBUG_OBJECT (lcms, |
| "Successfully opened embedded input ICC profile '%s'", icc_name); |
| if (lcms->cms_inp_format) { |
| gst_lcms_create_transform (lcms); |
| gst_lcms_init_lookup_table (lcms); |
| } |
| } |
| } else { |
| GST_DEBUG_OBJECT (lcms, |
| "disregarding embedded ICC profile because input profile file was explicitly specified"); |
| } |
| } else |
| GST_DEBUG_OBJECT (lcms, "attachment is not an ICC profile"); |
| } |
| |
| static void |
| gst_lcms_handle_tags (GstLcms * lcms, GstTagList * taglist) |
| { |
| guint tag_size; |
| |
| if (!taglist) |
| return; |
| |
| tag_size = gst_tag_list_get_tag_size (taglist, GST_TAG_ATTACHMENT); |
| if (tag_size > 0) { |
| guint index; |
| GstSample *sample; |
| for (index = 0; index < tag_size; index++) { |
| if (gst_tag_list_get_sample_index (taglist, GST_TAG_ATTACHMENT, index, |
| &sample)) { |
| gst_lcms_handle_tag_sample (lcms, sample); |
| gst_sample_unref (sample); |
| } |
| } |
| } |
| } |
| |
| static GstFlowReturn |
| gst_lcms_transform_frame_ip (GstVideoFilter * vfilter, GstVideoFrame * inframe) |
| { |
| GstLcms *lcms = GST_LCMS (vfilter); |
| if (!gst_base_transform_is_passthrough (GST_BASE_TRANSFORM (lcms))) |
| lcms->process (lcms, inframe, NULL); |
| return GST_FLOW_OK; |
| } |
| |
| static GstFlowReturn |
| gst_lcms_transform_frame (GstVideoFilter * vfilter, GstVideoFrame * inframe, |
| GstVideoFrame * outframe) |
| { |
| GstLcms *lcms = GST_LCMS (vfilter); |
| if (!gst_base_transform_is_passthrough (GST_BASE_TRANSFORM (lcms))) |
| lcms->process (lcms, inframe, outframe); |
| return GST_FLOW_OK; |
| } |
| |
| static void |
| gst_lcms_process_rgb (GstLcms * lcms, GstVideoFrame * inframe, |
| GstVideoFrame * outframe) |
| { |
| gint height; |
| gint width, in_stride, out_stride; |
| gint in_pixel_stride, out_pixel_stride; |
| gint in_offsets[4], out_offsets[4]; |
| guint8 *in_data, *out_data; |
| gint i, j; |
| gint in_row_wrap, out_row_wrap; |
| guint8 alpha = 0; |
| |
| in_data = (guint8 *) GST_VIDEO_FRAME_PLANE_DATA (inframe, 0); |
| in_stride = GST_VIDEO_FRAME_PLANE_STRIDE (inframe, 0); |
| width = GST_VIDEO_FRAME_COMP_WIDTH (inframe, 0); |
| height = GST_VIDEO_FRAME_COMP_HEIGHT (inframe, 0); |
| in_pixel_stride = GST_VIDEO_FRAME_COMP_PSTRIDE (inframe, 0); |
| |
| in_offsets[0] = GST_VIDEO_FRAME_COMP_OFFSET (inframe, 0); |
| in_offsets[1] = GST_VIDEO_FRAME_COMP_OFFSET (inframe, 1); |
| in_offsets[2] = GST_VIDEO_FRAME_COMP_OFFSET (inframe, 2); |
| in_offsets[3] = GST_VIDEO_FRAME_COMP_OFFSET (inframe, 3); |
| |
| if (outframe) { |
| if (width != GST_VIDEO_FRAME_COMP_WIDTH (outframe, 0) |
| || height != GST_VIDEO_FRAME_COMP_HEIGHT (outframe, 0)) { |
| GST_WARNING_OBJECT (lcms, |
| "can't transform, input dimensions != output dimensions!"); |
| return; |
| } |
| out_data = (guint8 *) GST_VIDEO_FRAME_PLANE_DATA (outframe, 0); |
| out_stride = GST_VIDEO_FRAME_PLANE_STRIDE (outframe, 0); |
| out_pixel_stride = GST_VIDEO_FRAME_COMP_PSTRIDE (outframe, 0); |
| out_offsets[0] = GST_VIDEO_FRAME_COMP_OFFSET (inframe, 0); |
| out_offsets[1] = GST_VIDEO_FRAME_COMP_OFFSET (inframe, 1); |
| out_offsets[2] = GST_VIDEO_FRAME_COMP_OFFSET (inframe, 2); |
| out_offsets[3] = GST_VIDEO_FRAME_COMP_OFFSET (inframe, 3); |
| GST_LOG_OBJECT (lcms, |
| "transforming frame (%ix%i) stride=%i->%i pixel_stride=%i->%i format=%s->%s", |
| width, height, in_stride, out_stride, in_pixel_stride, out_pixel_stride, |
| gst_video_format_to_string (inframe->info.finfo->format), |
| gst_video_format_to_string (outframe->info.finfo->format)); |
| } else { /* in-place transformation */ |
| GST_LOG_OBJECT (lcms, |
| "transforming frame IN-PLACE (%ix%i) pixel_stride=%i format=%s", width, |
| height, in_pixel_stride, |
| gst_video_format_to_string (inframe->info.finfo->format)); |
| out_data = in_data; |
| out_stride = in_stride; |
| out_pixel_stride = in_pixel_stride; |
| out_offsets[0] = in_offsets[0]; |
| out_offsets[1] = in_offsets[1]; |
| out_offsets[2] = in_offsets[2]; |
| out_offsets[3] = in_offsets[3]; |
| } |
| |
| in_row_wrap = in_stride - in_pixel_stride * width; |
| out_row_wrap = out_stride - out_pixel_stride * width; |
| |
| if (lcms->lookup_method == GST_LCMS_LOOKUP_METHOD_UNCACHED) { |
| if (!GST_VIDEO_FORMAT_INFO_HAS_ALPHA (inframe->info.finfo) |
| && !lcms->preserve_black) { |
| GST_DEBUG_OBJECT (lcms, |
| "GST_LCMS_LOOKUP_METHOD_UNCACHED WITHOUT alpha AND WITHOUT preserve-black -> picture-at-once transformation!"); |
| cmsDoTransformStride (lcms->cms_transform, in_data, out_data, |
| height * width, out_pixel_stride); |
| } else { |
| GST_DEBUG_OBJECT (lcms, |
| "GST_LCMS_LOOKUP_METHOD_UNCACHED WITH alpha or preserve-black -> pixel-by-pixel transformation!"); |
| for (i = 0; i < height; i++) { |
| for (j = 0; j < width; j++) { |
| if (GST_VIDEO_FORMAT_INFO_HAS_ALPHA (inframe->info.finfo)) |
| alpha = in_data[in_offsets[3]]; |
| if (lcms->preserve_black && (in_data[in_offsets[0]] == 0x00) |
| && (in_data[in_offsets[1]] == 0x00) |
| && (in_data[in_offsets[2]] == 0x0)) |
| out_data[out_offsets[0]] = out_data[out_offsets[1]] = |
| out_data[out_offsets[2]] = 0x00; |
| else |
| cmsDoTransformStride (lcms->cms_transform, in_data, out_data, 1, |
| in_pixel_stride); |
| if (alpha) |
| out_data[in_offsets[3]] = alpha; |
| in_data += in_pixel_stride; |
| out_data += out_pixel_stride; |
| } |
| in_data += in_row_wrap; |
| out_data += out_row_wrap; |
| } |
| } |
| } else if (lcms->lookup_method == GST_LCMS_LOOKUP_METHOD_PRECALCULATED) { |
| guint32 color, new_color; |
| GST_LOG_OBJECT (lcms, "GST_LCMS_LOOKUP_METHOD_PRECALCULATED"); |
| for (i = 0; i < height; i++) { |
| for (j = 0; j < width; j++) { |
| color = |
| in_data[in_offsets[0]] | |
| in_data[in_offsets[1]] << 0x08 | in_data[in_offsets[2]] << 0x10; |
| new_color = lcms->color_lut[color]; |
| out_data[out_offsets[0]] = (new_color & 0x0000FF) >> 0x00; |
| out_data[out_offsets[1]] = (new_color & 0x00FF00) >> 0x08; |
| out_data[out_offsets[2]] = (new_color & 0xFF0000) >> 0x10; |
| GST_TRACE_OBJECT (lcms, |
| "(%i:%i)@%p original color 0x%08X (dest was 0x%08X)", i, j, in_data, |
| color, new_color); |
| if (GST_VIDEO_FORMAT_INFO_HAS_ALPHA (inframe->info.finfo)) { |
| out_data[in_offsets[3]] = in_data[out_offsets[3]]; |
| } |
| in_data += in_pixel_stride; |
| out_data += out_pixel_stride; |
| } |
| in_data += in_row_wrap; |
| out_data += out_row_wrap; |
| } |
| } else if (lcms->lookup_method == GST_LCMS_LOOKUP_METHOD_CACHED) { |
| guint32 color, new_color; |
| GST_LOG_OBJECT (lcms, "GST_LCMS_LOOKUP_METHOD_CACHED"); |
| for (i = 0; i < height; i++) { |
| for (j = 0; j < width; j++) { |
| if (GST_VIDEO_FORMAT_INFO_HAS_ALPHA (inframe->info.finfo)) |
| alpha = in_data[in_offsets[3]]; |
| color = |
| in_data[in_offsets[0]] | |
| in_data[in_offsets[1]] << 0x08 | in_data[in_offsets[2]] << 0x10; |
| new_color = lcms->color_lut[color]; |
| if (new_color == 0xAAAAAAAA) { |
| cmsDoTransform (lcms->cms_transform, in_data, out_data, 1); |
| new_color = |
| out_data[out_offsets[0]] | |
| out_data[out_offsets[1]] << 0x08 | |
| out_data[out_offsets[2]] << 0x10; |
| GST_OBJECT_LOCK (lcms); |
| lcms->color_lut[color] = new_color; |
| GST_OBJECT_UNLOCK (lcms); |
| GST_TRACE_OBJECT (lcms, "cached color 0x%08X -> 0x%08X", color, |
| new_color); |
| } else { |
| out_data[out_offsets[0]] = (new_color & 0x0000FF) >> 0x00; |
| out_data[out_offsets[1]] = (new_color & 0x00FF00) >> 0x08; |
| out_data[out_offsets[2]] = (new_color & 0xFF0000) >> 0x10; |
| } |
| if (alpha) { |
| out_data[in_offsets[3]] = alpha; |
| } |
| in_data += in_pixel_stride; |
| out_data += out_pixel_stride; |
| } |
| in_data += in_row_wrap; |
| out_data += out_row_wrap; |
| } |
| } |
| } |