io-jp2.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. /* GdkPixbuf library - JPEG2000 Image Loader
  2. *
  3. * Copyright © 2020 Nichlas Severinsen
  4. *
  5. * This library is free software; you can redistribute it and/or
  6. * modify it under the terms of the GNU Lesser General Public
  7. * License as published by the Free Software Foundation; either
  8. * version 2.1 of the License, or (at your option) any later version.
  9. *
  10. * This library is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  13. * Lesser General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Lesser General Public
  16. * License along with this library; if not, write to the Free Software
  17. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  18. */
  19. #define GDK_PIXBUF_ENABLE_BACKEND
  20. #include <gdk-pixbuf/gdk-pixbuf.h>
  21. #undef GDK_PIXBUF_ENABLE_BACKEND
  22. #include <openjpeg.h>
  23. #include <string.h>
  24. #include <util.h>
  25. #include <color.h>
  26. typedef enum {
  27. IS_OUTPUT = 0,
  28. IS_INPUT = 1,
  29. } IS_IO;
  30. typedef enum {
  31. J2K_CFMT = 0,
  32. JP2_CFMT = 1,
  33. JPT_CFMT = 2,
  34. } CFMT;
  35. typedef struct {
  36. GdkPixbufModuleSizeFunc size_func;
  37. GdkPixbufModuleUpdatedFunc update_func;
  38. GdkPixbufModulePreparedFunc prepare_func;
  39. gpointer user_data;
  40. GdkPixbuf *pixbuf;
  41. GError **error;
  42. } JP2Context;
  43. static void free_buffer(guchar *pixels, gpointer data)
  44. {
  45. g_free(pixels);
  46. }
  47. static GdkPixbuf *gdk_pixbuf__jp2_image_load(FILE *fp, GError **error)
  48. {
  49. int codec_type;
  50. GdkPixbuf *pixbuf = NULL;
  51. opj_codec_t *codec = NULL;
  52. opj_image_t *image = NULL;
  53. opj_stream_t *stream = NULL;
  54. opj_dparameters_t parameters;
  55. opj_set_default_decoder_parameters(&parameters);
  56. stream = util_create_stream(fp, IS_INPUT);
  57. if(!stream)
  58. {
  59. util_destroy(codec, stream, image);
  60. g_set_error(error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED, "Failed to create stream from file pointer");
  61. return FALSE;
  62. }
  63. codec_type = util_identify(fp);
  64. if(codec_type < 0)
  65. {
  66. util_destroy(codec, stream, image);
  67. g_set_error(error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED, "Unknown filetype!");
  68. return FALSE;
  69. }
  70. codec = opj_create_decompress(codec_type);
  71. #if DEBUG == TRUE
  72. opj_set_info_handler(codec, info_callback, 00);
  73. opj_set_warning_handler(codec, warning_callback, 00);
  74. opj_set_error_handler(codec, error_callback, 00);
  75. #endif
  76. if(!opj_setup_decoder(codec, &parameters))
  77. {
  78. util_destroy(codec, stream, image);
  79. g_set_error(error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED, "Failed to setup decoder");
  80. return FALSE;
  81. }
  82. if(!opj_codec_set_threads(codec, 1))
  83. {
  84. util_destroy(codec, stream, image);
  85. g_set_error(error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED, "Failed to set thread count");
  86. return FALSE;
  87. }
  88. if(!opj_read_header(stream, codec, &image))
  89. {
  90. util_destroy(codec, stream, image);
  91. g_set_error(error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED, "Failed to read header");
  92. return FALSE;
  93. }
  94. /* Optional if decoding the entire image, which is why it's commented out
  95. if(!opj_set_decode_area(codec, image, (OPJ_INT32) parameters.DA_x0, (OPJ_INT32) parameters.DA_y0, (OPJ_INT32) parameters.DA_x1, (OPJ_INT32) parameters.DA_y1))
  96. {
  97. util_destroy(codec, stream, image);
  98. g_set_error(error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED, "Failed to set decode area");
  99. return FALSE;
  100. }
  101. */
  102. if(!opj_decode(codec, stream, image) && opj_end_decompress(codec, stream))
  103. {
  104. util_destroy(codec, stream, image);
  105. g_set_error(error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED, "Failed to decode the image");
  106. return FALSE;
  107. }
  108. opj_stream_destroy(stream);
  109. opj_destroy_codec(codec);
  110. // Get components and colorspace needed to convert to RGB
  111. int components = -1;
  112. COLOR_SPACE colorspace = -1;
  113. if(!color_info(image, &components, &colorspace))
  114. {
  115. util_destroy(NULL, NULL, image);
  116. g_set_error(error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED, "Unsupported colorspace");
  117. return FALSE;
  118. }
  119. // Allocate space for GdkPixbuf RGB
  120. guint8 *data = g_malloc(sizeof(guint8) * (int) image->comps[0].w * (int) image->comps[0].h * components);
  121. // Convert image to RGB depending on the colorspace
  122. switch(colorspace)
  123. {
  124. case COLOR_SPACE_RGB:
  125. color_convert_rgb(image, data);
  126. break;
  127. case COLOR_SPACE_GRAY:
  128. color_convert_gray(image, data);
  129. break;
  130. case COLOR_SPACE_GRAY12:
  131. color_convert_gray12(image, data);
  132. break;
  133. case COLOR_SPACE_SYCC420:
  134. color_convert_sycc420(image, data);
  135. break;
  136. case COLOR_SPACE_SYCC422:
  137. color_convert_sycc422(image, data);
  138. break;
  139. case COLOR_SPACE_SYCC444:
  140. color_convert_sycc444(image, data);
  141. break;
  142. case COLOR_SPACE_CMYK:
  143. color_convert_cmyk(image, data);
  144. break;
  145. }
  146. pixbuf = gdk_pixbuf_new_from_data(
  147. (const guchar*) data, // Actual data. RGB: {0, 0, 0}. RGBA: {0, 0, 0, 0}.
  148. GDK_COLORSPACE_RGB, // Colorspace (only RGB supported, lol, what's the point)
  149. (components == 4 || components == 2), // has_alpha
  150. 8, // bits_per_sample (only 8 bit supported, again, why even bother)
  151. (int) image->comps[0].w, // width
  152. (int) image->comps[0].h, // height
  153. util_rowstride(image, components), // rowstride: distance in bytes between row starts
  154. free_buffer, // destroy function
  155. NULL // closure data to pass to the destroy notification function
  156. );
  157. opj_image_destroy(image);
  158. return pixbuf;
  159. }
  160. #if FALSE
  161. static gpointer gdk_pixbuf__jp2_image_begin_load
  162. (
  163. GdkPixbufModuleSizeFunc size_func,
  164. GdkPixbufModulePreparedFunc prepare_func,
  165. GdkPixbufModuleUpdatedFunc update_func,
  166. gpointer user_data,
  167. GError **error
  168. ) {
  169. JP2Context *context = g_new0 (JP2Context, 1);
  170. context->size_func = size_func;
  171. context->prepare_func = prepare_func;
  172. context->update_func = update_func;
  173. context->user_data = user_data;
  174. return context;
  175. }
  176. static gboolean gdk_pixbuf__jp2_image_stop_load(gpointer context, GError **error)
  177. {
  178. JP2Context *data = (JP2Context *) context;
  179. g_return_val_if_fail(data != NULL, TRUE);
  180. if (data->pixbuf) {
  181. g_object_unref(data->pixbuf);
  182. }
  183. return TRUE;
  184. }
  185. static gboolean gdk_pixbuf__jp2_image_load_increment(gpointer context, const guchar *buf, guint size, GError **error)
  186. {
  187. JP2Context *data = (JP2Context *) context;
  188. g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED, "Bro, I just can't...");
  189. return FALSE;
  190. }
  191. static gboolean gdk_pixbuf__jp2_image_save_to_callback
  192. (
  193. GdkPixbufSaveFunc save_func,
  194. gpointer user_data,
  195. GdkPixbuf *pixbuf,
  196. gchar **keys,
  197. gchar **values,
  198. GError **error
  199. ) {
  200. return TRUE;
  201. }
  202. #endif
  203. static gboolean save_jp2
  204. (
  205. GdkPixbuf *pixbuf,
  206. gchar **keys,
  207. gchar **values,
  208. GError **error,
  209. FILE *fp
  210. ) {
  211. int counter = 0;
  212. guchar *pixels;
  213. gboolean has_alpha;
  214. opj_codec_t *codec = NULL;
  215. opj_image_t *image = NULL;
  216. opj_stream_t *stream = NULL;
  217. opj_cparameters_t parameters;
  218. int components, precision, width, height;
  219. opj_image_cmptparm_t component_parameters[4]; /* RGBA: max. 4 components */
  220. opj_set_default_encoder_parameters(&parameters);
  221. parameters.cod_format = JP2_CFMT;
  222. width = gdk_pixbuf_get_width(pixbuf);
  223. height = gdk_pixbuf_get_height(pixbuf);
  224. pixels = gdk_pixbuf_get_pixels(pixbuf);
  225. components = gdk_pixbuf_get_n_channels(pixbuf);
  226. precision = gdk_pixbuf_get_bits_per_sample(pixbuf);
  227. has_alpha = (components == 4);
  228. memset(&component_parameters[0], 0, (size_t) components * sizeof(opj_image_cmptparm_t));
  229. for(int i = 0; i < components; i++)
  230. {
  231. component_parameters[i].prec = (OPJ_UINT32) precision;
  232. component_parameters[i].bpp = (OPJ_UINT32) precision;
  233. component_parameters[i].sgnd = 0;
  234. component_parameters[i].dx = (OPJ_UINT32) 1;
  235. component_parameters[i].dy = (OPJ_UINT32) 1;
  236. component_parameters[i].w = (OPJ_UINT32) width;
  237. component_parameters[i].h = (OPJ_UINT32) height;
  238. }
  239. image = opj_image_create((OPJ_UINT32) components, &component_parameters[0], OPJ_CLRSPC_SRGB);
  240. if(!image)
  241. {
  242. g_set_error(error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED, "Failed to create image");
  243. return FALSE;
  244. }
  245. image->x0 = (OPJ_UINT32) 0;
  246. image->y0 = (OPJ_UINT32) 0;
  247. image->x1 = (OPJ_UINT32) width;
  248. image->y1 = (OPJ_UINT32) height;
  249. for(int i = 0; i < width * height; i++)
  250. {
  251. image->comps[0].data[i] = pixels[counter++];
  252. image->comps[1].data[i] = pixels[counter++];
  253. image->comps[2].data[i] = pixels[counter++];
  254. if(has_alpha)
  255. {
  256. image->comps[3].data[i] = pixels[counter++];
  257. }
  258. }
  259. // Encode
  260. switch(parameters.cod_format)
  261. {
  262. case J2K_CFMT:
  263. codec = opj_create_compress(OPJ_CODEC_J2K);
  264. break;
  265. case JP2_CFMT:
  266. codec = opj_create_compress(OPJ_CODEC_JP2);
  267. break;
  268. case JPT_CFMT:
  269. codec = opj_create_compress(OPJ_CODEC_JPT);
  270. break;
  271. default:
  272. g_set_error(error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED, "Failed to create compress");
  273. return FALSE;
  274. }
  275. #if DEBUG == TRUE
  276. opj_set_info_handler(codec, info_callback, 00);
  277. opj_set_warning_handler(codec, warning_callback, 00);
  278. opj_set_error_handler(codec, error_callback, 00);
  279. #endif
  280. if(!opj_setup_encoder(codec, &parameters, image))
  281. {
  282. util_destroy(codec, stream, image);
  283. g_set_error(error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED, "Failed to setup encoder");
  284. return FALSE;
  285. }
  286. stream = util_create_stream(fp, IS_OUTPUT);
  287. if(!stream)
  288. {
  289. util_destroy(codec, stream, image);
  290. g_set_error(error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED, "Failed to create stream from file pointer");
  291. return FALSE;
  292. }
  293. if(!opj_start_compress(codec, image, stream))
  294. {
  295. util_destroy(codec, stream, image);
  296. g_set_error(error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED, "Failed to start compressing the image");
  297. return FALSE;
  298. } else {
  299. if(!opj_encode(codec, stream))
  300. {
  301. util_destroy(codec, stream, image);
  302. g_set_error(error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED, "Failed to encode the image");
  303. return FALSE;
  304. }
  305. if(!opj_end_compress(codec, stream))
  306. {
  307. util_destroy(codec, stream, image);
  308. g_set_error(error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED, "Failed to end compressing the image");
  309. return FALSE;
  310. }
  311. }
  312. util_destroy(codec, stream, image);
  313. return TRUE;
  314. }
  315. static gboolean gdk_pixbuf__jp2_image_save
  316. (
  317. FILE *fp,
  318. GdkPixbuf *pixbuf,
  319. gchar **keys,
  320. gchar **values,
  321. GError **error
  322. ) {
  323. return save_jp2(pixbuf, keys, values, error, fp);
  324. }
  325. /*
  326. * Module entry points - This is where it all starts
  327. */
  328. G_MODULE_EXPORT
  329. void fill_vtable(GdkPixbufModule *module)
  330. {
  331. module->load = gdk_pixbuf__jp2_image_load;
  332. module->save = gdk_pixbuf__jp2_image_save;
  333. // TODO: consider implementing these
  334. //module->stop_load = gdk_pixbuf__jp2_image_stop_load;
  335. //module->begin_load = gdk_pixbuf__jp2_image_begin_load;
  336. //module->load_increment = gdk_pixbuf__jp2_image_load_increment;
  337. //module->save_to_callback = gdk_pixbuf__jp2_image_save_to_callback;
  338. }
  339. G_MODULE_EXPORT
  340. void fill_info(GdkPixbufFormat *info)
  341. {
  342. static GdkPixbufModulePattern signature[] =
  343. {
  344. { " jP", "!!!! ", 100 }, /* file begins with 'jP' at offset 4 */
  345. { "\xff\x4f\xff\x51\x00", NULL, 100 }, /* file starts with FF 4F FF 51 00 */
  346. { NULL, NULL, 0 }
  347. };
  348. static gchar *mime_types[] =
  349. {
  350. "image/jp2",
  351. "image/jpm",
  352. "image/jpx",
  353. "image/jpeg2000",
  354. "image/x-jp2-codestream",
  355. NULL
  356. };
  357. static gchar *extensions[] =
  358. {
  359. "j2c",
  360. "j2k",
  361. "jp2",
  362. "jpc",
  363. "jpf",
  364. "jpm",
  365. "jpx",
  366. NULL
  367. };
  368. info->description = "JPEG2000";
  369. info->extensions = extensions;
  370. info->flags = GDK_PIXBUF_FORMAT_WRITABLE | GDK_PIXBUF_FORMAT_THREADSAFE;
  371. info->license = "LGPL";
  372. info->mime_types = mime_types;
  373. info->name = "jp2";
  374. info->signature = signature;
  375. }