1 //========================================================================
2 //
3 // JPEG2000Stream.cc
4 //
5 // A JPX stream decoder using OpenJPEG
6 //
7 // Copyright 2008-2010, 2012, 2017-2021 Albert Astals Cid <aacid@kde.org>
8 // Copyright 2011 Daniel Glöckner <daniel-gl@gmx.net>
9 // Copyright 2014, 2016 Thomas Freitag <Thomas.Freitag@alfa.de>
10 // Copyright 2013, 2014 Adrian Johnson <ajohnson@redneon.com>
11 // Copyright 2015 Adam Reichold <adam.reichold@t-online.de>
12 // Copyright 2015 Jakub Wilk <jwilk@jwilk.net>
13 //
14 // Licensed under GPLv2 or later
15 //
16 //========================================================================
17
18 #include "config.h"
19 #include "JPEG2000Stream.h"
20 #include <openjpeg.h>
21
22 #define OPENJPEG_VERSION_ENCODE(major, minor, micro) (((major)*10000) + ((minor)*100) + ((micro)*1))
23
24 #ifdef OPJ_VERSION_MAJOR
25 # define OPENJPEG_VERSION OPENJPEG_VERSION_ENCODE(OPJ_VERSION_MAJOR, OPJ_VERSION_MINOR, OPJ_VERSION_BUILD)
26 #else
27 // OpenJPEG started providing version macros in version 2.1.
28 // If the version macro is not found, set the version to 2.0.0 and
29 // assume there will be no API changes in 2.0.x.
30 # define OPENJPEG_VERSION OPENJPEG_VERSION_ENCODE(2, 0, 0)
31 #endif
32
33 struct JPXStreamPrivate
34 {
35 opj_image_t *image;
36 int counter;
37 int ccounter;
38 int npixels;
39 int ncomps;
40 bool inited;
41 int smaskInData;
42 void init2(OPJ_CODEC_FORMAT format, unsigned char *buf, int length, bool indexed);
43 };
44
adjustComp(int r,int adjust,int depth,int sgndcorr,bool indexed)45 static inline unsigned char adjustComp(int r, int adjust, int depth, int sgndcorr, bool indexed)
46 {
47 if (!indexed) {
48 r += sgndcorr;
49 if (adjust) {
50 r = (r >> adjust) + ((r >> (adjust - 1)) % 2);
51 } else if (depth < 8) {
52 r = r << (8 - depth);
53 }
54 }
55 if (unlikely(r > 255))
56 r = 255;
57 return r;
58 }
59
doLookChar(JPXStreamPrivate * priv)60 static inline int doLookChar(JPXStreamPrivate *priv)
61 {
62 if (unlikely(priv->counter >= priv->npixels))
63 return EOF;
64
65 return ((unsigned char *)priv->image->comps[priv->ccounter].data)[priv->counter];
66 }
67
doGetChar(JPXStreamPrivate * priv)68 static inline int doGetChar(JPXStreamPrivate *priv)
69 {
70 const int result = doLookChar(priv);
71 if (++priv->ccounter == priv->ncomps) {
72 priv->ccounter = 0;
73 ++priv->counter;
74 }
75 return result;
76 }
77
JPXStream(Stream * strA)78 JPXStream::JPXStream(Stream *strA) : FilterStream(strA)
79 {
80 priv = new JPXStreamPrivate;
81 priv->inited = false;
82 priv->image = nullptr;
83 priv->npixels = 0;
84 priv->ncomps = 0;
85 }
86
~JPXStream()87 JPXStream::~JPXStream()
88 {
89 delete str;
90 close();
91 delete priv;
92 }
93
reset()94 void JPXStream::reset()
95 {
96 priv->counter = 0;
97 priv->ccounter = 0;
98 }
99
close()100 void JPXStream::close()
101 {
102 if (priv->image != nullptr) {
103 opj_image_destroy(priv->image);
104 priv->image = nullptr;
105 priv->npixels = 0;
106 }
107 }
108
getPos()109 Goffset JPXStream::getPos()
110 {
111 return priv->counter * priv->ncomps + priv->ccounter;
112 }
113
getChars(int nChars,unsigned char * buffer)114 int JPXStream::getChars(int nChars, unsigned char *buffer)
115 {
116 if (unlikely(priv->inited == false)) {
117 init();
118 }
119
120 for (int i = 0; i < nChars; ++i) {
121 const int c = doGetChar(priv);
122 if (likely(c != EOF))
123 buffer[i] = c;
124 else
125 return i;
126 }
127 return nChars;
128 }
129
getChar()130 int JPXStream::getChar()
131 {
132 if (unlikely(priv->inited == false)) {
133 init();
134 }
135
136 return doGetChar(priv);
137 }
138
lookChar()139 int JPXStream::lookChar()
140 {
141 if (unlikely(priv->inited == false)) {
142 init();
143 }
144
145 return doLookChar(priv);
146 }
147
getPSFilter(int psLevel,const char * indent)148 GooString *JPXStream::getPSFilter(int psLevel, const char *indent)
149 {
150 return nullptr;
151 }
152
isBinary(bool last) const153 bool JPXStream::isBinary(bool last) const
154 {
155 return str->isBinary(true);
156 }
157
getImageParams(int * bitsPerComponent,StreamColorSpaceMode * csMode)158 void JPXStream::getImageParams(int *bitsPerComponent, StreamColorSpaceMode *csMode)
159 {
160 if (unlikely(priv->inited == false)) {
161 init();
162 }
163
164 *bitsPerComponent = 8;
165 int numComps = (priv->image) ? priv->image->numcomps : 1;
166 if (priv->image) {
167 if (priv->image->color_space == OPJ_CLRSPC_SRGB && numComps == 4) {
168 numComps = 3;
169 } else if (priv->image->color_space == OPJ_CLRSPC_SYCC && numComps == 4) {
170 numComps = 3;
171 } else if (numComps == 2) {
172 numComps = 1;
173 } else if (numComps > 4) {
174 numComps = 4;
175 }
176 }
177 if (numComps == 3)
178 *csMode = streamCSDeviceRGB;
179 else if (numComps == 4)
180 *csMode = streamCSDeviceCMYK;
181 else
182 *csMode = streamCSDeviceGray;
183 }
184
libopenjpeg_error_callback(const char * msg,void *)185 static void libopenjpeg_error_callback(const char *msg, void * /*client_data*/)
186 {
187 error(errSyntaxError, -1, "{0:s}", msg);
188 }
189
libopenjpeg_warning_callback(const char * msg,void *)190 static void libopenjpeg_warning_callback(const char *msg, void * /*client_data*/)
191 {
192 error(errSyntaxWarning, -1, "{0:s}", msg);
193 }
194
195 typedef struct JPXData_s
196 {
197 unsigned char *data;
198 int size;
199 int pos;
200 } JPXData;
201
202 #define BUFFER_INITIAL_SIZE 4096
203
jpxRead_callback(void * p_buffer,OPJ_SIZE_T p_nb_bytes,void * p_user_data)204 static OPJ_SIZE_T jpxRead_callback(void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data)
205 {
206 JPXData *jpxData = (JPXData *)p_user_data;
207 int len;
208
209 len = jpxData->size - jpxData->pos;
210 if (len < 0)
211 len = 0;
212 if (len == 0)
213 return (OPJ_SIZE_T)-1; /* End of file! */
214 if ((OPJ_SIZE_T)len > p_nb_bytes)
215 len = p_nb_bytes;
216 memcpy(p_buffer, jpxData->data + jpxData->pos, len);
217 jpxData->pos += len;
218 return len;
219 }
220
jpxSkip_callback(OPJ_OFF_T skip,void * p_user_data)221 static OPJ_OFF_T jpxSkip_callback(OPJ_OFF_T skip, void *p_user_data)
222 {
223 JPXData *jpxData = (JPXData *)p_user_data;
224
225 jpxData->pos += (skip > jpxData->size - jpxData->pos) ? jpxData->size - jpxData->pos : skip;
226 /* Always return input value to avoid "Problem with skipping JPEG2000 box, stream error" */
227 return skip;
228 }
229
jpxSeek_callback(OPJ_OFF_T seek_pos,void * p_user_data)230 static OPJ_BOOL jpxSeek_callback(OPJ_OFF_T seek_pos, void *p_user_data)
231 {
232 JPXData *jpxData = (JPXData *)p_user_data;
233
234 if (seek_pos > jpxData->size)
235 return OPJ_FALSE;
236 jpxData->pos = seek_pos;
237 return OPJ_TRUE;
238 }
239
init()240 void JPXStream::init()
241 {
242 Object oLen, cspace, smaskInData;
243 if (getDict()) {
244 oLen = getDict()->lookup("Length");
245 cspace = getDict()->lookup("ColorSpace");
246 smaskInData = getDict()->lookup("SMaskInData");
247 }
248
249 int bufSize = BUFFER_INITIAL_SIZE;
250 if (oLen.isInt() && oLen.getInt() > 0)
251 bufSize = oLen.getInt();
252
253 bool indexed = false;
254 if (cspace.isArray() && cspace.arrayGetLength() > 0) {
255 const Object cstype = cspace.arrayGet(0);
256 if (cstype.isName("Indexed"))
257 indexed = true;
258 }
259
260 priv->smaskInData = 0;
261 if (smaskInData.isInt())
262 priv->smaskInData = smaskInData.getInt();
263
264 int length = 0;
265 unsigned char *buf = str->toUnsignedChars(&length, bufSize);
266 priv->init2(OPJ_CODEC_JP2, buf, length, indexed);
267 gfree(buf);
268
269 if (priv->image) {
270 int numComps = priv->image->numcomps;
271 int alpha = 0;
272 if (priv->image->color_space == OPJ_CLRSPC_SRGB && numComps == 4) {
273 numComps = 3;
274 alpha = 1;
275 } else if (priv->image->color_space == OPJ_CLRSPC_SYCC && numComps == 4) {
276 numComps = 3;
277 alpha = 1;
278 } else if (numComps == 2) {
279 numComps = 1;
280 alpha = 1;
281 } else if (numComps > 4) {
282 numComps = 4;
283 alpha = 1;
284 } else {
285 alpha = 0;
286 }
287 priv->npixels = priv->image->comps[0].w * priv->image->comps[0].h;
288 priv->ncomps = priv->image->numcomps;
289 if (alpha == 1 && priv->smaskInData == 0)
290 priv->ncomps--;
291 for (int component = 0; component < priv->ncomps; component++) {
292 if (priv->image->comps[component].data == nullptr) {
293 close();
294 break;
295 }
296 const int componentPixels = priv->image->comps[component].w * priv->image->comps[component].h;
297 if (componentPixels != priv->npixels) {
298 error(errSyntaxWarning, -1, "Component {0:d} has different WxH than component 0", component);
299 close();
300 break;
301 }
302 unsigned char *cdata = (unsigned char *)priv->image->comps[component].data;
303 int adjust = 0;
304 int depth = priv->image->comps[component].prec;
305 if (priv->image->comps[component].prec > 8)
306 adjust = priv->image->comps[component].prec - 8;
307 int sgndcorr = 0;
308 if (priv->image->comps[component].sgnd)
309 sgndcorr = 1 << (priv->image->comps[0].prec - 1);
310 for (int i = 0; i < priv->npixels; i++) {
311 int r = priv->image->comps[component].data[i];
312 *(cdata++) = adjustComp(r, adjust, depth, sgndcorr, indexed);
313 }
314 }
315 } else {
316 priv->npixels = 0;
317 }
318
319 priv->counter = 0;
320 priv->ccounter = 0;
321 priv->inited = true;
322 }
323
init2(OPJ_CODEC_FORMAT format,unsigned char * buf,int length,bool indexed)324 void JPXStreamPrivate::init2(OPJ_CODEC_FORMAT format, unsigned char *buf, int length, bool indexed)
325 {
326 JPXData jpxData;
327
328 jpxData.data = buf;
329 jpxData.pos = 0;
330 jpxData.size = length;
331
332 opj_stream_t *stream;
333
334 stream = opj_stream_default_create(OPJ_TRUE);
335
336 #if OPENJPEG_VERSION >= OPENJPEG_VERSION_ENCODE(2, 1, 0)
337 opj_stream_set_user_data(stream, &jpxData, nullptr);
338 #else
339 opj_stream_set_user_data(stream, &jpxData);
340 #endif
341
342 opj_stream_set_read_function(stream, jpxRead_callback);
343 opj_stream_set_skip_function(stream, jpxSkip_callback);
344 opj_stream_set_seek_function(stream, jpxSeek_callback);
345 /* Set the length to avoid an assert */
346 opj_stream_set_user_data_length(stream, length);
347
348 opj_codec_t *decoder;
349
350 /* Use default decompression parameters */
351 opj_dparameters_t parameters;
352 opj_set_default_decoder_parameters(¶meters);
353 if (indexed)
354 parameters.flags |= OPJ_DPARAMETERS_IGNORE_PCLR_CMAP_CDEF_FLAG;
355
356 /* Get the decoder handle of the format */
357 decoder = opj_create_decompress(format);
358 if (decoder == nullptr) {
359 error(errSyntaxWarning, -1, "Unable to create decoder");
360 goto error;
361 }
362
363 /* Catch events using our callbacks */
364 opj_set_warning_handler(decoder, libopenjpeg_warning_callback, nullptr);
365 opj_set_error_handler(decoder, libopenjpeg_error_callback, nullptr);
366
367 /* Setup the decoder decoding parameters */
368 if (!opj_setup_decoder(decoder, ¶meters)) {
369 error(errSyntaxWarning, -1, "Unable to set decoder parameters");
370 goto error;
371 }
372
373 /* Decode the stream and fill the image structure */
374 image = nullptr;
375 if (!opj_read_header(stream, decoder, &image)) {
376 error(errSyntaxWarning, -1, "Unable to read header");
377 goto error;
378 }
379
380 /* Optional if you want decode the entire image */
381 if (!opj_set_decode_area(decoder, image, parameters.DA_x0, parameters.DA_y0, parameters.DA_x1, parameters.DA_y1)) {
382 error(errSyntaxWarning, -1, "X2");
383 goto error;
384 }
385
386 /* Get the decoded image */
387 if (!(opj_decode(decoder, stream, image) && opj_end_decompress(decoder, stream))) {
388 error(errSyntaxWarning, -1, "Unable to decode image");
389 goto error;
390 }
391
392 opj_destroy_codec(decoder);
393 opj_stream_destroy(stream);
394
395 if (image != nullptr)
396 return;
397
398 error:
399 if (image != nullptr) {
400 opj_image_destroy(image);
401 image = nullptr;
402 }
403 opj_stream_destroy(stream);
404 opj_destroy_codec(decoder);
405 if (format == OPJ_CODEC_JP2) {
406 error(errSyntaxWarning, -1, "Did no succeed opening JPX Stream as JP2, trying as J2K.");
407 init2(OPJ_CODEC_J2K, buf, length, indexed);
408 } else if (format == OPJ_CODEC_J2K) {
409 error(errSyntaxWarning, -1, "Did no succeed opening JPX Stream as J2K, trying as JPT.");
410 init2(OPJ_CODEC_JPT, buf, length, indexed);
411 } else {
412 error(errSyntaxError, -1, "Did no succeed opening JPX Stream.");
413 }
414 }
415