1 /*********************************************************************
2 eps -- functions to write EPS 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 <string.h>
30 
31 #include <gnuastro/eps.h>
32 #include <gnuastro/list.h>
33 
34 #include <gnuastro-internal/timing.h>
35 #include <gnuastro-internal/checkset.h>
36 
37 
38 
39 
40 
41 
42 /*************************************************************
43  **************      Acceptable EPS names      ***************
44  *************************************************************/
45 int
gal_eps_name_is_eps(char * name)46 gal_eps_name_is_eps(char *name)
47 {
48   size_t len;
49   if(name)
50     {
51       len=strlen(name);
52       if ( ( len>=3 && strcmp(&name[len-3], "eps") == 0 )
53            || ( len>=3 && strcmp(&name[len-3], "EPS") == 0 )
54            || ( len>=4 && strcmp(&name[len-4], "epsf") == 0 )
55            || ( len>=4 && strcmp(&name[len-4], "epsi") == 0 ) )
56         return 1;
57       else
58         return 0;
59     }
60   else return 0;
61 }
62 
63 
64 
65 
66 
67 int
gal_eps_suffix_is_eps(char * name)68 gal_eps_suffix_is_eps(char *name)
69 {
70   if(name)
71     {
72       if (strcmp(name, "eps") == 0 || strcmp(name, ".eps") == 0
73           || strcmp(name, "EPS") == 0 || strcmp(name, ".EPS") == 0
74           || strcmp(name, "epsf") == 0 || strcmp(name, ".epsf") == 0
75           || strcmp(name, "epsi") == 0 || strcmp(name, ".epsi") == 0)
76         return 1;
77       else
78         return 0;
79     }
80   else return 0;
81 }
82 
83 
84 
85 
86 
87 
88 
89 
90 
91 
92 
93 
94 
95 
96 
97 
98 
99 
100 
101 
102 /*************************************************************
103  **************       Write an EPS image        **************
104  *************************************************************/
105 static int
eps_is_binary(gal_data_t * in,uint8_t * bitone)106 eps_is_binary(gal_data_t *in, uint8_t *bitone)
107 {
108   gal_data_t *channel;
109   uint8_t second_set=0;
110   unsigned char *i, *fi, first=0, second=0;
111 
112   /* Go through all the channels. */
113   for(channel=in; channel!=NULL; channel=channel->next)
114     {
115       /* Go through all the values and see if there is more than two values
116          in the array. */
117       fi = (i=channel->array) + channel->size;
118       first=*i;
119       do
120         if(*i!=first)
121           {
122             if(second_set)
123               { if(*i!=second) break; }
124             else
125               { second=*i; second_set=1; }
126           }
127       while(++i<fi);
128 
129       /* If we didn't get to the end of the array, then we have a
130          multi-valued (not binary) image. */
131       if(i!=fi)
132         return 0;
133     }
134 
135   /* If we get to this point, then all the channels were binary, so return
136      success. */
137   *bitone = first>second ? first : second;
138   return 1;
139 }
140 
141 
142 
143 
144 
145 /* Convert the channels into into a 0 and 1 bit stream. This function is
146    only called when the image is binary (has only two values). NOTE: each
147    row has to have an integer number of bytes, so when the number of pixels
148    in a row is not a multiple of 8, we'll add one. */
149 static gal_data_t *
eps_convert_to_bitstream(gal_data_t * in,size_t * numbytes,uint8_t bitone)150 eps_convert_to_bitstream(gal_data_t *in, size_t *numbytes, uint8_t bitone)
151 {
152   size_t i, j, k, bytesinrow;
153   gal_data_t *channel, *out=NULL;
154   unsigned char *bits, byte, curbit, *arr;
155   size_t s0=in->dsize[0], s1=in->dsize[1];
156 
157   /* Find the size values and allocate the array. */
158   if( s1 % 8 ) bytesinrow = s1/8 + 1;
159   else         bytesinrow = s1/8;
160   *numbytes = bytesinrow*s0;
161 
162   /* Go over all the channels. */
163   for(channel=in; channel!=NULL; channel=channel->next)
164     {
165       /* Allocate the array. Note that we currently don't have an
166          allocation system for bits, so we'll allocate space in bytes, then
167          convert  */
168       gal_list_data_add_alloc(&out, NULL, GAL_TYPE_UINT8, 1, numbytes,
169                               NULL, 0, -1, 1, NULL, NULL, NULL);
170       out->type=GAL_TYPE_BIT;
171       bits=out->array;
172 
173       /* Put the values in. */
174       arr=channel->array;
175       for(i=0;i<s0;++i)           /* i*s0+j is the byte, not bit position. */
176         for(j=0;j<bytesinrow;++j)
177           {
178             /* Set the 8 bits to zero. */
179             byte=0;
180 
181             /* Current bit position. */
182             curbit=0x80;
183 
184             /* Write the next 8 values as bits. */
185             for(k=0;k<8;++k)
186               {
187                 if( j*8+k < s1 )
188                   {
189                     if(arr[i*s1+j*8+k]==bitone)
190                       byte |= curbit;
191                     curbit >>= 1;
192                   }
193                 else break;
194               }
195 
196             /* Write the byte into the array. */
197             bits[i*bytesinrow+j]=byte;
198           }
199     }
200 
201   /* Reverse the list and return it. */
202   gal_list_data_reverse(&out);
203   return out;
204 }
205 
206 
207 
208 
209 
210 static void
eps_write_hex(gal_data_t * write,FILE * fp,size_t numbytes)211 eps_write_hex(gal_data_t *write, FILE *fp, size_t numbytes)
212 {
213   unsigned char *arr;
214   gal_data_t *channel;
215   size_t i=0, j, elem_for_newline=35;
216 
217   for(channel=write; channel!=NULL; channel=channel->next)
218     {
219       if(channel->status)       /* A blank channel has status==1. */
220         fprintf(fp, "{<00>} %% Channel %zu is blank\n", i);
221       else
222         {
223           arr=channel->array;
224           fprintf(fp, "{<");
225           for(j=0;j<numbytes;++j)
226             {
227               fprintf(fp, "%02X", arr[j]);
228               if(j%elem_for_newline==0) fprintf(fp, "\n");
229             }
230           fprintf(fp, ">}\n");
231         }
232       ++i;
233     }
234 }
235 
236 
237 
238 
239 
240 static void
eps_write_ascii85(gal_data_t * write,FILE * fp,size_t numbytes)241 eps_write_ascii85(gal_data_t *write, FILE *fp, size_t numbytes)
242 {
243   unsigned char *arr;
244   gal_data_t *channel;
245   uint32_t anint, base;
246   size_t i=0, j, k, elem_for_newline=15;   /* 15*5=75 */
247 
248   for(channel=write; channel!=NULL; channel=channel->next)
249     {
250       if(channel->status)
251         fprintf(fp, "{<00>} %% Channel %zu is blank\n", i);
252       else
253         {
254           arr=channel->array;
255           fprintf(fp, "{<~");
256           for(j=0;j<numbytes;j+=4)
257             {
258               /* This is the last four bytes */
259               if(numbytes-j<4)
260                 {
261                   anint=arr[j]*256*256*256;
262                   if(numbytes-j>1)  anint+=arr[j+1]*256*256;
263                   if(numbytes-j==3) anint+=arr[j+2]*256;
264                 }
265               else
266                 anint=( arr[j]*256*256*256 + arr[j+1]*256*256
267                         + arr[j+2]*256     + arr[j+3]         );
268 
269               /* If all four bytes are zero, then just print 'z'. */
270               if(anint==0) fprintf(fp, "z");
271               else
272                 {
273                   /* To check, just change the fprintf below to printf:
274                      printf("\n\n");
275                      printf("%u %u %u %u\n", in[i], in[i+1],
276                             in[i+2], in[i+3]);
277                   */
278                   base=85*85*85*85;
279                   /* Do the ASCII85 encoding: */
280                   for(k=0;k<5;++k)
281                     {
282                       fprintf(fp, "%c", anint/base+33);
283                       anint%=base;
284                       base/=85;
285                     }
286                 }
287               /* Go to the next line if on the right place: */
288               if(j%elem_for_newline==0) fprintf(fp, "\n");
289             }
290           fprintf(fp, "~>}\n");
291         }
292       ++i;
293     }
294 }
295 
296 
297 
298 
299 
300 static void
eps_write_image(gal_data_t * in,FILE * fp,int hex,int dontoptimize)301 eps_write_image(gal_data_t *in, FILE *fp, int hex, int dontoptimize)
302 {
303   int bpc=8;
304   uint8_t bitone;
305   gal_data_t *write;
306   size_t i, numbytes, *dsize=in->dsize;
307   size_t numch=gal_list_data_number(in);
308 
309   /* Set the number of bits per component. */
310   if( numch==1 && dontoptimize==0 && eps_is_binary(in, &bitone) )
311     {
312       bpc=1;
313       write=eps_convert_to_bitstream(in, &numbytes, bitone);
314     }
315   else
316     {
317       write=in;
318       numbytes=in->size;
319     }
320 
321   /* Write the basic meta data. */
322   switch(numch)
323     {
324     case 1: fprintf(fp, "/DeviceGray setcolorspace\n"); break;
325     case 3: fprintf(fp, "/DeviceRGB setcolorspace\n");  break;
326     case 4: fprintf(fp, "/DeviceCMYK setcolorspace\n"); break;
327     default:
328       error(EXIT_FAILURE, 0, "%s: a bug! The number of channels (%zu) is "
329             "not 1, 3 or 4. Please contact us so we can find the issue and "
330             "fix it", __func__, numch);
331     }
332   fprintf(fp, "<<\n");
333   fprintf(fp, "  /ImageType 1\n");
334   fprintf(fp, "  /Width %zu\n", dsize[1]);
335   fprintf(fp, "  /Height %zu\n", dsize[0]);
336   fprintf(fp, "  /ImageMatrix [ %zu 0 0 %zu 0 0 ]\n", dsize[1], dsize[0]);
337   fprintf(fp, "  /MultipleDataSources true\n");
338   fprintf(fp, "  /BitsPerComponent %d\n", bpc);
339   fprintf(fp, "  /Decode[");
340   for(i=0;i<numch;++i) {fprintf(fp, " 0 1");} fprintf(fp, " ]\n");
341   fprintf(fp, "  /Interpolate false\n");
342   fprintf(fp, "  /DataSource [\n");
343 
344   /* Based on the encoding, write the contents of the image. */
345   if(hex)
346     eps_write_hex(write, fp, numbytes);
347   else
348     eps_write_ascii85(write, fp, numbytes);
349 
350   /* Finish the file. */
351   fprintf(fp, "  ]\n");
352   fprintf(fp, ">>\n");
353   fprintf(fp, "image\n\n");
354 
355   /* Clean up. */
356   if(write!=in)
357     gal_list_data_free(write);
358 }
359 
360 
361 
362 
363 
364 void
gal_eps_to_pt(float widthincm,size_t * dsize,size_t * w_h_in_pt)365 gal_eps_to_pt(float widthincm, size_t *dsize, size_t *w_h_in_pt)
366 {
367   w_h_in_pt[0] = widthincm*72.0f/2.54f;
368   w_h_in_pt[1] = (float)( dsize[0] * w_h_in_pt[0] )/(float)(dsize[1]);
369 }
370 
371 
372 
373 
374 
375 void
gal_eps_write(gal_data_t * in,char * filename,float widthincm,uint32_t borderwidth,int hex,int dontoptimize,int forpdf)376 gal_eps_write(gal_data_t *in, char *filename, float widthincm,
377               uint32_t borderwidth, int hex, int dontoptimize, int forpdf)
378 {
379   FILE *fp;
380   float hbw;
381   time_t rawtime;
382   size_t numch=gal_list_data_number(in);
383   size_t w_h_in_pt[2], *dsize=in->dsize;
384 
385   /* Sanity checks. */
386   if(numch==2 || numch>4)
387     error(EXIT_FAILURE, 0, "%s: only 1, 3, and 4 color channels are "
388           "acceptable, input is a list of %zu data sets", __func__, numch);
389   if(in->type!=GAL_TYPE_UINT8)
390     error(EXIT_FAILURE, 0, "%s: input has a '%s' type, but JPEG images can "
391           "only have a 'uint8' type", __func__, gal_type_name(in->type, 1));
392 
393 
394   /* Read the time to write in the output. */
395   time(&rawtime);
396 
397 
398   /* Find the bounding box  */
399   hbw=(float)borderwidth/2.0;
400   gal_eps_to_pt(widthincm, dsize, w_h_in_pt);
401 
402 
403   /* Open the output file and write the top comments. */
404   errno=0;
405   fp=fopen(filename, "w");
406   if(fp==NULL)
407     error(EXIT_FAILURE, errno, "%s", filename);
408   fprintf(fp, "%%!PS-Adobe-3.0 EPSF-3.0\n");
409   fprintf(fp, "%%%%BoundingBox: 0 0 %zu %zu\n", w_h_in_pt[0]+2*borderwidth,
410           w_h_in_pt[1]+2*borderwidth);
411   fprintf(fp, "%%%%Creator: %s\n", PACKAGE_STRING);
412   fprintf(fp, "%%%%CreationDate: %s", ctime(&rawtime));
413   fprintf(fp, "%%%%LanuageLevel: 3\n");
414   fprintf(fp, "%%%%EndComments\n\n");
415   if(forpdf==0)
416     fprintf(fp, "gsave\n\n");
417 
418 
419   /* Commands to draw the border: */
420   if(borderwidth)
421     {
422       fprintf(fp, "%% Draw the border:\n");
423       fprintf(fp, "0 setgray\n");
424       fprintf(fp, "%d setlinewidth\n", borderwidth);
425       fprintf(fp, "%.1f %.1f moveto\n", hbw, hbw);
426       fprintf(fp, "0 %zu rlineto\n", w_h_in_pt[1]+borderwidth);
427       fprintf(fp, "%zu 0 rlineto\n", w_h_in_pt[0]+borderwidth);
428       fprintf(fp, "0 -%zu rlineto\n", w_h_in_pt[1]+borderwidth);
429       fprintf(fp, "closepath\n");
430       fprintf(fp, "stroke\n\n");
431     }
432 
433 
434   /* Write the image: */
435   fprintf(fp, "%% Draw the image:\n");
436   fprintf(fp, "%d %d translate\n", borderwidth, borderwidth);
437   fprintf(fp, "%zu %zu scale\n", w_h_in_pt[0], w_h_in_pt[1]);
438   eps_write_image(in, fp, hex, dontoptimize);
439 
440 
441   /* Ending of the EPS file: */
442   if(forpdf) fprintf(fp, "showpage\n");
443   else       fprintf(fp, "grestore\n");
444   fprintf(fp, "%%%%EOF");
445   fclose(fp);
446 }
447