1 /*********************************************************************
2 jpeg -- functions to read and write JPEG files.
3 This is part of GNU Astronomy Utilities (Gnuastro) package.
4
5 Original author:
6 Mohammad Akhlaghi <mohammad@akhlaghi.org>
7 Contributing author(s):
8 Copyright (C) 2015-2021, Free Software Foundation, Inc.
9
10 Gnuastro is free software: you can redistribute it and/or modify it
11 under the terms of the GNU General Public License as published by the
12 Free Software Foundation, either version 3 of the License, or (at your
13 option) any later version.
14
15 Gnuastro is distributed in the hope that it will be useful, but
16 WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with Gnuastro. If not, see <http://www.gnu.org/licenses/>.
22 **********************************************************************/
23 #include <config.h>
24
25 #include <stdio.h>
26 #include <errno.h>
27 #include <error.h>
28 #include <stdlib.h>
29 #include <setjmp.h>
30 #include <string.h>
31 #ifdef HAVE_LIBJPEG
32 #include <jpeglib.h>
33 #endif
34
35 #include <gnuastro/list.h>
36 #include <gnuastro/jpeg.h>
37
38 #include <gnuastro-internal/checkset.h>
39
40
41
42
43
44 /*************************************************************
45 ************** Basic access settings ************
46 *************************************************************/
47 #ifdef HAVE_LIBJPEG
48
49 /* Read the example.c in libjpeg's source code to understand the
50 details of what is going on here. */
51 struct my_error_mgr
52 {
53 struct jpeg_error_mgr pub; /* "public" fields */
54 jmp_buf setjmp_buffer; /* for return to caller */
55 };
56
57 typedef struct my_error_mgr *my_error_ptr;
58
59
60 METHODDEF(void)
jpeg_error_exit(j_common_ptr cinfo)61 jpeg_error_exit(j_common_ptr cinfo)
62 {
63 /* cinfo->err really points to a my_error_mgr struct, so coerce pointer */
64 my_error_ptr myerr = (my_error_ptr) cinfo->err;
65
66 /* Always display the message. */
67 /* We could postpone this until after returning, if we chose. */
68 (*cinfo->err->output_message) (cinfo);
69
70 /* Return control to the setjmp point */
71 longjmp(myerr->setjmp_buffer, 1);
72 }
73
74 #else
75
76 static void
jpeg_error_no_libjpeg(char * func)77 jpeg_error_no_libjpeg(char *func)
78 {
79 error(EXIT_FAILURE, 0, "%s: libjpeg was not found during the "
80 "configuration of %s on this system. To read from JPEG files, "
81 "libjpeg is required. Please install libjpeg and configure, make "
82 "and install %s again", func, PACKAGE_STRING, PACKAGE_STRING);
83 }
84
85 #endif
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106 /*************************************************************
107 ************** JPEG name and file identification ************
108 *************************************************************/
109 int
gal_jpeg_name_is_jpeg(char * name)110 gal_jpeg_name_is_jpeg(char *name)
111 {
112 size_t len;
113
114 if(name)
115 {
116 len=strlen(name);
117 if ( ( len>=3 && strcmp(&name[len-3], "jpg") == 0 )
118 || ( len>=3 && strcmp(&name[len-3], "JPG") == 0 )
119 || ( len>=4 && strcmp(&name[len-4], "jpeg") == 0 )
120 || ( len>=4 && strcmp(&name[len-4], "JPEG") == 0 )
121 || ( len>=3 && strcmp(&name[len-3], "jpe") == 0 )
122 || ( len>=3 && strcmp(&name[len-3], "jif") == 0 )
123 || ( len>=4 && strcmp(&name[len-4], "jfif") == 0 )
124 || ( len>=3 && strcmp(&name[len-3], "jfi") == 0 ) )
125 return 1;
126 else
127 return 0;
128 }
129 else return 0;
130 }
131
132
133
134
135
136 int
gal_jpeg_suffix_is_jpeg(char * name)137 gal_jpeg_suffix_is_jpeg(char *name)
138 {
139 if(name)
140 {
141 if (strcmp(name, "jpg") == 0 || strcmp(name, ".jpg") == 0
142 || strcmp(name, "JPG") == 0 || strcmp(name, ".JPG") == 0
143 || strcmp(name, "jpeg") == 0 || strcmp(name, ".jpeg") == 0
144 || strcmp(name, "JPEG") == 0 || strcmp(name, ".JPEG") == 0
145 || strcmp(name, "jpe") == 0 || strcmp(name, ".jpe") == 0
146 || strcmp(name, "jif") == 0 || strcmp(name, ".jif") == 0
147 || strcmp(name, "jfif") == 0 || strcmp(name, ".jfif") == 0
148 || strcmp(name, "jfi") == 0 || strcmp(name, ".jfi") == 0)
149 return 1;
150 else
151 return 0;
152 }
153 else return 0;
154 }
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175 /*************************************************************
176 ************** Read a JPEG image **************
177 *************************************************************/
178 #ifdef HAVE_LIBJPEG
179 static void
jpeg_jsample_make(JSAMPLE ** a,size_t size)180 jpeg_jsample_make(JSAMPLE **a, size_t size)
181 {
182 JSAMPLE *jsarr;
183
184 if(sizeof *jsarr!=1)
185 {
186 printf("\n\nJSAMPLE has to be unsigned char!\n\n");
187 exit(EXIT_FAILURE);
188 }
189
190 errno=0;
191 jsarr=malloc(size*sizeof *jsarr);
192 if(jsarr==NULL)
193 error(EXIT_FAILURE, errno, "%s: allocating %zu bytes for jsarr",
194 __func__, size*sizeof *jsarr);
195
196 *a=jsarr;
197 }
198
199
200
201
202
203 static unsigned char **
jpeg_read_to_array(char * inname,size_t * outs0,size_t * outs1,size_t * numcolors)204 jpeg_read_to_array(char *inname, size_t *outs0, size_t *outs1, size_t *numcolors)
205 {
206 FILE *infile;
207 JSAMPROW jrow;
208 JSAMPLE *jsamp;
209 int rowstride, c;
210 JSAMPARRAY jsarr;
211 unsigned char **all;
212 struct my_error_mgr jerr;
213 size_t i, j, size, nc, s0, s1;
214 struct jpeg_decompress_struct cinfo;
215
216 /* Open the input file */
217 errno=0;
218 if ((infile = fopen(inname, "rb")) == NULL)
219 error(EXIT_FAILURE, errno, "%s", inname);
220
221 /* Set up the error and decompressing (reading) functions. */
222 cinfo.err = jpeg_std_error(&jerr.pub);
223 jerr.pub.error_exit = jpeg_error_exit;
224 if (setjmp(jerr.setjmp_buffer))
225 {
226 jpeg_destroy_decompress(&cinfo);
227 fclose(infile);
228 error(EXIT_FAILURE, 0, "%s: problem in reading %s", __func__, inname);
229 }
230 jpeg_create_decompress(&cinfo);
231 jpeg_stdio_src(&cinfo, infile);
232
233 /* Read the JPEG header information and start de-compressing: */
234 jpeg_read_header(&cinfo, TRUE);
235 jpeg_start_decompress(&cinfo);
236
237 /* Get the array width and height and number of color channels: */
238 s0=*outs0=cinfo.output_height;
239 s1=*outs1=cinfo.output_width;
240 size=s0*s1;
241 nc=*numcolors=cinfo.output_components;
242 rowstride=s1*nc;
243 jpeg_jsample_make(&jsamp, size*nc);
244
245 /* Allocate all the arrays for each color: */
246 errno=0;
247 all=malloc(nc*sizeof *all);
248 if(all==NULL)
249 error(EXIT_FAILURE, errno, "%s: allocating %zu bytes for 'all'",
250 __func__, nc*sizeof *all);
251 for(i=0;i<nc;++i)
252 {
253 errno=0;
254 all[i]=malloc(s0*s1*sizeof *all[i]);
255 if(all[i]==NULL)
256 error(EXIT_FAILURE, errno, "%s: allocating %zu bytes for 'all[%zu]'",
257 __func__, s0*s1*sizeof *all[i], i);
258 }
259
260 /* Read the image line by line: */
261 c=s0-1;
262 while (cinfo.output_scanline < cinfo.output_height)
263 {
264 jrow=&jsamp[c-- * rowstride];
265 jsarr=&jrow;
266 jpeg_read_scanlines(&cinfo, jsarr, 1);
267 }
268
269 /* Put the different colors into the different arrays */
270 for(i=0;i<size;++i)
271 for(j=0;j<nc;++j)
272 all[j][i]=jsamp[i*nc+j];
273
274 /* Finish decompression, destroy it and close file: */
275 jpeg_finish_decompress(&cinfo);
276 jpeg_destroy_decompress(&cinfo);
277 fclose(infile);
278 free(jsamp);
279
280 return all;
281 }
282 #endif /* HAVE_LIBJPEG */
283
284
285
286
287
288 /* Read each color channel of a JPEG image as a separate array and put them
289 in a linked list of data-structures. */
290 gal_data_t *
gal_jpeg_read(char * filename,size_t minmapsize,int quietmmap)291 gal_jpeg_read(char *filename, size_t minmapsize, int quietmmap)
292 {
293 #ifdef HAVE_LIBJPEG
294 char *name;
295 gal_data_t *out=NULL;
296 size_t ndim=2, dsize[2];
297 unsigned char **allcolors;
298 size_t i, s0, s1, numcolors;
299
300 /* Read the JPEG image into the all array. */
301 allcolors=jpeg_read_to_array(filename, &s0, &s1, &numcolors);
302
303 /* Add the arrays to the linked list. */
304 for(i=0;i<numcolors;++i)
305 {
306 dsize[0]=s0;
307 dsize[1]=s1;
308 if( asprintf(&name, "JPEG_CH_%zu", i+1)<0 )
309 error(EXIT_FAILURE, 0, "%s: asprintf allocation", __func__);
310 gal_list_data_add_alloc(&out, allcolors[i], GAL_TYPE_UINT8, ndim,
311 dsize, NULL, 0, minmapsize, quietmmap,
312 name, NULL, NULL);
313 free(name);
314 }
315
316 /* Free the array keeping the pointers to each channel. Note that each
317 channel was allocated separately and goes out of this function with
318 the data structure, so we just have to free the outer array that kept
319 all the channels. */
320 free(allcolors);
321
322 /* Return the number of color channels. */
323 return out;
324 #else
325 jpeg_error_no_libjpeg(__func__);
326 return NULL;
327 #endif
328 }
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349 /*************************************************************
350 ************** Write a JPEG image **************
351 *************************************************************/
352 #ifdef HAVE_LIBJPEG
353 static void
jpeg_write_array(JSAMPLE * jsr,gal_data_t * in,char * filename,uint8_t quality,float widthincm)354 jpeg_write_array(JSAMPLE *jsr, gal_data_t *in, char *filename,
355 uint8_t quality, float widthincm)
356 {
357 JSAMPROW r[1];
358 FILE * outfile;
359 int row_stride=0, c;
360 size_t *dsize=in->dsize;
361 struct jpeg_error_mgr jerr;
362 struct jpeg_compress_struct cinfo;
363 size_t numch=gal_list_data_number(in);
364
365 /* A small sanity check. */
366 if(quality > 100)
367 error(EXIT_FAILURE, 0, "%s: quality value %u not acceptable. It must be "
368 "a value between zero and 100 (inclusive)", __func__, quality);
369
370 /* Begin the JPEG writing, following libjpeg's example.c */
371 cinfo.err = jpeg_std_error(&jerr);
372 jpeg_create_compress(&cinfo);
373
374 errno=0;
375 if ((outfile = fopen(filename, "wb")) == NULL)
376 error(EXIT_FAILURE, errno, "%s", filename);
377 jpeg_stdio_dest(&cinfo, outfile);
378
379 cinfo.image_width = dsize[1];
380 cinfo.image_height = dsize[0];
381 switch(numch)
382 {
383 case 1:
384 row_stride=dsize[1];
385 cinfo.input_components = 1;
386 cinfo.in_color_space = JCS_GRAYSCALE;
387 break;
388 case 3:
389 row_stride=3*dsize[1];
390 cinfo.input_components = 3;
391 cinfo.in_color_space = JCS_RGB;
392 break;
393 case 4:
394 row_stride=4*dsize[1];
395 cinfo.input_components = 4;
396 cinfo.in_color_space = JCS_CMYK;
397 break;
398 default:
399 error(EXIT_FAILURE, 0, "%s: a bug! The number of channels is not 1, 3 "
400 "or 4, but %zu. This should not happen. Please contact us so we "
401 "can fix the problem", __func__, numch);
402 }
403
404 jpeg_set_defaults(&cinfo);
405 jpeg_set_quality(&cinfo, quality, TRUE);
406 cinfo.density_unit=1;
407 cinfo.Y_density=cinfo.X_density=dsize[1]/(widthincm/2.54);
408 jpeg_start_compress(&cinfo, TRUE);
409
410 /* cinfo.next_scanline is 'unsigned int' */
411 c=dsize[0]-1; /* In JPEG the first row is on the bottom! */
412 while (cinfo.next_scanline < cinfo.image_height)
413 {
414 r[0] = & jsr[c-- * row_stride];
415 (void) jpeg_write_scanlines(&cinfo, r, 1);
416 }
417
418 jpeg_finish_compress(&cinfo);
419 fclose(outfile);
420 jpeg_destroy_compress(&cinfo);
421 }
422 #endif /* HAVE_LIBJPEG */
423
424
425
426
427
428 void
gal_jpeg_write(gal_data_t * in,char * filename,uint8_t quality,float widthincm)429 gal_jpeg_write(gal_data_t *in, char *filename, uint8_t quality,
430 float widthincm)
431 {
432 #ifdef HAVE_LIBJPEG
433 JSAMPLE *jsr;
434 gal_data_t *channel;
435 unsigned char *colors[4];
436 size_t i, pixel, color;
437 size_t numch=gal_list_data_number(in);
438
439 /* Small sanity checks. */
440 if(numch==2 || numch>4)
441 error(EXIT_FAILURE, 0, "%s: only 1, 3, and 4 color channels are "
442 "acceptable, input is a list of %zu data sets", __func__, numch);
443 if(in->type!=GAL_TYPE_UINT8)
444 error(EXIT_FAILURE, 0, "%s: input has a '%s' type, but JPEG images can "
445 "only have a 'uint8' type", __func__, gal_type_name(in->type, 1));
446
447 /* Make sure the file doesn't exist and that we have write
448 permission. Note that the JPEG standard doesn't have multple
449 extensions.*/
450 if( gal_checkset_writable_notexist(filename)==0 )
451 error(EXIT_FAILURE, 0, "%s: already exists or its directory doesn't "
452 "write permssion. Note that the JPEG standard only allows one "
453 "image per file", filename);
454
455 /* Make sure the JSAMPLE is 8bits, then allocate the necessary space
456 based on the number of channels. */
457 if(sizeof *jsr!=1)
458 error(EXIT_FAILURE, 0, "%s: JSAMPLE has to be 8bit", __func__);
459 errno=0;
460 jsr=malloc(numch * in->size * sizeof *jsr);
461 if(jsr==NULL)
462 error(EXIT_FAILURE, errno, "%s: allocating %zu bytes for jsr",
463 __func__, numch * in->size * sizeof *jsr );
464
465 /* Set the pointers to each color. */
466 i=0;
467 for(channel=in; channel!=NULL; channel=channel->next)
468 colors[i++]=channel->array;
469
470 /* Write the different colors into jsr. */
471 for(pixel=0; pixel<in->size; ++pixel)
472 for(color=0;color<numch;++color)
473 {
474 jsr[pixel*numch+color] = colors[color][pixel];
475 /*
476 printf("color: %zu, pixel: %zu, jsr: %d\n", color, pixel,
477 (int)jsr[pixel*numch+color]);
478 */
479 }
480
481 /* Write jsr to a JPEG image and clean up. */
482 jpeg_write_array(jsr, in, filename, quality, widthincm);
483 free(jsr);
484 #else
485 error(EXIT_FAILURE, 0, "%s: libjpeg was not found during the "
486 "configuration of %s on this system. To write JPEG files, libjpeg "
487 "is required. Please install libjpeg, then configure, make and "
488 "install %s again", __func__, PACKAGE_STRING, PACKAGE_STRING);
489 #endif /* HAVE_LIBJPEG */
490 }
491