plug-in: Initial support for CMYK/A JPEGXL import

Checks if file has an extra key channel for CMYK. If so, it is combined
with the image's CMY image to create a CMYK buffer for import.
Color profile is stored as image simulation profile.
If alpha channel is present, it is now loaded as well for CMKYA images.
This commit is contained in:
Alx Sa 2022-12-03 15:49:07 +00:00
parent 5c4dac0d67
commit b04f45f354
1 changed files with 282 additions and 15 deletions

View File

@ -73,6 +73,15 @@ static GimpValueArray *jpegxl_save (GimpProcedure *procedure,
const GimpValueArray *args, const GimpValueArray *args,
gpointer run_data); gpointer run_data);
static void create_cmyk_layer (GimpImage *image,
GimpLayer *layer,
const Babl *space,
const Babl *type,
gpointer picture_buffer,
gpointer key_buffer,
gint bit_depth,
gboolean has_alpha);
G_DEFINE_TYPE (JpegXL, jpegxl, GIMP_TYPE_PLUG_IN) G_DEFINE_TYPE (JpegXL, jpegxl, GIMP_TYPE_PLUG_IN)
@ -193,6 +202,12 @@ jpegxl_create_procedure (GimpPlugIn *plug_in,
FALSE, FALSE,
G_PARAM_READWRITE); G_PARAM_READWRITE);
GIMP_PROC_ARG_BOOLEAN (procedure, "cmyk",
"Export as CMY_K",
"Create a CMYK JPEG XL image using the soft-proofing color profile",
FALSE,
G_PARAM_READWRITE);
GIMP_PROC_ARG_BOOLEAN (procedure, "save-exif", GIMP_PROC_ARG_BOOLEAN (procedure, "save-exif",
_("Save Exi_f"), _("Save Exi_f"),
_("Toggle saving Exif data"), _("Toggle saving Exif data"),
@ -209,6 +224,160 @@ jpegxl_create_procedure (GimpPlugIn *plug_in,
return procedure; return procedure;
} }
/* The Key data is stored in a separate extra
* channel. We combine the CMY values from the
* main image with the K values to create
* the final layer buffer.
*/
static void
create_cmyk_layer (GimpImage *image,
GimpLayer *layer,
const Babl *type,
const Babl *space,
gpointer cmy_data,
gpointer key_data,
gint bit_depth,
gboolean has_alpha)
{
const Babl *cmy_format = NULL;
const Babl *cmyka_format = NULL;
const Babl *key_format = NULL;
const Babl *rgb_format = NULL;
GeglBuffer *output_buffer;
GeglBuffer *picture_buffer;
GeglBuffer *cmy_buffer;
GeglBuffer *key_buffer;
GeglBufferIterator *iter;
GeglColor *fill_color = gegl_color_new ("rgba(0.0,0.0,0.0,0.0)");
gint width;
gint height;
gint n_components = 3;
width = gimp_image_get_width (image);
height = gimp_image_get_height (image);
if (has_alpha)
n_components = 4;
gimp_image_insert_layer (image, layer, NULL, 0);
output_buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (layer));
if (bit_depth == 1)
rgb_format = babl_format (has_alpha ? "R'G'B'A u8" : "R'G'B' u8");
else
rgb_format = babl_format (has_alpha ? "R'G'B'A u16" : "R'G'B' u16");
if (bit_depth == 1)
cmy_format = babl_format_with_space ("cmyk u8", space);
else
cmy_format = babl_format_with_space ("cmyk u16", space);
if (bit_depth == 1)
cmyka_format = babl_format_with_space ("cmykA u8", space);
else
cmyka_format = babl_format_with_space ("cmykA u16", space);
key_format = babl_format_new (babl_model ("Y"),
type,
babl_component ("Y"),
NULL);
cmy_format = babl_format_with_space (babl_format_get_encoding (cmy_format),
space);
cmyka_format = babl_format_with_space (babl_format_get_encoding (cmyka_format),
space);
key_format = babl_format_with_space (babl_format_get_encoding (key_format),
space);
picture_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, width, height),
has_alpha ? cmyka_format : cmy_format);
gegl_buffer_set_color (picture_buffer, NULL, fill_color);
cmy_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, width, height),
babl_format_n (type, n_components));
key_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, width, height),
key_format);
gegl_buffer_set (cmy_buffer, GEGL_RECTANGLE (0, 0, width, height), 0,
babl_format_n (type, n_components), cmy_data,
GEGL_AUTO_ROWSTRIDE);
gegl_buffer_set (key_buffer, GEGL_RECTANGLE (0, 0, width, height), 0,
key_format, key_data, GEGL_AUTO_ROWSTRIDE);
iter = gegl_buffer_iterator_new (picture_buffer,
GEGL_RECTANGLE (0, 0, width, height), 0,
has_alpha ? cmyka_format : cmy_format,
GEGL_BUFFER_READWRITE,
GEGL_ABYSS_NONE, 4);
gegl_buffer_iterator_add (iter, cmy_buffer,
GEGL_RECTANGLE (0, 0, width, height), 0,
babl_format_n (type, n_components),
GEGL_ACCESS_READ, GEGL_ABYSS_NONE);
gegl_buffer_iterator_add (iter, key_buffer,
GEGL_RECTANGLE (0, 0, width, height), 0,
key_format, GEGL_ACCESS_READ, GEGL_ABYSS_NONE);
gegl_buffer_iterator_add (iter, output_buffer,
GEGL_RECTANGLE (0, 0, width, height), 0,
rgb_format, GEGL_BUFFER_READWRITE, GEGL_ABYSS_NONE);
while (gegl_buffer_iterator_next (iter))
{
guchar *pixel = iter->items[0].data;
guchar *cmy = iter->items[1].data;
guchar *k = iter->items[2].data;
guchar *output = iter->items[3].data;
gint length = iter->length;
gint row = length;
while (length--)
{
gint i;
for (i = 0; i < 3 * bit_depth; i++)
pixel[i] = cmy[i];
for (i = 0; i < bit_depth; i++)
pixel[i + (3 * bit_depth)] = k[i];
if (has_alpha)
{
for (i = 0; i < bit_depth; i++)
pixel[i + (4 * bit_depth)] = cmy[i + (3 * bit_depth)];
}
pixel += 4 * bit_depth;
cmy += 3 * bit_depth;
k += bit_depth;
if (has_alpha)
{
pixel += bit_depth;
cmy += bit_depth;
}
output += 4 * bit_depth;
}
/* Convert row from CMYK/A to RGB, due to layer buffers
* having a maximum of 4 colors currently */
pixel -= (4 * bit_depth) * row;
if (has_alpha)
pixel -= row * bit_depth;
output -= (4 * bit_depth) * row;
babl_process (babl_fish (has_alpha ? cmyka_format : cmy_format, rgb_format),
pixel, output, row);
}
g_object_unref (output_buffer);
g_object_unref (picture_buffer);
g_object_unref (cmy_buffer);
g_object_unref (key_buffer);
g_free (key_data);
}
static GimpImage * static GimpImage *
load_image (GFile *file, load_image (GFile *file,
GimpRunMode runmode, GimpRunMode runmode,
@ -226,16 +395,22 @@ load_image (GFile *file,
JxlDecoderStatus status; JxlDecoderStatus status;
JxlPixelFormat pixel_format; JxlPixelFormat pixel_format;
JxlColorEncoding color_encoding; JxlColorEncoding color_encoding;
size_t icc_size = 0; size_t icc_size = 0;
GimpColorProfile *profile = NULL; GimpColorProfile *profile = NULL;
gboolean loadlinear = FALSE; gboolean loadlinear = FALSE;
size_t channel_depth; size_t channel_depth;
size_t result_size; size_t result_size;
size_t extra_channel_size = 0;
gpointer picture_buffer; gpointer picture_buffer;
gpointer key_buffer = NULL;
gboolean is_cmyk = FALSE;
gboolean has_alpha = FALSE;
gint cmyk_channel_id = -1;
GimpImage *image; GimpImage *image;
GimpLayer *layer; GimpLayer *layer;
GeglBuffer *buffer; GeglBuffer *buffer;
const Babl *space = NULL;
const Babl *type;
GimpPrecision precision_linear; GimpPrecision precision_linear;
GimpPrecision precision_non_linear; GimpPrecision precision_non_linear;
@ -401,6 +576,7 @@ load_image (GFile *file,
channel_depth = 4; channel_depth = 4;
precision_linear = GIMP_PRECISION_FLOAT_LINEAR; precision_linear = GIMP_PRECISION_FLOAT_LINEAR;
precision_non_linear = GIMP_PRECISION_FLOAT_NON_LINEAR; precision_non_linear = GIMP_PRECISION_FLOAT_NON_LINEAR;
type = babl_type ("float");
} }
else if (basicinfo.bits_per_sample <= 8) else if (basicinfo.bits_per_sample <= 8)
{ {
@ -408,6 +584,7 @@ load_image (GFile *file,
channel_depth = 1; channel_depth = 1;
precision_linear = GIMP_PRECISION_U8_LINEAR; precision_linear = GIMP_PRECISION_U8_LINEAR;
precision_non_linear = GIMP_PRECISION_U8_NON_LINEAR; precision_non_linear = GIMP_PRECISION_U8_NON_LINEAR;
type = babl_type ("u8");
} }
else else
{ {
@ -415,6 +592,7 @@ load_image (GFile *file,
channel_depth = 2; channel_depth = 2;
precision_linear = GIMP_PRECISION_U16_LINEAR; precision_linear = GIMP_PRECISION_U16_LINEAR;
precision_non_linear = GIMP_PRECISION_U16_NON_LINEAR; precision_non_linear = GIMP_PRECISION_U16_NON_LINEAR;
type = babl_type ("u16");
} }
if (basicinfo.num_color_channels == 1) /* grayscale */ if (basicinfo.num_color_channels == 1) /* grayscale */
@ -441,7 +619,34 @@ load_image (GFile *file,
} }
} }
/* Check for extra channels */
for (gint32 i = 0; i < basicinfo.num_extra_channels; i++)
{
JxlExtraChannelInfo extra;
if (JXL_DEC_SUCCESS != JxlDecoderGetExtraChannelInfo (decoder, i, &extra))
break;
/* K channel for CMYK images */
if (extra.type == JXL_CHANNEL_BLACK)
{
is_cmyk = TRUE;
cmyk_channel_id = i;
if (pixel_format.num_channels < 3)
pixel_format.num_channels = 3;
}
/* Confirm presence of alpha channel */
if (extra.type == JXL_CHANNEL_ALPHA)
{
has_alpha = TRUE;
pixel_format.num_channels = 4;
}
}
result_size = channel_depth * pixel_format.num_channels * (size_t) basicinfo.xsize * (size_t) basicinfo.ysize; result_size = channel_depth * pixel_format.num_channels * (size_t) basicinfo.xsize * (size_t) basicinfo.ysize;
extra_channel_size = channel_depth * basicinfo.num_extra_channels * (size_t) basicinfo.xsize * (size_t) basicinfo.ysize;
result_size += extra_channel_size;
if (JxlDecoderGetColorAsEncodedProfile (decoder, &pixel_format, if (JxlDecoderGetColorAsEncodedProfile (decoder, &pixel_format,
JXL_COLOR_PROFILE_TARGET_DATA, JXL_COLOR_PROFILE_TARGET_DATA,
@ -485,7 +690,7 @@ load_image (GFile *file,
} }
} }
if (!profile) if (! profile)
{ {
if (JxlDecoderGetICCProfileSize (decoder, &pixel_format, if (JxlDecoderGetICCProfileSize (decoder, &pixel_format,
JXL_COLOR_PROFILE_TARGET_DATA, JXL_COLOR_PROFILE_TARGET_DATA,
@ -545,7 +750,7 @@ load_image (GFile *file,
} }
picture_buffer = g_try_malloc (result_size); picture_buffer = g_try_malloc (result_size);
if (!picture_buffer) if (! picture_buffer)
{ {
g_set_error (error, G_FILE_ERROR, 0, "Memory could not be allocated."); g_set_error (error, G_FILE_ERROR, 0, "Memory could not be allocated.");
if (profile) if (profile)
@ -572,6 +777,55 @@ load_image (GFile *file,
return NULL; return NULL;
} }
/* Loading key channel buffer data */
if (is_cmyk)
{
if (JxlDecoderExtraChannelBufferSize (decoder, &pixel_format,
&result_size, cmyk_channel_id)
!= JXL_DEC_SUCCESS)
{
g_set_error (error, G_FILE_ERROR, 0,
"ERROR: JxlDecoderExtraChannelBufferSize failed");
if (profile)
g_object_unref (profile);
JxlThreadParallelRunnerDestroy (runner);
JxlDecoderDestroy (decoder);
g_free (memory);
return NULL;
}
key_buffer = g_try_malloc (result_size);
if (! key_buffer)
{
g_set_error (error, G_FILE_ERROR, 0, "Memory could not be allocated.");
if (profile)
g_object_unref (profile);
JxlThreadParallelRunnerDestroy (runner);
JxlDecoderDestroy (decoder);
g_free (memory);
return NULL;
}
if (JxlDecoderSetExtraChannelBuffer (decoder, &pixel_format, key_buffer,
result_size, cmyk_channel_id)
!= JXL_DEC_SUCCESS)
{
g_set_error (error, G_FILE_ERROR, 0,
"ERROR: JxlDecoderSetExtraChannelBuffer failed");
if (profile)
g_object_unref (profile);
JxlThreadParallelRunnerDestroy (runner);
JxlDecoderDestroy (decoder);
g_free (memory);
return NULL;
}
}
status = JxlDecoderProcessInput (decoder); status = JxlDecoderProcessInput (decoder);
if (status != JXL_DEC_FULL_IMAGE) if (status != JXL_DEC_FULL_IMAGE)
{ {
@ -588,7 +842,6 @@ load_image (GFile *file,
return NULL; return NULL;
} }
if (basicinfo.num_color_channels == 1) /* grayscale */ if (basicinfo.num_color_channels == 1) /* grayscale */
{ {
image = gimp_image_new_with_precision (basicinfo.xsize, basicinfo.ysize, GIMP_GRAY, image = gimp_image_new_with_precision (basicinfo.xsize, basicinfo.ysize, GIMP_GRAY,
@ -607,7 +860,7 @@ load_image (GFile *file,
(basicinfo.alpha_bits > 0) ? GIMP_GRAYA_IMAGE : GIMP_GRAY_IMAGE, 100, (basicinfo.alpha_bits > 0) ? GIMP_GRAYA_IMAGE : GIMP_GRAY_IMAGE, 100,
gimp_image_get_default_new_layer_mode (image)); gimp_image_get_default_new_layer_mode (image));
} }
else /* RGB */ else /* RGB or CMYK */
{ {
image = gimp_image_new_with_precision (basicinfo.xsize, basicinfo.ysize, GIMP_RGB, image = gimp_image_new_with_precision (basicinfo.xsize, basicinfo.ysize, GIMP_RGB,
loadlinear ? precision_linear : precision_non_linear); loadlinear ? precision_linear : precision_non_linear);
@ -618,24 +871,38 @@ load_image (GFile *file,
{ {
gimp_image_set_color_profile (image, profile); gimp_image_set_color_profile (image, profile);
} }
else if (is_cmyk && gimp_color_profile_is_cmyk (profile))
{
gimp_image_set_simulation_profile (image, profile);
space = gimp_color_profile_get_space (profile,
GIMP_COLOR_RENDERING_INTENT_RELATIVE_COLORIMETRIC,
NULL);
}
} }
layer = gimp_layer_new (image, "Background", layer = gimp_layer_new (image, "Background",
basicinfo.xsize, basicinfo.ysize, basicinfo.xsize, basicinfo.ysize,
(basicinfo.alpha_bits > 0) ? GIMP_RGBA_IMAGE : GIMP_RGB_IMAGE, 100, (basicinfo.alpha_bits > 0) ? GIMP_RGBA_IMAGE : GIMP_RGB_IMAGE, 100,
gimp_image_get_default_new_layer_mode (image)); gimp_image_get_default_new_layer_mode (image));
} }
gimp_image_insert_layer (image, layer, NULL, 0); if (is_cmyk)
{
create_cmyk_layer (image, layer, type, space, picture_buffer,
key_buffer, channel_depth, has_alpha);
}
else
{
gimp_image_insert_layer (image, layer, NULL, 0);
buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (layer)); buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (layer));
gegl_buffer_set (buffer, GEGL_RECTANGLE (0, 0, basicinfo.xsize, basicinfo.ysize), 0, gegl_buffer_set (buffer, GEGL_RECTANGLE (0, 0, basicinfo.xsize, basicinfo.ysize), 0,
NULL, picture_buffer, GEGL_AUTO_ROWSTRIDE); NULL, picture_buffer, GEGL_AUTO_ROWSTRIDE);
g_object_unref (buffer);
g_object_unref (buffer);
}
g_free (picture_buffer); g_free (picture_buffer);
if (profile) if (profile)