beta-0.89.2
[luatex.git] / source / texk / web2c / mplibdir / pngout.w
blob7a4a0b44006a5fe952266947805d87c9483d211a
1 % Copyright 2012 Taco Hoekwater.
3 % This program is free software: you can redistribute it and/or modify
4 % it under the terms of the GNU Lesser General Public License as published by
5 % the Free Software Foundation, either version 3 of the License, or
6 % (at your option) any later version.
8 % This program is distributed in the hope that it will be useful,
9 % but WITHOUT ANY WARRANTY; without even the implied warranty of
10 % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 % GNU Lesser General Public License for more details.
13 % You should have received a copy of the GNU Lesser General Public License
14 % along with this program. If not, see <http://www.gnu.org/licenses/>.
16 % TeX is a trademark of the American Mathematical Society.
17 % METAFONT is a trademark of Addison-Wesley Publishing Company.
18 % PostScript is a trademark of Adobe Systems Incorporated.
20 % Here is TeX material that gets inserted after \input webmac
22 \font\tenlogo=logo10 % font used for the METAFONT logo
23 \font\logos=logosl10
24 \def\MF{{\tenlogo META}\-{\tenlogo FONT}}
25 \def\MP{{\tenlogo META}\-{\tenlogo POST}}
26 \def\<#1>{$\langle#1\rangle$}
27 \def\section{\mathhexbox278}
28 \def\[#1]{} % from pascal web
29 \def\(#1){} % this is used to make section names sort themselves better
30 \def\9#1{} % this is used for sort keys in the index via @@:sort key}{entry@@>
32 \def\title{MetaPost PNG output}
33 \def\topofcontents{\hsize 5.5in
34 \vglue -30pt plus 1fil minus 1.5in
35 \def\?##1]{\hbox to 1in{\hfil##1.\ }}
37 \def\botofcontents{\vskip 0pt plus 1fil minus 1.5in}
38 \pdfoutput=1
39 \pageno=3
42 @d zero_t ((math_data *)mp->math)->zero_t
43 @d number_zero(A) (((math_data *)(mp->math))->equal)(A,zero_t)
44 @d number_greater(A,B) (((math_data *)(mp->math))->greater)(A,B)
45 @d number_positive(A) number_greater(A, zero_t)
46 @d number_to_scaled(A) (((math_data *)(mp->math))->to_scaled)(A)
47 @d round_unscaled(A) (((math_data *)(mp->math))->round_unscaled)(A)
48 @d true 1
49 @d false 0
50 @d null_font 0
51 @d null 0
52 @d unity 1.0
53 @d incr(A) (A)=(A)+1 /* increase a variable by unity */
54 @d decr(A) (A)=(A)-1 /* decrease a variable by unity */
55 @d negate(A) (A)=-(A) /* change the sign of a variable */
58 #include <w2c/config.h>
59 #include <stdio.h>
60 #include <stdlib.h>
61 #include <string.h>
62 #include <math.h>
63 #include "mplib.h"
64 #include "mplibps.h" /* external header */
65 #include "mplibpng.h" /* external header */
66 #include "mpmp.h" /* internal header */
67 #include "mppsout.h" /* internal header */
68 #include "mppngout.h" /* internal header */
69 #include "mpmath.h" /* internal header */
71 @<Types in the outer block@>
72 @<Declarations@>
74 @ There is a small bit of code from the backend that bleads through
75 to the frontend because I do not know how to set up the includes
76 properly. That is |typedef struct pngout_data_struct * pngout_data|.
78 @ @(mppngout.h@>=
79 #ifndef MPPNGOUT_H
80 #define MPPNGOUT_H 1
81 #include "cairo.h"
82 #define PNG_SKIP_SETJMP_CHECK 1
83 #include "png.h"
84 #include "mplib.h"
85 #include "mpmp.h"
86 #include "mplibps.h"
87 typedef struct pngout_data_struct {
88 @<Globals@>
89 } pngout_data_struct ;
90 @<Exported function headers@>
91 #endif
93 @ @<Exported function headers@>=
94 void mp_png_backend_initialize (MP mp) ;
95 void mp_png_backend_free (MP mp) ;
97 @ @c
98 void mp_png_backend_initialize (MP mp) {
99 mp->png = mp_xmalloc(mp,1,sizeof(pngout_data_struct));
100 memset(mp->png,0,sizeof(pngout_data_struct));
102 void mp_png_backend_free (MP mp) {
103 mp_xfree(mp->png);
104 mp->png = NULL;
107 @ Writing to PNG files
109 @<Globals@>=
110 cairo_surface_t *surface;
111 cairo_t *cr;
113 @ We often need to print a pair of coordinates, and these need to
114 offset so that all coordinates are positive.
116 @<Globals@>=
117 integer dx;
118 integer dy;
121 @<Declarations@>=
122 static void mp_png_start(MP mp,mp_edge_object *hh, double hppp, double vppp, int colormodel, int antialias);
126 void mp_png_start(MP mp,mp_edge_object *hh, double hppp, double vppp, int colormodel, int antialias) {
127 double w, h;
128 if ( hh->minx>hh->maxx) {
129 w = 1;
130 h = 1;
131 mp->png->dx = 0;
132 mp->png->dy = 0;
133 } else {
134 w = (ceil(hh->maxx) - floor(hh->minx)) / hppp;
135 h = (ceil(hh->maxy) - floor(hh->miny)) / vppp;
136 mp->png->dx = -floor(hh->minx);
137 mp->png->dy = -floor(hh->miny);
139 mp->png->surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h);
140 mp->png->cr = cairo_create (mp->png->surface);
141 /* if there is no alpha channel, a white background is needed */
142 if (colormodel == PNG_COLOR_TYPE_RGB ||
143 colormodel == PNG_COLOR_TYPE_GRAY) {
144 cairo_save(mp->png->cr);
145 cairo_set_source_rgb(mp->png->cr, 1.0, 1.0, 1.0);
146 cairo_rectangle(mp->png->cr, 0, 0, w, h);
147 cairo_fill(mp->png->cr);
148 cairo_restore(mp->png->cr);
150 cairo_scale(mp->png->cr, 1/hppp, -1/vppp);
151 cairo_translate(mp->png->cr, 0, -(h*vppp));
152 cairo_translate(mp->png->cr, mp->png->dx, mp->png->dy);
153 cairo_set_antialias(mp->png->cr, antialias);
156 @ Outputting a color specification.
158 @d set_color_objects(pq)
159 object_color_model = pq->color_model;
160 object_color_a = pq->color.a_val;
161 object_color_b = pq->color.b_val;
162 object_color_c = pq->color.c_val;
163 object_color_d = pq->color.d_val;
166 static void mp_png_color_out (MP mp, mp_graphic_object *p) {
167 int object_color_model;
168 double object_color_a, object_color_b, object_color_c, object_color_d ;
169 if (gr_type(p) == mp_fill_code) {
170 mp_fill_object *pq = (mp_fill_object *)p;
171 set_color_objects(pq);
172 } else if (gr_type(p) == mp_stroked_code) {
173 mp_stroked_object *pq = (mp_stroked_object *)p;
174 set_color_objects(pq);
175 } else {
176 mp_text_object *pq = (mp_text_object *)p;
177 set_color_objects(pq);
179 if ( object_color_model==mp_no_model ) {
180 cairo_set_source_rgb (mp->png->cr, 0, 0, 0);
181 } else {
182 if ( object_color_model==mp_grey_model ) {
183 object_color_b = object_color_a;
184 object_color_c = object_color_a;
185 } else if ( object_color_model==mp_cmyk_model) {
186 int c,m,y,k;
187 c = object_color_a;
188 m = object_color_b;
189 y = object_color_c;
190 k = object_color_d;
191 object_color_a = unity - (c+k>unity ? unity : c+k);
192 object_color_b = unity - (m+k>unity ? unity : m+k);
193 object_color_c = unity - (y+k>unity ? unity : y+k);
195 cairo_set_source_rgb (mp->png->cr, object_color_a, object_color_b, object_color_c);
199 @ @<Declarations@>=
200 static void mp_png_color_out (MP mp, mp_graphic_object *p);
202 @ This is the information that comes from a pen
204 @<Types...@>=
205 typedef struct mp_pen_info {
206 double tx, ty;
207 double sx, rx, ry, sy;
208 double ww;
209 } mp_pen_info;
212 @ (Re)discover the characteristics of an elliptical pen
214 @<Declarations@>=
215 mp_pen_info *mp_png_pen_info(MP mp, mp_gr_knot pp, mp_gr_knot p);
217 @ The next two constants come from the original web source.
218 Together with the two helper functions, they will tell whether
219 the |x| or the |y| direction of the path is the most important
221 @d aspect_bound (10/65536.0)
222 @d aspect_default 1
225 static double coord_range_x (mp_gr_knot h, double dz) {
226 double z;
227 double zlo = 0, zhi = 0;
228 mp_gr_knot f = h;
229 while (h != NULL) {
230 z = gr_x_coord(h);
231 if (z < zlo) zlo = z; else if (z > zhi) zhi = z;
232 z = gr_right_x(h);
233 if (z < zlo) zlo = z; else if (z > zhi) zhi = z;
234 z = gr_left_x(h);
235 if (z < zlo) zlo = z; else if (z > zhi) zhi = z;
236 h = gr_next_knot(h);
237 if (h==f)
238 break;
240 return (zhi - zlo <= dz ? aspect_bound : aspect_default);
242 static double coord_range_y (mp_gr_knot h, double dz) {
243 double z;
244 double zlo = 0, zhi = 0;
245 mp_gr_knot f = h;
246 while (h != NULL) {
247 z = gr_y_coord(h);
248 if (z < zlo) zlo = z; else if (z > zhi) zhi = z;
249 z = gr_right_y(h);
250 if (z < zlo) zlo = z; else if (z > zhi) zhi = z;
251 z = gr_left_y(h);
252 if (z < zlo) zlo = z; else if (z > zhi) zhi = z;
253 h = gr_next_knot(h);
254 if (h==f)
255 break;
257 return (zhi - zlo <= dz ? aspect_bound : aspect_default);
262 mp_pen_info *mp_png_pen_info(MP mp, mp_gr_knot pp, mp_gr_knot p) {
263 double wx, wy; /* temporary pen widths, in either direction */
264 struct mp_pen_info *pen; /* return structure */
265 if (p == NULL)
266 return NULL;
267 pen = mp_xmalloc(mp, 1, sizeof(mp_pen_info));
268 pen->rx = unity;
269 pen->ry = unity;
270 pen->ww = unity;
271 if ((gr_right_x(p) == gr_x_coord(p))
273 (gr_left_y(p) == gr_y_coord(p))) {
274 wx = fabs(gr_left_x(p) - gr_x_coord(p));
275 wy = fabs(gr_right_y(p) - gr_y_coord(p));
276 } else {
277 double arg1, arg2;
278 arg1 = gr_left_x(p)-gr_x_coord(p);
279 arg2 = gr_right_x(p)-gr_x_coord(p);
280 wx = sqrt(arg1*arg1 + arg2*arg2);
281 arg1 = gr_left_y(p)-gr_y_coord(p);
282 arg2 = gr_right_y(p)-gr_y_coord(p);
283 wy = sqrt(arg1*arg1 + arg2*arg2);
285 if ((wy/coord_range_x(pp, wx)) >= (wx/coord_range_y(pp, wy)))
286 pen->ww = wy;
287 else
288 pen->ww = wx;
289 pen->tx = gr_x_coord(p);
290 pen->ty = gr_y_coord(p);
291 pen->sx = gr_left_x(p) - pen->tx;
292 pen->rx = gr_left_y(p) - pen->ty;
293 pen->ry = gr_right_x(p) - pen->tx;
294 pen->sy = gr_right_y(p) - pen->ty;
295 if (pen->ww != unity) {
296 if (pen->ww == 0) {
297 pen->sx = unity;
298 pen->sy = unity;
299 } else {
300 pen->rx = pen->rx / pen->ww;
301 pen->ry = pen->ry / pen->ww;
302 pen->sx = pen->sx / pen->ww;
303 pen->sy = pen->sy / pen->ww;
306 return pen;
309 @ Two types of straight lines come up often in \MP\ paths:
310 cubics with zero initial and final velocity as created by |make_path| or
311 |make_envelope|, and cubics with control points uniformly spaced on a line
312 as created by |make_choices|.
314 @<Declarations@>=
315 static boolean mp_is_curved(mp_gr_knot p, mp_gr_knot q) ;
319 @d bend_tolerance (131/65536.0) /* allow rounding error of $2\cdot10^{-3}$ */
322 boolean mp_is_curved(mp_gr_knot p, mp_gr_knot q) {
323 double d; /* a temporary value */
324 if ( gr_right_x(p)==gr_x_coord(p) )
325 if ( gr_right_y(p)==gr_y_coord(p) )
326 if ( gr_left_x(q)==gr_x_coord(q) )
327 if ( gr_left_y(q)==gr_y_coord(q) )
328 return false;
329 d=gr_left_x(q)-gr_right_x(p);
330 if ( fabs(gr_right_x(p)-gr_x_coord(p)-d)<=bend_tolerance )
331 if ( fabs(gr_x_coord(q)-gr_left_x(q)-d)<=bend_tolerance ) {
332 d=gr_left_y(q)-gr_right_y(p);
333 if ( fabs(gr_right_y(p)-gr_y_coord(p)-d)<=bend_tolerance )
334 if ( fabs(gr_y_coord(q)-gr_left_y(q)-d)<=bend_tolerance )
335 return false;
337 return true;
341 @ Cairo does not want to draw a path that consists of only a moveto,
342 so make sure there is some kind of line even for single-pair paths.
344 static void mp_png_path_out (MP mp, mp_gr_knot h) {
345 mp_gr_knot p, q; /* for scanning the path */
346 int steps = 0;
347 cairo_move_to (mp->png->cr, gr_x_coord(h),gr_y_coord(h));
348 p=h;
349 do {
350 if ( gr_right_type(p)==mp_endpoint ) {
351 if (steps==0) {
352 cairo_line_to (mp->png->cr, gr_x_coord(p),gr_y_coord(p));
354 return;
356 q=gr_next_knot(p);
357 if (mp_is_curved(p, q)){
358 cairo_curve_to (mp->png->cr, gr_right_x(p),gr_right_y(p),
359 gr_left_x(q),gr_left_y(q),
360 gr_x_coord(q),gr_y_coord(q));
361 } else {
362 cairo_line_to (mp->png->cr, gr_x_coord(q),gr_y_coord(q));
364 p=q;
365 steps++;
366 } while (p!=h);
367 if ((gr_x_coord(p) == gr_x_coord(h)) && (gr_y_coord(p) == gr_y_coord(h)) &&
368 gr_right_type(p)!=mp_endpoint) {
369 cairo_close_path(mp->png->cr);
373 @ Now for outputting the actual graphic objects.
375 @<Declarations@>=
376 static double mp_png_choose_scale (MP mp, mp_graphic_object *p) ;
378 @ @c double mp_png_choose_scale (MP mp, mp_graphic_object *p) {
379 /* |p| should point to a text node */
380 double a,b,c,d,ad,bc; /* temporary values */
381 double ret1, ret2;
382 a=gr_txx_val(p);
383 b=gr_txy_val(p);
384 c=gr_tyx_val(p);
385 d=gr_tyy_val(p);
386 if ( a<0 ) negate(a);
387 if ( b<0 ) negate(b);
388 if ( c<0 ) negate(c);
389 if ( d<0 ) negate(d);
390 ad=(a-d)/2.0;
391 bc=(b-c)/2.0;
392 ret1 = sqrt((d+ad)*(d+ad) + ad*ad);
393 ret2 = sqrt((c+bc)*(c+bc) + bc*bc);
394 return sqrt(ret1*ret1 + ret2*ret2);
398 @d xrealloc(P,A,B) mp_xrealloc(mp,P,(size_t)A,B)
399 @d XREALLOC(a,b,c) a = xrealloc(a,(b+1),sizeof(c));
402 void mp_reallocate_psfonts (MP mp, int l) {
403 if (l>=mp->png->font_max) {
404 int f;
405 mp->png->last_fnum = mp->png->font_max;
406 XREALLOC (mp->png->psfonts, l, mp_edge_object *);
407 for (f = (mp->png->last_fnum + 1); f <= l; f++) {
408 mp->png->psfonts[f] = NULL;
410 mp->png->font_max = l;
414 @ @<Declarations@>=
415 void mp_reallocate_psfonts (MP mp, int l);
417 @ @<Globals@>=
418 mp_edge_object **psfonts;
419 int font_max;
420 int last_fnum;
422 @ @<Declarations@>=
423 static void mp_png_text_out (MP mp, mp_text_object *p) ;
425 @ @c
426 void mp_png_text_out (MP mp, mp_text_object *p) {
427 double ds; /* design size and scale factor for a text node */
428 unsigned char *s = (unsigned char *)gr_text_p(p);
429 size_t l = gr_text_l(p); /* string length */
430 boolean transformed= (gr_txx_val(p)!=unity)||(gr_tyy_val(p)!=unity)||
431 (gr_txy_val(p)!=0)||(gr_tyx_val(p)!=0);
432 int fn = gr_font_n(p);
433 mp_ps_font *f;
434 double scf;
435 ds =(mp->font_dsize[fn]+8) / (16*65536.0);
436 scf = mp_png_choose_scale (mp,(mp_graphic_object *)p);
437 cairo_save(mp->png->cr);
438 if ( transformed ) {
439 cairo_matrix_t matrix = {0,0,0,0,0,0};
440 cairo_matrix_init(&matrix, (gr_txx_val(p)/scf), (gr_tyx_val(p)/scf),
441 (gr_txy_val(p)/scf), (gr_tyy_val(p)/scf),
442 gr_tx_val(p),gr_ty_val(p));
443 cairo_transform (mp->png->cr, &matrix);
444 cairo_move_to(mp->png->cr, 0, 0);
445 } else {
446 cairo_translate (mp->png->cr,gr_tx_val(p),gr_ty_val(p));
448 cairo_scale(mp->png->cr, ((ds/1000.0)*scf), ((ds/1000.0)*scf));
449 mp_png_color_out(mp,(mp_graphic_object *)p);
450 while (l-->0) {
451 mp_edge_object *ch;
452 int k = (int)*s++;
453 double wd = 0.0; /* this is in PS design units */
454 mp_reallocate_psfonts(mp, ((fn+1) * 256));
455 ch = mp->png->psfonts[(fn*256)+k];
456 if (ch == NULL) {
457 f = mp_ps_font_parse(mp, fn);
458 if (f == NULL) return;
459 ch = mp_ps_font_charstring(mp,f,k);
460 mp->png->psfonts[(fn*256)+k] = ch;
462 if (ch != NULL) {
463 mp_graphic_object *pp = ch->body;
464 while (pp!=NULL) {
465 mp_png_path_out(mp, gr_path_p((mp_fill_object *)pp));
466 pp=pp->next;
468 cairo_fill(mp->png->cr);
470 wd = mp_get_char_dimension (mp, mp->font_name[fn], k, 'w');
471 /* wd/100 is the size in PS point , ie wd =100*real_wd */
472 /* but _without_ considering scaling. */
473 /* We have a scale factor of */
474 /* (ds/1000.0)*scf */
475 /* so to match the scale wd should be */
476 /* 1000*real_wd *scf /(ds *scf) */
477 /* i.e. 10*wd/ds */
478 wd *= 10.0/ds;
479 cairo_translate(mp->png->cr,wd,0);
481 cairo_restore(mp->png->cr);
484 @ When stroking a path with an elliptical pen, it is necessary to transform
485 the coordinate system so that a unit circular pen will have the desired shape.
486 To keep this transformation local, we enclose it in a $$\&{<g>}\ldots\&{</g>}$$
487 block. Any translation component must be applied to the path being stroked
488 while the rest of the transformation must apply only to the pen.
489 If |fill_also=true|, the path is to be filled as well as stroked so we must
490 insert commands to do this after giving the path.
492 @<Declarations@>=
493 static void mp_png_stroke_out (MP mp, mp_graphic_object *h,
494 mp_pen_info *pen, boolean fill_also) ;
497 @ @c
498 void mp_png_stroke_out (MP mp, mp_graphic_object *h,
499 mp_pen_info *pen, boolean fill_also) {
500 boolean transformed = false;
501 if (fill_also) {
502 cairo_save(mp->png->cr);
503 mp_png_path_out(mp, gr_path_p((mp_stroked_object *)h));
504 cairo_close_path (mp->png->cr);
505 cairo_fill (mp->png->cr);
506 cairo_restore(mp->png->cr);
508 cairo_save(mp->png->cr);
509 if (pen != NULL) {
510 transformed = true;
511 if ((pen->sx==unity) &&
512 (pen->rx==0) &&
513 (pen->ry==0) &&
514 (pen->sy==unity) &&
515 (pen->tx==0) &&
516 (pen->ty==0)) {
517 transformed = false;
520 if (pen != NULL) {
521 cairo_set_line_width(mp->png->cr, pen->ww);
522 } else {
523 cairo_set_line_width(mp->png->cr, 0);
525 if (gr_lcap_val(h)!=0) {
526 switch (gr_lcap_val(h)) {
527 case 1: cairo_set_line_cap(mp->png->cr,CAIRO_LINE_CAP_ROUND); break;
528 case 2: cairo_set_line_cap(mp->png->cr,CAIRO_LINE_CAP_SQUARE); break;
529 default:cairo_set_line_cap(mp->png->cr,CAIRO_LINE_CAP_BUTT); break;
532 if (gr_type(h)!=mp_fill_code) {
533 mp_dash_object *hh= gr_dash_p(h);
534 if (hh != NULL && hh->array != NULL) {
535 int i;
536 for (i=0; *(hh->array+i) != -1;i++)
538 cairo_set_dash (mp->png->cr, hh->array, i, hh->offset);
541 if (gr_ljoin_val((mp_stroked_object *)h)!=0) {
542 switch (gr_ljoin_val((mp_stroked_object *)h)) {
543 case 1: cairo_set_line_join(mp->png->cr,CAIRO_LINE_JOIN_ROUND); break;
544 case 2: cairo_set_line_join(mp->png->cr,CAIRO_LINE_JOIN_BEVEL); break;
545 default: cairo_set_line_join(mp->png->cr,CAIRO_LINE_JOIN_MITER); break;
548 cairo_set_miter_limit(mp->png->cr,gr_miterlim_val((mp_stroked_object *)h));
549 if (transformed) {
550 cairo_matrix_t matrix = {0,0,0,0,0,0};
551 cairo_matrix_init(&matrix, pen->sx, pen->rx, pen->ry, pen->sy, pen->tx, pen->ty);
552 cairo_transform (mp->png->cr, &matrix);
554 mp_png_path_out(mp, gr_path_p((mp_stroked_object *)h));
555 cairo_stroke (mp->png->cr);
556 cairo_restore(mp->png->cr);
559 @ Here is a simple routine that just fills a cycle.
561 @<Declarations@>=
562 static void mp_png_fill_out (MP mp, mp_gr_knot p, mp_graphic_object *h);
564 @ @c
565 void mp_png_fill_out (MP mp, mp_gr_knot p, mp_graphic_object *h) {
566 cairo_save(mp->png->cr);
567 mp_png_path_out(mp, p);
568 cairo_close_path (mp->png->cr);
569 cairo_fill (mp->png->cr);
570 cairo_restore(mp->png->cr);
573 @ The main output function
575 @d pen_is_elliptical(A) ((A)==gr_next_knot((A)))
576 @d gr_has_color(A) (gr_type((A))<mp_start_clip_code)
578 @<Exported function ...@>=
579 int mp_png_gr_ship_out (mp_edge_object *hh, const char *options, int standalone) ;
581 @ This is a structure to ship data from cairo to our png writer. |width| and
582 |height| could have been stored in our private |mp| instance, but this is just
583 as easy.
585 @<Types...@>=
586 typedef struct {
587 unsigned char * data;
588 int height;
589 int width;
590 } bitmap_t;
592 @ This is a small structure that is needed so that the png writer callbacks
593 can actually access the |mp| object instance.
595 @<Types...@>=
596 typedef struct {
597 void *fp;
598 MP mp;
599 } mp_png_io;
601 @ Output a png chunk: the libpng callbacks
603 static void mp_write_png_data(png_structp png_ptr, png_bytep data, png_size_t length)
605 mp_png_io *ioptr = (mp_png_io *)png_get_io_ptr(png_ptr);
606 MP mp = ioptr->mp;
607 (mp->write_binary_file)(mp,ioptr->fp, (void *)data, (size_t)length);
609 static void mp_write_png_flush(png_structp png_ptr)
611 /* nothing to do */
615 @ Write |bitmap| to a PNG file specified by |path|; returns 0 on
616 success, non-zero on error. The original of this function was
617 borrowed from an internet post, and extended as needed.
619 @<Declarations@>=
620 int mp_png_save_to_file (MP mp, const bitmap_t * bitmap, const char *path, int colormodel);
622 @ @c
623 int mp_png_save_to_file (MP mp, const bitmap_t * bitmap, const char *path, int colormodel)
625 mp_png_io io;
626 png_structp png_ptr = NULL;
627 png_infop info_ptr = NULL;
628 size_t y;
629 png_byte ** row_pointers = NULL;
630 int status = -1;
631 int depth = 8;
632 int dpi = 72;
633 int ppm_x;
634 int ppm_y; /* pixels per metre */
636 io.mp = mp;
637 io.fp = (mp->open_file)(mp, path, "wb", mp_filetype_bitmap);
638 if (!io.fp) {
639 goto fopen_failed;
642 png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
643 if (png_ptr == NULL) {
644 goto png_create_write_struct_failed;
647 info_ptr = png_create_info_struct (png_ptr);
648 if (info_ptr == NULL) {
649 goto png_create_info_struct_failed;
652 /* Set up error handling. */
654 if (setjmp (png_jmpbuf (png_ptr))) {
655 goto png_failure;
658 /* Set image attributes. */
660 png_set_IHDR (png_ptr,
661 info_ptr,
662 bitmap->width,
663 bitmap->height,
664 depth,
665 colormodel,
666 PNG_INTERLACE_NONE,
667 PNG_COMPRESSION_TYPE_DEFAULT,
668 PNG_FILTER_TYPE_DEFAULT);
670 /* Compression level |3| appears the best tradeoff between
671 disk size and compression speed */
672 png_set_compression_level(png_ptr, 3);
673 png_set_filter(png_ptr,0,PNG_FILTER_NONE);
674 /* setup some information */
675 if (1) {
676 png_text text[2];
677 char *a, *b, *c, *d; /* to get rid of a typecast warning */
678 a = xstrdup("Title");
679 b = xstrdup(path);
680 c = xstrdup("Software");
681 d = xstrdup("Generated by Metapost version " metapost_version);
683 text[0].compression = PNG_TEXT_COMPRESSION_NONE;
684 text[0].key = a;
685 text[0].text = b;
686 text[1].compression = PNG_TEXT_COMPRESSION_NONE;
687 text[1].key = c;
688 text[1].text = d;
689 png_set_text(png_ptr, info_ptr, text, 2);
691 free(a);
692 free(b);
693 free(c);
694 free(d);
697 /* The original plan was to add |hppp| and |vppp| values in here,
698 but that seems to have negative effects on various bits of
699 software. Better keep the DPI at 72 */
700 ppm_x = dpi / 0.0254;
701 ppm_y = dpi / 0.0254;
702 png_set_pHYs(png_ptr, info_ptr, ppm_x, ppm_y, PNG_RESOLUTION_METER);
704 /* Initialize rows of PNG. */
706 row_pointers = malloc (bitmap->height * sizeof (png_byte *));
707 for (y = 0; y < bitmap->height; ++y) {
708 if (colormodel == PNG_COLOR_TYPE_GRAY) {
709 row_pointers[y] = bitmap->data + bitmap->width * y;
710 } else if (colormodel == PNG_COLOR_TYPE_GRAY_ALPHA) {
711 row_pointers[y] = bitmap->data + bitmap->width * 2 * y;
712 } else {
713 row_pointers[y] = bitmap->data + bitmap->width * 4 * y;
717 /* Write the image data to |io| */
718 png_set_write_fn(png_ptr, &io, mp_write_png_data, mp_write_png_flush);
720 png_set_rows (png_ptr, info_ptr, row_pointers);
721 if (colormodel == PNG_COLOR_TYPE_RGB) {
722 /* Unfortunately, |png_write_png| does not have enough |PNG_TRANSFORM| options
723 to do this properly, so we have to modify the bitmap data */
724 int i;
725 for (i = 0; i < bitmap->width*bitmap->height*4; i+=4) {
726 unsigned char b = bitmap->data[i];
727 unsigned char g = bitmap->data[i+1];
728 bitmap->data[i] = bitmap->data[i+3];
729 bitmap->data[i+1] = bitmap->data[i+2];
730 bitmap->data[i+2] = g;
731 bitmap->data[i+3] = b;
733 png_write_png (png_ptr, info_ptr, PNG_TRANSFORM_STRIP_FILLER, NULL);
734 } else if (colormodel == PNG_COLOR_TYPE_RGB_ALPHA) {
735 png_write_png (png_ptr, info_ptr, PNG_TRANSFORM_BGR, NULL);
736 } else if (colormodel == PNG_COLOR_TYPE_GRAY ||
737 colormodel == PNG_COLOR_TYPE_GRAY_ALPHA) {
738 int i, j;
739 j = 0;
740 for (i = 0; i < bitmap->width*bitmap->height*4; i+=4) {
741 unsigned char b = bitmap->data[i];
742 unsigned char g = bitmap->data[i+1];
743 unsigned char r = bitmap->data[i+2];
744 bitmap->data[j++] = ((r==g && r==b) ? r : 0.2126*r + 0.7152*g + 0.0722*b);
745 if (colormodel == PNG_COLOR_TYPE_GRAY_ALPHA)
746 bitmap->data[j++] = bitmap->data[i+3];
748 png_write_png (png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
750 status = 0;
752 free (row_pointers);
754 png_failure:
755 png_create_info_struct_failed:
756 png_destroy_write_struct (&png_ptr, &info_ptr);
757 png_create_write_struct_failed:
758 (mp->close_file)(mp, io.fp);
759 fopen_failed:
760 return status;
765 @d number_to_double(A) (((math_data *)(mp->math))->to_double)(A)
768 int mp_png_gr_ship_out (mp_edge_object *hh, const char *options, int standalone) {
769 char *ss;
770 mp_graphic_object *p;
771 mp_pen_info *pen = NULL;
772 MP mp = hh->parent;
773 bitmap_t bitmap;
774 const char *currentoption = options;
775 int colormodel = PNG_COLOR_TYPE_RGB_ALPHA;
776 int antialias = CAIRO_ANTIALIAS_FAST;
777 int c;
778 while (currentoption && *currentoption) {
779 if (strncmp(currentoption,"format=",7)==0) {
780 currentoption += 7;
781 if (strncmp(currentoption,"rgba",4)==0) {
782 colormodel = PNG_COLOR_TYPE_RGB_ALPHA;
783 currentoption += 4;
784 } else if (strncmp(currentoption,"rgb",3)==0) {
785 colormodel = PNG_COLOR_TYPE_RGB;
786 currentoption += 3;
787 } else if (strncmp(currentoption,"graya",5)==0) {
788 colormodel = PNG_COLOR_TYPE_GRAY_ALPHA;
789 currentoption += 5;
790 } else if (strncmp(currentoption,"gray",4)==0) {
791 colormodel = PNG_COLOR_TYPE_GRAY;
792 currentoption += 4;
794 } else if (strncmp(currentoption,"antialias=",10)==0) {
795 currentoption += 10;
796 if (strncmp(currentoption,"none",4)==0) {
797 antialias = CAIRO_ANTIALIAS_NONE;
798 currentoption += 4;
799 } else if (strncmp(currentoption,"fast",4)==0) {
800 antialias = CAIRO_ANTIALIAS_FAST;
801 currentoption += 4;
802 } else if (strncmp(currentoption,"good",4)==0) {
803 antialias = CAIRO_ANTIALIAS_GOOD;
804 currentoption += 4;
805 } else if (strncmp(currentoption,"best",4)==0) {
806 antialias = CAIRO_ANTIALIAS_BEST;
807 currentoption += 4;
810 currentoption = strchr(currentoption,' ');
811 if (currentoption) {
812 while (*currentoption == ' ')
813 currentoption++;
816 c = round_unscaled(internal_value (mp_char_code));
817 if (standalone) {
818 mp->jump_buf = malloc(sizeof(jmp_buf));
819 if (mp->jump_buf == NULL || setjmp(*(mp->jump_buf)))
820 return 0;
822 if (mp->history >= mp_fatal_error_stop ) return 1;
823 mp_png_start(mp, hh, number_to_double(internal_value(mp_hppp)), number_to_double(internal_value(mp_vppp)),
824 colormodel, antialias);
825 p = hh->body;
826 while ( p!=NULL ) {
827 if (gr_has_color (p))
828 mp_png_color_out (mp, p);
829 switch (gr_type(p)) {
830 case mp_fill_code:
832 mp_fill_object *ph = (mp_fill_object *)p;
833 if ( gr_pen_p(ph)==NULL ) {
834 mp_png_fill_out(mp, gr_path_p(ph), p);
835 } else if ( pen_is_elliptical(gr_pen_p(ph)) ) {
836 pen = mp_png_pen_info(mp, gr_path_p(ph), gr_pen_p(ph));
837 mp_png_stroke_out(mp, p, pen, true);
838 mp_xfree(pen);
839 } else {
840 mp_png_fill_out(mp, gr_path_p(ph), p);
841 mp_png_fill_out(mp, gr_htap_p(ph), p);
844 break;
845 case mp_stroked_code:
847 mp_stroked_object *ph = (mp_stroked_object *)p;
848 if ( pen_is_elliptical(gr_pen_p(ph))) {
849 pen = mp_png_pen_info(mp, gr_path_p(ph), gr_pen_p(ph));
850 mp_png_stroke_out(mp, p, pen, false);
851 mp_xfree(pen);
852 } else {
853 mp_png_fill_out(mp, gr_path_p(ph), p);
856 break;
857 case mp_text_code:
858 if ( (gr_font_n(p)!=null_font) && (gr_text_l(p)>0) ) {
859 mp_png_text_out(mp, (mp_text_object *)p);
861 break;
862 case mp_start_clip_code:
863 cairo_save(mp->png->cr);
864 mp_png_path_out(mp, gr_path_p((mp_clip_object *)p));
865 cairo_clip(mp->png->cr);
866 cairo_new_path(mp->png->cr);
867 break;
868 case mp_stop_clip_code:
869 cairo_restore(mp->png->cr);
870 break;
871 case mp_start_bounds_code:
872 case mp_stop_bounds_code:
873 break;
874 case mp_special_code:
875 break;
876 } /* all cases are enumerated */
877 p=gr_link(p);
879 (void)mp_set_output_file_name (mp, c);
880 mp_store_true_output_filename (mp, c);
881 ss = xstrdup(mp->name_of_file);
882 cairo_surface_flush (mp->png->surface);
883 cairo_destroy (mp->png->cr);
884 bitmap.data = cairo_image_surface_get_data (mp->png->surface);
885 bitmap.width = cairo_image_surface_get_width (mp->png->surface);
886 bitmap.height = cairo_image_surface_get_height (mp->png->surface);
887 mp_png_save_to_file (mp, &bitmap, ss, colormodel);
888 cairo_surface_destroy (mp->png->surface);
889 free(ss);
890 return 1;
893 @ @(mplibpng.h@>=
894 #ifndef MPLIBPNG_H
895 #define MPLIBPNG_H 1
896 int mp_png_ship_out (mp_edge_object *hh, const char *options) ;
897 #endif
899 @ @c
900 int mp_png_ship_out (mp_edge_object *hh, const char *options) {
901 return mp_png_gr_ship_out (hh, options, (int)true);