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(&parameters);
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, &parameters)) {
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