]> Pileus Git - ~andy/gtk/blob - gdk-pixbuf/io-jasper.c
If pure sniffing yields uncertain results, try again with the filename.
[~andy/gtk] / gdk-pixbuf / io-jasper.c
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, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  */
21
22 #include <stdlib.h>
23 #include <string.h>
24 #include <errno.h>
25
26 #include "gdk-pixbuf-private.h"
27 #include "gdk-pixbuf-io.h"
28
29 #include <jasper/jasper.h>
30
31 G_MODULE_EXPORT void fill_vtable (GdkPixbufModule * module);
32 G_MODULE_EXPORT void fill_info (GdkPixbufFormat * info);
33
34 struct jasper_context {
35         GdkPixbuf *pixbuf;
36
37         GdkPixbufModuleSizeFunc size_func;
38         GdkPixbufModuleUpdatedFunc updated_func;
39         GdkPixbufModulePreparedFunc prepared_func;
40         gpointer user_data;
41
42         jas_stream_t *stream;
43
44         int width, height;
45 };
46
47 static void
48 free_jasper_context (struct jasper_context *context)
49 {
50         if (!context)
51                 return;
52
53         if (context->stream) {
54                 jas_stream_close (context->stream);
55                 context->stream = NULL;
56         }
57
58         g_free (context);
59 }
60
61 static gpointer
62 jasper_image_begin_load (GdkPixbufModuleSizeFunc size_func,
63                          GdkPixbufModulePreparedFunc prepared_func,
64                          GdkPixbufModuleUpdatedFunc updated_func,
65                          gpointer user_data, GError **error)
66 {
67         struct jasper_context *context;
68         jas_stream_t *stream;
69
70         jas_init ();
71
72         stream = jas_stream_memopen (NULL, -1);
73         if (!stream) {
74                 g_set_error_literal (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
75                                      _("Couldn't allocate memory for stream"));
76                 return NULL;
77         }
78
79         context = g_new0 (struct jasper_context, 1);
80         if (!context)
81                 return NULL;
82
83         context->size_func = size_func;
84         context->updated_func = updated_func;
85         context->prepared_func = prepared_func;
86         context->user_data = user_data;
87         context->width = context->height = -1;
88
89         context->stream = stream;
90
91         return context;
92 }
93
94 static gboolean
95 jasper_image_try_load (struct jasper_context *context, GError **error)
96 {
97         jas_image_t *raw_image, *image;
98         int num_components, colourspace_family;
99         int i, rowstride, shift;
100         guchar *pixels;
101
102         raw_image = jas_image_decode (context->stream, -1, 0);
103         if (!raw_image) {
104                 g_set_error_literal (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
105                                      _("Couldn't decode image"));
106                 return FALSE;
107         }
108
109         if (context->width == -1 && context->height == -1) {
110                 int width, height;
111
112                 context->width = width = jas_image_cmptwidth (raw_image, 0);
113                 context->height = height = jas_image_cmptheight (raw_image, 0);
114
115                 if (context->size_func) {
116                         (*context->size_func) (&width, &height, context->user_data);
117
118                         if (width == 0 || height == 0) {
119                                 jas_image_destroy(raw_image);
120                                 g_set_error_literal (error,
121                                                      GDK_PIXBUF_ERROR,
122                                                      GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
123                                                      _("Transformed JPEG2000 has zero width or height"));
124                                 return FALSE;
125                         }
126                 }
127         }
128
129         /* We only know how to handle grayscale and RGB images */
130         num_components = jas_image_numcmpts (raw_image);
131         colourspace_family = jas_clrspc_fam (jas_image_clrspc (raw_image));
132
133         if ((num_components != 3 && num_components != 4 && num_components != 1) ||
134             (colourspace_family != JAS_CLRSPC_FAM_RGB  && colourspace_family != JAS_CLRSPC_FAM_GRAY)) {
135                 jas_image_destroy (raw_image);
136                 g_set_error_literal (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_UNKNOWN_TYPE,
137                                      _("Image type currently not supported"));
138                 return FALSE;
139         }
140
141         /* Apply the colour profile to the image, creating a new one */
142         if (jas_image_clrspc (raw_image) != JAS_CLRSPC_SRGB) {
143                 jas_cmprof_t *profile;
144
145                 profile = jas_cmprof_createfromclrspc (JAS_CLRSPC_SRGB);
146                 if (!profile) {
147                         jas_image_destroy (raw_image);
148                         g_set_error_literal (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
149                                              _("Couldn't allocate memory for color profile"));
150                         return FALSE;
151                 }
152
153                 image = jas_image_chclrspc (raw_image, profile, JAS_CMXFORM_INTENT_PER);
154                 if (!image) {
155                         jas_image_destroy (raw_image);
156                         g_set_error_literal (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
157                                              _("Couldn't allocate memory for color profile"));
158                         return FALSE;
159                 }
160         } else {
161                 image = raw_image;
162         }
163
164         if (!context->pixbuf) {
165                 int bits_per_sample;
166                 guchar *data;
167
168                 /* Unfortunately, gdk-pixbuf doesn't support 16 bpp images
169                  * bits_per_sample = jas_image_cmptprec (image, 0);
170                 if (bits_per_sample < 8)
171                         bits_per_sample = 8;
172                 else if (bits_per_sample > 8)
173                         bits_per_sample = 16;
174                 */
175                 bits_per_sample = 8;
176
177                 data = g_try_malloc0 (context->width * context->height * bits_per_sample / 8);
178                 if (data == NULL) {
179                         g_set_error_literal (error,
180                                              GDK_PIXBUF_ERROR,
181                                              GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
182                                              _("Insufficient memory to open JPEG 2000 file"));
183                         return FALSE;
184                 }
185                 context->pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
186                                                   FALSE, bits_per_sample,
187                                                   context->width, context->height);
188                 if (context->prepared_func)
189                         context->prepared_func (context->pixbuf, NULL, context->user_data);
190         }
191
192         /* We calculate how much we should shift the pixel
193          * data by to make it fit into our pixbuf */
194         shift = MAX (jas_image_cmptprec (image, 0) - gdk_pixbuf_get_bits_per_sample (context->pixbuf), 0);
195
196         /* Loop over the 3 colourspaces */
197         rowstride = gdk_pixbuf_get_rowstride (context->pixbuf);
198         pixels = gdk_pixbuf_get_pixels (context->pixbuf);
199
200         for (i = 0; i < num_components; i++) {
201                 jas_matrix_t *matrix;
202                 int j;
203
204                 matrix = jas_matrix_create (context->height, context->width);
205
206                 /* in libjasper, R is 0, G is 1, etc. we're lucky :)
207                  * but we need to handle the "opacity" channel ourselves */
208                 if (i != 4) {
209                         jas_image_readcmpt (image, i, 0, 0, context->width, context->height, matrix);
210                 } else {
211                         jas_image_readcmpt (image, JAS_IMAGE_CT_OPACITY, 0, 0, context->width, context->height, matrix);
212                 }
213
214                 for (j = 0; j < context->height; j++) {
215                         int k;
216
217                         for (k = 0; k < context->width; k++) {
218                                 if (num_components == 3 || num_components == 4) {
219                                         pixels[j * rowstride + k * 3 + i] = jas_matrix_get (matrix, j, k) >> shift;
220                                 } else {
221                                         pixels[j * rowstride + k * 3] =
222                                                 pixels[j * rowstride + k * 3 + 1] =
223                                                 pixels[j * rowstride + k * 3 + 2] = jas_matrix_get (matrix, j, k) >> shift;
224                                 }
225                         }
226                         /* Update once per line for the last component, otherwise
227                          * we might contain garbage */
228                         if (context->updated_func && (i == num_components - 1) && k != 0) {
229                                 context->updated_func (context->pixbuf, 0, j, k, 1, context->user_data);
230                         }
231                 }
232
233                 jas_matrix_destroy (matrix);
234         }
235
236         if (image != raw_image)
237                 jas_image_destroy (image);
238         jas_image_destroy (raw_image);
239
240         return TRUE;
241 }
242
243 static gboolean
244 jasper_image_stop_load (gpointer data, GError **error)
245 {
246         struct jasper_context *context = (struct jasper_context *) data;
247         gboolean ret;
248
249         jas_stream_rewind (context->stream);
250         ret = jasper_image_try_load (context, error);
251
252         free_jasper_context (context);
253
254         return ret;
255 }
256
257 static gboolean
258 jasper_image_load_increment (gpointer data, const guchar *buf, guint size, GError **error)
259 {
260         struct jasper_context *context = (struct jasper_context *) data;
261
262         if (jas_stream_write (context->stream, buf, size) < 0) {
263                 g_set_error_literal (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
264                                      _("Couldn't allocate memory to buffer image data"));
265                 return FALSE;
266         }
267
268         return TRUE;
269 }
270
271 #ifndef INCLUDE_jasper
272 #define MODULE_ENTRY(function) G_MODULE_EXPORT void function
273 #else
274 #define MODULE_ENTRY(function) void _gdk_pixbuf__jasper_ ## function
275 #endif
276
277 MODULE_ENTRY (fill_vtable) (GdkPixbufModule * module)
278 {
279         module->begin_load = jasper_image_begin_load;
280         module->stop_load = jasper_image_stop_load;
281         module->load_increment = jasper_image_load_increment;
282 }
283
284 MODULE_ENTRY (fill_info) (GdkPixbufFormat * info)
285 {
286         static GdkPixbufModulePattern signature[] = {
287                 { "    jP", "!!!!  ", 100 },            /* file begins with 'jP' at offset 4 */
288                 { "\xff\x4f\xff\x51\x00", NULL, 100 },  /* file starts with FF 4F FF 51 00 */
289                 { NULL, NULL, 0 }
290         };
291         static gchar *mime_types[] = {
292                 "image/jp2",
293                 "image/jpeg2000",
294                 "image/jpx",
295                 NULL
296         };
297         static gchar *extensions[] = {
298                 "jp2",
299                 "jpc",
300                 "jpx",
301                 "j2k",
302                 "jpf",
303                 NULL
304         };
305
306         info->name = "jpeg2000";
307         info->signature = signature;
308         info->description = N_("The JPEG 2000 image format");
309         info->mime_types = mime_types;
310         info->extensions = extensions;
311         info->flags = GDK_PIXBUF_FORMAT_THREADSAFE;
312         info->license = "LGPL";
313         info->disabled = FALSE;
314 }
315