1 | /** |
2 | * QImageIO Routines to read/write JPEG2000 images. |
3 | * copyright (c) 2002 Michael Ritzert <michael@ritzert.de> |
4 | * |
5 | * This library is distributed under the conditions of the GNU LGPL. |
6 | */ |
7 | |
8 | #include "jp2.h" |
9 | |
10 | #include <config.h> |
11 | |
12 | #ifdef HAVE_SYS_TYPES_H |
13 | #include <sys/types.h> |
14 | #endif |
15 | |
16 | #ifdef HAVE_STDINT_H |
17 | #include <stdint.h> |
18 | #endif |
19 | |
20 | #include <QImage> |
21 | #include <QVariant> |
22 | #include <QTextStream> |
23 | |
24 | // dirty, but avoids a warning because jasper.h includes jas_config.h. |
25 | #undef PACKAGE |
26 | #undef VERSION |
27 | #include <jasper/jasper.h> |
28 | |
29 | // code taken in parts from JasPer's jiv.c |
30 | |
31 | #define DEFAULT_RATE 0.10 |
32 | #define MAXCMPTS 256 |
33 | |
34 | |
35 | /************************* JasPer QIODevice stream ***********************/ |
36 | |
37 | //unfortunately this is declared as static in JasPer libraries |
38 | static jas_stream_t *jas_stream_create() |
39 | { |
40 | jas_stream_t *stream; |
41 | |
42 | if (!(stream = (jas_stream_t*)jas_malloc(sizeof(jas_stream_t)))) { |
43 | return 0; |
44 | } |
45 | stream->openmode_ = 0; |
46 | stream->bufmode_ = 0; |
47 | stream->flags_ = 0; |
48 | stream->bufbase_ = 0; |
49 | stream->bufstart_ = 0; |
50 | stream->bufsize_ = 0; |
51 | stream->ptr_ = 0; |
52 | stream->cnt_ = 0; |
53 | stream->ops_ = 0; |
54 | stream->obj_ = 0; |
55 | stream->rwcnt_ = 0; |
56 | stream->rwlimit_ = -1; |
57 | |
58 | return stream; |
59 | } |
60 | |
61 | //unfortunately this is declared as static in JasPer libraries |
62 | static void jas_stream_initbuf(jas_stream_t *stream, int bufmode, char *buf, |
63 | int bufsize) |
64 | { |
65 | /* If this function is being called, the buffer should not have been |
66 | initialized yet. */ |
67 | assert(!stream->bufbase_); |
68 | |
69 | if (bufmode != JAS_STREAM_UNBUF) { |
70 | /* The full- or line-buffered mode is being employed. */ |
71 | if (!buf) { |
72 | /* The caller has not specified a buffer to employ, so allocate |
73 | one. */ |
74 | if ((stream->bufbase_ = (unsigned char*)jas_malloc(JAS_STREAM_BUFSIZE + |
75 | JAS_STREAM_MAXPUTBACK))) { |
76 | stream->bufmode_ |= JAS_STREAM_FREEBUF; |
77 | stream->bufsize_ = JAS_STREAM_BUFSIZE; |
78 | } else { |
79 | /* The buffer allocation has failed. Resort to unbuffered |
80 | operation. */ |
81 | stream->bufbase_ = stream->tinybuf_; |
82 | stream->bufsize_ = 1; |
83 | } |
84 | } else { |
85 | /* The caller has specified a buffer to employ. */ |
86 | /* The buffer must be large enough to accommodate maximum |
87 | putback. */ |
88 | assert(bufsize > JAS_STREAM_MAXPUTBACK); |
89 | stream->bufbase_ = JAS_CAST(uchar *, buf); |
90 | stream->bufsize_ = bufsize - JAS_STREAM_MAXPUTBACK; |
91 | } |
92 | } else { |
93 | /* The unbuffered mode is being employed. */ |
94 | /* A buffer should not have been supplied by the caller. */ |
95 | assert(!buf); |
96 | /* Use a trivial one-character buffer. */ |
97 | stream->bufbase_ = stream->tinybuf_; |
98 | stream->bufsize_ = 1; |
99 | } |
100 | stream->bufstart_ = &stream->bufbase_[JAS_STREAM_MAXPUTBACK]; |
101 | stream->ptr_ = stream->bufstart_; |
102 | stream->cnt_ = 0; |
103 | stream->bufmode_ |= bufmode & JAS_STREAM_BUFMODEMASK; |
104 | } |
105 | |
106 | static int qiodevice_read(jas_stream_obj_t *obj, char *buf, int cnt) |
107 | { |
108 | QIODevice *io = (QIODevice*) obj; |
109 | return io->read(buf, cnt); |
110 | } |
111 | |
112 | static int qiodevice_write(jas_stream_obj_t *obj, char *buf, int cnt) |
113 | { |
114 | QIODevice *io = (QIODevice*) obj; |
115 | return io->write(buf, cnt); |
116 | } |
117 | |
118 | static long qiodevice_seek(jas_stream_obj_t *obj, long offset, int origin) |
119 | { |
120 | QIODevice *io = (QIODevice*) obj; |
121 | long newpos; |
122 | |
123 | switch (origin) { |
124 | case SEEK_SET: |
125 | newpos = offset; |
126 | break; |
127 | case SEEK_END: |
128 | newpos = io->size() - offset; |
129 | break; |
130 | case SEEK_CUR: |
131 | newpos = io->pos() + offset; |
132 | break; |
133 | default: |
134 | return -1; |
135 | } |
136 | if (newpos < 0) { |
137 | return -1; |
138 | } |
139 | if ( io->seek(newpos) ) |
140 | return newpos; |
141 | else |
142 | return -1; |
143 | } |
144 | |
145 | static int qiodevice_close(jas_stream_obj_t *) |
146 | { |
147 | return 0; |
148 | } |
149 | |
150 | static jas_stream_ops_t jas_stream_qiodeviceops = { |
151 | qiodevice_read, |
152 | qiodevice_write, |
153 | qiodevice_seek, |
154 | qiodevice_close |
155 | }; |
156 | |
157 | static jas_stream_t *jas_stream_qiodevice(QIODevice *iodevice) |
158 | { |
159 | jas_stream_t *stream; |
160 | |
161 | if ( !iodevice ) return 0; |
162 | if (!(stream = jas_stream_create())) { |
163 | return 0; |
164 | } |
165 | |
166 | /* A stream associated with a memory buffer is always opened |
167 | for both reading and writing in binary mode. */ |
168 | stream->openmode_ = JAS_STREAM_READ | JAS_STREAM_WRITE | JAS_STREAM_BINARY; |
169 | |
170 | jas_stream_initbuf(stream, JAS_STREAM_FULLBUF, 0, 0); |
171 | |
172 | /* Select the operations for a memory stream. */ |
173 | stream->obj_ = (void *)iodevice; |
174 | stream->ops_ = &jas_stream_qiodeviceops; |
175 | |
176 | return stream; |
177 | } |
178 | |
179 | /************************ End of JasPer QIODevice stream ****************/ |
180 | |
181 | typedef struct { |
182 | jas_image_t* image; |
183 | |
184 | int cmptlut[MAXCMPTS]; |
185 | |
186 | jas_image_t* altimage; |
187 | } gs_t; |
188 | |
189 | |
190 | static jas_image_t* |
191 | read_image( QIODevice* io ) |
192 | { |
193 | jas_stream_t* in = 0; |
194 | |
195 | in = jas_stream_qiodevice( io ); |
196 | |
197 | if( !in ) return 0; |
198 | |
199 | jas_image_t* image = jas_image_decode( in, -1, 0 ); |
200 | jas_stream_close( in ); |
201 | |
202 | // image may be 0, but that's Ok |
203 | return image; |
204 | } // read_image |
205 | |
206 | static bool |
207 | convert_colorspace( gs_t& gs ) |
208 | { |
209 | jas_cmprof_t *outprof = jas_cmprof_createfromclrspc( JAS_CLRSPC_SRGB ); |
210 | if( !outprof ) return false; |
211 | |
212 | gs.altimage = jas_image_chclrspc( gs.image, outprof, |
213 | JAS_CMXFORM_INTENT_PER ); |
214 | if( !gs.altimage ) return false; |
215 | |
216 | return true; |
217 | } // convert_colorspace |
218 | |
219 | static bool |
220 | render_view( gs_t& gs, QImage* outImage ) |
221 | { |
222 | if ( !gs.altimage ) return false; |
223 | QImage qti; |
224 | if((gs.cmptlut[0] = jas_image_getcmptbytype(gs.altimage, |
225 | JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_R))) < 0 || |
226 | (gs.cmptlut[1] = jas_image_getcmptbytype(gs.altimage, |
227 | JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_G))) < 0 || |
228 | (gs.cmptlut[2] = jas_image_getcmptbytype(gs.altimage, |
229 | JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_B))) < 0) { |
230 | return false; |
231 | } // if |
232 | |
233 | const int* cmptlut = gs.cmptlut; |
234 | int v[3]; |
235 | |
236 | // check that all components have the same size. |
237 | const int width = jas_image_cmptwidth( gs.altimage, cmptlut[0] ); |
238 | const int height = jas_image_cmptheight( gs.altimage, cmptlut[0] ); |
239 | for( int i = 1; i < 3; ++i ) { |
240 | if (jas_image_cmptwidth( gs.altimage, cmptlut[i] ) != width || |
241 | jas_image_cmptheight( gs.altimage, cmptlut[i] ) != height) |
242 | return false; |
243 | } // for |
244 | |
245 | jas_matrix_t *cmptmatrix[3]; |
246 | jas_seqent_t *buf[3]; |
247 | int prec[3]; |
248 | |
249 | for (int k = 0; k < 3; ++k ) { |
250 | prec[k] = jas_image_cmptprec(gs.altimage, cmptlut[k]); |
251 | if (!(cmptmatrix[k] = jas_matrix_create(1, width))) { |
252 | return false; |
253 | } |
254 | } |
255 | |
256 | qti = QImage( jas_image_width( gs.altimage ), jas_image_height( gs.altimage ), |
257 | QImage::Format_RGB32 ); |
258 | if (qti.isNull()) { |
259 | return false; |
260 | } |
261 | uint32_t* data = (uint32_t*)qti.bits(); |
262 | |
263 | for( int y = 0; y < height; ++y ) { |
264 | for( int k = 0; k < 3; ++k ) { |
265 | if (jas_image_readcmpt(gs.altimage, cmptlut[k], 0, y, width, 1, cmptmatrix[k])) { |
266 | return false; |
267 | } |
268 | buf[k] = jas_matrix_getref(cmptmatrix[k], 0, 0); |
269 | } |
270 | for( int x = 0; x < width; ++x ) { |
271 | for( int k = 0; k < 3; ++k ) { |
272 | v[k] = *buf[k]; |
273 | // if the precision of the component is too small, increase |
274 | // it to use the complete value range. |
275 | v[k] <<= 8 - prec[k]; |
276 | |
277 | if( v[k] < 0 ) v[k] = 0; |
278 | else if( v[k] > 255 ) v[k] = 255; |
279 | ++buf[k]; |
280 | } // for k |
281 | |
282 | *data++ = qRgb( v[0], v[1], v[2] ); |
283 | } // for x |
284 | } // for y |
285 | |
286 | for (int k = 0; k < 3; ++k ) { |
287 | if (cmptmatrix[k]) { |
288 | jas_matrix_destroy(cmptmatrix[k]); |
289 | } |
290 | } |
291 | |
292 | *outImage = qti; |
293 | return true; |
294 | } // render_view |
295 | |
296 | |
297 | static jas_image_t* |
298 | create_image( const QImage& qi ) |
299 | { |
300 | // prepare the component parameters |
301 | jas_image_cmptparm_t* cmptparms = new jas_image_cmptparm_t[ 3 ]; |
302 | |
303 | for ( int i = 0; i < 3; ++i ) { |
304 | // x and y offset |
305 | cmptparms[i].tlx = 0; |
306 | cmptparms[i].tly = 0; |
307 | |
308 | // the resulting image will be hstep*width x vstep*height ! |
309 | cmptparms[i].hstep = 1; |
310 | cmptparms[i].vstep = 1; |
311 | cmptparms[i].width = qi.width(); |
312 | cmptparms[i].height = qi.height(); |
313 | |
314 | // we write everything as 24bit truecolor ATM |
315 | cmptparms[i].prec = 8; |
316 | cmptparms[i].sgnd = false; |
317 | } |
318 | |
319 | jas_image_t* ji = jas_image_create( 3 /* number components */, cmptparms, JAS_CLRSPC_UNKNOWN ); |
320 | delete[] cmptparms; |
321 | |
322 | // returning 0 is ok |
323 | return ji; |
324 | } // create_image |
325 | |
326 | |
327 | static bool |
328 | write_components( jas_image_t* ji, const QImage& qi ) |
329 | { |
330 | const unsigned height = qi.height(); |
331 | const unsigned width = qi.width(); |
332 | |
333 | jas_matrix_t* m = jas_matrix_create( height, width ); |
334 | if( !m ) return false; |
335 | |
336 | jas_image_setclrspc( ji, JAS_CLRSPC_SRGB ); |
337 | |
338 | jas_image_setcmpttype( ji, 0, JAS_IMAGE_CT_RGB_R ); |
339 | for( uint y = 0; y < height; ++y ) |
340 | for( uint x = 0; x < width; ++x ) |
341 | jas_matrix_set( m, y, x, qRed( qi.pixel( x, y ) ) ); |
342 | jas_image_writecmpt( ji, 0, 0, 0, width, height, m ); |
343 | |
344 | jas_image_setcmpttype( ji, 1, JAS_IMAGE_CT_RGB_G ); |
345 | for( uint y = 0; y < height; ++y ) |
346 | for( uint x = 0; x < width; ++x ) |
347 | jas_matrix_set( m, y, x, qGreen( qi.pixel( x, y ) ) ); |
348 | jas_image_writecmpt( ji, 1, 0, 0, width, height, m ); |
349 | |
350 | jas_image_setcmpttype( ji, 2, JAS_IMAGE_CT_RGB_B ); |
351 | for( uint y = 0; y < height; ++y ) |
352 | for( uint x = 0; x < width; ++x ) |
353 | jas_matrix_set( m, y, x, qBlue( qi.pixel( x, y ) ) ); |
354 | jas_image_writecmpt( ji, 2, 0, 0, width, height, m ); |
355 | jas_matrix_destroy( m ); |
356 | |
357 | return true; |
358 | } // write_components |
359 | |
360 | static bool |
361 | write_image( const QImage &image, QIODevice* io, int quality ) |
362 | { |
363 | jas_stream_t* stream = 0; |
364 | stream = jas_stream_qiodevice( io ); |
365 | |
366 | // by here, a jas_stream_t is open |
367 | if( !stream ) return false; |
368 | |
369 | jas_image_t* ji = create_image( image ); |
370 | if( !ji ) { |
371 | jas_stream_close( stream ); |
372 | return false; |
373 | } // if |
374 | |
375 | if( !write_components( ji, image ) ) { |
376 | jas_stream_close( stream ); |
377 | jas_image_destroy( ji ); |
378 | return false; |
379 | } // if |
380 | |
381 | // optstr: |
382 | // - rate=#B => the resulting file size is about # bytes |
383 | // - rate=0.0 .. 1.0 => the resulting file size is about the factor times |
384 | // the uncompressed size |
385 | // use sprintf for locale-aware string |
386 | char rateBuffer[16]; |
387 | sprintf(rateBuffer, "rate=%.2g\n" , (quality < 0) ? DEFAULT_RATE : quality / 100.0); |
388 | int i = jp2_encode( ji, stream, rateBuffer); |
389 | |
390 | jas_image_destroy( ji ); |
391 | jas_stream_close( stream ); |
392 | |
393 | if( i != 0 ) return false; |
394 | |
395 | return true; |
396 | } |
397 | |
398 | JP2Handler::JP2Handler() |
399 | { |
400 | quality = 75; |
401 | jas_init(); |
402 | } |
403 | |
404 | JP2Handler::~JP2Handler() |
405 | { |
406 | jas_cleanup(); |
407 | } |
408 | |
409 | bool JP2Handler::canRead() const |
410 | { |
411 | if (canRead(device())) { |
412 | setFormat("jp2" ); |
413 | return true; |
414 | } |
415 | return false; |
416 | } |
417 | |
418 | bool JP2Handler::canRead(QIODevice *device) |
419 | { |
420 | if (!device) { |
421 | return false; |
422 | } |
423 | return device->peek(6) == QByteArray("\x00\x00\x00\x0C\x6A\x50" , 6); |
424 | } |
425 | |
426 | bool JP2Handler::read(QImage *image) |
427 | { |
428 | if (!canRead()) return false; |
429 | |
430 | gs_t gs; |
431 | if( !(gs.image = read_image( device() )) ) return false; |
432 | |
433 | if( !convert_colorspace( gs ) ) return false; |
434 | |
435 | render_view( gs, image ); |
436 | |
437 | if( gs.image ) jas_image_destroy( gs.image ); |
438 | if( gs.altimage ) jas_image_destroy( gs.altimage ); |
439 | return true; |
440 | |
441 | } |
442 | |
443 | bool JP2Handler::write(const QImage &image) |
444 | { |
445 | return write_image(image, device(),quality); |
446 | } |
447 | |
448 | bool JP2Handler::supportsOption(ImageOption option) const |
449 | { |
450 | return option == Quality; |
451 | } |
452 | |
453 | QVariant JP2Handler::option(ImageOption option) const |
454 | { |
455 | if (option == Quality) |
456 | return quality; |
457 | return QVariant(); |
458 | } |
459 | |
460 | void JP2Handler::setOption(ImageOption option, const QVariant &value) |
461 | { |
462 | if (option == Quality) |
463 | quality = qBound(-1, value.toInt(), 100); |
464 | } |
465 | |
466 | QByteArray JP2Handler::name() const |
467 | { |
468 | return "jp2" ; |
469 | } |
470 | |
471 | class JP2Plugin : public QImageIOPlugin |
472 | { |
473 | public: |
474 | QStringList keys() const; |
475 | Capabilities capabilities(QIODevice *device, const QByteArray &format) const; |
476 | QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const; |
477 | }; |
478 | |
479 | QStringList JP2Plugin::keys() const |
480 | { |
481 | return QStringList() << "jp2" ; |
482 | } |
483 | |
484 | QImageIOPlugin::Capabilities JP2Plugin::capabilities(QIODevice *device, const QByteArray &format) const |
485 | { |
486 | if (format == "jp2" ) |
487 | return Capabilities(CanRead | CanWrite); |
488 | if (!format.isEmpty()) |
489 | return 0; |
490 | if (!device->isOpen()) |
491 | return 0; |
492 | |
493 | Capabilities cap; |
494 | if (device->isReadable() && JP2Handler::canRead(device)) |
495 | cap |= CanRead; |
496 | if (device->isWritable()) |
497 | cap |= CanWrite; |
498 | return cap; |
499 | } |
500 | |
501 | QImageIOHandler *JP2Plugin::create(QIODevice *device, const QByteArray &format) const |
502 | { |
503 | QImageIOHandler *handler = new JP2Handler; |
504 | handler->setDevice(device); |
505 | handler->setFormat(format); |
506 | return handler; |
507 | } |
508 | |
509 | Q_EXPORT_STATIC_PLUGIN(JP2Plugin) |
510 | Q_EXPORT_PLUGIN2(jp2, JP2Plugin) |
511 | |
512 | |
513 | |