1 /* JPEG 2000 loader
2  *
3  * Copyright (c) 2007 Bastien Nocera <hadess@hadess.net>
4  * Inspired by work by Ben Karel <web+moz@eschew.org>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include "config.h"
21 #include <stdlib.h>
22 #include <string.h>
23 #include <errno.h>
24 
25 #include <jasper/jasper.h>
26 
27 #include <glib/gi18n-lib.h>
28 #include "gdk-pixbuf-io.h"
29 
30 G_MODULE_EXPORT void fill_vtable (GdkPixbufModule * module);
31 G_MODULE_EXPORT void fill_info (GdkPixbufFormat * info);
32 
33 struct jasper_context {
34 	GdkPixbuf *pixbuf;
35 
36 	GdkPixbufModuleSizeFunc size_func;
37 	GdkPixbufModuleUpdatedFunc updated_func;
38 	GdkPixbufModulePreparedFunc prepared_func;
39 	gpointer user_data;
40 
41 	jas_stream_t *stream;
42 
43 	int width, height;
44 };
45 
46 static void
free_jasper_context(struct jasper_context * context)47 free_jasper_context (struct jasper_context *context)
48 {
49 	if (!context)
50 		return;
51 
52 	if (context->stream) {
53 		jas_stream_close (context->stream);
54 		context->stream = NULL;
55 	}
56 
57 	g_free (context);
58 }
59 
60 static gpointer
jasper_image_begin_load(GdkPixbufModuleSizeFunc size_func,GdkPixbufModulePreparedFunc prepared_func,GdkPixbufModuleUpdatedFunc updated_func,gpointer user_data,GError ** error)61 jasper_image_begin_load (GdkPixbufModuleSizeFunc size_func,
62 			 GdkPixbufModulePreparedFunc prepared_func,
63 			 GdkPixbufModuleUpdatedFunc updated_func,
64 			 gpointer user_data, GError **error)
65 {
66 	struct jasper_context *context;
67 	jas_stream_t *stream;
68 
69 	jas_init ();
70 
71 	stream = jas_stream_memopen (NULL, 0);
72 	if (!stream) {
73 		g_set_error_literal (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
74                                      _("Couldn’t allocate memory for stream"));
75 		return NULL;
76 	}
77 
78 	context = g_new0 (struct jasper_context, 1);
79 	if (!context)
80 		return NULL;
81 
82 	context->size_func = size_func;
83 	context->updated_func = updated_func;
84 	context->prepared_func = prepared_func;
85 	context->user_data = user_data;
86 	context->width = context->height = -1;
87 
88 	context->stream = stream;
89 
90 	return context;
91 }
92 
93 static const char *
colourspace_to_str(int c)94 colourspace_to_str (int c)
95 {
96 	switch (c) {
97 	case JAS_CLRSPC_FAM_UNKNOWN:
98 		return "Unknown";
99 	case JAS_CLRSPC_FAM_XYZ:
100 		return "XYZ";
101 	case JAS_CLRSPC_FAM_LAB:
102 		return "LAB";
103 	case JAS_CLRSPC_FAM_GRAY:
104 		return "GRAY";
105 	case JAS_CLRSPC_FAM_RGB:
106 		return "RGB";
107 	case JAS_CLRSPC_FAM_YCBCR:
108 		return "YCbCr";
109 	default:
110 		return "Invalid";
111 	}
112 }
113 
114 static gboolean
jasper_image_try_load(struct jasper_context * context,GError ** error)115 jasper_image_try_load (struct jasper_context *context, GError **error)
116 {
117 	jas_image_t *raw_image, *image;
118 	int num_components, colourspace_family;
119 	int i, rowstride, shift;
120 	guchar *pixels;
121 
122 	raw_image = jas_image_decode (context->stream, -1, 0);
123 	if (!raw_image) {
124 		g_set_error_literal (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
125                                      _("Couldn’t decode image"));
126 		return FALSE;
127 	}
128 
129 	if (context->width == -1 && context->height == -1) {
130 		int width, height;
131 
132 		context->width = width = jas_image_cmptwidth (raw_image, 0);
133 		context->height = height = jas_image_cmptheight (raw_image, 0);
134 
135 		if (context->size_func) {
136 			(*context->size_func) (&width, &height, context->user_data);
137 
138 			if (width == 0 || height == 0) {
139 				jas_image_destroy(raw_image);
140 				g_set_error_literal (error,
141                                                      GDK_PIXBUF_ERROR,
142                                                      GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
143                                                      _("Transformed JPEG2000 has zero width or height"));
144 				return FALSE;
145 			}
146 		}
147 	}
148 
149 	/* We only know how to handle grayscale and RGB images */
150 	num_components = jas_image_numcmpts (raw_image);
151 	colourspace_family = jas_clrspc_fam (jas_image_clrspc (raw_image));
152 
153 	if ((num_components != 3 && num_components != 4 && num_components != 1) ||
154 	    (colourspace_family != JAS_CLRSPC_FAM_RGB  && colourspace_family != JAS_CLRSPC_FAM_GRAY)) {
155 		jas_image_destroy (raw_image);
156 		g_debug ("Unsupported colourspace %s (num components: %d)",
157 			 colourspace_to_str (colourspace_family), num_components);
158 		g_set_error_literal (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_UNKNOWN_TYPE,
159                                      _("Image type currently not supported"));
160 		return FALSE;
161 	}
162 
163 	/* Apply the colour profile to the image, creating a new one */
164 	if (jas_image_clrspc (raw_image) != JAS_CLRSPC_SRGB) {
165 		jas_cmprof_t *profile;
166 
167 		profile = jas_cmprof_createfromclrspc (JAS_CLRSPC_SRGB);
168 		if (!profile) {
169 			jas_image_destroy (raw_image);
170 			g_set_error_literal (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
171                                              _("Couldn’t allocate memory for color profile"));
172 			return FALSE;
173 		}
174 
175 		image = jas_image_chclrspc (raw_image, profile, JAS_CMXFORM_INTENT_PER);
176 		if (!image) {
177 			jas_image_destroy (raw_image);
178 			g_set_error_literal (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
179                                              _("Couldn’t allocate memory for color profile"));
180 			return FALSE;
181 		}
182 	} else {
183 		image = raw_image;
184 	}
185 
186 	if (!context->pixbuf) {
187 		int bits_per_sample;
188 
189 		/* Unfortunately, gdk-pixbuf doesn't support 16 bpp images
190 		 * bits_per_sample = jas_image_cmptprec (image, 0);
191 		if (bits_per_sample < 8)
192 			bits_per_sample = 8;
193 		else if (bits_per_sample > 8)
194 			bits_per_sample = 16;
195 		*/
196 		bits_per_sample = 8;
197 
198 		context->pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
199 						  FALSE, bits_per_sample,
200 						  context->width, context->height);
201 		if (context->pixbuf == NULL) {
202 			g_set_error_literal (error,
203                                              GDK_PIXBUF_ERROR,
204                                              GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
205                                              _("Insufficient memory to open JPEG 2000 file"));
206 			return FALSE;
207 		}
208 		if (context->prepared_func)
209 			context->prepared_func (context->pixbuf, NULL, context->user_data);
210 	}
211 
212 	/* We calculate how much we should shift the pixel
213 	 * data by to make it fit into our pixbuf */
214 	shift = MAX (jas_image_cmptprec (image, 0) - gdk_pixbuf_get_bits_per_sample (context->pixbuf), 0);
215 
216 	/* Loop over the 3 colourspaces */
217 	rowstride = gdk_pixbuf_get_rowstride (context->pixbuf);
218 	pixels = gdk_pixbuf_get_pixels (context->pixbuf);
219 
220 	for (i = 0; i < num_components; i++) {
221 		jas_matrix_t *matrix;
222 		int j;
223 
224 		matrix = jas_matrix_create (context->height, context->width);
225 
226 		if (matrix == NULL) {
227 			g_set_error_literal (error,
228                                              GDK_PIXBUF_ERROR,
229                                              GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
230                                              _("Insufficient memory to open JPEG 2000 file"));
231 			return FALSE;
232 		}
233 
234 		/* in libjasper, R is 0, G is 1, etc. we're lucky :)
235 		 * but we need to handle the "opacity" channel ourselves */
236 		if (i != 4) {
237 			jas_image_readcmpt (image, i, 0, 0, context->width, context->height, matrix);
238 		} else {
239 			jas_image_readcmpt (image, JAS_IMAGE_CT_OPACITY, 0, 0, context->width, context->height, matrix);
240 		}
241 
242 		for (j = 0; j < context->height; j++) {
243 			int k;
244 
245 			for (k = 0; k < context->width; k++) {
246 				if (num_components == 3 || num_components == 4) {
247 					pixels[j * rowstride + k * 3 + i] = jas_matrix_get (matrix, j, k) >> shift;
248 				} else {
249 					pixels[j * rowstride + k * 3] =
250 						pixels[j * rowstride + k * 3 + 1] =
251 						pixels[j * rowstride + k * 3 + 2] = jas_matrix_get (matrix, j, k) >> shift;
252 				}
253 			}
254 			/* Update once per line for the last component, otherwise
255 			 * we might contain garbage */
256 			if (context->updated_func && (i == num_components - 1) && k != 0) {
257 				context->updated_func (context->pixbuf, 0, j, k, 1, context->user_data);
258 			}
259 		}
260 
261 		jas_matrix_destroy (matrix);
262 	}
263 
264 	if (image != raw_image)
265 		jas_image_destroy (image);
266 	jas_image_destroy (raw_image);
267 
268 	return TRUE;
269 }
270 
271 static gboolean
jasper_image_stop_load(gpointer data,GError ** error)272 jasper_image_stop_load (gpointer data, GError **error)
273 {
274 	struct jasper_context *context = (struct jasper_context *) data;
275 	gboolean ret;
276 
277 	jas_stream_rewind (context->stream);
278 	ret = jasper_image_try_load (context, error);
279 
280 	free_jasper_context (context);
281 
282 	return ret;
283 }
284 
285 static gboolean
jasper_image_load_increment(gpointer data,const guchar * buf,guint size,GError ** error)286 jasper_image_load_increment (gpointer data, const guchar *buf, guint size, GError **error)
287 {
288 	struct jasper_context *context = (struct jasper_context *) data;
289 
290 	if (jas_stream_write (context->stream, buf, size) < 0) {
291 		g_set_error_literal (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
292                                      _("Couldn’t allocate memory to buffer image data"));
293 		return FALSE;
294 	}
295 
296 	return TRUE;
297 }
298 
299 #ifndef INCLUDE_jasper
300 #define MODULE_ENTRY(function) G_MODULE_EXPORT void function
301 #else
302 #define MODULE_ENTRY(function) void _gdk_pixbuf__jasper_ ## function
303 #endif
304 
MODULE_ENTRY(fill_vtable)305 MODULE_ENTRY (fill_vtable) (GdkPixbufModule * module)
306 {
307 	module->begin_load = jasper_image_begin_load;
308 	module->stop_load = jasper_image_stop_load;
309 	module->load_increment = jasper_image_load_increment;
310 }
311 
MODULE_ENTRY(fill_info)312 MODULE_ENTRY (fill_info) (GdkPixbufFormat * info)
313 {
314 	static const GdkPixbufModulePattern signature[] = {
315 		{ "    jP", "!!!!  ", 100 },		/* file begins with 'jP' at offset 4 */
316 		{ "\xff\x4f\xff\x51\x00", NULL, 100 },	/* file starts with FF 4F FF 51 00 */
317 		{ NULL, NULL, 0 }
318 	};
319 	static const gchar *mime_types[] = {
320 		"image/jp2",
321 		"image/jpeg2000",
322 		"image/jpx",
323 		NULL
324 	};
325 	static const gchar *extensions[] = {
326 		"jp2",
327 		"jpc",
328 		"jpx",
329 		"j2k",
330 		"jpf",
331 		NULL
332 	};
333 
334 	info->name = "jpeg2000";
335 	info->signature = (GdkPixbufModulePattern *) signature;
336 	info->description = NC_("image format", "JPEG 2000");
337 	info->mime_types = (gchar **) mime_types;
338 	info->extensions = (gchar **) extensions;
339 	info->flags = GDK_PIXBUF_FORMAT_THREADSAFE;
340 	info->license = "LGPL";
341 	info->disabled = FALSE;
342 }
343 
344