1 /********************************************************************************
2 * *
3 * J P E G - 2 0 0 0 I n p u t / O u t p u t *
4 * *
5 *********************************************************************************
6 * Copyright (C) 2009,2021 by Jeroen van der Zijp. All Rights Reserved. *
7 *********************************************************************************
8 * This library is free software; you can redistribute it and/or modify *
9 * it under the terms of the GNU Lesser General Public License as published by *
10 * the Free Software Foundation; either version 3 of the License, or *
11 * (at your option) any later version. *
12 * *
13 * This library is distributed in the hope that it will be useful, *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16 * GNU Lesser General Public License for more details. *
17 * *
18 * You should have received a copy of the GNU Lesser General Public License *
19 * along with this program. If not, see <http://www.gnu.org/licenses/> *
20 ********************************************************************************/
21 #include "xincs.h"
22 #include "fxver.h"
23 #include "fxdefs.h"
24 #include "fxmath.h"
25 #include "FXArray.h"
26 #include "FXHash.h"
27 #include "FXElement.h"
28 #include "FXStream.h"
29 #ifdef HAVE_JP2_H
30 #include "openjpeg.h"
31 #endif
32
33
34 /*
35 Notes:
36 - Support for JPEG 2000 image file compression.
37 */
38
39 // Contents of signature box
40 #define SIGNATURE 0x0d0a870a // Value for signature box
41
42 // File level boxes
43 #define BOX_JP 0x6a502020 // File signature box
44 #define BOX_FTYP 0x66747970 // File type box
45 #define BOX_JP2H 0x6a703268 // JP2 Header box
46 #define BOX_MHDR 0x6D686472 // Compound image header box
47 #define BOX_DBTL 0x6474626c // Data reference box
48 #define BOX_URL 0x75726c20 // URL box
49 #define BOX_PCOL 0x70636F6C // Page collection box
50 #define BOX_LBL 0x6C626C20 // Label box
51 #define BOX_PAGT 0x70616774 // Page table box
52 #define BOX_SDAT 0x73646174 // Shared data box
53 #define BOX_SREF 0x73726566 // Shared reference box
54 #define BOX_PAGE 0x70616765 // Page box
55 #define BOX_PHDR 0x70686472 // Page header box
56 #define BOX_RES 0x72657320 // Resolution box
57 #define BOX_BCLR 0x62636C72 // Base color box
58 #define BOX_LOBJ 0x6C6F626A // Layout object box
59 #define BOX_LHDR 0x6C686472 // Layout object header box
60 #define BOX_OBJC 0x6F626A63 // Object box
61 #define BOX_OHDR 0x6F686472 // Object header box
62 #define BOX_SCAL 0x7363616C // Object scale box
63 #define BOX_FTBL 0x6672626C // Fragment table box
64 #define BOX_FLST 0x666C7374 // Fragment list box
65 #define BOX_MDAT 0x6D646174 // Media data box
66 #define BOX_JP2C 0x6a703263 // Contiguous codestream box
67 #define BOX_RESC 0x72657363 // Capture resolution box
68 #define BOX_RESD 0x72657364 // Default display resolution box
69 #define BOX_IHDR 0x69686472 // Image Header box
70 #define BOX_PCLR 0x70636C72 // Palette box
71 #define BOX_COLR 0x636f6c72 // Colour specification box
72 #define BOX_CMAP 0x636D6170 // Component mapping box
73 #define BOX_FTBL 0x6672626C // Fragment table box
74 #define BOX_FREE 0x66726565 // Free box
75 #define BOX_JP2 0x6a703220 // JP2 box
76 #define BOX_BPCC 0x62706363 // Bits per component box
77 #define BOX_XML 0x786d6c20 // XML box
78 #define BOX_RREQ 0x72726571 // RREQ box
79
80 using namespace FX;
81
82 /*******************************************************************************/
83
84 namespace FX {
85
86
87 #ifndef FXLOADJP2
88 extern FXAPI FXbool fxcheckJP2(FXStream& store);
89 extern FXAPI FXbool fxloadJP2(FXStream& store,FXColor*& data,FXint& width,FXint& height,FXint& quality);
90 extern FXAPI FXbool fxsaveJP2(FXStream& store,const FXColor* data,FXint width,FXint height,FXint quality);
91 #endif
92
93
94 #undef HAVE_JP2_H
95 #ifdef HAVE_JP2_H
96
97 /*******************************************************************************/
98
99
100 // Report error
j2k_error_callback(const char * msg,void * client_data)101 void j2k_error_callback(const char *msg, void *client_data){
102 FXTRACE((100,"fxjp2io: error: %s.\n",msg));
103 }
104
105
106 // Report warning
j2k_warning_callback(const char * msg,void * client_data)107 void j2k_warning_callback(const char *msg, void *client_data){
108 FXTRACE((100,"fxjp2io: warning: %s.\n",msg));
109 }
110
111
112 // Report info
j2k_info_callback(const char * msg,void * client_data)113 void j2k_info_callback(const char *msg, void *client_data){
114 FXTRACE((100,"fxjp2io: info: %s.\n",msg));
115 }
116
117
118 // Check if stream contains a JPG
fxcheckJP2(FXStream & store)119 FXbool fxcheckJP2(FXStream& store){
120 FXuchar ss[12];
121 store.load(ss,12);
122 store.position(-12,FXFromCurrent);
123 return ss[0]==0 && ss[1]==0 && ss[2]==0 && ss[3]==12 && ss[4]=='j' && ss[5]=='P' && ss[6]==' ' && ss[7]==' ' && ss[8]==0x0D && ss[9]==0x0A && ss[10]==0x87 && ss[11]==0x0A;
124 }
125
126
127 // Load a JPEG image
fxloadJP2(FXStream & store,FXColor * & data,FXint & width,FXint & height,FXint &)128 FXbool fxloadJP2(FXStream& store,FXColor*& data,FXint& width,FXint& height,FXint&){
129 FXint x,y,cw,rsh,gsh,bsh,ash,rof,gof,bof,aof;
130 FXuchar r,g,b,a;
131 FXbool swap=store.swapBytes();
132 FXlong pos=store.position();
133 FXbool result=false;
134 FXuint box[4];
135 FXlong boxsize;
136 FXuint size;
137 FXuchar *ptr;
138
139 // Null out
140 data=NULL;
141 width=0;
142 height=0;
143
144 // Switch big-endian to grab header
145 store.setBigEndian(true);
146
147 // Grab signature
148 store.load(box,3);
149
150 // Check signature, bail quickly if no match
151 if(box[0]==12 && box[1]==BOX_JP && box[2]==SIGNATURE){
152
153 // Figure size
154 store.position(0,FXFromEnd);
155 size=store.position()-pos;
156 store.position(pos);
157
158 FXTRACE((100,"fxloadJP2: file size=%d\n",size));
159
160 // Allocate chunk for file data
161 if(allocElms(ptr,size)){
162
163 // Load entire file
164 store.load(ptr,size);
165
166 // Create decompressor
167 opj_dinfo_t *decompressor=opj_create_decompress(CODEC_JP2);
168 if(decompressor){
169 opj_dparameters_t parameters;
170 opj_event_mgr_t event_mgr;
171 opj_cio_t *cio=NULL;
172 opj_image_t *image=NULL;
173
174 // Set up callbacks
175 event_mgr.error_handler=j2k_error_callback;
176 event_mgr.warning_handler=j2k_warning_callback;
177 event_mgr.info_handler=j2k_info_callback;
178
179 // Set event manager
180 opj_set_event_mgr((opj_common_ptr)decompressor,&event_mgr,NULL);
181
182 // Initialize decompression parameters
183 opj_set_default_decoder_parameters(¶meters);
184
185 // Setup the decoder decoding parameters using user parameters
186 opj_setup_decoder(decompressor,¶meters);
187
188 // Open a byte stream */
189 cio=opj_cio_open((opj_common_ptr)decompressor,ptr,size);
190 if(cio){
191
192 // Decode the stream and fill the image structure
193 image=opj_decode(decompressor,cio);
194 if(image){
195
196 // Image size
197 width=image->x1-image->x0;
198 height=image->y1-image->y0;
199
200 FXTRACE((100,"fxloadJP2: width=%d height=%d numcomps=%d color_space=%d\n",width,height,image->numcomps,image->color_space));
201
202 // Only support GREY, RGB, and RGBA
203 if(((image->numcomps==1) && (image->color_space==CLRSPC_GRAY)) || ((image->numcomps==3 || image->numcomps==4) && (image->color_space==CLRSPC_SRGB))){
204
205 // Allocate image data
206 if(allocElms(data,width*height)){
207 rof=gof=bof=aof=rsh=gsh=bsh=ash=0;
208 switch(image->numcomps){
209 case 1:
210 if(image->comps[0].sgnd) gof=1<<(image->comps[0].prec-1);
211 gsh=image->comps[0].prec-8;
212 cw=image->comps[0].w;
213 for(y=0; y<height; ++y){
214 for(x=0; x<width; ++x){
215 g=(image->comps[0].data[y*cw+x]+gof)>>gsh;
216 data[y*width+x]=FXRGB(g,g,g);
217 }
218 }
219 break;
220 case 3:
221 if(image->comps[0].sgnd) rof=1<<(image->comps[0].prec-1);
222 if(image->comps[1].sgnd) gof=1<<(image->comps[1].prec-1);
223 if(image->comps[2].sgnd) bof=1<<(image->comps[2].prec-1);
224 rsh=image->comps[0].prec-8;
225 gsh=image->comps[1].prec-8;
226 bsh=image->comps[2].prec-8;
227 cw=image->comps[0].w;
228 for(y=0; y<height; ++y){
229 for(x=0; x<width; ++x){
230 r=(image->comps[0].data[y*cw+x]+rof)>>rsh;
231 g=(image->comps[1].data[y*cw+x]+gof)>>gsh;
232 b=(image->comps[2].data[y*cw+x]+bof)>>bsh;
233 data[y*width+x]=FXRGB(r,g,b);
234 }
235 }
236 break;
237 default:
238 if(image->comps[0].sgnd) rof=1<<(image->comps[0].prec-1);
239 if(image->comps[1].sgnd) gof=1<<(image->comps[1].prec-1);
240 if(image->comps[2].sgnd) bof=1<<(image->comps[2].prec-1);
241 if(image->comps[3].sgnd) aof=1<<(image->comps[3].prec-1);
242 rsh=image->comps[0].prec-8;
243 gsh=image->comps[1].prec-8;
244 bsh=image->comps[2].prec-8;
245 ash=image->comps[3].prec-8;
246 cw=image->comps[0].w;
247 for(y=0; y<height; ++y){
248 for(x=0; x<width; ++x){
249 r=(image->comps[0].data[y*cw+x]+rof)>>rsh;
250 g=(image->comps[1].data[y*cw+x]+gof)>>gsh;
251 b=(image->comps[2].data[y*cw+x]+bof)>>bsh;
252 a=(image->comps[3].data[y*cw+x]+aof)>>ash;
253 data[y*width+x]=FXRGBA(r,g,b,a);
254 }
255 }
256 break;
257 }
258 result=true;
259 }
260 }
261 opj_image_destroy(image);
262 }
263 opj_cio_close(cio);
264 }
265 opj_destroy_decompress(decompressor);
266 }
267 freeElms(ptr);
268 }
269 }
270 store.swapBytes(swap);
271 return result;
272 }
273
274
275 /*******************************************************************************/
276
277
278 // Save a JPEG image
fxsaveJP2(FXStream & store,const FXColor * data,FXint width,FXint height,FXint quality)279 FXbool fxsaveJP2(FXStream& store,const FXColor* data,FXint width,FXint height,FXint quality){
280 FXint x,y,c,p;
281 FXbool result=false;
282
283 // Must make sense
284 if(data && 0<width && 0<height){
285 opj_cinfo_t* compressor=opj_create_compress(CODEC_JP2);
286 if(compressor){
287 opj_event_mgr_t event_mgr;
288 opj_cparameters_t parameters;
289 opj_cio_t *cio=NULL;
290 opj_image_t *image=NULL;
291 opj_image_cmptparm_t components[3];
292 FXColor color;
293
294 // Set up callbacks
295 event_mgr.error_handler=j2k_error_callback;
296 event_mgr.warning_handler=j2k_warning_callback;
297 event_mgr.info_handler=j2k_info_callback;
298
299 // Set event manager
300 opj_set_event_mgr((opj_common_ptr)compressor,&event_mgr,NULL);
301
302 // Set encoding parameters to default values
303 opj_set_default_encoder_parameters(¶meters);
304
305 // parameters.tcp_rates[0]=((100-quality)/90.0f*99.0f)+1;
306 parameters.tcp_rates[0]=16;
307 parameters.tcp_numlayers=1;
308 parameters.cp_disto_alloc=1;
309
310 // Set up parameters
311 for(c=0; c<3; c++){
312 components[c].dx=parameters.subsampling_dx;
313 components[c].dy=parameters.subsampling_dy;
314 components[c].w=width;
315 components[c].h=height;
316 components[c].x0=0;
317 components[c].y0=0;
318 components[c].prec=8;
319 components[c].bpp=8;
320 components[c].sgnd=0;
321 }
322
323 // Create image
324 image=opj_image_create(3,components,CLRSPC_SRGB);
325 if(image){
326
327 /* set image offset and reference grid */
328 image->x0=parameters.image_offset_x0;
329 image->y0=parameters.image_offset_y0;
330 image->x1=parameters.image_offset_x0+(width-1)*parameters.subsampling_dx+1;
331 image->y1=parameters.image_offset_y0+(height-1)*parameters.subsampling_dy+1;
332
333 // Setup encoder for image
334 opj_setup_encoder(compressor,¶meters,image);
335
336 // Fill image buffers
337 for(y=p=0; y<height; ++y){
338 for(x=0; x<width; ++x){
339 color=data[y*width+x];
340 image->comps[0].data[p]=FXREDVAL(color);
341 image->comps[1].data[p]=FXGREENVAL(color);
342 image->comps[2].data[p]=FXBLUEVAL(color);
343 p++;
344 }
345 }
346
347 // Open code stream
348 cio=opj_cio_open((opj_common_ptr)compressor,NULL,0);
349 if(cio){
350
351 // Encode the image
352 result=opj_encode(compressor,cio,image,NULL);
353
354 // Encoded properly
355 if(result){
356
357 // Write to store
358 store.save(cio->buffer,cio_tell(cio));
359
360 // Check if write was successful
361 if(store.status()!=FXStreamOK) result=false;
362 }
363
364 // Close stream
365 opj_cio_close(cio);
366 }
367
368 // Destroy image
369 opj_image_destroy(image);
370 }
371 opj_destroy_compress(compressor);
372 }
373 }
374 return result;
375 }
376
377 /*******************************************************************************/
378
379 #else
380
381 // Check if stream contains a JPG
fxcheckJP2(FXStream &)382 FXbool fxcheckJP2(FXStream&){
383 return false;
384 }
385
386
387 // Stub routine
fxloadJP2(FXStream &,FXColor * & data,FXint & width,FXint & height,FXint & quality)388 FXbool fxloadJP2(FXStream&,FXColor*& data,FXint& width,FXint& height,FXint& quality){
389 static const FXColor color[2]={FXRGB(0,0,0),FXRGB(255,255,255)};
390 static const FXuchar image_bits[]={
391 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x80, 0xfd, 0xff, 0xff, 0xbf,
392 0x05, 0x00, 0x00, 0xa0, 0x05, 0x00, 0x00, 0xa0, 0x05, 0x00, 0x00, 0xa0,
393 0x05, 0x00, 0x00, 0xa0, 0x05, 0x00, 0x00, 0xa0, 0x05, 0x00, 0x00, 0xa0,
394 0x05, 0x00, 0x00, 0xa0, 0x05, 0x00, 0x00, 0xa0, 0x05, 0x00, 0x00, 0xa0,
395 0x05, 0x00, 0x00, 0xa0, 0x05, 0x00, 0x00, 0xa0, 0x05, 0x00, 0x00, 0xa0,
396 0x05, 0x00, 0x00, 0xa0, 0x05, 0x00, 0x00, 0xa0, 0xc5, 0xe7, 0xc3, 0xa1,
397 0x45, 0x44, 0x24, 0xa2, 0x05, 0x44, 0x04, 0xa2, 0x05, 0x44, 0x04, 0xa2,
398 0x05, 0x44, 0x04, 0xa1, 0x05, 0xc4, 0xc3, 0xa0, 0x05, 0x44, 0x20, 0xa0,
399 0x45, 0x44, 0x20, 0xa2, 0x85, 0xe3, 0xe0, 0xa3, 0x05, 0x00, 0x00, 0xa0,
400 0x05, 0x00, 0x00, 0xa0, 0x05, 0x00, 0x00, 0xa0, 0xfd, 0xff, 0xff, 0xbf,
401 0x01, 0x00, 0x00, 0x80, 0xff, 0xff, 0xff, 0xff};
402 allocElms(data,32*32);
403 for(FXint p=0; p<32*32; p++){
404 data[p]=color[(image_bits[p>>3]>>(p&7))&1];
405 }
406 width=32;
407 height=32;
408 quality=75;
409 return true;
410 }
411
412
413 // Stub routine
fxsaveJP2(FXStream &,const FXColor *,FXint,FXint,FXint)414 FXbool fxsaveJP2(FXStream&,const FXColor*,FXint,FXint,FXint){
415 return false;
416 }
417
418 #endif
419
420 }
421