fix tricky regression noticed by Vyacheslav Tokarev on Google Reader.
[kdelibs.git] / kimgio / jp2.cpp
blob7cf619cfb0909b4ec83906b3fa47b413c7fab4f2
1 /**
2 * QImageIO Routines to read/write JPEG2000 images.
3 * copyright (c) 2002 Michael Ritzert <michael@ritzert.de>
5 * This library is distributed under the conditions of the GNU LGPL.
6 */
8 #include "jp2.h"
10 #include <config.h>
12 #ifdef HAVE_SYS_TYPES_H
13 #include <sys/types.h>
14 #endif
16 #ifdef HAVE_STDINT_H
17 #include <stdint.h>
18 #endif
20 #include <QImage>
21 #include <QVariant>
22 #include <QTextStream>
24 // dirty, but avoids a warning because jasper.h includes jas_config.h.
25 #undef PACKAGE
26 #undef VERSION
27 #include <jasper/jasper.h>
29 // code taken in parts from JasPer's jiv.c
31 #define DEFAULT_RATE 0.10
32 #define MAXCMPTS 256
35 /************************* JasPer QIODevice stream ***********************/
37 //unfortunately this is declared as static in JasPer libraries
38 static jas_stream_t *jas_stream_create()
40 jas_stream_t *stream;
42 if (!(stream = (jas_stream_t*)jas_malloc(sizeof(jas_stream_t)))) {
43 return 0;
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;
58 return stream;
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)
65 /* If this function is being called, the buffer should not have been
66 initialized yet. */
67 assert(!stream->bufbase_);
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;
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;
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;
100 stream->bufstart_ = &stream->bufbase_[JAS_STREAM_MAXPUTBACK];
101 stream->ptr_ = stream->bufstart_;
102 stream->cnt_ = 0;
103 stream->bufmode_ |= bufmode & JAS_STREAM_BUFMODEMASK;
106 static int qiodevice_read(jas_stream_obj_t *obj, char *buf, int cnt)
108 QIODevice *io = (QIODevice*) obj;
109 return io->read(buf, cnt);
112 static int qiodevice_write(jas_stream_obj_t *obj, char *buf, int cnt)
114 QIODevice *io = (QIODevice*) obj;
115 return io->write(buf, cnt);
118 static long qiodevice_seek(jas_stream_obj_t *obj, long offset, int origin)
120 QIODevice *io = (QIODevice*) obj;
121 long newpos;
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->size() + offset;
132 break;
133 default:
134 return -1;
136 if (newpos < 0) {
137 return -1;
139 if ( io->seek(newpos) )
140 return newpos;
141 else
142 return -1;
145 static int qiodevice_close(jas_stream_obj_t *)
147 return 0;
150 static jas_stream_ops_t jas_stream_qiodeviceops = {
151 qiodevice_read,
152 qiodevice_write,
153 qiodevice_seek,
154 qiodevice_close
157 static jas_stream_t *jas_stream_qiodevice(QIODevice *iodevice)
159 jas_stream_t *stream;
161 if ( !iodevice ) return 0;
162 if (!(stream = jas_stream_create())) {
163 return 0;
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;
170 jas_stream_initbuf(stream, JAS_STREAM_FULLBUF, 0, 0);
172 /* Select the operations for a memory stream. */
173 stream->obj_ = (void *)iodevice;
174 stream->ops_ = &jas_stream_qiodeviceops;
176 return stream;
179 /************************ End of JasPer QIODevice stream ****************/
181 typedef struct {
182 jas_image_t* image;
184 int cmptlut[MAXCMPTS];
186 jas_image_t* altimage;
187 } gs_t;
190 static jas_image_t*
191 read_image( QIODevice* io )
193 jas_stream_t* in = 0;
195 in = jas_stream_qiodevice( io );
197 if( !in ) return 0;
199 jas_image_t* image = jas_image_decode( in, -1, 0 );
200 jas_stream_close( in );
202 // image may be 0, but that's Ok
203 return image;
204 } // read_image
206 static bool
207 convert_colorspace( gs_t& gs )
209 jas_cmprof_t *outprof = jas_cmprof_createfromclrspc( JAS_CLRSPC_SRGB );
210 if( !outprof ) return false;
212 gs.altimage = jas_image_chclrspc( gs.image, outprof,
213 JAS_CMXFORM_INTENT_PER );
214 if( !gs.altimage ) return false;
216 return true;
217 } // convert_colorspace
219 static bool
220 render_view( gs_t& gs, QImage* outImage )
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
233 const int* cmptlut = gs.cmptlut;
234 int v[3];
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
245 qti = QImage( jas_image_width( gs.altimage ), jas_image_height( gs.altimage ),
246 QImage::Format_RGB32 );
248 uint32_t* data = (uint32_t*)qti.bits();
250 for( int y = 0; y < height; ++y ) {
251 for( int x = 0; x < width; ++x ) {
252 for( int k = 0; k < 3; ++k ) {
253 v[k] = jas_image_readcmptsample( gs.altimage, cmptlut[k], x, y );
254 // if the precision of the component is too small, increase
255 // it to use the complete value range.
256 v[k] <<= 8 - jas_image_cmptprec( gs.altimage, cmptlut[k] );
258 if( v[k] < 0 ) v[k] = 0;
259 else if( v[k] > 255 ) v[k] = 255;
260 } // for k
262 *data++ = qRgb( v[0], v[1], v[2] );
263 } // for x
264 } // for y
265 *outImage = qti;
266 return true;
267 } // render_view
270 static jas_image_t*
271 create_image( const QImage& qi )
273 // prepare the component parameters
274 jas_image_cmptparm_t* cmptparms = new jas_image_cmptparm_t[ 3 ];
276 for ( int i = 0; i < 3; ++i ) {
277 // x and y offset
278 cmptparms[i].tlx = 0;
279 cmptparms[i].tly = 0;
281 // the resulting image will be hstep*width x vstep*height !
282 cmptparms[i].hstep = 1;
283 cmptparms[i].vstep = 1;
284 cmptparms[i].width = qi.width();
285 cmptparms[i].height = qi.height();
287 // we write everything as 24bit truecolor ATM
288 cmptparms[i].prec = 8;
289 cmptparms[i].sgnd = false;
292 jas_image_t* ji = jas_image_create( 3 /* number components */, cmptparms, JAS_CLRSPC_UNKNOWN );
293 delete[] cmptparms;
295 // returning 0 is ok
296 return ji;
297 } // create_image
300 static bool
301 write_components( jas_image_t* ji, const QImage& qi )
303 const unsigned height = qi.height();
304 const unsigned width = qi.width();
306 jas_matrix_t* m = jas_matrix_create( height, width );
307 if( !m ) return false;
309 jas_image_setclrspc( ji, JAS_CLRSPC_SRGB );
311 jas_image_setcmpttype( ji, 0, JAS_IMAGE_CT_RGB_R );
312 for( uint y = 0; y < height; ++y )
313 for( uint x = 0; x < width; ++x )
314 jas_matrix_set( m, y, x, qRed( qi.pixel( x, y ) ) );
315 jas_image_writecmpt( ji, 0, 0, 0, width, height, m );
317 jas_image_setcmpttype( ji, 1, JAS_IMAGE_CT_RGB_G );
318 for( uint y = 0; y < height; ++y )
319 for( uint x = 0; x < width; ++x )
320 jas_matrix_set( m, y, x, qGreen( qi.pixel( x, y ) ) );
321 jas_image_writecmpt( ji, 1, 0, 0, width, height, m );
323 jas_image_setcmpttype( ji, 2, JAS_IMAGE_CT_RGB_B );
324 for( uint y = 0; y < height; ++y )
325 for( uint x = 0; x < width; ++x )
326 jas_matrix_set( m, y, x, qBlue( qi.pixel( x, y ) ) );
327 jas_image_writecmpt( ji, 2, 0, 0, width, height, m );
328 jas_matrix_destroy( m );
330 return true;
331 } // write_components
333 static bool
334 write_image( const QImage &image, QIODevice* io, int quality )
336 jas_stream_t* stream = 0;
337 stream = jas_stream_qiodevice( io );
339 // by here, a jas_stream_t is open
340 if( !stream ) return false;
342 jas_image_t* ji = create_image( image );
343 if( !ji ) {
344 jas_stream_close( stream );
345 return false;
346 } // if
348 if( !write_components( ji, image ) ) {
349 jas_stream_close( stream );
350 jas_image_destroy( ji );
351 return false;
352 } // if
354 // optstr:
355 // - rate=#B => the resulting file size is about # bytes
356 // - rate=0.0 .. 1.0 => the resulting file size is about the factor times
357 // the uncompressed size
358 QString rate;
359 QTextStream ts( &rate, QIODevice::WriteOnly );
360 ts << "rate="
361 << ( (quality < 0) ? DEFAULT_RATE : quality / 100.0F );
362 int i = jp2_encode( ji, stream, rate.toUtf8().data() );
364 jas_image_destroy( ji );
365 jas_stream_close( stream );
367 if( i != 0 ) return false;
369 return true;
372 JP2Handler::JP2Handler()
374 quality = 75;
375 jas_init();
378 JP2Handler::~JP2Handler()
380 jas_cleanup();
383 bool JP2Handler::canRead() const
385 if (canRead(device())) {
386 setFormat("jp2");
387 return true;
389 return false;
392 bool JP2Handler::canRead(QIODevice *device)
394 if (!device) {
395 return false;
397 return device->peek(6) == QByteArray("\x00\x00\x00\x0C\x6A\x50", 6);
400 bool JP2Handler::read(QImage *image)
402 if (!canRead()) return false;
404 gs_t gs;
405 if( !(gs.image = read_image( device() )) ) return false;
407 if( !convert_colorspace( gs ) ) return false;
409 render_view( gs, image );
411 if( gs.image ) jas_image_destroy( gs.image );
412 if( gs.altimage ) jas_image_destroy( gs.altimage );
413 return true;
417 bool JP2Handler::write(const QImage &image)
419 return write_image(image, device(),quality);
422 bool JP2Handler::supportsOption(ImageOption option) const
424 return option == Quality;
427 QVariant JP2Handler::option(ImageOption option) const
429 if (option == Quality)
430 return quality;
431 return QVariant();
434 QByteArray JP2Handler::name() const
436 return "jp2";
439 class JP2Plugin : public QImageIOPlugin
441 public:
442 QStringList keys() const;
443 Capabilities capabilities(QIODevice *device, const QByteArray &format) const;
444 QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const;
447 QStringList JP2Plugin::keys() const
449 return QStringList() << "jp2";
452 QImageIOPlugin::Capabilities JP2Plugin::capabilities(QIODevice *device, const QByteArray &format) const
454 if (format == "jp2")
455 return Capabilities(CanRead | CanWrite);
456 if (!format.isEmpty())
457 return 0;
458 if (!device->isOpen())
459 return 0;
461 Capabilities cap;
462 if (device->isReadable() && JP2Handler::canRead(device))
463 cap |= CanRead;
464 if (device->isWritable())
465 cap |= CanWrite;
466 return cap;
469 QImageIOHandler *JP2Plugin::create(QIODevice *device, const QByteArray &format) const
471 QImageIOHandler *handler = new JP2Handler;
472 handler->setDevice(device);
473 handler->setFormat(format);
474 return handler;
477 Q_EXPORT_STATIC_PLUGIN(JP2Plugin)
478 Q_EXPORT_PLUGIN2(jp2, JP2Plugin)