% 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 .
%
% 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
#include
#include
#include
#include
#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
@
@
@ 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"
const char *COMPILED_CAIRO_VERSION_STRING = CAIRO_VERSION_STRING;
#include "pixman.h"
const char *COMPILED_PIXMAN_VERSION_STRING = PIXMAN_VERSION_STRING;
#define PNG_SKIP_SETJMP_CHECK 1
#include "png.h"
#include "mplib.h"
#include "mpmp.h"
#include "mplibps.h"
typedef struct pngout_data_struct {
@
} pngout_data_struct ;
@
#endif
@ @=
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
@=
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.
@=
integer dx;
integer dy;
@
@=
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);
}
}
@ @=
static void mp_png_color_out (MP mp, mp_graphic_object *p);
@ This is the information that comes from a pen
@=
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
@=
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|.
@=
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;
}
@ Cairo does not want to draw a path that consists of only a moveto,
so make sure there is some kind of line even for single-pair paths.
@c
static void mp_png_path_out (MP mp, mp_gr_knot h) {
mp_gr_knot p, q; /* for scanning the path */
int steps = 0;
cairo_move_to (mp->png->cr, gr_x_coord(h),gr_y_coord(h));
p=h;
do {
if ( gr_right_type(p)==mp_endpoint ) {
if (steps==0) {
cairo_line_to (mp->png->cr, gr_x_coord(p),gr_y_coord(p));
}
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 {
cairo_line_to (mp->png->cr, gr_x_coord(q),gr_y_coord(q));
}
p=q;
steps++;
} while (p!=h);
if ((gr_x_coord(p) == gr_x_coord(h)) && (gr_y_coord(p) == gr_y_coord(h)) &&
gr_right_type(p)!=mp_endpoint) {
cairo_close_path(mp->png->cr);
}
}
@ Now for outputting the actual graphic objects.
@=
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;
}
}
@ @=
void mp_reallocate_psfonts (MP mp, int l);
@ @=
mp_edge_object **psfonts;
int font_max;
int last_fnum;
@ @=
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);
cairo_save(mp->png->cr);
if ( transformed ) {
cairo_matrix_t matrix = {0,0,0,0,0,0};
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');
/* $wd/100$ is the size in PS point , i.e, $wd =100\cdot real\_wd$
but without considering scaling.
We have a scale factor of
$(ds/1000.0)\cdot scf$
so to match the scale wd should be
$1000\cdot real_wd\cdot scf /(ds\cdot scf)$
i.e. $wd = 10\cdot wd/ds$.
*/
wd *= 10.0/ds;
cairo_translate(mp->png->cr,wd,0);
}
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 $$\&{\}\ldots\&{\}$$
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.
@=
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 (fill_also) {
cairo_save(mp->png->cr);
mp_png_path_out(mp, gr_path_p((mp_stroked_object *)h));
cairo_close_path (mp->png->cr);
cairo_fill (mp->png->cr);
cairo_restore(mp->png->cr);
}
cairo_save(mp->png->cr);
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 (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);
cairo_restore(mp->png->cr);
} else {
cairo_stroke (mp->png->cr);
}
cairo_restore(mp->png->cr);
}
@ Here is a simple routine that just fills a cycle.
@=
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) {
cairo_save(mp->png->cr);
mp_png_path_out(mp, p);
cairo_close_path (mp->png->cr);
cairo_fill (mp->png->cr);
cairo_restore(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))=
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.
@=
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.
@=
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.
@=
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 = strchr(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);
}