/* PIKA - Photo and Image Kooker Application * a rebranding of The GNU Image Manipulation Program (created with heckimp) * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * * Original copyright, applying to most contents (license remains unchanged): * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * file-jp2.c -- JPEG 2000 file format plug-in * Copyright (C) 2009 Aurimas Juška * Copyright (C) 2004 Florian Traverse * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /* * Portions of this plug-in code (color conversion, etc.) were imported * from the OpenJPEG project covered under the following GNU GPL * compatible license: * * The copyright in this software is being made available under the 2-clauses * BSD License, included below. This software may be subject to other third * party and contributor rights, including patent rights, and no such rights * are granted under this license. * * Copyright (c) 2002-2014, Universite catholique de Louvain (UCL), Belgium * Copyright (c) 2002-2014, Professor Benoit Macq * Copyright (c) 2001-2003, David Janssens * Copyright (c) 2002-2003, Yannick Verschueren * Copyright (c) 2003-2007, Francois-Olivier Devaux * Copyright (c) 2003-2014, Antonin Descampe * Copyright (c) 2005, Herve Drolon, FreeImage Team * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS `AS IS' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #ifdef HAVE_UNISTD_H #include #endif #include #include #include #include #ifdef G_OS_WIN32 #include #endif #ifndef _O_BINARY #define _O_BINARY 0 #endif #include #include #include "libpika/stdplugins-intl.h" #include #define LOAD_JP2_PROC "file-jp2-load" #define LOAD_J2K_PROC "file-j2k-load" #define PLUG_IN_BINARY "file-jp2-load" #define PLUG_IN_ROLE "pika-file-jp2-load" typedef struct _Jp2 Jp2; typedef struct _Jp2Class Jp2Class; struct _Jp2 { PikaPlugIn parent_instance; }; struct _Jp2Class { PikaPlugInClass parent_class; }; #define JP2_TYPE (jp2_get_type ()) #define JP2 (obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), JP2_TYPE, Jp2)) GType jp2_get_type (void) G_GNUC_CONST; static GList * jp2_query_procedures (PikaPlugIn *plug_in); static PikaProcedure * jp2_create_procedure (PikaPlugIn *plug_in, const gchar *name); static PikaValueArray * jp2_load (PikaProcedure *procedure, PikaRunMode run_mode, GFile *file, PikaMetadata *metadata, PikaMetadataLoadFlags *flags, PikaProcedureConfig *config, gpointer run_data); static PikaImage * load_image (PikaProcedure *procedure, GObject *config, GFile *file, OPJ_CODEC_FORMAT format, OPJ_COLOR_SPACE color_space, gboolean interactive, gboolean *profile_loaded, GError **error); static OPJ_COLOR_SPACE open_dialog (PikaProcedure *procedure, GObject *config, GFile *file, OPJ_CODEC_FORMAT format, gint num_components, GError **error); G_DEFINE_TYPE (Jp2, jp2, PIKA_TYPE_PLUG_IN) PIKA_MAIN (JP2_TYPE) DEFINE_STD_SET_I18N static void jp2_class_init (Jp2Class *klass) { PikaPlugInClass *plug_in_class = PIKA_PLUG_IN_CLASS (klass); plug_in_class->query_procedures = jp2_query_procedures; plug_in_class->create_procedure = jp2_create_procedure; plug_in_class->set_i18n = STD_SET_I18N; } static void jp2_init (Jp2 *jp2) { } static GList * jp2_query_procedures (PikaPlugIn *plug_in) { GList *list = NULL; list = g_list_append (list, g_strdup (LOAD_JP2_PROC)); list = g_list_append (list, g_strdup (LOAD_J2K_PROC)); return list; } static PikaProcedure * jp2_create_procedure (PikaPlugIn *plug_in, const gchar *name) { PikaProcedure *procedure = NULL; if (! strcmp (name, LOAD_JP2_PROC)) { procedure = pika_load_procedure_new (plug_in, name, PIKA_PDB_PROC_TYPE_PLUGIN, jp2_load, NULL, NULL); pika_procedure_set_menu_label (procedure, _("JPEG 2000 image")); pika_file_procedure_set_format_name (PIKA_FILE_PROCEDURE (procedure), _("JPEG 2000")); pika_procedure_set_documentation (procedure, "Loads JPEG 2000 images.", "The JPEG 2000 image loader.", name); pika_procedure_set_attribution (procedure, "Aurimas Juška", "Aurimas Juška, Florian Traverse", "2009"); pika_file_procedure_set_mime_types (PIKA_FILE_PROCEDURE (procedure), "image/jp2"); pika_file_procedure_set_extensions (PIKA_FILE_PROCEDURE (procedure), "jp2"); /* XXX: more complete magic number would be: * "0,string,\x00\x00\x00\x0C\x6A\x50\x20\x20\x0D\x0A\x87\x0A" * But the '\0' character makes problem in a 0-terminated string * obviously, as well as some other space characters, it would * seem. The below smaller version seems ok and not interfering * with other formats. */ pika_file_procedure_set_magics (PIKA_FILE_PROCEDURE (procedure), "3,string,\x0CjP"); } else if (! strcmp (name, LOAD_J2K_PROC)) { procedure = pika_load_procedure_new (plug_in, name, PIKA_PDB_PROC_TYPE_PLUGIN, jp2_load, NULL, NULL); pika_procedure_set_menu_label (procedure, _("JPEG 2000 codestream")); pika_file_procedure_set_format_name (PIKA_FILE_PROCEDURE (procedure), _("JPEG 2000")); pika_procedure_set_documentation (procedure, _("Loads JPEG 2000 codestream."), _("Loads JPEG 2000 codestream. " "If the color space is set to " "UNKNOWN (0), we will try to guess, " "which is only possible for few " "spaces (such as grayscale). Most " "such calls will fail. You are rather " "expected to know the color space of " "your data."), name); pika_procedure_set_attribution (procedure, "Jehan", "Jehan", "2009"); pika_file_procedure_set_mime_types (PIKA_FILE_PROCEDURE (procedure), "image/x-jp2-codestream"); pika_file_procedure_set_extensions (PIKA_FILE_PROCEDURE (procedure), "j2k,j2c,jpc"); PIKA_PROC_ARG_INT (procedure, "colorspace", _("Color s_pace"), _("Color space { UNKNOWN (0), GRAYSCALE (1), RGB (2), " "CMYK (3), YCbCr (4), xvYCC (5) }"), 0, 5, 0, G_PARAM_READWRITE); } return procedure; } static PikaValueArray * jp2_load (PikaProcedure *procedure, PikaRunMode run_mode, GFile *file, PikaMetadata *metadata, PikaMetadataLoadFlags *flags, PikaProcedureConfig *config, gpointer run_data) { PikaValueArray *return_vals; PikaImage *image = NULL; OPJ_COLOR_SPACE color_space = OPJ_CLRSPC_UNKNOWN; gboolean interactive; gboolean profile_loaded = FALSE; PikaPDBStatusType status = PIKA_PDB_SUCCESS; GError *error = NULL; gegl_init (NULL, NULL); switch (run_mode) { case PIKA_RUN_INTERACTIVE: pika_ui_init (PLUG_IN_BINARY); interactive = TRUE; break; default: if (! strcmp (pika_procedure_get_name (procedure), LOAD_J2K_PROC)) { gint color_space_argument = 0; g_object_get (config, "colorspace", &color_space_argument, NULL); /* Order is not the same as OpenJPEG enum on purpose, * since it's better to not rely on a given order or * on enum values. */ switch (color_space_argument) { case 1: color_space = OPJ_CLRSPC_GRAY; break; case 2: color_space = OPJ_CLRSPC_SRGB; break; case 3: color_space = OPJ_CLRSPC_CMYK; break; case 4: color_space = OPJ_CLRSPC_SYCC; break; case 5: color_space = OPJ_CLRSPC_EYCC; break; default: break; } } interactive = FALSE; break; } if (! strcmp (pika_procedure_get_name (procedure), LOAD_JP2_PROC)) { image = load_image (procedure, G_OBJECT (config), file, OPJ_CODEC_JP2, color_space, interactive, &profile_loaded, &error); } else { image = load_image (procedure, G_OBJECT (config), file, OPJ_CODEC_J2K, color_space, interactive, &profile_loaded, &error); } if (! image) { status = error ? PIKA_PDB_EXECUTION_ERROR : PIKA_PDB_CANCEL; return pika_procedure_new_return_values (procedure, status, error); } if (profile_loaded) *flags &= ~PIKA_METADATA_LOAD_COLORSPACE; return_vals = pika_procedure_new_return_values (procedure, PIKA_PDB_SUCCESS, NULL); PIKA_VALUES_SET_IMAGE (return_vals, 1, image); return return_vals; } static void sycc_to_rgb (int offset, int upb, int y, int cb, int cr, int *out_r, int *out_g, int *out_b) { int r, g, b; cb -= offset; cr -= offset; r = y + (int) (1.402 * (float) cr); if (r < 0) r = 0; else if (r > upb) r = upb; *out_r = r; g = y - (int) (0.344 * (float) cb + 0.714 * (float) cr); if (g < 0) g = 0; else if (g > upb) g = upb; *out_g = g; b = y + (int) (1.772 * (float) cb); if (b < 0) b = 0; else if (b > upb) b = upb; *out_b = b; } static gboolean sycc420_to_rgb (opj_image_t *img) { int *d0, *d1, *d2, *r, *g, *b, *nr, *ng, *nb; const int *y, *cb, *cr, *ny; size_t maxw, maxh, max, offx, loopmaxw, offy, loopmaxh; int offset, upb; size_t i; upb = (int) img->comps[0].prec; offset = 1 << (upb - 1); upb = (1 << upb) - 1; maxw = (size_t) img->comps[0].w; maxh = (size_t) img->comps[0].h; max = maxw * maxh; y = img->comps[0].data; cb = img->comps[1].data; cr = img->comps[2].data; d0 = r = (int *) malloc (sizeof (int) * max); d1 = g = (int *) malloc (sizeof (int) * max); d2 = b = (int *) malloc (sizeof (int) * max); if (r == NULL || g == NULL || b == NULL) { g_warning ("malloc() failed in sycc420_to_rgb()"); goto out; } /* if img->x0 is odd, then first column shall use Cb/Cr = 0 */ offx = img->x0 & 1U; loopmaxw = maxw - offx; /* if img->y0 is odd, then first line shall use Cb/Cr = 0 */ offy = img->y0 & 1U; loopmaxh = maxh - offy; if (offy > 0U) { size_t j; for (j = 0; j < maxw; ++j) { sycc_to_rgb (offset, upb, *y, 0, 0, r, g, b); ++y; ++r; ++g; ++b; } } for (i = 0U; i < (loopmaxh & ~(size_t) 1U); i += 2U) { size_t j; ny = y + maxw; nr = r + maxw; ng = g + maxw; nb = b + maxw; if (offx > 0U) { sycc_to_rgb (offset, upb, *y, 0, 0, r, g, b); ++y; ++r; ++g; ++b; sycc_to_rgb (offset, upb, *ny, *cb, *cr, nr, ng, nb); ++ny; ++nr; ++ng; ++nb; } for (j = 0; j < (loopmaxw & ~(size_t) 1U); j += 2U) { sycc_to_rgb (offset, upb, *y, *cb, *cr, r, g, b); ++y; ++r; ++g; ++b; sycc_to_rgb (offset, upb, *y, *cb, *cr, r, g, b); ++y; ++r; ++g; ++b; sycc_to_rgb (offset, upb, *ny, *cb, *cr, nr, ng, nb); ++ny; ++nr; ++ng; ++nb; sycc_to_rgb (offset, upb, *ny, *cb, *cr, nr, ng, nb); ++ny; ++nr; ++ng; ++nb; ++cb; ++cr; } if (j < loopmaxw) { sycc_to_rgb (offset, upb, *y, *cb, *cr, r, g, b); ++y; ++r; ++g; ++b; sycc_to_rgb (offset, upb, *ny, *cb, *cr, nr, ng, nb); ++ny; ++nr; ++ng; ++nb; ++cb; ++cr; } y += maxw; r += maxw; g += maxw; b += maxw; } if (i < loopmaxh) { size_t j; for (j = 0U; j < (maxw & ~(size_t) 1U); j += 2U) { sycc_to_rgb (offset, upb, *y, *cb, *cr, r, g, b); ++y; ++r; ++g; ++b; sycc_to_rgb (offset, upb, *y, *cb, *cr, r, g, b); ++y; ++r; ++g; ++b; ++cb; ++cr; } if (j < maxw) { sycc_to_rgb (offset, upb, *y, *cb, *cr, r, g, b); } } free (img->comps[0].data); img->comps[0].data = d0; free (img->comps[1].data); img->comps[1].data = d1; free (img->comps[2].data); img->comps[2].data = d2; img->comps[1].w = img->comps[2].w = img->comps[0].w; img->comps[1].h = img->comps[2].h = img->comps[0].h; img->comps[1].dx = img->comps[2].dx = img->comps[0].dx; img->comps[1].dy = img->comps[2].dy = img->comps[0].dy; img->color_space = OPJ_CLRSPC_SRGB; return TRUE; out: free (r); free (g); free (b); return FALSE; } static gboolean sycc422_to_rgb (opj_image_t *img) { int *d0, *d1, *d2, *r, *g, *b; const int *y, *cb, *cr; size_t maxw, maxh, max, offx, loopmaxw; int offset, upb; size_t i; upb = (int) img->comps[0].prec; offset = 1 <<(upb - 1); upb = (1 << upb) - 1; maxw = (size_t) img->comps[0].w; maxh = (size_t) img->comps[0].h; max = maxw * maxh; y = img->comps[0].data; cb = img->comps[1].data; cr = img->comps[2].data; d0 = r = (int *) malloc (sizeof (int) * max); d1 = g = (int *) malloc (sizeof (int) * max); d2 = b = (int *) malloc (sizeof (int) * max); if (r == NULL || g == NULL || b == NULL) { g_warning ("malloc() failed in sycc422_to_rgb()"); goto out; } /* if img->x0 is odd, then first column shall use Cb/Cr = 0 */ offx = img->x0 & 1U; loopmaxw = maxw - offx; for (i = 0U; i < maxh; ++i) { size_t j; if (offx > 0U) { sycc_to_rgb (offset, upb, *y, 0, 0, r, g, b); ++y; ++r; ++g; ++b; } for (j = 0U; j < (loopmaxw & ~(size_t) 1U); j += 2U) { sycc_to_rgb (offset, upb, *y, *cb, *cr, r, g, b); ++y; ++r; ++g; ++b; sycc_to_rgb (offset, upb, *y, *cb, *cr, r, g, b); ++y; ++r; ++g; ++b; ++cb; ++cr; } if (j < loopmaxw) { sycc_to_rgb (offset, upb, *y, *cb, *cr, r, g, b); ++y; ++r; ++g; ++b; ++cb; ++cr; } } free (img->comps[0].data); img->comps[0].data = d0; free (img->comps[1].data); img->comps[1].data = d1; free (img->comps[2].data); img->comps[2].data = d2; img->comps[1].w = img->comps[2].w = img->comps[0].w; img->comps[1].h = img->comps[2].h = img->comps[0].h; img->comps[1].dx = img->comps[2].dx = img->comps[0].dx; img->comps[1].dy = img->comps[2].dy = img->comps[0].dy; img->color_space = OPJ_CLRSPC_SRGB; return (TRUE); out: free (r); free (g); free (b); return (FALSE); } static gboolean sycc444_to_rgb (opj_image_t *img) { int *d0, *d1, *d2, *r, *g, *b; const int *y, *cb, *cr; size_t maxw, maxh, max, i; int offset, upb; upb = (int) img->comps[0].prec; offset = 1 << (upb - 1); upb = (1 << upb) - 1; maxw = (size_t) img->comps[0].w; maxh = (size_t) img->comps[0].h; max = maxw * maxh; y = img->comps[0].data; cb = img->comps[1].data; cr = img->comps[2].data; d0 = r = (int *) malloc(sizeof (int) * max); d1 = g = (int *) malloc(sizeof (int) * max); d2 = b = (int *) malloc(sizeof (int) * max); if (r == NULL || g == NULL || b == NULL) { g_warning ("malloc() failed in sycc444_to_rgb()"); goto out; } for (i = 0U; i < max; ++i) { sycc_to_rgb (offset, upb, *y, *cb, *cr, r, g, b); ++y; ++cb; ++cr; ++r; ++g; ++b; } free (img->comps[0].data); img->comps[0].data = d0; free (img->comps[1].data); img->comps[1].data = d1; free (img->comps[2].data); img->comps[2].data = d2; img->color_space = OPJ_CLRSPC_SRGB; return TRUE; out: free (r); free (g); free (b); return FALSE; } static gboolean color_sycc_to_rgb (opj_image_t *img) { if (img->numcomps < 3) { img->color_space = OPJ_CLRSPC_GRAY; return TRUE; } else if ((img->comps[0].dx == 1) && (img->comps[1].dx == 2) && (img->comps[2].dx == 2) && (img->comps[0].dy == 1) && (img->comps[1].dy == 2) && (img->comps[2].dy == 2)) { /* horizontal and vertical sub-sample */ return sycc420_to_rgb (img); } else if ((img->comps[0].dx == 1) && (img->comps[1].dx == 2) && (img->comps[2].dx == 2) && (img->comps[0].dy == 1) && (img->comps[1].dy == 1) && (img->comps[2].dy == 1)) { /* horizontal sub-sample only */ return sycc422_to_rgb (img); } else if ((img->comps[0].dx == 1) && (img->comps[1].dx == 1) && (img->comps[2].dx == 1) && (img->comps[0].dy == 1) && (img->comps[1].dy == 1) && (img->comps[2].dy == 1)) { /* no sub-sample */ return sycc444_to_rgb (img); } else { g_warning ("Cannot convert in color_sycc_to_rgb()"); return FALSE; } } static gboolean color_cmyk_to_rgb (opj_image_t *image) { float C, M, Y, K; float sC, sM, sY, sK; unsigned int w, h, max, i; w = image->comps[0].w; h = image->comps[0].h; if ((image->numcomps < 4) || (image->comps[0].dx != image->comps[1].dx) || (image->comps[0].dx != image->comps[2].dx) || (image->comps[0].dx != image->comps[3].dx) || (image->comps[0].dy != image->comps[1].dy) || (image->comps[0].dy != image->comps[2].dy) || (image->comps[0].dy != image->comps[3].dy)) { g_warning ("Cannot convert in color_cmyk_to_rgb()"); return FALSE; } max = w * h; sC = 1.0f / (float) ((1 << image->comps[0].prec) - 1); sM = 1.0f / (float) ((1 << image->comps[1].prec) - 1); sY = 1.0f / (float) ((1 << image->comps[2].prec) - 1); sK = 1.0f / (float) ((1 << image->comps[3].prec) - 1); for (i = 0; i < max; ++i) { /* CMYK values from 0 to 1 */ C = (float) (image->comps[0].data[i]) * sC; M = (float) (image->comps[1].data[i]) * sM; Y = (float) (image->comps[2].data[i]) * sY; K = (float) (image->comps[3].data[i]) * sK; /* Invert all CMYK values */ C = 1.0f - C; M = 1.0f - M; Y = 1.0f - Y; K = 1.0f - K; /* CMYK -> RGB : RGB results from 0 to 255 */ image->comps[0].data[i] = (int) (255.0f * C * K); /* R */ image->comps[1].data[i] = (int) (255.0f * M * K); /* G */ image->comps[2].data[i] = (int) (255.0f * Y * K); /* B */ } free (image->comps[3].data); image->comps[3].data = NULL; image->comps[0].prec = 8; image->comps[1].prec = 8; image->comps[2].prec = 8; image->numcomps -= 1; image->color_space = OPJ_CLRSPC_SRGB; for (i = 3; i < image->numcomps; ++i) { memcpy(&(image->comps[i]), &(image->comps[i + 1]), sizeof (image->comps[i])); } return TRUE; } /* * This code has been adopted from sjpx_openjpeg.c of ghostscript */ static gboolean color_esycc_to_rgb (opj_image_t *image) { int y, cb, cr, sign1, sign2, val; unsigned int w, h, max, i; int flip_value; int max_value; flip_value = (1 << (image->comps[0].prec - 1)); max_value = (1 << image->comps[0].prec) - 1; if ((image->numcomps < 3) || (image->comps[0].dx != image->comps[1].dx) || (image->comps[0].dx != image->comps[2].dx) || (image->comps[0].dy != image->comps[1].dy) || (image->comps[0].dy != image->comps[2].dy)) { g_warning ("Cannot convert in color_esycc_to_rgb()"); return FALSE; } w = image->comps[0].w; h = image->comps[0].h; sign1 = (int)image->comps[1].sgnd; sign2 = (int)image->comps[2].sgnd; max = w * h; for (i = 0; i < max; ++i) { y = image->comps[0].data[i]; cb = image->comps[1].data[i]; cr = image->comps[2].data[i]; if (! sign1) cb -= flip_value; if (! sign2) cr -= flip_value; val = (int) ((float) y - (float) 0.0000368 * (float) cb + (float) 1.40199 * (float) cr + (float) 0.5); if (val > max_value) val = max_value; else if (val < 0) val = 0; image->comps[0].data[i] = val; val = (int) ((float) 1.0003 * (float) y - (float) 0.344125 * (float) cb - (float) 0.7141128 * (float) cr + (float) 0.5); if (val > max_value) val = max_value; else if(val < 0) val = 0; image->comps[1].data[i] = val; val = (int) ((float) 0.999823 * (float) y + (float) 1.77204 * (float) cb - (float) 0.000008 * (float) cr + (float) 0.5); if (val > max_value) val = max_value; else if (val < 0) val = 0; image->comps[2].data[i] = val; } image->color_space = OPJ_CLRSPC_SRGB; return TRUE; } /* * get_valid_precision() converts given precision to standard precision * of pika i.e. 8, 16, 32 * e.g 12-bit to 16-bit , 24-bit to 32-bit */ static gint get_valid_precision (gint precision_actual) { if (precision_actual <= 8) return 8; else if (precision_actual <= 16) return 16; else return 32; } static PikaPrecision get_image_precision (gint precision, gboolean linear) { switch (precision) { case 32: if (linear) return PIKA_PRECISION_U32_LINEAR; return PIKA_PRECISION_U32_NON_LINEAR; case 16: if (linear) return PIKA_PRECISION_U16_LINEAR; return PIKA_PRECISION_U16_NON_LINEAR; default: if (linear) return PIKA_PRECISION_U8_LINEAR; return PIKA_PRECISION_U8_NON_LINEAR; } } static OPJ_COLOR_SPACE open_dialog (PikaProcedure *procedure, GObject *config, GFile *file, OPJ_CODEC_FORMAT format, gint num_components, GError **error) { const gchar *title; GtkWidget *dialog; GtkListStore *store = NULL; gboolean run; OPJ_COLOR_SPACE color_space = OPJ_CLRSPC_SRGB; if (format == OPJ_CODEC_J2K) /* Not having color information is expected. */ title = "Opening JPEG 2000 codestream"; else /* Unexpected, but let's be a bit flexible and ask. */ title = "JPEG 2000 image with no color space"; pika_ui_init (PLUG_IN_BINARY); dialog = pika_procedure_dialog_new (procedure, PIKA_PROCEDURE_CONFIG (config), _(title)); pika_dialog_set_alternative_button_order (GTK_DIALOG (dialog), GTK_RESPONSE_OK, GTK_RESPONSE_CANCEL, -1); if (num_components == 3) { /* Can be RGB, YUV and YCC. */ store = pika_int_store_new (_("sRGB"), OPJ_CLRSPC_SRGB, _("YCbCr"), OPJ_CLRSPC_SYCC, _("xvYCC"), OPJ_CLRSPC_EYCC, NULL); } else if (num_components == 4) { /* Can be RGB, YUV and YCC with alpha or CMYK. */ store = pika_int_store_new (_("sRGB"), OPJ_CLRSPC_SRGB, _("YCbCr"), OPJ_CLRSPC_SYCC, _("xvYCC"), OPJ_CLRSPC_EYCC, _("CMYK"), OPJ_CLRSPC_CMYK, NULL); } else { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, _("Unsupported JPEG 2000%s '%s' with %d components."), (format == OPJ_CODEC_J2K) ? " codestream" : "", pika_file_get_utf8_name (file), num_components); color_space = OPJ_CLRSPC_UNKNOWN; } if (store) { pika_procedure_dialog_get_int_combo (PIKA_PROCEDURE_DIALOG (dialog), "colorspace", PIKA_INT_STORE (store)); /* By default, RGB is active. */ g_object_set (config, "colorspace", color_space, NULL); pika_procedure_dialog_fill (PIKA_PROCEDURE_DIALOG (dialog), NULL); gtk_widget_show (dialog); run = pika_procedure_dialog_run (PIKA_PROCEDURE_DIALOG (dialog)); if (! run) /* Do not set an error here. The import was simply canceled. * No error occurred. */ color_space = OPJ_CLRSPC_UNKNOWN; else g_object_get (config, "colorspace", &color_space, NULL); } gtk_widget_destroy (dialog); return color_space; } static PikaImage * load_image (PikaProcedure *procedure, GObject *config, GFile *file, OPJ_CODEC_FORMAT format, OPJ_COLOR_SPACE color_space, gboolean interactive, gboolean *profile_loaded, GError **error) { opj_stream_t *stream = NULL; opj_codec_t *codec = NULL; opj_dparameters_t parameters; opj_image_t *image = NULL; PikaColorProfile *profile = NULL; PikaImage *pika_image = NULL; PikaLayer *layer; PikaImageType image_type; PikaImageBaseType base_type; gint width; gint height; gint num_components; GeglBuffer *buffer; gint i, j, k, it; guchar *pixels; const Babl *file_format; gint bpp; PikaPrecision image_precision; gint precision_actual, precision_scaled; gint temp; gboolean linear = FALSE; unsigned char *c = NULL; gint n_threads; pika_progress_init_printf (_("Opening '%s'"), pika_file_get_utf8_name (file)); stream = opj_stream_create_default_file_stream (g_file_peek_path (file), OPJ_TRUE); if (! stream) { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, _("Could not open '%s' for reading"), pika_file_get_utf8_name (file)); goto out; } codec = opj_create_decompress (format); if (! codec) { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, _("Failed to initialize decoder for '%s', out of memory?"), pika_file_get_utf8_name (file)); goto out; } n_threads = pika_get_num_processors (); if (n_threads >= 2 && ! opj_codec_set_threads (codec, n_threads)) { g_warning ("Couldn't set number of threads on decoder for '%s'.", pika_file_get_utf8_name (file)); } opj_set_default_decoder_parameters (¶meters); if (opj_setup_decoder (codec, ¶meters) != OPJ_TRUE) { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, _("Couldn't set parameters on decoder for '%s'."), pika_file_get_utf8_name (file)); goto out; } if (opj_read_header (stream, codec, &image) != OPJ_TRUE) { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, _("Couldn't read JP2 header from '%s'."), pika_file_get_utf8_name (file)); goto out; } if (opj_decode (codec, stream, image) != OPJ_TRUE) { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, _("Couldn't decode JP2 image in '%s'."), pika_file_get_utf8_name (file)); goto out; } if (opj_end_decompress (codec, stream) != OPJ_TRUE) { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, _("Couldn't decompress JP2 image in '%s'."), pika_file_get_utf8_name (file)); goto out; } if (image->icc_profile_buf) { if (image->icc_profile_len) { profile = pika_color_profile_new_from_icc_profile (image->icc_profile_buf, image->icc_profile_len, error); if (! profile) goto out; *profile_loaded = TRUE; if (image->color_space == OPJ_CLRSPC_UNSPECIFIED || image->color_space == OPJ_CLRSPC_UNKNOWN) { if (pika_color_profile_is_rgb (profile)) image->color_space = OPJ_CLRSPC_SRGB; else if (pika_color_profile_is_gray (profile)) image->color_space = OPJ_CLRSPC_GRAY; else if (pika_color_profile_is_cmyk (profile)) image->color_space = OPJ_CLRSPC_CMYK; } } else { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, _("Couldn't decode CIELAB JP2 image in '%s'."), pika_file_get_utf8_name (file)); goto out; } free (image->icc_profile_buf); image->icc_profile_buf = NULL; image->icc_profile_len = 0; } num_components = image->numcomps; if ((image->color_space == OPJ_CLRSPC_UNSPECIFIED || image->color_space == OPJ_CLRSPC_UNKNOWN) && ! interactive) image->color_space = color_space; if (image->color_space == OPJ_CLRSPC_UNSPECIFIED || image->color_space == OPJ_CLRSPC_UNKNOWN) { /* Sometimes the color space is not set at this point, which * sucks. This happens always with codestream images (.j2c or * .j2k) which are meant to be embedded by other files. * * It might also happen with JP2 in case the header does not have * color space and the ICC profile is absent (though this may mean * that the JP2 is broken, but let's be flexible and allow manual * fallback). * Assuming RGB/RGBA space is bogus since this format can handle * so much more. Therefore we instead pop-up a dialog asking one * to specify the color space in interactive mode. */ if (num_components == 1 || num_components == 2) { /* Only possibility is gray. */ image->color_space = OPJ_CLRSPC_GRAY; } else if (num_components == 5) { /* Can only be CMYK with Alpha. */ image->color_space = OPJ_CLRSPC_CMYK; } else if (interactive) { image->color_space = open_dialog (procedure, config, file, format, num_components, error); if (image->color_space == OPJ_CLRSPC_UNKNOWN) goto out; } else /* ! interactive */ { /* API call where color space was set to UNKNOWN. We don't * want to guess or assume anything. It is much better to just * fail. It is the responsibility of the developer to know its * data when loading it in a script. */ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, _("Unknown color space in JP2 codestream '%s'."), pika_file_get_utf8_name (file)); goto out; } } if (image->color_space == OPJ_CLRSPC_SYCC) { if (! color_sycc_to_rgb (image)) { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, _("Couldn't convert YCbCr JP2 image '%s' to RGB."), pika_file_get_utf8_name (file)); goto out; } } else if ((image->color_space == OPJ_CLRSPC_CMYK)) { if (! color_cmyk_to_rgb (image)) { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, _("Couldn't convert CMYK JP2 image in '%s' to RGB."), pika_file_get_utf8_name (file)); goto out; } } else if (image->color_space == OPJ_CLRSPC_EYCC) { if (! color_esycc_to_rgb (image)) { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, _("Couldn't convert xvYCC JP2 image in '%s' to RGB."), pika_file_get_utf8_name (file)); goto out; } } /* At this point, the image should be converted to Gray or RGB. */ if (image->color_space == OPJ_CLRSPC_GRAY) { base_type = PIKA_GRAY; image_type = PIKA_GRAY_IMAGE; if (num_components == 2) image_type = PIKA_GRAYA_IMAGE; } else if (image->color_space == OPJ_CLRSPC_SRGB) { base_type = PIKA_RGB; image_type = PIKA_RGB_IMAGE; if (num_components == 4) image_type = PIKA_RGBA_IMAGE; } else { /* If not gray or RGB, this is an image we cannot handle. */ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, _("Unsupported color space in JP2 image '%s'."), pika_file_get_utf8_name (file)); goto out; } width = image->comps[0].w; height = image->comps[0].h; if (profile) linear = pika_color_profile_is_linear (profile); precision_actual = image->comps[0].prec; precision_scaled = get_valid_precision (precision_actual); image_precision = get_image_precision (precision_scaled, linear); pika_image = pika_image_new_with_precision (width, height, base_type, image_precision); if (profile) pika_image_set_color_profile (pika_image, profile); layer = pika_layer_new (pika_image, _("Background"), width, height, image_type, 100, pika_image_get_default_new_layer_mode (pika_image)); pika_image_insert_layer (pika_image, layer, NULL, 0); file_format = pika_drawable_get_format (PIKA_DRAWABLE (layer)); bpp = babl_format_get_bytes_per_pixel (file_format); buffer = pika_drawable_get_buffer (PIKA_DRAWABLE (layer)); pixels = g_new0 (guchar, width * bpp); for (i = 0; i < height; i++) { for (j = 0; j < num_components; j++) { int shift = precision_scaled - precision_actual; for (k = 0; k < width; k++) { if (shift >= 0) temp = image->comps[j].data[i * width + k] << shift; else /* precision_actual > 32 */ temp = image->comps[j].data[i * width + k] >> (- shift); c = (unsigned char *) &temp; for (it = 0; it < (precision_scaled / 8); it++) { pixels[k * bpp + j * (precision_scaled / 8) + it] = c[it]; } } } gegl_buffer_set (buffer, GEGL_RECTANGLE (0, i, width, 1), 0, file_format, pixels, GEGL_AUTO_ROWSTRIDE); } g_free (pixels); g_object_unref (buffer); pika_progress_update (1.0); out: if (profile) g_object_unref (profile); if (image) opj_image_destroy (image); if (codec) opj_destroy_codec (codec); if (stream) opj_stream_destroy (stream); return pika_image; }