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

source code of poppler/poppler/JPEG2000Stream.cc