File indexing completed on 2024-12-22 04:04:14

0001 /* Copyright (C) 2001-2019 Peter Selinger.
0002    This file is part of Potrace. It is free software and it is covered
0003    by the GNU General Public License. See the file COPYING for details. */
0004 
0005 #ifdef HAVE_CONFIG_H
0006 #include <config.h>
0007 #endif
0008 
0009 #include <stdio.h>
0010 #include <stdlib.h>
0011 #include <math.h>
0012 #include <string.h>
0013 
0014 #include "render.h"
0015 #include "greymap.h"
0016 #include "auxiliary.h"
0017 
0018 /* ---------------------------------------------------------------------- */
0019 /* routines for anti-aliased rendering of curves */
0020 
0021 /* we use the following method. Given a point (x,y) (with real-valued
0022    coordinates) in the plane, let (xi,yi) be the integer part of the
0023    coordinates, i.e., xi=floor(x), yi=floor(y). Define a path from
0024    (x,y) to infinity as follows: path(x,y) =
0025    (x,y)--(xi+1,y)--(xi+1,yi)--(+infty,yi).  Now as the point (x,y)
0026    moves smoothly across the plane, the path path(x,y) sweeps
0027    (non-smoothly) across a certain area. We proportionately blacken
0028    the area as the path moves "downward", and we whiten the area as
0029    the path moves "upward". This way, after the point has traversed a
0030    closed curve, the interior of the curve has been darkened
0031    (counterclockwise movement) or lightened (clockwise movement). (The
0032    "grey shift" is actually proportional to the winding number). By
0033    choosing the above path with mostly integer coordinates, we achieve
0034    that only pixels close to (x,y) receive grey values and are subject
0035    to round-off errors. The grey value of pixels far away from (x,y)
0036    is always in "integer" (where 0=black, 1=white).  As a special
0037    trick, we keep an accumulator rm->a1, which holds a double value to
0038    be added to the grey value to be added to the current pixel
0039    (xi,yi).  Only when changing "current" pixels, we convert this
0040    double value to an integer. This way we avoid round-off errors at
0041    the meeting points of line segments. Another speedup measure is
0042    that we sometimes use the rm->incrow_buf array to postpone
0043    incrementing or decrementing an entire row. If incrow_buf[y]=x+1!=0,
0044    then all the pixels (x,y),(x+1,y),(x+2,y),... are scheduled to be
0045    incremented/decremented (which one is the case will be clear from 
0046    context). This keeps the greymap operations reasonably local. */
0047 
0048 /* allocate a new rendering state */
0049 render_t *render_new(greymap_t *gm) {
0050   render_t *rm;
0051 
0052   rm = (render_t *) malloc(sizeof(render_t));
0053   if (!rm) {
0054     return NULL;
0055   }
0056   memset(rm, 0, sizeof(render_t));
0057   rm->gm = gm;
0058   rm->incrow_buf = (int *) calloc(gm->h, sizeof(int));
0059   if (!rm->incrow_buf) {
0060     free(rm);
0061     return NULL;
0062   }
0063   return rm;
0064 }
0065 
0066 /* free a given rendering state. Note: this does not free the
0067    underlying greymap. */
0068 void render_free(render_t *rm) {
0069   free(rm->incrow_buf);
0070   free(rm);
0071 }
0072 
0073 /* close path */
0074 void render_close(render_t *rm) {
0075   if (rm->x0 != rm->x1 || rm->y0 != rm->y1) {
0076     render_lineto(rm, rm->x0, rm->y0);
0077   }
0078   GM_INC(rm->gm, rm->x0i, rm->y0i, (rm->a0+rm->a1)*255);
0079 
0080   /* assert (rm->x0i != rm->x1i || rm->y0i != rm->y1i); */
0081   
0082   /* the persistent state is now undefined */
0083 }
0084 
0085 /* move point */
0086 void render_moveto(render_t *rm, double x, double y) {
0087   /* close the previous path */
0088   render_close(rm);
0089 
0090   rm->x0 = rm->x1 = x;
0091   rm->y0 = rm->y1 = y;
0092   rm->x0i = (int)floor(rm->x0);
0093   rm->x1i = (int)floor(rm->x1);
0094   rm->y0i = (int)floor(rm->y0);
0095   rm->y1i = (int)floor(rm->y1);
0096   rm->a0 = rm->a1 = 0;
0097 }
0098 
0099 /* add b to pixels (x,y) and all pixels to the right of it. However,
0100    use rm->incrow_buf as a buffer to economize on multiple calls */
0101 static void incrow(render_t *rm, int x, int y, int b) {
0102   int i, x0;
0103 
0104   if (y < 0 || y >= rm->gm->h) {
0105     return;
0106   }
0107 
0108   if (x < 0) {
0109     x = 0;
0110   } else if (x > rm->gm->w) {
0111     x = rm->gm->w;
0112   }
0113   if (rm->incrow_buf[y] == 0) {
0114     rm->incrow_buf[y] = x+1; /* store x+1 so that we can use 0 for "vacant" */
0115     return;
0116   }
0117   x0 = rm->incrow_buf[y]-1;
0118   rm->incrow_buf[y] = 0;
0119   if (x0 < x) {
0120     for (i=x0; i<x; i++) {
0121       GM_INC(rm->gm, i, y, -b);
0122     }
0123   } else {
0124     for (i=x; i<x0; i++) {
0125       GM_INC(rm->gm, i, y, b);
0126     }
0127   }    
0128 }
0129 
0130 /* render a straight line */
0131 void render_lineto(render_t *rm, double x2, double y2) {
0132   int x2i, y2i;
0133   double t0=2, s0=2;
0134   int sn, tn;
0135   double ss=2, ts=2;
0136   double r0, r1;
0137   int i, j;
0138   int rxi, ryi;
0139   int s;
0140 
0141   x2i = (int)floor(x2);
0142   y2i = (int)floor(y2);
0143 
0144   sn = abs(x2i - rm->x1i);
0145   tn = abs(y2i - rm->y1i);
0146 
0147   if (sn) {
0148     s0 = ((x2>rm->x1 ? rm->x1i+1 : rm->x1i) - rm->x1)/(x2-rm->x1);
0149     ss = fabs(1.0/(x2-rm->x1));
0150   }
0151   if (tn) {
0152     t0 = ((y2>rm->y1 ? rm->y1i+1 : rm->y1i) - rm->y1)/(y2-rm->y1);
0153     ts = fabs(1.0/(y2-rm->y1));
0154   }
0155 
0156   r0 = 0;
0157 
0158   i = 0;
0159   j = 0;
0160 
0161   rxi = rm->x1i;
0162   ryi = rm->y1i;
0163 
0164   while (i<sn || j<tn) {
0165     if (j>=tn || (i<sn && s0+i*ss < t0+j*ts)) {
0166       r1 = s0+i*ss;
0167       i++;
0168       s = 1;
0169     } else {
0170       r1 = t0+j*ts;
0171       j++;
0172       s = 0;
0173     }
0174     /* render line from r0 to r1 segment of (rm->x1,rm->y1)..(x2,y2) */
0175     
0176     /* move point to r1 */
0177     rm->a1 += (r1-r0)*(y2-rm->y1)*(rxi+1-((r0+r1)/2.0*(x2-rm->x1)+rm->x1));
0178 
0179     /* move point across pixel boundary */
0180     if (s && x2>rm->x1) {
0181       GM_INC(rm->gm, rxi, ryi, rm->a1*255);
0182       rm->a1 = 0;
0183       rxi++;
0184       rm->a1 += rm->y1+r1*(y2-rm->y1)-ryi;
0185     } else if (!s && y2>rm->y1) {
0186       GM_INC(rm->gm, rxi, ryi, rm->a1*255);
0187       rm->a1 = 0;
0188       incrow(rm, rxi+1, ryi, 255);
0189       ryi++;
0190     } else if (s && x2<=rm->x1) {
0191       rm->a1 -= rm->y1+r1*(y2-rm->y1)-ryi;
0192       GM_INC(rm->gm, rxi, ryi, rm->a1*255);
0193       rm->a1 = 0;
0194       rxi--;
0195     } else if (!s && y2<=rm->y1) {
0196       GM_INC(rm->gm, rxi, ryi, rm->a1*255);
0197       rm->a1 = 0;
0198       ryi--;
0199       incrow(rm, rxi+1, ryi, -255);
0200     }
0201 
0202     r0 = r1;
0203   }
0204   
0205   /* move point to (x2,y2) */
0206   
0207   r1 = 1;
0208   rm->a1 += (r1-r0)*(y2-rm->y1)*(rxi+1-((r0+r1)/2.0*(x2-rm->x1)+rm->x1));
0209 
0210   rm->x1i = x2i;
0211   rm->y1i = y2i;
0212   rm->x1 = x2;
0213   rm->y1 = y2;
0214 
0215   /* assert (rxi != rm->x1i || ryi != rm->y1i); */
0216 }
0217 
0218 /* render a Bezier curve. */
0219 void render_curveto(render_t *rm, double x2, double y2, double x3, double y3, double x4, double y4) {
0220   double x1, y1, dd0, dd1, dd, delta, e2, epsilon, t;
0221 
0222   x1 = rm->x1;  /* starting point */
0223   y1 = rm->y1;
0224 
0225   /* we approximate the curve by small line segments. The interval
0226      size, epsilon, is determined on the fly so that the distance
0227      between the true curve and its approximation does not exceed the
0228      desired accuracy delta. */
0229 
0230   delta = .1;  /* desired accuracy, in pixels */
0231 
0232   /* let dd = maximal value of 2nd derivative over curve - this must
0233      occur at an endpoint. */
0234   dd0 = sq(x1-2*x2+x3) + sq(y1-2*y2+y3);
0235   dd1 = sq(x2-2*x3+x4) + sq(y2-2*y3+y4);
0236   dd = 6*sqrt(max(dd0, dd1));
0237   e2 = 8*delta <= dd ? 8*delta/dd : 1;
0238   epsilon = sqrt(e2);  /* necessary interval size */
0239 
0240   for (t=epsilon; t<1; t+=epsilon) {
0241     render_lineto(rm, x1*cu(1-t)+3*x2*sq(1-t)*t+3*x3*(1-t)*sq(t)+x4*cu(t),
0242           y1*cu(1-t)+3*y2*sq(1-t)*t+3*y3*(1-t)*sq(t)+y4*cu(t));
0243   }
0244   render_lineto(rm, x4, y4);
0245 }