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 | |
23 | struct 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 | |
34 | static 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 | |
50 | static 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 | |
59 | static 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 | |
69 | JPXStream::JPXStream(Stream *strA) : FilterStream(strA) |
70 | { |
71 | priv = new JPXStreamPrivate; |
72 | } |
73 | |
74 | JPXStream::~JPXStream() |
75 | { |
76 | delete str; |
77 | close(); |
78 | delete priv; |
79 | } |
80 | |
81 | void JPXStream::reset() |
82 | { |
83 | priv->counter = 0; |
84 | priv->ccounter = 0; |
85 | } |
86 | |
87 | void 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 | |
96 | Goffset JPXStream::getPos() |
97 | { |
98 | return priv->counter * priv->ncomps + priv->ccounter; |
99 | } |
100 | |
101 | int 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 | |
118 | int JPXStream::getChar() |
119 | { |
120 | if (unlikely(priv->inited == false)) { |
121 | init(); |
122 | } |
123 | |
124 | return doGetChar(priv); |
125 | } |
126 | |
127 | int JPXStream::lookChar() |
128 | { |
129 | if (unlikely(priv->inited == false)) { |
130 | init(); |
131 | } |
132 | |
133 | return doLookChar(priv); |
134 | } |
135 | |
136 | GooString *JPXStream::getPSFilter(int psLevel, const char *indent) |
137 | { |
138 | return nullptr; |
139 | } |
140 | |
141 | bool JPXStream::isBinary(bool last) const |
142 | { |
143 | return str->isBinary(last: true); |
144 | } |
145 | |
146 | void 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 | |
174 | static void libopenjpeg_error_callback(const char *msg, void * /*client_data*/) |
175 | { |
176 | error(category: errSyntaxError, pos: -1, msg: "{0:s}" , msg); |
177 | } |
178 | |
179 | static void libopenjpeg_warning_callback(const char *msg, void * /*client_data*/) |
180 | { |
181 | error(category: errSyntaxWarning, pos: -1, msg: "{0:s}" , msg); |
182 | } |
183 | |
184 | typedef 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 | |
193 | static 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 | |
209 | static 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 | |
218 | static 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 | |
229 | void 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 | |
313 | void 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: ¶meters); |
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: ¶meters)) { |
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 | |
385 | error: |
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 | |