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