643 lines
21 KiB
C
643 lines
21 KiB
C
/* LIBPIKA - The PIKA Library
|
|
* Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
|
|
*
|
|
* pikacolortransform.c
|
|
* Copyright (C) 2014 Michael Natterer <mitch@gimp.org>
|
|
* Elle Stone <ellestone@ninedegreesbelow.com>
|
|
* Øyvind Kolås <pippin@gimp.org>
|
|
*
|
|
* 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
|
|
* <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include <lcms2.h>
|
|
|
|
#include <gio/gio.h>
|
|
#include <gegl.h>
|
|
|
|
#include "libpikabase/pikabase.h"
|
|
#include "libpikaconfig/pikaconfig.h"
|
|
|
|
#include "pikacolortypes.h"
|
|
|
|
#include "pikacolorprofile.h"
|
|
#include "pikacolortransform.h"
|
|
|
|
#include "libpika/libpika-intl.h"
|
|
|
|
|
|
/**
|
|
* SECTION: pikacolortransform
|
|
* @title: PikaColorTransform
|
|
* @short_description: Definitions and Functions relating to LCMS.
|
|
*
|
|
* Definitions and Functions relating to LCMS.
|
|
**/
|
|
|
|
/**
|
|
* PikaColorTransform:
|
|
*
|
|
* Simply a typedef to #gpointer, but actually is a cmsHTRANSFORM. It's
|
|
* used in public PIKA APIs in order to avoid having to include LCMS
|
|
* headers.
|
|
**/
|
|
|
|
|
|
enum
|
|
{
|
|
PROGRESS,
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
|
|
struct _PikaColorTransformPrivate
|
|
{
|
|
PikaColorProfile *src_profile;
|
|
const Babl *src_format;
|
|
|
|
PikaColorProfile *dest_profile;
|
|
const Babl *dest_format;
|
|
|
|
cmsHTRANSFORM transform;
|
|
const Babl *fish;
|
|
};
|
|
|
|
|
|
static void pika_color_transform_finalize (GObject *object);
|
|
|
|
|
|
G_DEFINE_TYPE_WITH_PRIVATE (PikaColorTransform, pika_color_transform,
|
|
G_TYPE_OBJECT)
|
|
|
|
#define parent_class pika_color_transform_parent_class
|
|
|
|
static guint pika_color_transform_signals[LAST_SIGNAL] = { 0 };
|
|
|
|
static gchar *lcms_last_error = NULL;
|
|
|
|
|
|
static void
|
|
lcms_error_clear (void)
|
|
{
|
|
if (lcms_last_error)
|
|
{
|
|
g_free (lcms_last_error);
|
|
lcms_last_error = NULL;
|
|
}
|
|
}
|
|
|
|
static void
|
|
lcms_error_handler (cmsContext ContextID,
|
|
cmsUInt32Number ErrorCode,
|
|
const gchar *text)
|
|
{
|
|
lcms_error_clear ();
|
|
|
|
lcms_last_error = g_strdup_printf ("lcms2 error %d: %s", ErrorCode, text);
|
|
}
|
|
|
|
static void
|
|
pika_color_transform_class_init (PikaColorTransformClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
object_class->finalize = pika_color_transform_finalize;
|
|
|
|
pika_color_transform_signals[PROGRESS] =
|
|
g_signal_new ("progress",
|
|
G_OBJECT_CLASS_TYPE (object_class),
|
|
G_SIGNAL_RUN_FIRST,
|
|
G_STRUCT_OFFSET (PikaColorTransformClass,
|
|
progress),
|
|
NULL, NULL, NULL,
|
|
G_TYPE_NONE, 1,
|
|
G_TYPE_DOUBLE);
|
|
|
|
cmsSetLogErrorHandler (lcms_error_handler);
|
|
}
|
|
|
|
static void
|
|
pika_color_transform_init (PikaColorTransform *transform)
|
|
{
|
|
transform->priv = pika_color_transform_get_instance_private (transform);
|
|
}
|
|
|
|
static void
|
|
pika_color_transform_finalize (GObject *object)
|
|
{
|
|
PikaColorTransform *transform = PIKA_COLOR_TRANSFORM (object);
|
|
|
|
g_clear_object (&transform->priv->src_profile);
|
|
g_clear_object (&transform->priv->dest_profile);
|
|
|
|
g_clear_pointer (&transform->priv->transform, cmsDeleteTransform);
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
|
|
/**
|
|
* pika_color_transform_new:
|
|
* @src_profile: the source #PikaColorProfile
|
|
* @src_format: the source #Babl format
|
|
* @dest_profile: the destination #PikaColorProfile
|
|
* @dest_format: the destination #Babl format
|
|
* @rendering_intent: the rendering intent
|
|
* @flags: transform flags
|
|
*
|
|
* This function creates an color transform.
|
|
*
|
|
* The color transform is determined exclusively by @src_profile and
|
|
* @dest_profile. The color spaces of @src_format and @dest_format are
|
|
* ignored, the formats are only used to decide between what pixel
|
|
* encodings to transform.
|
|
*
|
|
* Note: this function used to return %NULL if
|
|
* pika_color_transform_can_gegl_copy() returned %TRUE for
|
|
* @src_profile and @dest_profile. This is no longer the case because
|
|
* special care has to be taken not to perform multiple implicit color
|
|
* transforms caused by babl formats with color spaces. Now, it always
|
|
* returns a non-%NULL transform and the code takes care of doing only
|
|
* exactly the requested color transform.
|
|
*
|
|
* Returns: (nullable): the #PikaColorTransform, or %NULL if there was an error.
|
|
*
|
|
* Since: 2.10
|
|
**/
|
|
PikaColorTransform *
|
|
pika_color_transform_new (PikaColorProfile *src_profile,
|
|
const Babl *src_format,
|
|
PikaColorProfile *dest_profile,
|
|
const Babl *dest_format,
|
|
PikaColorRenderingIntent rendering_intent,
|
|
PikaColorTransformFlags flags)
|
|
{
|
|
PikaColorTransform *transform;
|
|
PikaColorTransformPrivate *priv;
|
|
cmsHPROFILE src_lcms;
|
|
cmsHPROFILE dest_lcms;
|
|
cmsUInt32Number lcms_src_format;
|
|
cmsUInt32Number lcms_dest_format;
|
|
GError *error = NULL;
|
|
|
|
g_return_val_if_fail (PIKA_IS_COLOR_PROFILE (src_profile), NULL);
|
|
g_return_val_if_fail (src_format != NULL, NULL);
|
|
g_return_val_if_fail (PIKA_IS_COLOR_PROFILE (dest_profile), NULL);
|
|
g_return_val_if_fail (dest_format != NULL, NULL);
|
|
|
|
transform = g_object_new (PIKA_TYPE_COLOR_TRANSFORM, NULL);
|
|
|
|
priv = transform->priv;
|
|
|
|
/* only src_profile and dest_profile must determine the transform's
|
|
* color spaces, create formats with src_format's and dest_format's
|
|
* encoding, and the profiles' color spaces; see process_pixels()
|
|
* and process_buffer().
|
|
*/
|
|
|
|
priv->src_format = pika_color_profile_get_format (src_profile,
|
|
src_format,
|
|
PIKA_COLOR_RENDERING_INTENT_RELATIVE_COLORIMETRIC,
|
|
&error);
|
|
if (! priv->src_format)
|
|
{
|
|
g_printerr ("%s: error making src format: %s\n",
|
|
G_STRFUNC, error->message);
|
|
g_clear_error (&error);
|
|
}
|
|
|
|
priv->dest_format = pika_color_profile_get_format (dest_profile,
|
|
dest_format,
|
|
rendering_intent,
|
|
&error);
|
|
if (! priv->dest_format)
|
|
{
|
|
g_printerr ("%s: error making dest format: %s\n",
|
|
G_STRFUNC, error->message);
|
|
g_clear_error (&error);
|
|
}
|
|
|
|
if (! g_getenv ("PIKA_COLOR_TRANSFORM_DISABLE_BABL") &&
|
|
priv->src_format && priv->dest_format)
|
|
{
|
|
priv->fish = babl_fish (priv->src_format,
|
|
priv->dest_format);
|
|
|
|
g_debug ("%s: using babl for '%s' -> '%s'",
|
|
G_STRFUNC,
|
|
pika_color_profile_get_label (src_profile),
|
|
pika_color_profile_get_label (dest_profile));
|
|
|
|
return transform;
|
|
}
|
|
|
|
/* see above: when using lcms, don't mess with formats with color
|
|
* spaces, pika_color_profile_get_lcms_format() might return the
|
|
* same format and it must be without space
|
|
*/
|
|
src_format = babl_format_with_space ((const gchar *) src_format, NULL);
|
|
dest_format = babl_format_with_space ((const gchar *) dest_format, NULL);
|
|
|
|
priv->src_format = pika_color_profile_get_lcms_format (src_format,
|
|
&lcms_src_format);
|
|
priv->dest_format = pika_color_profile_get_lcms_format (dest_format,
|
|
&lcms_dest_format);
|
|
|
|
src_lcms = pika_color_profile_get_lcms_profile (src_profile);
|
|
dest_lcms = pika_color_profile_get_lcms_profile (dest_profile);
|
|
|
|
lcms_error_clear ();
|
|
|
|
priv->transform = cmsCreateTransform (src_lcms, lcms_src_format,
|
|
dest_lcms, lcms_dest_format,
|
|
rendering_intent,
|
|
flags |
|
|
cmsFLAGS_COPY_ALPHA);
|
|
|
|
if (lcms_last_error)
|
|
{
|
|
if (priv->transform)
|
|
{
|
|
cmsDeleteTransform (priv->transform);
|
|
priv->transform = NULL;
|
|
}
|
|
|
|
g_printerr ("%s: %s\n", G_STRFUNC, lcms_last_error);
|
|
}
|
|
|
|
if (! priv->transform)
|
|
{
|
|
g_object_unref (transform);
|
|
transform = NULL;
|
|
}
|
|
|
|
return transform;
|
|
}
|
|
|
|
/**
|
|
* pika_color_transform_new_proofing:
|
|
* @src_profile: the source #PikaColorProfile
|
|
* @src_format: the source #Babl format
|
|
* @dest_profile: the destination #PikaColorProfile
|
|
* @dest_format: the destination #Babl format
|
|
* @proof_profile: the proof #PikaColorProfile
|
|
* @proof_intent: the proof intent
|
|
* @display_intent: the display intent
|
|
* @flags: transform flags
|
|
*
|
|
* This function creates a simulation / proofing color transform.
|
|
*
|
|
* See pika_color_transform_new() about the color spaces to transform
|
|
* between.
|
|
*
|
|
* Returns: (nullable): the #PikaColorTransform, or %NULL if there was an error.
|
|
*
|
|
* Since: 2.10
|
|
**/
|
|
PikaColorTransform *
|
|
pika_color_transform_new_proofing (PikaColorProfile *src_profile,
|
|
const Babl *src_format,
|
|
PikaColorProfile *dest_profile,
|
|
const Babl *dest_format,
|
|
PikaColorProfile *proof_profile,
|
|
PikaColorRenderingIntent proof_intent,
|
|
PikaColorRenderingIntent display_intent,
|
|
PikaColorTransformFlags flags)
|
|
{
|
|
PikaColorTransform *transform;
|
|
PikaColorTransformPrivate *priv;
|
|
cmsHPROFILE src_lcms;
|
|
cmsHPROFILE dest_lcms;
|
|
cmsHPROFILE proof_lcms;
|
|
cmsUInt32Number lcms_src_format;
|
|
cmsUInt32Number lcms_dest_format;
|
|
|
|
g_return_val_if_fail (PIKA_IS_COLOR_PROFILE (src_profile), NULL);
|
|
g_return_val_if_fail (src_format != NULL, NULL);
|
|
g_return_val_if_fail (PIKA_IS_COLOR_PROFILE (dest_profile), NULL);
|
|
g_return_val_if_fail (dest_format != NULL, NULL);
|
|
g_return_val_if_fail (PIKA_IS_COLOR_PROFILE (proof_profile), NULL);
|
|
|
|
transform = g_object_new (PIKA_TYPE_COLOR_TRANSFORM, NULL);
|
|
|
|
priv = transform->priv;
|
|
|
|
src_lcms = pika_color_profile_get_lcms_profile (src_profile);
|
|
dest_lcms = pika_color_profile_get_lcms_profile (dest_profile);
|
|
proof_lcms = pika_color_profile_get_lcms_profile (proof_profile);
|
|
|
|
/* see pika_color_transform_new(), we can't have color spaces
|
|
* on the formats
|
|
*/
|
|
src_format = babl_format_with_space ((const gchar *) src_format, NULL);
|
|
dest_format = babl_format_with_space ((const gchar *) dest_format, NULL);
|
|
|
|
priv->src_format = pika_color_profile_get_lcms_format (src_format,
|
|
&lcms_src_format);
|
|
priv->dest_format = pika_color_profile_get_lcms_format (dest_format,
|
|
&lcms_dest_format);
|
|
|
|
lcms_error_clear ();
|
|
|
|
priv->transform = cmsCreateProofingTransform (src_lcms, lcms_src_format,
|
|
dest_lcms, lcms_dest_format,
|
|
proof_lcms,
|
|
display_intent,
|
|
proof_intent,
|
|
flags |
|
|
cmsFLAGS_SOFTPROOFING |
|
|
cmsFLAGS_COPY_ALPHA);
|
|
|
|
if (lcms_last_error)
|
|
{
|
|
if (priv->transform)
|
|
{
|
|
cmsDeleteTransform (priv->transform);
|
|
priv->transform = NULL;
|
|
}
|
|
|
|
g_printerr ("%s: %s\n", G_STRFUNC, lcms_last_error);
|
|
}
|
|
|
|
if (! priv->transform)
|
|
{
|
|
g_object_unref (transform);
|
|
transform = NULL;
|
|
}
|
|
|
|
return transform;
|
|
}
|
|
|
|
/**
|
|
* pika_color_transform_process_pixels:
|
|
* @transform: a #PikaColorTransform
|
|
* @src_format: #Babl format of @src_pixels
|
|
* @src_pixels: pointer to the source pixels
|
|
* @dest_format: #Babl format of @dest_pixels
|
|
* @dest_pixels: pointer to the destination pixels
|
|
* @length: number of pixels to process
|
|
*
|
|
* This function transforms a contiguous line of pixels.
|
|
*
|
|
* See pika_color_transform_new(): only the pixel encoding of
|
|
* @src_format and @dest_format is honored, their color spaces are
|
|
* ignored. The transform always takes place between the color spaces
|
|
* determined by @transform's color profiles.
|
|
*
|
|
* Since: 2.10
|
|
**/
|
|
void
|
|
pika_color_transform_process_pixels (PikaColorTransform *transform,
|
|
const Babl *src_format,
|
|
gconstpointer src_pixels,
|
|
const Babl *dest_format,
|
|
gpointer dest_pixels,
|
|
gsize length)
|
|
{
|
|
PikaColorTransformPrivate *priv;
|
|
gpointer *src;
|
|
gpointer *dest;
|
|
|
|
g_return_if_fail (PIKA_IS_COLOR_TRANSFORM (transform));
|
|
g_return_if_fail (src_format != NULL);
|
|
g_return_if_fail (src_pixels != NULL);
|
|
g_return_if_fail (dest_format != NULL);
|
|
g_return_if_fail (dest_pixels != NULL);
|
|
|
|
priv = transform->priv;
|
|
|
|
/* we must not do any babl color transforms when reading from
|
|
* src_pixels or writing to dest_pixels, so construct formats with
|
|
* src_format's and dest_format's encoding, and the transform's
|
|
* input and output color spaces.
|
|
*/
|
|
src_format =
|
|
babl_format_with_space ((const gchar *) src_format,
|
|
babl_format_get_space (priv->src_format));
|
|
dest_format =
|
|
babl_format_with_space ((const gchar *) dest_format,
|
|
babl_format_get_space (priv->dest_format));
|
|
|
|
if (src_format != priv->src_format)
|
|
{
|
|
src = g_malloc (length * babl_format_get_bytes_per_pixel (priv->src_format));
|
|
|
|
babl_process (babl_fish (src_format,
|
|
priv->src_format),
|
|
src_pixels, src, length);
|
|
}
|
|
else
|
|
{
|
|
src = (gpointer) src_pixels;
|
|
}
|
|
|
|
if (dest_format != priv->dest_format)
|
|
{
|
|
dest = g_malloc (length * babl_format_get_bytes_per_pixel (priv->dest_format));
|
|
}
|
|
else
|
|
{
|
|
dest = dest_pixels;
|
|
}
|
|
|
|
if (priv->transform)
|
|
{
|
|
cmsDoTransform (priv->transform, src, dest, length);
|
|
}
|
|
else
|
|
{
|
|
babl_process (priv->fish, src, dest, length);
|
|
}
|
|
|
|
if (src_format != priv->src_format)
|
|
{
|
|
g_free (src);
|
|
}
|
|
|
|
if (dest_format != priv->dest_format)
|
|
{
|
|
babl_process (babl_fish (priv->dest_format,
|
|
dest_format),
|
|
dest, dest_pixels, length);
|
|
|
|
g_free (dest);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* pika_color_transform_process_buffer:
|
|
* @transform: a #PikaColorTransform
|
|
* @src_buffer: source #GeglBuffer
|
|
* @src_rect: rectangle in @src_buffer
|
|
* @dest_buffer: destination #GeglBuffer
|
|
* @dest_rect: rectangle in @dest_buffer
|
|
*
|
|
* This function transforms buffer into another buffer.
|
|
*
|
|
* See pika_color_transform_new(): only the pixel encoding of
|
|
* @src_buffer's and @dest_buffer's formats honored, their color
|
|
* spaces are ignored. The transform always takes place between the
|
|
* color spaces determined by @transform's color profiles.
|
|
*
|
|
* Since: 2.10
|
|
**/
|
|
void
|
|
pika_color_transform_process_buffer (PikaColorTransform *transform,
|
|
GeglBuffer *src_buffer,
|
|
const GeglRectangle *src_rect,
|
|
GeglBuffer *dest_buffer,
|
|
const GeglRectangle *dest_rect)
|
|
{
|
|
PikaColorTransformPrivate *priv;
|
|
const Babl *src_format;
|
|
const Babl *dest_format;
|
|
GeglBufferIterator *iter;
|
|
gint total_pixels;
|
|
gint done_pixels = 0;
|
|
|
|
g_return_if_fail (PIKA_IS_COLOR_TRANSFORM (transform));
|
|
g_return_if_fail (GEGL_IS_BUFFER (src_buffer));
|
|
g_return_if_fail (GEGL_IS_BUFFER (dest_buffer));
|
|
|
|
priv = transform->priv;
|
|
|
|
if (src_rect)
|
|
{
|
|
total_pixels = src_rect->width * src_rect->height;
|
|
}
|
|
else
|
|
{
|
|
total_pixels = (gegl_buffer_get_width (src_buffer) *
|
|
gegl_buffer_get_height (src_buffer));
|
|
}
|
|
|
|
/* we must not do any babl color transforms when reading from
|
|
* src_buffer or writing to dest_buffer, so construct formats with
|
|
* the transform's expected input and output encoding and
|
|
* src_buffer's and dest_buffers's color spaces.
|
|
*/
|
|
src_format = gegl_buffer_get_format (src_buffer);
|
|
dest_format = gegl_buffer_get_format (dest_buffer);
|
|
|
|
src_format =
|
|
babl_format_with_space ((const gchar *) priv->src_format,
|
|
babl_format_get_space (src_format));
|
|
dest_format =
|
|
babl_format_with_space ((const gchar *) priv->dest_format,
|
|
babl_format_get_space (dest_format));
|
|
|
|
if (src_buffer != dest_buffer)
|
|
{
|
|
iter = gegl_buffer_iterator_new (src_buffer, src_rect, 0,
|
|
src_format,
|
|
GEGL_ACCESS_READ,
|
|
GEGL_ABYSS_NONE, 2);
|
|
|
|
gegl_buffer_iterator_add (iter, dest_buffer, dest_rect, 0,
|
|
dest_format,
|
|
GEGL_ACCESS_WRITE,
|
|
GEGL_ABYSS_NONE);
|
|
|
|
while (gegl_buffer_iterator_next (iter))
|
|
{
|
|
if (priv->transform)
|
|
{
|
|
cmsDoTransform (priv->transform,
|
|
iter->items[0].data, iter->items[1].data, iter->length);
|
|
}
|
|
else
|
|
{
|
|
babl_process (priv->fish,
|
|
iter->items[0].data, iter->items[1].data, iter->length);
|
|
}
|
|
|
|
done_pixels += iter->items[0].roi.width * iter->items[0].roi.height;
|
|
|
|
g_signal_emit (transform, pika_color_transform_signals[PROGRESS], 0,
|
|
(gdouble) done_pixels /
|
|
(gdouble) total_pixels);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
iter = gegl_buffer_iterator_new (src_buffer, src_rect, 0,
|
|
src_format,
|
|
GEGL_ACCESS_READWRITE,
|
|
GEGL_ABYSS_NONE, 1);
|
|
|
|
while (gegl_buffer_iterator_next (iter))
|
|
{
|
|
if (priv->transform)
|
|
{
|
|
cmsDoTransform (priv->transform,
|
|
iter->items[0].data, iter->items[0].data, iter->length);
|
|
}
|
|
else
|
|
{
|
|
babl_process (priv->fish,
|
|
iter->items[0].data, iter->items[0].data, iter->length);
|
|
}
|
|
|
|
done_pixels += iter->items[0].roi.width * iter->items[0].roi.height;
|
|
|
|
g_signal_emit (transform, pika_color_transform_signals[PROGRESS], 0,
|
|
(gdouble) done_pixels /
|
|
(gdouble) total_pixels);
|
|
}
|
|
}
|
|
|
|
g_signal_emit (transform, pika_color_transform_signals[PROGRESS], 0,
|
|
1.0);
|
|
}
|
|
|
|
/**
|
|
* pika_color_transform_can_gegl_copy:
|
|
* @src_profile: source #PikaColorProfile
|
|
* @dest_profile: destination #PikaColorProfile
|
|
*
|
|
* This function checks if a PikaColorTransform is needed at all.
|
|
*
|
|
* Returns: %TRUE if pixels can be correctly converted between
|
|
* @src_profile and @dest_profile by simply using
|
|
* gegl_buffer_copy(), babl_process() or similar.
|
|
*
|
|
* Since: 2.10
|
|
**/
|
|
gboolean
|
|
pika_color_transform_can_gegl_copy (PikaColorProfile *src_profile,
|
|
PikaColorProfile *dest_profile)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_COLOR_PROFILE (src_profile), FALSE);
|
|
g_return_val_if_fail (PIKA_IS_COLOR_PROFILE (dest_profile), FALSE);
|
|
|
|
if (pika_color_profile_is_equal (src_profile, dest_profile))
|
|
return TRUE;
|
|
|
|
if (pika_color_profile_get_space (src_profile,
|
|
PIKA_COLOR_RENDERING_INTENT_RELATIVE_COLORIMETRIC,
|
|
NULL) &&
|
|
pika_color_profile_get_space (dest_profile,
|
|
PIKA_COLOR_RENDERING_INTENT_RELATIVE_COLORIMETRIC,
|
|
NULL))
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|