diff options
Diffstat (limited to 'Build/source/texk/web2c/mplibdir/pngout.w')
-rw-r--r-- | Build/source/texk/web2c/mplibdir/pngout.w | 886 |
1 files changed, 886 insertions, 0 deletions
diff --git a/Build/source/texk/web2c/mplibdir/pngout.w b/Build/source/texk/web2c/mplibdir/pngout.w new file mode 100644 index 00000000000..eb838b0595f --- /dev/null +++ b/Build/source/texk/web2c/mplibdir/pngout.w @@ -0,0 +1,886 @@ +% $Id: pngout.w 1706 2012-09-28 08:12:34Z taco $ +% +% Copyright 2012 Taco Hoekwater. +% +% This program is free software: you can redistribute it and/or modify +% it under the terms of the GNU Lesser General Public License as published by +% the Free Software Foundation, either version 3 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU Lesser General Public License for more details. +% +% You should have received a copy of the GNU Lesser General Public License +% along with this program. If not, see <http://www.gnu.org/licenses/>. +% +% TeX is a trademark of the American Mathematical Society. +% METAFONT is a trademark of Addison-Wesley Publishing Company. +% PostScript is a trademark of Adobe Systems Incorporated. + +% Here is TeX material that gets inserted after \input webmac + +\font\tenlogo=logo10 % font used for the METAFONT logo +\font\logos=logosl10 +\def\MF{{\tenlogo META}\-{\tenlogo FONT}} +\def\MP{{\tenlogo META}\-{\tenlogo POST}} +\def\<#1>{$\langle#1\rangle$} +\def\section{\mathhexbox278} +\def\[#1]{} % from pascal web +\def\(#1){} % this is used to make section names sort themselves better +\def\9#1{} % this is used for sort keys in the index via @@:sort key}{entry@@> + +\def\title{MetaPost PNG output} +\def\topofcontents{\hsize 5.5in + \vglue -30pt plus 1fil minus 1.5in + \def\?##1]{\hbox to 1in{\hfil##1.\ }} + } +\def\botofcontents{\vskip 0pt plus 1fil minus 1.5in} +\pdfoutput=1 +\pageno=3 + +@ +@d zero_t ((math_data *)mp->math)->zero_t +@d number_zero(A) (((math_data *)(mp->math))->equal)(A,zero_t) +@d number_greater(A,B) (((math_data *)(mp->math))->greater)(A,B) +@d number_positive(A) number_greater(A, zero_t) +@d number_to_scaled(A) (((math_data *)(mp->math))->to_scaled)(A) +@d round_unscaled(A) (((math_data *)(mp->math))->round_unscaled)(A) +@d true 1 +@d false 0 +@d null_font 0 +@d null 0 +@d unity 1.0 +@d incr(A) (A)=(A)+1 /* increase a variable by unity */ +@d decr(A) (A)=(A)-1 /* decrease a variable by unity */ +@d negate(A) (A)=-(A) /* change the sign of a variable */ + +@c +#include <w2c/config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "mplib.h" +#include "mplibps.h" /* external header */ +#include "mplibpng.h" /* external header */ +#include "mpmp.h" /* internal header */ +#include "mppsout.h" /* internal header */ +#include "mppngout.h" /* internal header */ +#include "mpmath.h" /* internal header */ +@h +@<Types in the outer block@> +@<Declarations@> + +@ There is a small bit of code from the backend that bleads through +to the frontend because I do not know how to set up the includes +properly. That is |typedef struct pngout_data_struct * pngout_data|. + +@ @(mppngout.h@>= +#ifndef MPPNGOUT_H +#define MPPNGOUT_H 1 +#include "cairo.h" +#define PNG_SKIP_SETJMP_CHECK 1 +#include "png.h" +#include "mplib.h" +#include "mpmp.h" +#include "mplibps.h" +typedef struct pngout_data_struct { + @<Globals@> +} pngout_data_struct ; +@<Exported function headers@> +#endif + +@ @<Exported function headers@>= +void mp_png_backend_initialize (MP mp) ; +void mp_png_backend_free (MP mp) ; + +@ @c +void mp_png_backend_initialize (MP mp) { + mp->png = mp_xmalloc(mp,1,sizeof(pngout_data_struct)); + memset(mp->png,0,sizeof(pngout_data_struct)); +} +void mp_png_backend_free (MP mp) { + mp_xfree(mp->png); + mp->png = NULL; +} + +@ Writing to PNG files + +@<Globals@>= +cairo_surface_t *surface; +cairo_t *cr; + +@ We often need to print a pair of coordinates, and these need to +offset so that all coordinates are positive. + +@<Globals@>= +integer dx; +integer dy; + +@ +@<Declarations@>= +static void mp_png_start(MP mp,mp_edge_object *hh, double hppp, double vppp, int colormodel, int antialias); + +@ +@c +void mp_png_start(MP mp,mp_edge_object *hh, double hppp, double vppp, int colormodel, int antialias) { + double w, h; + if ( hh->minx>hh->maxx) { + w = 1; + h = 1; + mp->png->dx = 0; + mp->png->dy = 0; + } else { + w = (ceil(hh->maxx) - floor(hh->minx)) / hppp; + h = (ceil(hh->maxy) - floor(hh->miny)) / vppp; + mp->png->dx = -floor(hh->minx); + mp->png->dy = -floor(hh->miny); + } + mp->png->surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h); + mp->png->cr = cairo_create (mp->png->surface); + /* if there is no alpha channel, a white background is needed */ + if (colormodel == PNG_COLOR_TYPE_RGB || + colormodel == PNG_COLOR_TYPE_GRAY) { + cairo_save(mp->png->cr); + cairo_set_source_rgb(mp->png->cr, 1.0, 1.0, 1.0); + cairo_rectangle(mp->png->cr, 0, 0, w, h); + cairo_fill(mp->png->cr); + cairo_restore(mp->png->cr); + } + cairo_scale(mp->png->cr, 1/hppp, -1/vppp); + cairo_translate(mp->png->cr, 0, -(h*vppp)); + cairo_translate(mp->png->cr, mp->png->dx, mp->png->dy); + cairo_set_antialias(mp->png->cr, antialias); +} + +@ Outputting a color specification. + +@d set_color_objects(pq) + object_color_model = pq->color_model; + object_color_a = pq->color.a_val; + object_color_b = pq->color.b_val; + object_color_c = pq->color.c_val; + object_color_d = pq->color.d_val; + +@c +static void mp_png_color_out (MP mp, mp_graphic_object *p) { + int object_color_model; + double object_color_a, object_color_b, object_color_c, object_color_d ; + if (gr_type(p) == mp_fill_code) { + mp_fill_object *pq = (mp_fill_object *)p; + set_color_objects(pq); + } else if (gr_type(p) == mp_stroked_code) { + mp_stroked_object *pq = (mp_stroked_object *)p; + set_color_objects(pq); + } else { + mp_text_object *pq = (mp_text_object *)p; + set_color_objects(pq); + } + if ( object_color_model==mp_no_model ) { + cairo_set_source_rgb (mp->png->cr, 0, 0, 0); + } else { + if ( object_color_model==mp_grey_model ) { + object_color_b = object_color_a; + object_color_c = object_color_a; + } else if ( object_color_model==mp_cmyk_model) { + int c,m,y,k; + c = object_color_a; + m = object_color_b; + y = object_color_c; + k = object_color_d; + object_color_a = unity - (c+k>unity ? unity : c+k); + object_color_b = unity - (m+k>unity ? unity : m+k); + object_color_c = unity - (y+k>unity ? unity : y+k); + } + cairo_set_source_rgb (mp->png->cr, object_color_a, object_color_b, object_color_c); + } +} + +@ @<Declarations@>= +static void mp_png_color_out (MP mp, mp_graphic_object *p); + +@ This is the information that comes from a pen + +@<Types...@>= +typedef struct mp_pen_info { + double tx, ty; + double sx, rx, ry, sy; + double ww; +} mp_pen_info; + + +@ (Re)discover the characteristics of an elliptical pen + +@<Declarations@>= +mp_pen_info *mp_png_pen_info(MP mp, mp_gr_knot pp, mp_gr_knot p); + +@ The next two constants come from the original web source. +Together with the two helper functions, they will tell whether +the |x| or the |y| direction of the path is the most important + +@d aspect_bound (10/65536.0) +@d aspect_default 1 + +@c +static double coord_range_x (mp_gr_knot h, double dz) { + double z; + double zlo = 0, zhi = 0; + mp_gr_knot f = h; + while (h != NULL) { + z = gr_x_coord(h); + if (z < zlo) zlo = z; else if (z > zhi) zhi = z; + z = gr_right_x(h); + if (z < zlo) zlo = z; else if (z > zhi) zhi = z; + z = gr_left_x(h); + if (z < zlo) zlo = z; else if (z > zhi) zhi = z; + h = gr_next_knot(h); + if (h==f) + break; + } + return (zhi - zlo <= dz ? aspect_bound : aspect_default); +} +static double coord_range_y (mp_gr_knot h, double dz) { + double z; + double zlo = 0, zhi = 0; + mp_gr_knot f = h; + while (h != NULL) { + z = gr_y_coord(h); + if (z < zlo) zlo = z; else if (z > zhi) zhi = z; + z = gr_right_y(h); + if (z < zlo) zlo = z; else if (z > zhi) zhi = z; + z = gr_left_y(h); + if (z < zlo) zlo = z; else if (z > zhi) zhi = z; + h = gr_next_knot(h); + if (h==f) + break; + } + return (zhi - zlo <= dz ? aspect_bound : aspect_default); +} + +@ +@c +mp_pen_info *mp_png_pen_info(MP mp, mp_gr_knot pp, mp_gr_knot p) { + double wx, wy; /* temporary pen widths, in either direction */ + struct mp_pen_info *pen; /* return structure */ + if (p == NULL) + return NULL; + pen = mp_xmalloc(mp, 1, sizeof(mp_pen_info)); + pen->rx = unity; + pen->ry = unity; + pen->ww = unity; + if ((gr_right_x(p) == gr_x_coord(p)) + && + (gr_left_y(p) == gr_y_coord(p))) { + wx = fabs(gr_left_x(p) - gr_x_coord(p)); + wy = fabs(gr_right_y(p) - gr_y_coord(p)); + } else { + double arg1, arg2; + arg1 = gr_left_x(p)-gr_x_coord(p); + arg2 = gr_right_x(p)-gr_x_coord(p); + wx = sqrt(arg1*arg1 + arg2*arg2); + arg1 = gr_left_y(p)-gr_y_coord(p); + arg2 = gr_right_y(p)-gr_y_coord(p); + wy = sqrt(arg1*arg1 + arg2*arg2); + } + if ((wy/coord_range_x(pp, wx)) >= (wx/coord_range_y(pp, wy))) + pen->ww = wy; + else + pen->ww = wx; + pen->tx = gr_x_coord(p); + pen->ty = gr_y_coord(p); + pen->sx = gr_left_x(p) - pen->tx; + pen->rx = gr_left_y(p) - pen->ty; + pen->ry = gr_right_x(p) - pen->tx; + pen->sy = gr_right_y(p) - pen->ty; + if (pen->ww != unity) { + if (pen->ww == 0) { + pen->sx = unity; + pen->sy = unity; + } else { + pen->rx = pen->rx / pen->ww; + pen->ry = pen->ry / pen->ww; + pen->sx = pen->sx / pen->ww; + pen->sy = pen->sy / pen->ww; + } + } + return pen; +} + +@ Two types of straight lines come up often in \MP\ paths: +cubics with zero initial and final velocity as created by |make_path| or +|make_envelope|, and cubics with control points uniformly spaced on a line +as created by |make_choices|. + +@<Declarations@>= +static boolean mp_is_curved(mp_gr_knot p, mp_gr_knot q) ; + + +@ +@d bend_tolerance (131/65536.0) /* allow rounding error of $2\cdot10^{-3}$ */ + +@c +boolean mp_is_curved(mp_gr_knot p, mp_gr_knot q) { + double d; /* a temporary value */ + if ( gr_right_x(p)==gr_x_coord(p) ) + if ( gr_right_y(p)==gr_y_coord(p) ) + if ( gr_left_x(q)==gr_x_coord(q) ) + if ( gr_left_y(q)==gr_y_coord(q) ) + return false; + d=gr_left_x(q)-gr_right_x(p); + if ( fabs(gr_right_x(p)-gr_x_coord(p)-d)<=bend_tolerance ) + if ( fabs(gr_x_coord(q)-gr_left_x(q)-d)<=bend_tolerance ) { + d=gr_left_y(q)-gr_right_y(p); + if ( fabs(gr_right_y(p)-gr_y_coord(p)-d)<=bend_tolerance ) + if ( fabs(gr_y_coord(q)-gr_left_y(q)-d)<=bend_tolerance ) + return false; + } + return true; +} + + +@ @c +static void mp_png_path_out (MP mp, mp_gr_knot h) { + mp_gr_knot p, q; /* for scanning the path */ + cairo_move_to (mp->png->cr, gr_x_coord(h),gr_y_coord(h)); + p=h; + do { + if ( gr_right_type(p)==mp_endpoint ) { + return; + } + q=gr_next_knot(p); + if (mp_is_curved(p, q)){ + cairo_curve_to (mp->png->cr, gr_right_x(p),gr_right_y(p), + gr_left_x(q),gr_left_y(q), + gr_x_coord(q),gr_y_coord(q)); + } else if ( q!=h ){ + cairo_line_to (mp->png->cr, gr_x_coord(q),gr_y_coord(q)); + } + p=q; + } while (p!=h); +} + +@ Now for outputting the actual graphic objects. + +@<Declarations@>= +static double mp_png_choose_scale (MP mp, mp_graphic_object *p) ; + +@ @c double mp_png_choose_scale (MP mp, mp_graphic_object *p) { + /* |p| should point to a text node */ + double a,b,c,d,ad,bc; /* temporary values */ + double ret1, ret2; + a=gr_txx_val(p); + b=gr_txy_val(p); + c=gr_tyx_val(p); + d=gr_tyy_val(p); + if ( a<0 ) negate(a); + if ( b<0 ) negate(b); + if ( c<0 ) negate(c); + if ( d<0 ) negate(d); + ad=(a-d)/2.0; + bc=(b-c)/2.0; + ret1 = sqrt((d+ad)*(d+ad) + ad*ad); + ret2 = sqrt((c+bc)*(c+bc) + bc*bc); + return sqrt(ret1*ret1 + ret2*ret2); +} + +@ +@d xrealloc(P,A,B) mp_xrealloc(mp,P,(size_t)A,B) +@d XREALLOC(a,b,c) a = xrealloc(a,(b+1),sizeof(c)); + +@c +void mp_reallocate_psfonts (MP mp, int l) { + if (l>=mp->png->font_max) { + int f; + mp->png->last_fnum = mp->png->font_max; + XREALLOC (mp->png->psfonts, l, mp_edge_object *); + for (f = (mp->png->last_fnum + 1); f <= l; f++) { + mp->png->psfonts[f] = NULL; + } + mp->png->font_max = l; + } +} + +@ @<Declarations@>= +void mp_reallocate_psfonts (MP mp, int l); + +@ @<Globals@>= +mp_edge_object **psfonts; +int font_max; +int last_fnum; + +@ @<Declarations@>= +static void mp_png_text_out (MP mp, mp_text_object *p) ; + +@ @c +void mp_png_text_out (MP mp, mp_text_object *p) { + double ds; /* design size and scale factor for a text node */ + unsigned char *s = (unsigned char *)gr_text_p(p); + size_t l = gr_text_l(p); /* string length */ + boolean transformed= (gr_txx_val(p)!=unity)||(gr_tyy_val(p)!=unity)|| + (gr_txy_val(p)!=0)||(gr_tyx_val(p)!=0); + int fn = gr_font_n(p); + mp_ps_font *f; + double scf; + ds =(mp->font_dsize[fn]+8) / (16*65536.0); + scf = mp_png_choose_scale (mp,(mp_graphic_object *)p); + if ( transformed ) { + cairo_matrix_t matrix = {0,0,0,0,0,0}; + cairo_save(mp->png->cr); + cairo_matrix_init(&matrix, (gr_txx_val(p)/scf), (gr_tyx_val(p)/scf), + (gr_txy_val(p)/scf), (gr_tyy_val(p)/scf), + gr_tx_val(p),gr_ty_val(p)); + cairo_transform (mp->png->cr, &matrix); + cairo_move_to(mp->png->cr, 0, 0); + } else { + cairo_translate (mp->png->cr,gr_tx_val(p),gr_ty_val(p)); + } + cairo_scale(mp->png->cr, ((ds/1000.0)*scf), ((ds/1000.0)*scf)); + mp_png_color_out(mp,(mp_graphic_object *)p); + while (l-->0) { + mp_edge_object *ch; + int k = (int)*s++; + double wd = 0.0; /* this is in PS design units */ + mp_reallocate_psfonts(mp, ((fn+1) * 256)); + ch = mp->png->psfonts[(fn*256)+k]; + if (ch == NULL) { + f = mp_ps_font_parse(mp, fn); + if (f == NULL) return; + ch = mp_ps_font_charstring(mp,f,k); + mp->png->psfonts[(fn*256)+k] = ch; + } + if (ch != NULL) { + mp_graphic_object *pp = ch->body; + while (pp!=NULL) { + mp_png_path_out(mp, gr_path_p((mp_fill_object *)pp)); + pp=pp->next; + } + cairo_fill(mp->png->cr); + } + wd = mp_get_char_dimension (mp, mp->font_name[fn], k, 'w'); + cairo_translate(mp->png->cr,wd,0); + } + if ( transformed ) { + cairo_restore(mp->png->cr); + } +} + +@ When stroking a path with an elliptical pen, it is necessary to transform +the coordinate system so that a unit circular pen will have the desired shape. +To keep this transformation local, we enclose it in a $$\&{<g>}\ldots\&{</g>}$$ +block. Any translation component must be applied to the path being stroked +while the rest of the transformation must apply only to the pen. +If |fill_also=true|, the path is to be filled as well as stroked so we must +insert commands to do this after giving the path. + +@<Declarations@>= +static void mp_png_stroke_out (MP mp, mp_graphic_object *h, + mp_pen_info *pen, boolean fill_also) ; + + +@ @c +void mp_png_stroke_out (MP mp, mp_graphic_object *h, + mp_pen_info *pen, boolean fill_also) { + boolean transformed = false; + if (pen != NULL) { + transformed = true; + if ((pen->sx==unity) && + (pen->rx==0) && + (pen->ry==0) && + (pen->sy==unity) && + (pen->tx==0) && + (pen->ty==0)) { + transformed = false; + } + } + if (pen != NULL) { + cairo_set_line_width(mp->png->cr, pen->ww); + } else { + cairo_set_line_width(mp->png->cr, 0); + } + if (gr_lcap_val(h)!=0) { + switch (gr_lcap_val(h)) { + case 1: cairo_set_line_cap(mp->png->cr,CAIRO_LINE_CAP_ROUND); break; + case 2: cairo_set_line_cap(mp->png->cr,CAIRO_LINE_CAP_SQUARE); break; + default:cairo_set_line_cap(mp->png->cr,CAIRO_LINE_CAP_BUTT); break; + } + } + if (gr_type(h)!=mp_fill_code) { + mp_dash_object *hh= gr_dash_p(h); + if (hh != NULL && hh->array != NULL) { + int i; + for (i=0; *(hh->array+i) != -1;i++) + ; + cairo_set_dash (mp->png->cr, hh->array, i, hh->offset); + } + } + if (gr_ljoin_val((mp_stroked_object *)h)!=0) { + switch (gr_ljoin_val((mp_stroked_object *)h)) { + case 1: cairo_set_line_join(mp->png->cr,CAIRO_LINE_JOIN_ROUND); break; + case 2: cairo_set_line_join(mp->png->cr,CAIRO_LINE_JOIN_BEVEL); break; + default: cairo_set_line_join(mp->png->cr,CAIRO_LINE_JOIN_MITER); break; + } + } + cairo_set_miter_limit(mp->png->cr,gr_miterlim_val((mp_stroked_object *)h)); + mp_png_path_out(mp, gr_path_p((mp_stroked_object *)h)); + if (fill_also) { + cairo_save(mp->png->cr); + cairo_close_path (mp->png->cr); + cairo_fill (mp->png->cr); + cairo_restore(mp->png->cr); + } + if (transformed) { + cairo_matrix_t matrix = {0,0,0,0,0,0}; + cairo_save(mp->png->cr); + cairo_matrix_init(&matrix, pen->sx, pen->rx, pen->ry, pen->sy, pen->tx, pen->ty); + cairo_transform (mp->png->cr, &matrix); + } + cairo_stroke (mp->png->cr); + if (transformed) { + cairo_restore(mp->png->cr); + } +} + +@ Here is a simple routine that just fills a cycle. + +@<Declarations@>= +static void mp_png_fill_out (MP mp, mp_gr_knot p, mp_graphic_object *h); + +@ @c +void mp_png_fill_out (MP mp, mp_gr_knot p, mp_graphic_object *h) { + mp_png_path_out(mp, p); + cairo_close_path (mp->png->cr); + cairo_fill (mp->png->cr); +} + +@ The main output function + +@d pen_is_elliptical(A) ((A)==gr_next_knot((A))) +@d gr_has_color(A) (gr_type((A))<mp_start_clip_code) + +@<Exported function ...@>= +int mp_png_gr_ship_out (mp_edge_object *hh, const char *options, int standalone) ; + +@ This is a structure to ship data from cairo to our png writer. |width| and +|height| could have been stored in our private |mp| instance, but this is just +as easy. + +@<Types...@>= +typedef struct { + unsigned char * data; + int height; + int width; +} bitmap_t; + +@ This is a small structure that is needed so that the png writer callbacks +can actually access the |mp| object instance. + +@<Types...@>= +typedef struct { + void *fp; + MP mp; +} mp_png_io; + +@ Output a png chunk: the libpng callbacks +@c +static void mp_write_png_data(png_structp png_ptr, png_bytep data, png_size_t length) +{ + mp_png_io *ioptr = (mp_png_io *)png_get_io_ptr(png_ptr); + MP mp = ioptr->mp; + (mp->write_binary_file)(mp,ioptr->fp, (void *)data, (size_t)length); +} +static void mp_write_png_flush(png_structp png_ptr) +{ + /* nothing to do */ +} + + +@ Write |bitmap| to a PNG file specified by |path|; returns 0 on +success, non-zero on error. The original of this function was +borrowed from an internet post, and extended as needed. + +@<Declarations@>= +int mp_png_save_to_file (MP mp, const bitmap_t * bitmap, const char *path, int colormodel); + +@ @c +int mp_png_save_to_file (MP mp, const bitmap_t * bitmap, const char *path, int colormodel) +{ + mp_png_io io; + png_structp png_ptr = NULL; + png_infop info_ptr = NULL; + size_t y; + png_byte ** row_pointers = NULL; + int status = -1; + int depth = 8; + int dpi = 72; + int ppm_x; + int ppm_y; /* pixels per metre */ + + io.mp = mp; + io.fp = (mp->open_file)(mp, path, "wb", mp_filetype_bitmap); + if (!io.fp) { + goto fopen_failed; + } + + png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (png_ptr == NULL) { + goto png_create_write_struct_failed; + } + + info_ptr = png_create_info_struct (png_ptr); + if (info_ptr == NULL) { + goto png_create_info_struct_failed; + } + + /* Set up error handling. */ + + if (setjmp (png_jmpbuf (png_ptr))) { + goto png_failure; + } + + /* Set image attributes. */ + + png_set_IHDR (png_ptr, + info_ptr, + bitmap->width, + bitmap->height, + depth, + colormodel, + PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT); + + /* Compression level |3| appears the best tradeoff between + disk size and compression speed */ + png_set_compression_level(png_ptr, 3); + png_set_filter(png_ptr,0,PNG_FILTER_NONE); + /* setup some information */ + if (1) { + png_text text[2]; + char *a, *b, *c, *d; /* to get rid of a typecast warning */ + a = xstrdup("Title"); + b = xstrdup(path); + c = xstrdup("Software"); + d = xstrdup("Generated by Metapost version " metapost_version); + + text[0].compression = PNG_TEXT_COMPRESSION_NONE; + text[0].key = a; + text[0].text = b; + text[1].compression = PNG_TEXT_COMPRESSION_NONE; + text[1].key = c; + text[1].text = d; + png_set_text(png_ptr, info_ptr, text, 2); + + free(a); + free(b); + free(c); + free(d); + } + + /* The original plan was to add |hppp| and |vppp| values in here, + but that seems to have negative effects on various bits of + software. Better keep the DPI at 72 */ + ppm_x = dpi / 0.0254; + ppm_y = dpi / 0.0254; + png_set_pHYs(png_ptr, info_ptr, ppm_x, ppm_y, PNG_RESOLUTION_METER); + + /* Initialize rows of PNG. */ + + row_pointers = malloc (bitmap->height * sizeof (png_byte *)); + for (y = 0; y < bitmap->height; ++y) { + if (colormodel == PNG_COLOR_TYPE_GRAY) { + row_pointers[y] = bitmap->data + bitmap->width * y; + } else if (colormodel == PNG_COLOR_TYPE_GRAY_ALPHA) { + row_pointers[y] = bitmap->data + bitmap->width * 2 * y; + } else { + row_pointers[y] = bitmap->data + bitmap->width * 4 * y; + } + } + + /* Write the image data to |io| */ + png_set_write_fn(png_ptr, &io, mp_write_png_data, mp_write_png_flush); + + png_set_rows (png_ptr, info_ptr, row_pointers); + if (colormodel == PNG_COLOR_TYPE_RGB) { + /* Unfortunately, |png_write_png| does not have enough |PNG_TRANSFORM| options + to do this properly, so we have to modify the bitmap data */ + int i; + for (i = 0; i < bitmap->width*bitmap->height*4; i+=4) { + unsigned char b = bitmap->data[i]; + unsigned char g = bitmap->data[i+1]; + bitmap->data[i] = bitmap->data[i+3]; + bitmap->data[i+1] = bitmap->data[i+2]; + bitmap->data[i+2] = g; + bitmap->data[i+3] = b; + } + png_write_png (png_ptr, info_ptr, PNG_TRANSFORM_STRIP_FILLER, NULL); + } else if (colormodel == PNG_COLOR_TYPE_RGB_ALPHA) { + png_write_png (png_ptr, info_ptr, PNG_TRANSFORM_BGR, NULL); + } else if (colormodel == PNG_COLOR_TYPE_GRAY || + colormodel == PNG_COLOR_TYPE_GRAY_ALPHA) { + int i, j; + j = 0; + for (i = 0; i < bitmap->width*bitmap->height*4; i+=4) { + unsigned char b = bitmap->data[i]; + unsigned char g = bitmap->data[i+1]; + unsigned char r = bitmap->data[i+2]; + bitmap->data[j++] = ((r==g && r==b) ? r : 0.2126*r + 0.7152*g + 0.0722*b); + if (colormodel == PNG_COLOR_TYPE_GRAY_ALPHA) + bitmap->data[j++] = bitmap->data[i+3]; + } + png_write_png (png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL); + } + status = 0; + + free (row_pointers); + + png_failure: + png_create_info_struct_failed: + png_destroy_write_struct (&png_ptr, &info_ptr); + png_create_write_struct_failed: + (mp->close_file)(mp, io.fp); + fopen_failed: + return status; +} + + +@ +@d number_to_double(A) (((math_data *)(mp->math))->to_double)(A) + +@c +int mp_png_gr_ship_out (mp_edge_object *hh, const char *options, int standalone) { + char *ss; + mp_graphic_object *p; + mp_pen_info *pen = NULL; + MP mp = hh->parent; + bitmap_t bitmap; + const char *currentoption = options; + int colormodel = PNG_COLOR_TYPE_RGB_ALPHA; + int antialias = CAIRO_ANTIALIAS_FAST; + int c; + while (currentoption && *currentoption) { + if (strncmp(currentoption,"format=",7)==0) { + currentoption += 7; + if (strncmp(currentoption,"rgba",4)==0) { + colormodel = PNG_COLOR_TYPE_RGB_ALPHA; + currentoption += 4; + } else if (strncmp(currentoption,"rgb",3)==0) { + colormodel = PNG_COLOR_TYPE_RGB; + currentoption += 3; + } else if (strncmp(currentoption,"graya",5)==0) { + colormodel = PNG_COLOR_TYPE_GRAY_ALPHA; + currentoption += 5; + } else if (strncmp(currentoption,"gray",4)==0) { + colormodel = PNG_COLOR_TYPE_GRAY; + currentoption += 4; + } + } else if (strncmp(currentoption,"antialias=",10)==0) { + currentoption += 10; + if (strncmp(currentoption,"none",4)==0) { + antialias = CAIRO_ANTIALIAS_NONE; + currentoption += 4; + } else if (strncmp(currentoption,"fast",4)==0) { + antialias = CAIRO_ANTIALIAS_FAST; + currentoption += 4; + } else if (strncmp(currentoption,"good",4)==0) { + antialias = CAIRO_ANTIALIAS_GOOD; + currentoption += 4; + } else if (strncmp(currentoption,"best",4)==0) { + antialias = CAIRO_ANTIALIAS_BEST; + currentoption += 4; + } + } + currentoption = index(currentoption,' '); + if (currentoption) { + while (*currentoption == ' ') + currentoption++; + } + } + c = round_unscaled(internal_value (mp_char_code)); + if (standalone) { + mp->jump_buf = malloc(sizeof(jmp_buf)); + if (mp->jump_buf == NULL || setjmp(*(mp->jump_buf))) + return 0; + } + if (mp->history >= mp_fatal_error_stop ) return 1; + mp_png_start(mp, hh, number_to_double(internal_value(mp_hppp)), number_to_double(internal_value(mp_vppp)), + colormodel, antialias); + p = hh->body; + while ( p!=NULL ) { + if (gr_has_color (p)) + mp_png_color_out (mp, p); + switch (gr_type(p)) { + case mp_fill_code: + { + mp_fill_object *ph = (mp_fill_object *)p; + if ( gr_pen_p(ph)==NULL ) { + mp_png_fill_out(mp, gr_path_p(ph), p); + } else if ( pen_is_elliptical(gr_pen_p(ph)) ) { + pen = mp_png_pen_info(mp, gr_path_p(ph), gr_pen_p(ph)); + mp_png_stroke_out(mp, p, pen, true); + mp_xfree(pen); + } else { + mp_png_fill_out(mp, gr_path_p(ph), p); + mp_png_fill_out(mp, gr_htap_p(ph), p); + } + } + break; + case mp_stroked_code: + { + mp_stroked_object *ph = (mp_stroked_object *)p; + if ( pen_is_elliptical(gr_pen_p(ph))) { + pen = mp_png_pen_info(mp, gr_path_p(ph), gr_pen_p(ph)); + mp_png_stroke_out(mp, p, pen, false); + mp_xfree(pen); + } else { + mp_png_fill_out(mp, gr_path_p(ph), p); + } + } + break; + case mp_text_code: + if ( (gr_font_n(p)!=null_font) && (gr_text_l(p)>0) ) { + mp_png_text_out(mp, (mp_text_object *)p); + } + break; + case mp_start_clip_code: + cairo_save(mp->png->cr); + mp_png_path_out(mp, gr_path_p((mp_clip_object *)p)); + cairo_clip(mp->png->cr); + cairo_new_path(mp->png->cr); + break; + case mp_stop_clip_code: + cairo_restore(mp->png->cr); + break; + case mp_start_bounds_code: + case mp_stop_bounds_code: + break; + case mp_special_code: + break; + } /* all cases are enumerated */ + p=gr_link(p); + } + (void)mp_set_output_file_name (mp, c); + mp_store_true_output_filename (mp, c); + ss = xstrdup(mp->name_of_file); + cairo_surface_flush (mp->png->surface); + cairo_destroy (mp->png->cr); + bitmap.data = cairo_image_surface_get_data (mp->png->surface); + bitmap.width = cairo_image_surface_get_width (mp->png->surface); + bitmap.height = cairo_image_surface_get_height (mp->png->surface); + mp_png_save_to_file (mp, &bitmap, ss, colormodel); + cairo_surface_destroy (mp->png->surface); + free(ss); + return 1; +} + +@ @(mplibpng.h@>= +#ifndef MPLIBPNG_H +#define MPLIBPNG_H 1 +int mp_png_ship_out (mp_edge_object *hh, const char *options) ; +#endif + +@ @c +int mp_png_ship_out (mp_edge_object *hh, const char *options) { + return mp_png_gr_ship_out (hh, options, (int)true); +} + |