gimp/libgimpcolor/gimpcolorprofile.c

948 lines
24 KiB
C
Raw Normal View History

/* LIBGIMP - The GIMP Library
* Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
*
* gimpcolorprofile.c
* Copyright (C) 2014 Michael Natterer <mitch@gimp.org>
* Elle Stone <ellestone@ninedegreesbelow.com>
*
* This library is free software: you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 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 Lesser General Public
* License along with this library. If not, see
* <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <string.h>
#include <lcms2.h>
#include <gio/gio.h>
#include <gegl.h>
#include "libgimpbase/gimpbase.h"
#include "gimpcolortypes.h"
#include "gimpcolorprofile.h"
#include "libgimp/libgimp-intl.h"
/**
* SECTION: gimpcolorprofile
* @title: GimpColorProfile
* @short_description: Definitions and Functions relating to LCMS.
*
* Definitions and Functions relating to LCMS.
**/
/**
* GimpColorProfile:
*
* Simply a typedef to #gpointer, but actually is a cmsHPROFILE. It's
* used in public GIMP APIs in order to avoid having to include LCMS
* headers.
**/
struct _GimpColorProfilePrivate
{
cmsHPROFILE lcms_profile;
guint8 *data;
gsize length;
};
static void gimp_color_profile_finalize (GObject *object);
G_DEFINE_TYPE (GimpColorProfile, gimp_color_profile,
G_TYPE_OBJECT);
#define parent_class gimp_color_profile_parent_class
static GQuark
gimp_color_profile_error_quark (void)
{
static GQuark quark = 0;
if (G_UNLIKELY (quark == 0))
quark = g_quark_from_static_string ("gimp-color-profile-error-quark");
return quark;
}
static void
gimp_color_profile_class_init (GimpColorProfileClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = gimp_color_profile_finalize;
g_type_class_add_private (klass, sizeof (GimpColorProfilePrivate));
}
static void
gimp_color_profile_init (GimpColorProfile *profile)
{
profile->priv = G_TYPE_INSTANCE_GET_PRIVATE (profile,
GIMP_TYPE_COLOR_PROFILE,
GimpColorProfilePrivate);
}
static void
gimp_color_profile_finalize (GObject *object)
{
GimpColorProfile *profile = GIMP_COLOR_PROFILE (object);
if (profile->priv->lcms_profile)
{
cmsCloseProfile (profile->priv->lcms_profile);
profile->priv->lcms_profile = NULL;
}
if (profile->priv->data)
{
g_free (profile->priv->data);
profile->priv->data = NULL;
profile->priv->length = 0;
}
G_OBJECT_CLASS (parent_class)->finalize (object);
}
/**
* gimp_color_profile_new_from_file:
* @file: a #GFile
* @error: return location for #GError
*
* This function opens an ICC color profile from @file.
*
* Return value: the #GimpColorProfile, or %NULL. On error, %NULL is
* returned and @error is set.
*
* Since: 2.10
**/
GimpColorProfile *
gimp_color_profile_new_from_file (GFile *file,
GError **error)
{
GimpColorProfile *profile = NULL;
cmsHPROFILE lcms_profile = NULL;
guint8 *data = NULL;
gsize length = 0;
gchar *path;
g_return_val_if_fail (G_IS_FILE (file), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
path = g_file_get_path (file);
if (path)
{
GMappedFile *mapped;
mapped = g_mapped_file_new (path, FALSE, error);
if (! mapped)
return NULL;
length = g_mapped_file_get_length (mapped);
data = g_memdup (g_mapped_file_get_contents (mapped), length);
lcms_profile = cmsOpenProfileFromMem (data, length);
g_mapped_file_unref (mapped);
}
else
{
GFileInfo *info;
info = g_file_query_info (file,
G_FILE_ATTRIBUTE_STANDARD_SIZE,
G_FILE_QUERY_INFO_NONE,
NULL, error);
if (info)
{
GInputStream *input;
length = g_file_info_get_size (info);
data = g_malloc (length);
g_object_unref (info);
input = G_INPUT_STREAM (g_file_read (file, NULL, error));
if (input)
{
gsize bytes_read;
if (g_input_stream_read_all (input, data, length,
&bytes_read, NULL, error) &&
bytes_read == length)
{
lcms_profile = cmsOpenProfileFromMem (data, length);
}
g_object_unref (input);
}
}
}
if (lcms_profile)
{
profile = g_object_new (GIMP_TYPE_COLOR_PROFILE, NULL);
profile->priv->lcms_profile = lcms_profile;
profile->priv->data = data;
profile->priv->length = length;
}
else
{
if (data)
g_free (data);
if (error && *error == NULL)
{
g_set_error (error, gimp_color_profile_error_quark (), 0,
_("'%s' does not appear to be an ICC color profile"),
gimp_file_get_utf8_name (file));
}
}
return profile;
}
/**
* gimp_color_profile_new_from_icc_profile:
* @data: pointer to memory containing an ICC profile
* @length: lenght of the profile in memory, in bytes
* @error: return location for #GError
*
* This function opens an ICC color profile from memory. On error,
* %NULL is returned and @error is set.
*
* Return value: the #GimpColorProfile, or %NULL.
*
* Since: 2.10
**/
GimpColorProfile *
gimp_color_profile_new_from_icc_profile (const guint8 *data,
gsize length,
GError **error)
{
cmsHPROFILE lcms_profile;
GimpColorProfile *profile = NULL;
g_return_val_if_fail (data != NULL, NULL);
g_return_val_if_fail (length > 0, NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
lcms_profile = cmsOpenProfileFromMem (data, length);
if (lcms_profile)
{
profile = g_object_new (GIMP_TYPE_COLOR_PROFILE, NULL);
profile->priv->lcms_profile = lcms_profile;
profile->priv->data = g_memdup (data, length);
profile->priv->length = length;
}
else
{
g_set_error_literal (error, gimp_color_profile_error_quark (), 0,
_("Data does not appear to be an ICC color profile"));
}
return profile;
}
/**
* gimp_color_profile_new_from_lcms_profile:
* @lcms_profile: an LCMS cmsHPROFILE pointer
* @error: return location for #GError
*
* This function creates a GimpColorProfile from a cmsHPROFILE. On
* error, %NULL is returned and @error is set. The passed
* @lcms_profile pointer is not retained by the created
* #GimpColorProfile.
*
* Return value: the #GimpColorProfile, or %NULL.
*
* Since: 2.10
**/
GimpColorProfile *
gimp_color_profile_new_from_lcms_profile (gpointer lcms_profile,
GError **error)
{
cmsUInt32Number size;
g_return_val_if_fail (lcms_profile != NULL, NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
if (cmsSaveProfileToMem (lcms_profile, NULL, &size))
{
guint8 *data = g_malloc (size);
if (cmsSaveProfileToMem (lcms_profile, data, &size))
{
gsize length = size;
lcms_profile = cmsOpenProfileFromMem (data, length);
if (lcms_profile)
{
GimpColorProfile *profile;
profile = g_object_new (GIMP_TYPE_COLOR_PROFILE, NULL);
profile->priv->lcms_profile = lcms_profile;
profile->priv->data = data;
profile->priv->length = length;
return profile;
}
}
g_free (data);
}
g_set_error_literal (error, gimp_color_profile_error_quark (), 0,
_("Could not save color profile to memory"));
return NULL;
}
/**
* gimp_color_profile_get_icc_profile:
* @profile: a #GimpColorProfile
* @length: return location for the number of bytes
* @error: return location for #GError
*
* This function returns @profile as ICC profile data. The returned
* memory belongs to @profile and must not be modified or freed.
*
* Return value: a pointer to the IIC profile data.
*
* Since: 2.10
**/
const guint8 *
gimp_color_profile_get_icc_profile (GimpColorProfile *profile,
gsize *length)
{
g_return_val_if_fail (GIMP_IS_COLOR_PROFILE (profile), NULL);
g_return_val_if_fail (length != NULL, NULL);
*length = profile->priv->length;
return profile->priv->data;
}
/**
* gimp_color_profile_get_lcms_profile:
* @profile: a #GimpColorProfile
*
* This function returns @profile's cmsHPROFILE. The returned
* value belongs to @profile and must not be modified or freed.
*
* Return value: a pointer to the cmsHPROFILE.
*
* Since: 2.10
**/
gpointer
gimp_color_profile_get_lcms_profile (GimpColorProfile *profile)
{
g_return_val_if_fail (GIMP_IS_COLOR_PROFILE (profile), NULL);
return profile->priv->lcms_profile;
}
static gchar *
gimp_color_profile_get_info (GimpColorProfile *profile,
cmsInfoType info)
{
cmsUInt32Number size;
gchar *text = NULL;
g_return_val_if_fail (GIMP_IS_COLOR_PROFILE (profile), NULL);
size = cmsGetProfileInfoASCII (profile->priv->lcms_profile, info,
"en", "US", NULL, 0);
if (size > 0)
{
gchar *data = g_new (gchar, size + 1);
size = cmsGetProfileInfoASCII (profile->priv->lcms_profile, info,
"en", "US", data, size);
if (size > 0)
text = gimp_any_to_utf8 (data, -1, NULL);
g_free (data);
}
return text;
}
/**
* gimp_color_profile_get_description:
* @profile: a #GimpColorProfile
*
* Return value: a newly allocated string containing @profile's
* description. Free with g_free().
*
* Since: 2.10
**/
gchar *
gimp_color_profile_get_description (GimpColorProfile *profile)
{
return gimp_color_profile_get_info (profile, cmsInfoDescription);
}
/**
* gimp_color_profile_get_manufacturer:
* @profile: a #GimpColorProfile
*
* Return value: a newly allocated string containing @profile's
* manufacturer. Free with g_free().
*
* Since: 2.10
**/
gchar *
gimp_color_profile_get_manufacturer (GimpColorProfile *profile)
{
return gimp_color_profile_get_info (profile, cmsInfoManufacturer);
}
/**
* gimp_color_profile_get_model:
* @profile: a #GimpColorProfile
*
* Return value: a newly allocated string containing @profile's
* model. Free with g_free().
*
* Since: 2.10
**/
gchar *
gimp_color_profile_get_model (GimpColorProfile *profile)
{
return gimp_color_profile_get_info (profile, cmsInfoModel);
}
/**
* gimp_color_profile_get_copyright:
* @profile: a #GimpColorProfile
*
* Return value: a newly allocated string containing @profile's
* copyright. Free with g_free().
*
* Since: 2.10
**/
gchar *
gimp_color_profile_get_copyright (GimpColorProfile *profile)
{
return gimp_color_profile_get_info (profile, cmsInfoCopyright);
}
/**
* gimp_color_profile_get_label:
* @profile: a #GimpColorProfile
*
* This function returns a newly allocated string containing
* @profile's "title", a string that can be used to label the profile
* in a user interface.
*
* Return value: the @profile's label. Free with g_free().
*
* Since: 2.10
**/
gchar *
gimp_color_profile_get_label (GimpColorProfile *profile)
{
gchar *label;
g_return_val_if_fail (GIMP_IS_COLOR_PROFILE (profile), NULL);
label = gimp_color_profile_get_description (profile);
if (label && ! strlen (label))
{
g_free (label);
label = NULL;
}
if (! label)
label = gimp_color_profile_get_model (profile);
if (label && ! strlen (label))
{
g_free (label);
label = NULL;
}
if (! label)
label = g_strdup (_("(unnamed profile)"));
return label;
}
/**
* gimp_color_profile_get_summary:
* @profile: a #GimpColorProfile
*
* This function return a newly allocated string containing a
* multi-line summary of @profile's description, model, manufacturer
* and copyright, to be used as detailled information about the
* prpfile in a user interface.
*
* Return value: the @profile's summary. Free with g_free().
*
* Since: 2.10
**/
gchar *
gimp_color_profile_get_summary (GimpColorProfile *profile)
{
GString *string;
gchar *text;
g_return_val_if_fail (GIMP_IS_COLOR_PROFILE (profile), NULL);
string = g_string_new (NULL);
text = gimp_color_profile_get_description (profile);
if (text)
{
g_string_append (string, text);
g_free (text);
}
text = gimp_color_profile_get_model (profile);
if (text)
{
if (string->len > 0)
g_string_append (string, "\n");
g_string_append (string, text);
g_free (text);
}
text = gimp_color_profile_get_manufacturer (profile);
if (text)
{
if (string->len > 0)
g_string_append (string, "\n");
g_string_append (string, text);
g_free (text);
}
text = gimp_color_profile_get_copyright (profile);
if (text)
{
if (string->len > 0)
g_string_append (string, "\n");
g_string_append (string, text);
g_free (text);
}
return g_string_free (string, FALSE);
}
/**
* gimp_color_profile_is_equal:
* @profile1: a #GimpColorProfile
* @profile2: a #GimpColorProfile
*
* Compares two profiles.
*
* Return value: %TRUE if the profiles are equal, %FALSE otherwise.
*
* Since: 2.10
**/
gboolean
gimp_color_profile_is_equal (GimpColorProfile *profile1,
GimpColorProfile *profile2)
{
const gsize header_len = sizeof (cmsICCHeader);
g_return_val_if_fail (GIMP_IS_COLOR_PROFILE (profile1), FALSE);
g_return_val_if_fail (GIMP_IS_COLOR_PROFILE (profile1), FALSE);
return (profile1->priv->length == profile2->priv->length &&
memcmp (profile1->priv->data + header_len,
profile2->priv->data + header_len,
profile1->priv->length - header_len) == 0);
}
/**
* gimp_color_profile_is_rgb:
* @profile: a #GimpColorProfile
*
* Return value: %TRUE if the profile's color space is RGB, %FALSE
* otherwise.
*
* Since: 2.10
**/
gboolean
gimp_color_profile_is_rgb (GimpColorProfile *profile)
{
g_return_val_if_fail (GIMP_IS_COLOR_PROFILE (profile), FALSE);
return (cmsGetColorSpace (profile->priv->lcms_profile) == cmsSigRgbData);
}
/**
* gimp_color_profile_is_cmyk:
* @profile: a #GimpColorProfile
*
* Return value: %TRUE if the profile's color space is CMYK, %FALSE
* otherwise.
*
* Since: 2.10
**/
gboolean
gimp_color_profile_is_cmyk (GimpColorProfile *profile)
{
g_return_val_if_fail (GIMP_IS_COLOR_PROFILE (profile), FALSE);
return (cmsGetColorSpace (profile->priv->lcms_profile) == cmsSigCmykData);
}
static void
gimp_color_profile_set_tag (cmsHPROFILE profile,
cmsTagSignature sig,
const gchar *tag)
{
cmsMLU *mlu;
mlu = cmsMLUalloc (NULL, 1);
cmsMLUsetASCII (mlu, "en", "US", tag);
cmsWriteTag (profile, sig, mlu);
cmsMLUfree (mlu);
}
static cmsHPROFILE *
gimp_color_profile_new_srgb_internal (void)
{
cmsHPROFILE profile;
cmsCIExyY d65_srgb_specs = { 0.3127, 0.3290, 1.0 };
cmsCIExyYTRIPLE srgb_primaries_pre_quantized =
{
{ 0.639998686, 0.330010138, 1.0 },
{ 0.300003784, 0.600003357, 1.0 },
{ 0.150002046, 0.059997204, 1.0 }
};
cmsFloat64Number srgb_parameters[5] =
{ 2.4, 1.0 / 1.055, 0.055 / 1.055, 1.0 / 12.92, 0.04045 };
cmsToneCurve *srgb_parametric_curve =
cmsBuildParametricToneCurve (NULL, 4, srgb_parameters);
cmsToneCurve *tone_curve[3];
tone_curve[0] = tone_curve[1] = tone_curve[2] = srgb_parametric_curve;
profile = cmsCreateRGBProfile (&d65_srgb_specs,
&srgb_primaries_pre_quantized,
tone_curve);
cmsFreeToneCurve (srgb_parametric_curve);
gimp_color_profile_set_tag (profile, cmsSigProfileDescriptionTag,
"GIMP built-in sRGB");
gimp_color_profile_set_tag (profile, cmsSigDeviceMfgDescTag,
"GIMP");
gimp_color_profile_set_tag (profile, cmsSigDeviceModelDescTag,
"sRGB");
gimp_color_profile_set_tag (profile, cmsSigCopyrightTag,
"Public Domain");
/* The following line produces a V2 profile with a point curve TRC.
* Profiles with point curve TRCs can't be used in LCMS2 unbounded
* mode ICC profile conversions. A V2 profile might be appropriate
* for embedding in sRGB images saved to disk, if the image is to be
* opened by an image editing application that doesn't understand V4
* profiles.
*
* cmsSetProfileVersion (srgb_profile, 2.1);
*/
return profile;
}
/**
* gimp_color_profile_new_srgb:
*
* This function is a replacement for cmsCreate_sRGBProfile() and
* returns an sRGB profile that is functionally the same as the
* ArgyllCMS sRGB.icm profile. "Functionally the same" means it has
* the same red, green, and blue colorants and the V4 "chad"
* equivalent of the ArgyllCMS V2 white point. The profile TRC is also
* functionally equivalent to the ArgyllCMS sRGB.icm TRC and is the
* same as the LCMS sRGB built-in profile TRC.
*
* The actual primaries in the sRGB specification are
* red xy: {0.6400, 0.3300, 1.0}
* green xy: {0.3000, 0.6000, 1.0}
* blue xy: {0.1500, 0.0600, 1.0}
*
* The sRGB primaries given below are "pre-quantized" to compensate
* for hexadecimal quantization during the profile-making process.
* Unless the profile-making code compensates for this quantization,
* the resulting profile's red, green, and blue colorants will deviate
* slightly from the correct XYZ values.
*
* LCMS2 doesn't compensate for hexadecimal quantization. The
* "pre-quantized" primaries below were back-calculated from the
* ArgyllCMS sRGB.icm profile. The resulting sRGB profile's colorants
* exactly matches the ArgyllCMS sRGB.icm profile colorants.
*
* Return value: the sRGB #GimpColorProfile.
*
* Since: 2.10
**/
GimpColorProfile *
gimp_color_profile_new_srgb (void)
{
static GimpColorProfile *profile = NULL;
const guint8 *data;
gsize length;
if (G_UNLIKELY (profile == NULL))
{
cmsHPROFILE lcms_profile = gimp_color_profile_new_srgb_internal ();
profile = gimp_color_profile_new_from_lcms_profile (lcms_profile, NULL);
cmsCloseProfile (lcms_profile);
}
data = gimp_color_profile_get_icc_profile (profile, &length);
return gimp_color_profile_new_from_icc_profile (data, length, NULL);
}
static cmsHPROFILE
gimp_color_profile_new_linear_rgb_internal (void)
{
cmsHPROFILE profile;
/* white point is D65 from the sRGB specs */
cmsCIExyY whitepoint = { 0.3127, 0.3290, 1.0 };
/* primaries are ITUR BT.7095 (xYY), which are also the primaries
* from the sRGB specs, modified to properly account for hexadecimal
* quantization during the profile making process.
*/
cmsCIExyYTRIPLE primaries =
{
/* R { 0.6400, 0.3300, 1.0 }, */
/* G { 0.3000, 0.6000, 1.0 }, */
/* B { 0.1500, 0.0600, 1.0 } */
/* R */ { 0.639998686, 0.330010138, 1.0 },
/* G */ { 0.300003784, 0.600003357, 1.0 },
/* B */ { 0.150002046, 0.059997204, 1.0 }
};
/* linear light */
cmsToneCurve *linear[3];
linear[0] = linear[1] = linear[2] = cmsBuildGamma (NULL, 1.0);
/* create the profile, cleanup, and return */
profile = cmsCreateRGBProfile (&whitepoint, &primaries, linear);
cmsFreeToneCurve (linear[0]);
gimp_color_profile_set_tag (profile, cmsSigProfileDescriptionTag,
"GIMP built-in Linear RGB");
gimp_color_profile_set_tag (profile, cmsSigDeviceMfgDescTag,
"GIMP");
gimp_color_profile_set_tag (profile, cmsSigDeviceModelDescTag,
"Linear RGB");
gimp_color_profile_set_tag (profile, cmsSigCopyrightTag,
"Public Domain");
return profile;
}
/**
* gimp_color_profile_new_linear_rgb:
*
* This function creates a profile for babl_model("RGB"). Please
* somebody write someting smarter here.
*
* Return value: the linear RGB #GimpColorProfile.
*
* Since: 2.10
**/
GimpColorProfile *
gimp_color_profile_new_linear_rgb (void)
{
static GimpColorProfile *profile = NULL;
const guint8 *data;
gsize length;
if (G_UNLIKELY (profile == NULL))
{
cmsHPROFILE lcms_profile = gimp_color_profile_new_linear_rgb_internal ();
profile = gimp_color_profile_new_from_lcms_profile (lcms_profile, NULL);
cmsCloseProfile (lcms_profile);
}
data = gimp_color_profile_get_icc_profile (profile, &length);
return gimp_color_profile_new_from_icc_profile (data, length, NULL);
}
/**
* gimp_color_profile_get_format:
* @format: a #Babl format
* @lcms_format: return location for an lcms format
*
* This function takes a #Babl format and returns the lcms format to
* be used with that @format. It also returns a #Babl format to be
* used instead of the passed @format, which usually is the same as
* @format, unless lcms doesn't support @format.
*
* Note that this function currently only supports RGB, RGBA, R'G'B' and
* R'G'B'A formats.
*
* Return value: the #Babl format to be used instead of @format, or %NULL
* is the passed @format is not supported at all.
*
* Since: 2.10
**/
const Babl *
gimp_color_profile_get_format (const Babl *format,
guint32 *lcms_format)
{
const Babl *output_format = NULL;
const Babl *type;
const Babl *model;
gboolean has_alpha;
gboolean linear;
g_return_val_if_fail (format != NULL, NULL);
g_return_val_if_fail (lcms_format != NULL, NULL);
has_alpha = babl_format_has_alpha (format);
type = babl_format_get_type (format, 0);
model = babl_format_get_model (format);
if (model == babl_model ("RGB") ||
model == babl_model ("RGBA"))
{
linear = TRUE;
}
else if (model == babl_model ("R'G'B'") ||
model == babl_model ("R'G'B'A"))
{
linear = FALSE;
}
else
{
g_return_val_if_reached (NULL);
}
*lcms_format = 0;
if (type == babl_type ("u8"))
{
if (has_alpha)
*lcms_format = TYPE_RGBA_8;
else
*lcms_format = TYPE_RGB_8;
output_format = format;
}
else if (type == babl_type ("u16"))
{
if (has_alpha)
*lcms_format = TYPE_RGBA_16;
else
*lcms_format = TYPE_RGB_16;
output_format = format;
}
else if (type == babl_type ("half")) /* 16-bit floating point (half) */
{
if (has_alpha)
*lcms_format = TYPE_RGBA_HALF_FLT;
else
*lcms_format = TYPE_RGB_HALF_FLT;
output_format = format;
}
else if (type == babl_type ("float"))
{
if (has_alpha)
*lcms_format = TYPE_RGBA_FLT;
else
*lcms_format = TYPE_RGB_FLT;
output_format = format;
}
else if (type == babl_type ("double"))
{
if (has_alpha)
{
#ifdef TYPE_RGBA_DBL
/* RGBA double not implemented in lcms */
*lcms_format = TYPE_RGBA_DBL;
output_format = format;
#endif /* TYPE_RGBA_DBL */
}
else
{
*lcms_format = TYPE_RGB_DBL;
output_format = format;
}
}
if (*lcms_format == 0)
{
g_printerr ("%s: layer format %s not supported, "
"falling back to float\n",
G_STRFUNC, babl_get_name (format));
if (has_alpha)
{
*lcms_format = TYPE_RGBA_FLT;
if (linear)
output_format = babl_format ("RGBA float");
else
output_format = babl_format ("R'G'B'A float");
}
else
{
*lcms_format = TYPE_RGB_FLT;
if (linear)
output_format = babl_format ("RGB float");
else
output_format = babl_format ("R'G'B' float");
}
}
return output_format;
}