1 /*
2  * Dump an image block as a png image.
3  * This routine is used by several terminal drivers so it gets a file by itself.
4  * May 2016 Daniel Sebald: Support routines to write image in Base64 encoding.
5  */
6 #ifdef  TERM_BODY
7 #ifndef WRITE_PNG_IMAGE
8 #define WRITE_PNG_IMAGE
9 
10 typedef struct base64state {
11   int           shift;
12   unsigned char bit6;
13   unsigned int  byte4;
14   FILE*         out;
15 } base64s;
16 
17 static const unsigned char base64_lut[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
18 
19 void
init_base64_state_data(base64s * b64,FILE * out)20 init_base64_state_data (base64s *b64, FILE *out) {
21   b64->shift = 6;
22   b64->bit6 = 0;
23   b64->byte4 = 0;
24   b64->out = out;
25 }
26 
27 static int
piecemeal_write_base64_data_finish(base64s * b64)28 piecemeal_write_base64_data_finish (base64s *b64) {
29 
30   if (b64->shift < 6) {
31     if (fputc(base64_lut[b64->bit6 & 0x3F], b64->out) == EOF)
32       return 1;
33     if (b64->byte4 == 0)
34       b64->byte4 = 3;
35     else
36       b64->byte4--;
37   }
38   while (b64->byte4 > 0) {
39     if (fputc('=', b64->out) == EOF)
40       return 1;
41     b64->byte4--;
42   }
43 
44   return 0;
45 }
46 
47 /* To use repeatedly, must initialize base64state first, then when done call finish.  See write_base64_data(). */
48 static int
piecemeal_write_base64_data(const unsigned char * data,unsigned int length,base64s * b64)49 piecemeal_write_base64_data (const unsigned char *data, unsigned int length, base64s *b64) {
50   unsigned int i_data = 0;
51 
52   while (1) {
53     unsigned int databyte = 0;
54     if (b64->shift > 0) {
55       if (i_data >= length)
56         break;
57       else {
58         databyte = data[i_data++];
59         b64->shift -= 8;
60         if (b64->shift >= 0)
61           b64->bit6 |= (databyte << b64->shift);
62         else
63           b64->bit6 |= (databyte >> -b64->shift);
64       }
65     }
66     if (fputc(base64_lut[b64->bit6 & 0x3F], b64->out) == EOF)
67       return 1;
68     b64->shift += 6;
69     b64->bit6 = (databyte << b64->shift);
70     if (b64->byte4 == 0)
71       b64->byte4 = 3;
72     else
73       b64->byte4--;
74   }
75 
76   return 0;
77 }
78 
79 static int
write_base64_data(const unsigned char * data,unsigned int length,FILE * out)80 write_base64_data (const unsigned char *data, unsigned int length, FILE *out) {
81   base64s *b64;
82   int retval = 0;
83 
84   b64 = gp_alloc(sizeof(base64s), "base64s");
85   if (b64 == NULL)
86     return 1;
87 
88   init_base64_state_data (b64, out);
89 
90   if (piecemeal_write_base64_data (data, length, b64) != 0)
91     retval = 1;
92   else
93     retval = piecemeal_write_base64_data_finish (b64);
94 
95   free(b64);
96 
97   return retval;
98 }
99 
100 #ifdef HAVE_CAIROPDF
101 
102 #include "cairo-pdf.h"
103 #include "wxterminal/gp_cairo.h"
104 #include "wxterminal/gp_cairo_helpers.h"
105 
106 /* cairo PNG code */
107 static int
write_png_image(unsigned m,unsigned n,coordval * image,t_imagecolor color_mode,const char * filename)108 write_png_image (unsigned m, unsigned n, coordval *image, t_imagecolor color_mode, const char *filename) {
109   cairo_surface_t *image_surface;
110   cairo_status_t cairo_stat;
111   unsigned int *image255;
112 
113   image255 = gp_cairo_helper_coordval_to_chars(image, m, n, color_mode);
114   image_surface = cairo_image_surface_create_for_data((unsigned char*) image255, CAIRO_FORMAT_ARGB32, m, n, 4*m);
115   cairo_stat = cairo_surface_write_to_png(image_surface, filename);
116   cairo_surface_destroy(image_surface);
117   if (cairo_stat != CAIRO_STATUS_SUCCESS) {
118     int_warn(NO_CARET, "write_png_image cairo: could not write image file '%s': %s.", filename, cairo_status_to_string(cairo_stat));
119     return 1;
120   } else
121     return 0;
122 }
123 
124 cairo_status_t
cairo_write_base64_callback(void * closure,const unsigned char * data,unsigned int length)125 cairo_write_base64_callback (void *closure, const unsigned char *data, unsigned int length) {
126   if (piecemeal_write_base64_data (data, length, closure) == 0)
127     return CAIRO_STATUS_SUCCESS;
128   else
129     return CAIRO_STATUS_WRITE_ERROR;
130 }
131 
132 static int
write_png_base64_image(unsigned m,unsigned n,coordval * image,t_imagecolor color_mode,FILE * out)133 write_png_base64_image (unsigned m, unsigned n, coordval *image, t_imagecolor color_mode, FILE *out) {
134   cairo_surface_t *image_surface;
135   cairo_status_t cairo_stat;
136   unsigned int *image255;
137   base64s *b64;
138   int retval = 0;
139 
140   b64 = gp_alloc(sizeof(base64s), "base64s");
141   if (b64 == NULL)
142     return 1;
143 
144   image255 = gp_cairo_helper_coordval_to_chars(image, m, n, color_mode);
145   image_surface = cairo_image_surface_create_for_data((unsigned char*) image255, CAIRO_FORMAT_ARGB32, m, n, 4*m);
146 
147   init_base64_state_data (b64, out);
148   cairo_stat = cairo_surface_write_to_png_stream(image_surface, cairo_write_base64_callback, b64);
149   cairo_surface_destroy(image_surface);
150   if (cairo_stat != CAIRO_STATUS_SUCCESS) {
151     int_warn(NO_CARET, "write_png_image cairo: could not write image file: %s.", cairo_status_to_string(cairo_stat));
152     retval = 1;
153   } else
154     retval = piecemeal_write_base64_data_finish (b64);
155 
156   free(b64);
157 
158   return retval;
159 }
160 
161 #else      /* libgd PNG code mainly taken from gd.trm */
162 #include <gd.h>
163 
164 gdImagePtr
construct_gd_image(unsigned M,unsigned N,coordval * image,t_imagecolor color_mode)165 construct_gd_image (unsigned M, unsigned N, coordval *image, t_imagecolor color_mode) {
166   int m, n, pixel;
167   gdImagePtr im;
168 
169   im = gdImageCreateTrueColor(M, N);
170   if (!im) {
171     int_warn(NO_CARET, "libgd: failed to create image structure");
172     return im;
173   }
174   /* gdImageColorAllocateAlpha(im, 255, 255, 255, 127); */
175   gdImageSaveAlpha(im, 1);
176   gdImageAlphaBlending(im, 0);
177 
178   if (color_mode == IC_RGBA) {
179     /* RGB + Alpha channel */
180     for (n=0; n<N; n++) {
181       for (m=0; m<M; m++) {
182         rgb_color rgb1;
183         rgb255_color rgb255;
184         int alpha;
185         rgb1.r = *image++;
186         rgb1.g = *image++;
187         rgb1.b = *image++;
188         alpha  = *image++;
189         alpha  = 127 - (alpha>>1);  /* input is [0:255] but gd wants [127:0] */
190         rgb255_from_rgb1( rgb1, &rgb255 );
191         pixel = gdImageColorResolveAlpha(im, (int)rgb255.r, (int)rgb255.g, (int)rgb255.b, alpha);
192         gdImageSetPixel( im, m, n, pixel );
193       }
194     }
195   } else if (color_mode == IC_RGB) {
196     /* TrueColor 24-bit color mode */
197     for (n=0; n<N; n++) {
198       for (m=0; m<M; m++) {
199         rgb_color rgb1;
200         rgb255_color rgb255;
201         rgb1.r = *image++;
202         rgb1.g = *image++;
203         rgb1.b = *image++;
204         rgb255_from_rgb1( rgb1, &rgb255 );
205         pixel = gdImageColorResolve(im, (int)rgb255.r, (int)rgb255.g, (int)rgb255.b );
206         gdImageSetPixel( im, m, n, pixel );
207       }
208     }
209   } else if (color_mode == IC_PALETTE) {
210     /* Palette color lookup from gray value */
211     for (n=0; n<N; n++) {
212       for (m=0; m<M; m++) {
213         rgb255_color rgb;
214         if (isnan(*image)) {
215           /* FIXME: tried to take the comment from gd.trm into account but needs a testcase */
216           pixel = gdImageColorResolveAlpha(im, 0, 0, 0, 127);
217           image++;
218         } else {
219           rgb255maxcolors_from_gray( *image++, &rgb );
220           pixel = gdImageColorResolve( im, (int)rgb.r, (int)rgb.g, (int)rgb.b );
221         }
222         gdImageSetPixel( im, m, n, pixel );
223       }
224     }
225   }
226 
227   return im;
228 }
229 
230 static int
write_png_image(unsigned M,unsigned N,coordval * image,t_imagecolor color_mode,const char * filename)231 write_png_image (unsigned M, unsigned N, coordval *image, t_imagecolor color_mode, const char *filename) {
232   gdImagePtr im;
233   FILE *out;
234 
235   im = construct_gd_image (M, N, image, color_mode);
236   if (!im)
237     return 1;
238 
239   out = fopen(filename, "wb");
240   if (!out) {
241     int_warn(NO_CARET, "write_png_image libgd: could not write image file '%s'", filename);
242     gdImageDestroy(im);
243     return 1;
244   }
245   gdImagePng(im, out);
246   fclose(out);
247   gdImageDestroy(im);
248 
249   return 0;
250 }
251 
252 static int
write_png_base64_image(unsigned M,unsigned N,coordval * image,t_imagecolor color_mode,FILE * out)253 write_png_base64_image (unsigned M, unsigned N, coordval *image, t_imagecolor color_mode, FILE *out) {
254   gdImagePtr im;
255   void *pngdata;
256   int pngsize;
257   int retval = 0;
258 
259   im = construct_gd_image (M, N, image, color_mode);
260   if (!im)
261     return 1;
262 
263   if ((pngdata = gdImagePngPtr(im, &pngsize)) == NULL) {
264     gdImageDestroy(im);
265     return 1;
266   }
267 
268   retval = write_base64_data (pngdata, pngsize, out);
269 
270   gdFree(pngdata);
271   gdImageDestroy(im);
272 
273   return retval;
274 }
275 #endif
276 #endif
277 #endif
278