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 #ifdef HAVE_CAIROPDF
80 
81 #include "cairo-pdf.h"
82 #include "wxterminal/gp_cairo.h"
83 #include "wxterminal/gp_cairo_helpers.h"
84 
85 /* cairo PNG code */
86 static int
write_png_image(unsigned m,unsigned n,coordval * image,t_imagecolor color_mode,const char * filename)87 write_png_image (unsigned m, unsigned n, coordval *image, t_imagecolor color_mode, const char *filename) {
88   cairo_surface_t *image_surface;
89   cairo_status_t cairo_stat;
90   unsigned int *image255;
91 
92   image255 = gp_cairo_helper_coordval_to_chars(image, m, n, color_mode);
93   image_surface = cairo_image_surface_create_for_data((unsigned char*) image255, CAIRO_FORMAT_ARGB32, m, n, 4*m);
94   cairo_stat = cairo_surface_write_to_png(image_surface, filename);
95   cairo_surface_destroy(image_surface);
96   if (cairo_stat != CAIRO_STATUS_SUCCESS) {
97     int_warn(NO_CARET, "write_png_image cairo: could not write image file '%s': %s.", filename, cairo_status_to_string(cairo_stat));
98     return 1;
99   } else
100     return 0;
101 }
102 
103 cairo_status_t
cairo_write_base64_callback(void * closure,const unsigned char * data,unsigned int length)104 cairo_write_base64_callback (void *closure, const unsigned char *data, unsigned int length) {
105   if (piecemeal_write_base64_data (data, length, closure) == 0)
106     return CAIRO_STATUS_SUCCESS;
107   else
108     return CAIRO_STATUS_WRITE_ERROR;
109 }
110 
111 static int
write_png_base64_image(unsigned m,unsigned n,coordval * image,t_imagecolor color_mode,FILE * out)112 write_png_base64_image (unsigned m, unsigned n, coordval *image, t_imagecolor color_mode, FILE *out) {
113   cairo_surface_t *image_surface;
114   cairo_status_t cairo_stat;
115   unsigned int *image255;
116   base64s *b64;
117   int retval = 0;
118 
119   b64 = gp_alloc(sizeof(base64s), "base64s");
120   if (b64 == NULL)
121     return 1;
122 
123   image255 = gp_cairo_helper_coordval_to_chars(image, m, n, color_mode);
124   image_surface = cairo_image_surface_create_for_data((unsigned char*) image255, CAIRO_FORMAT_ARGB32, m, n, 4*m);
125 
126   init_base64_state_data (b64, out);
127   cairo_stat = cairo_surface_write_to_png_stream(image_surface, cairo_write_base64_callback, b64);
128   cairo_surface_destroy(image_surface);
129   if (cairo_stat != CAIRO_STATUS_SUCCESS) {
130     int_warn(NO_CARET, "write_png_image cairo: could not write image file: %s.", cairo_status_to_string(cairo_stat));
131     retval = 1;
132   } else
133     retval = piecemeal_write_base64_data_finish (b64);
134 
135   free(b64);
136 
137   return retval;
138 }
139 
140 #else      /* libgd PNG code mainly taken from gd.trm */
141 #include <gd.h>
142 
143 gdImagePtr
construct_gd_image(unsigned M,unsigned N,coordval * image,t_imagecolor color_mode)144 construct_gd_image (unsigned M, unsigned N, coordval *image, t_imagecolor color_mode) {
145   int m, n, pixel;
146   gdImagePtr im;
147 
148   im = gdImageCreateTrueColor(M, N);
149   if (!im) {
150     int_warn(NO_CARET, "libgd: failed to create image structure");
151     return im;
152   }
153   /* gdImageColorAllocateAlpha(im, 255, 255, 255, 127); */
154   gdImageSaveAlpha(im, 1);
155   gdImageAlphaBlending(im, 0);
156 
157   if (color_mode == IC_RGBA) {
158     /* RGB + Alpha channel */
159     for (n=0; n<N; n++) {
160       for (m=0; m<M; m++) {
161         rgb_color rgb1;
162         rgb255_color rgb255;
163         int alpha;
164         rgb1.r = *image++;
165         rgb1.g = *image++;
166         rgb1.b = *image++;
167         alpha  = *image++;
168         alpha  = 127 - (alpha>>1);  /* input is [0:255] but gd wants [127:0] */
169         rgb255_from_rgb1( rgb1, &rgb255 );
170         pixel = gdImageColorResolveAlpha(im, (int)rgb255.r, (int)rgb255.g, (int)rgb255.b, alpha);
171         gdImageSetPixel( im, m, n, pixel );
172       }
173     }
174   } else if (color_mode == IC_RGB) {
175     /* TrueColor 24-bit color mode */
176     for (n=0; n<N; n++) {
177       for (m=0; m<M; m++) {
178         rgb_color rgb1;
179         rgb255_color rgb255;
180         rgb1.r = *image++;
181         rgb1.g = *image++;
182         rgb1.b = *image++;
183         rgb255_from_rgb1( rgb1, &rgb255 );
184         pixel = gdImageColorResolve(im, (int)rgb255.r, (int)rgb255.g, (int)rgb255.b );
185         gdImageSetPixel( im, m, n, pixel );
186       }
187     }
188   } else if (color_mode == IC_PALETTE) {
189     /* Palette color lookup from gray value */
190     for (n=0; n<N; n++) {
191       for (m=0; m<M; m++) {
192         rgb255_color rgb;
193         if (isnan(*image)) {
194           /* FIXME: tried to take the comment from gd.trm into account but needs a testcase */
195           pixel = gdImageColorResolveAlpha(im, 0, 0, 0, 127);
196           image++;
197         } else {
198           rgb255maxcolors_from_gray( *image++, &rgb );
199           pixel = gdImageColorResolve( im, (int)rgb.r, (int)rgb.g, (int)rgb.b );
200         }
201         gdImageSetPixel( im, m, n, pixel );
202       }
203     }
204   }
205 
206   return im;
207 }
208 
209 static int
write_png_image(unsigned M,unsigned N,coordval * image,t_imagecolor color_mode,const char * filename)210 write_png_image (unsigned M, unsigned N, coordval *image, t_imagecolor color_mode, const char *filename) {
211   gdImagePtr im;
212   FILE *out;
213 
214   im = construct_gd_image (M, N, image, color_mode);
215   if (!im)
216     return 1;
217 
218   out = fopen(filename, "wb");
219   if (!out) {
220     int_warn(NO_CARET, "write_png_image libgd: could not write image file '%s'", filename);
221     gdImageDestroy(im);
222     return 1;
223   }
224   gdImagePng(im, out);
225   fclose(out);
226   gdImageDestroy(im);
227 
228   return 0;
229 }
230 
231 static int
write_base64_data(const unsigned char * data,unsigned int length,FILE * out)232 write_base64_data (const unsigned char *data, unsigned int length, FILE *out) {
233   base64s *b64;
234   int retval = 0;
235 
236   b64 = gp_alloc(sizeof(base64s), "base64s");
237   if (b64 == NULL)
238     return 1;
239 
240   init_base64_state_data (b64, out);
241 
242   if (piecemeal_write_base64_data (data, length, b64) != 0)
243     retval = 1;
244   else
245     retval = piecemeal_write_base64_data_finish (b64);
246 
247   free(b64);
248 
249   return retval;
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 /* HAVE_CAIRO_PDF */
276 #endif /* WRITE_PNG_IMAGE */
277 #endif /* TERM_BODY */
278