
The include claimed to be because of "inline" in the lcms header, but that was probably lcms1, since there is no trace of inline in the lcms2 header.
428 lines
11 KiB
C
428 lines
11 KiB
C
/* LIBGIMP - The GIMP Library
|
|
* Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
|
|
*
|
|
* gimplcms.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 "gimplcms.h"
|
|
|
|
#include "libgimp/libgimp-intl.h"
|
|
|
|
|
|
/**
|
|
* SECTION: gimplcms
|
|
* @title: GimpLcms
|
|
* @short_description: Definitions and Functions relating to LCMS.
|
|
*
|
|
* Definitions and Functions relating to LCMS.
|
|
**/
|
|
|
|
|
|
#define GIMP_LCMS_MD5_DIGEST_LENGTH 16
|
|
|
|
|
|
static GQuark
|
|
gimp_lcms_error_quark (void)
|
|
{
|
|
static GQuark quark = 0;
|
|
|
|
if (G_UNLIKELY (quark == 0))
|
|
quark = g_quark_from_static_string ("gimp-lcms-error-quark");
|
|
|
|
return quark;
|
|
}
|
|
|
|
GimpColorProfile
|
|
gimp_lcms_profile_open_from_file (const gchar *filename,
|
|
GError **error)
|
|
{
|
|
GimpColorProfile profile;
|
|
GMappedFile *file;
|
|
const guint8 *data;
|
|
gsize length;
|
|
|
|
g_return_val_if_fail (filename != NULL, NULL);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
|
|
|
|
file = g_mapped_file_new (filename, FALSE, error);
|
|
|
|
if (! file)
|
|
return NULL;
|
|
|
|
data = (const guint8 *) g_mapped_file_get_contents (file);
|
|
length = g_mapped_file_get_length (file);
|
|
|
|
profile = cmsOpenProfileFromMem (data, length);
|
|
|
|
if (! profile)
|
|
g_set_error (error, gimp_lcms_error_quark (), 0,
|
|
_("'%s' does not appear to be an ICC color profile"),
|
|
gimp_filename_to_utf8 (filename));
|
|
|
|
g_mapped_file_unref (file);
|
|
|
|
return profile;
|
|
}
|
|
|
|
GimpColorProfile
|
|
gimp_lcms_profile_open_from_data (const guint8 *data,
|
|
gsize length,
|
|
GError **error)
|
|
{
|
|
GimpColorProfile profile;
|
|
|
|
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);
|
|
|
|
profile = cmsOpenProfileFromMem (data, length);
|
|
|
|
if (! profile)
|
|
g_set_error_literal (error, gimp_lcms_error_quark (), 0,
|
|
_("Data does not appear to be an ICC color profile"));
|
|
|
|
return profile;
|
|
}
|
|
|
|
guint8 *
|
|
gimp_lcms_profile_save_to_data (GimpColorProfile profile,
|
|
gsize *length,
|
|
GError **error)
|
|
{
|
|
cmsUInt32Number size;
|
|
|
|
g_return_val_if_fail (profile != NULL, NULL);
|
|
g_return_val_if_fail (length != NULL, NULL);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
|
|
|
|
if (cmsSaveProfileToMem (profile, NULL, &size))
|
|
{
|
|
guint8 *data = g_malloc (size);
|
|
|
|
if (cmsSaveProfileToMem (profile, data, &size))
|
|
{
|
|
*length = size;
|
|
|
|
return data;
|
|
}
|
|
|
|
g_free (data);
|
|
}
|
|
|
|
g_set_error_literal (error, gimp_lcms_error_quark (), 0,
|
|
_("Could not save color profile to memory"));
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static gchar *
|
|
gimp_lcms_profile_get_info (GimpColorProfile profile,
|
|
cmsInfoType info)
|
|
{
|
|
cmsUInt32Number size;
|
|
gchar *text = NULL;
|
|
|
|
g_return_val_if_fail (profile != NULL, NULL);
|
|
|
|
size = cmsGetProfileInfoASCII (profile, info,
|
|
"en", "US", NULL, 0);
|
|
if (size > 0)
|
|
{
|
|
gchar *data = g_new (gchar, size + 1);
|
|
|
|
size = cmsGetProfileInfoASCII (profile, info,
|
|
"en", "US", data, size);
|
|
if (size > 0)
|
|
text = gimp_any_to_utf8 (data, -1, NULL);
|
|
|
|
g_free (data);
|
|
}
|
|
|
|
return text;
|
|
}
|
|
|
|
gchar *
|
|
gimp_lcms_profile_get_description (GimpColorProfile profile)
|
|
{
|
|
return gimp_lcms_profile_get_info (profile, cmsInfoDescription);
|
|
}
|
|
|
|
gchar *
|
|
gimp_lcms_profile_get_manufacturer (GimpColorProfile profile)
|
|
{
|
|
return gimp_lcms_profile_get_info (profile, cmsInfoManufacturer);
|
|
}
|
|
|
|
gchar *
|
|
gimp_lcms_profile_get_model (GimpColorProfile profile)
|
|
{
|
|
return gimp_lcms_profile_get_info (profile, cmsInfoModel);
|
|
}
|
|
|
|
gchar *
|
|
gimp_lcms_profile_get_copyright (GimpColorProfile profile)
|
|
{
|
|
return gimp_lcms_profile_get_info (profile, cmsInfoCopyright);
|
|
}
|
|
|
|
gchar *
|
|
gimp_lcms_profile_get_label (GimpColorProfile profile)
|
|
{
|
|
gchar *label;
|
|
|
|
g_return_val_if_fail (profile != NULL, NULL);
|
|
|
|
label = gimp_lcms_profile_get_description (profile);
|
|
|
|
if (label && ! strlen (label))
|
|
{
|
|
g_free (label);
|
|
label = NULL;
|
|
}
|
|
|
|
if (! label)
|
|
label = gimp_lcms_profile_get_model (profile);
|
|
|
|
if (label && ! strlen (label))
|
|
{
|
|
g_free (label);
|
|
label = NULL;
|
|
}
|
|
|
|
if (! label)
|
|
label = g_strdup (_("(unnamed profile)"));
|
|
|
|
return label;
|
|
}
|
|
|
|
gchar *
|
|
gimp_lcms_profile_get_summary (GimpColorProfile profile)
|
|
{
|
|
GString *string;
|
|
gchar *text;
|
|
|
|
g_return_val_if_fail (profile != NULL, NULL);
|
|
|
|
string = g_string_new (NULL);
|
|
|
|
text = gimp_lcms_profile_get_description (profile);
|
|
if (text)
|
|
{
|
|
g_string_append (string, text);
|
|
g_free (text);
|
|
}
|
|
|
|
text = gimp_lcms_profile_get_model (profile);
|
|
if (text)
|
|
{
|
|
if (string->len > 0)
|
|
g_string_append (string, "\n");
|
|
|
|
g_string_append (string, text);
|
|
}
|
|
|
|
text = gimp_lcms_profile_get_manufacturer (profile);
|
|
if (text)
|
|
{
|
|
if (string->len > 0)
|
|
g_string_append (string, "\n");
|
|
|
|
g_string_append (string, text);
|
|
}
|
|
|
|
text = gimp_lcms_profile_get_copyright (profile);
|
|
if (text)
|
|
{
|
|
if (string->len > 0)
|
|
g_string_append (string, "\n");
|
|
|
|
g_string_append (string, text);
|
|
}
|
|
|
|
return g_string_free (string, FALSE);
|
|
}
|
|
|
|
gboolean
|
|
gimp_lcms_profile_is_equal (GimpColorProfile profile1,
|
|
GimpColorProfile profile2)
|
|
{
|
|
cmsUInt8Number digest1[GIMP_LCMS_MD5_DIGEST_LENGTH];
|
|
cmsUInt8Number digest2[GIMP_LCMS_MD5_DIGEST_LENGTH];
|
|
|
|
g_return_val_if_fail (profile1 != NULL, FALSE);
|
|
g_return_val_if_fail (profile2 != NULL, FALSE);
|
|
|
|
if (! cmsMD5computeID (profile1) ||
|
|
! cmsMD5computeID (profile2))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
cmsGetHeaderProfileID (profile1, digest1);
|
|
cmsGetHeaderProfileID (profile2, digest2);
|
|
|
|
return (memcmp (digest1, digest2, GIMP_LCMS_MD5_DIGEST_LENGTH) == 0);
|
|
}
|
|
|
|
gboolean
|
|
gimp_lcms_profile_is_rgb (GimpColorProfile profile)
|
|
{
|
|
g_return_val_if_fail (profile != NULL, FALSE);
|
|
|
|
return (cmsGetColorSpace (profile) == cmsSigRgbData);
|
|
}
|
|
|
|
gboolean
|
|
gimp_lcms_profile_is_cmyk (GimpColorProfile profile)
|
|
{
|
|
g_return_val_if_fail (profile != NULL, FALSE);
|
|
|
|
return (cmsGetColorSpace (profile) == cmsSigCmykData);
|
|
}
|
|
|
|
static void
|
|
gimp_lcms_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 GimpColorProfile
|
|
gimp_lcms_create_srgb_profile_internal (void)
|
|
{
|
|
cmsHPROFILE srgb_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;
|
|
|
|
srgb_profile = cmsCreateRGBProfile (&d65_srgb_specs,
|
|
&srgb_primaries_pre_quantized,
|
|
tone_curve);
|
|
|
|
cmsFreeToneCurve (srgb_parametric_curve);
|
|
|
|
gimp_lcms_profile_set_tag (srgb_profile, cmsSigProfileDescriptionTag,
|
|
"GIMP built-in sRGB");
|
|
gimp_lcms_profile_set_tag (srgb_profile, cmsSigDeviceMfgDescTag,
|
|
"GIMP");
|
|
gimp_lcms_profile_set_tag (srgb_profile, cmsSigDeviceModelDescTag,
|
|
"sRGB");
|
|
gimp_lcms_profile_set_tag (srgb_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 srgb_profile;
|
|
}
|
|
|
|
/**
|
|
* gimp_lcms_create_srgb_profile:
|
|
*
|
|
* 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 cmsHPROFILE.
|
|
*
|
|
* Since: GIMP 2.10
|
|
**/
|
|
GimpColorProfile
|
|
gimp_lcms_create_srgb_profile (void)
|
|
{
|
|
static guint8 *profile_data = NULL;
|
|
static gsize profile_length = 0;
|
|
|
|
if (G_UNLIKELY (profile_data == NULL))
|
|
{
|
|
GimpColorProfile profile;
|
|
|
|
profile = gimp_lcms_create_srgb_profile_internal ();
|
|
|
|
profile_data = gimp_lcms_profile_save_to_data (profile, &profile_length,
|
|
NULL);
|
|
|
|
cmsCloseProfile (profile);
|
|
}
|
|
|
|
return gimp_lcms_profile_open_from_data (profile_data, profile_length, NULL);
|
|
}
|