From f4285384e780638c72572ca8625e3e7f4cec83ec Mon Sep 17 00:00:00 2001 From: Giles Atkinson <“gatk555@gmail.com”> Date: Wed, 26 Jan 2022 18:53:32 +0000 Subject: [PATCH] Add code in graf.c to merge connected lines with the same slope before plotting. This speeds output and reduces file size when the vector length is much larger than the number of pixels. It should help with Feature Request #58: "Graph plotting under windows redraw very slow and hard to work with", but is not a full fix. --- src/frontend/plotting/graf.c | 185 ++++++++++++++++++++++++++++++++++- 1 file changed, 182 insertions(+), 3 deletions(-) diff --git a/src/frontend/plotting/graf.c b/src/frontend/plotting/graf.c index 648ba104b..4369ae784 100644 --- a/src/frontend/plotting/graf.c +++ b/src/frontend/plotting/graf.c @@ -264,10 +264,169 @@ int gr_init(double *xlims, double *ylims, /* The size of the screen. */ } +/* Once the line compression code is thorougly tested, checking code can + * be removed. But for now ... + */ +#define LINE_COMPRESSION_CHECKS + +/* Data and functions for line compression: + * try to not keep drawing the same pixels by combining co-linear segments. + */ + +static struct { + enum { EMPTY, LINE, VERTICAL } state; + int x_start, y_start, x_end, y_end; + int lc_min, lc_max; +#define prev_x2 lc_min // Alternate name +#ifdef LINE_COMPRESSION_CHECKS + struct dvec *dv; // Sanity checking. +#endif +} LC; + +/* Flush pending line drawing. */ + +static void LC_flush(void) +{ + switch (LC.state) { + case EMPTY: + return; + case LINE: + DevDrawLine(LC.x_start, LC.y_start, LC.x_end, LC.y_end, FALSE); + break; + case VERTICAL: + DevDrawLine(LC.x_start, LC.lc_min, LC.x_start, LC.lc_max, FALSE); + break; + } + LC.state = EMPTY; +} + +/* This replaces DevDrawLine() - low-level line drawing call. */ + +#ifdef LINE_COMPRESSION_CHECKS +static void drawLine(int x1, int y1, int x2, int y2, struct dvec *dv) +{ + if (LC.dv) { + if (LC.dv != dv) { + fprintf(cp_err, "LC: DV changed!\n"); + LC_flush(); + LC.dv = dv; + } + } else { + LC.dv = dv; + if (LC.state != EMPTY) { + fprintf(cp_err, "LC: State %d but DV NULL.\n", (int)LC.state); + LC_flush(); + } + } +#else +static void drawLine(int x1, int y1, int x2, int y2) +{ +#endif + switch (LC.state) { + refill: + LC_flush(); + // Fall through ... + case EMPTY: + if (x1 == x2) { + /* Vertical */ + + LC.state = VERTICAL; + LC.x_start = x1; + LC.y_end = y2; + if (y1 < y2) { + LC.lc_min = y1; + LC.lc_max = y2; + } else { + LC.lc_min = y2; + LC.lc_max = y1; + } + } else { + LC.state = LINE; + LC.prev_x2 = x2; + + /* Store with LC.x_start < LC.x_end. */ + + if (x1 < x2) { + LC.x_start = x1; + LC.y_start = y1; + LC.x_end = x2; + LC.y_end = y2; + } else { + LC.x_start = x2; + LC.y_start = y2; + LC.x_end = x1; + LC.y_end = y1; + } + } + break; + case LINE: + if ((int64_t)(x2 - x1) * (LC.y_end - LC.y_start) != + (int64_t)(y2 - y1) * (LC.x_end - LC.x_start)) { + /* Not in line. */ + + goto refill; + } + if (x1 != LC.prev_x2) { + /* Not contiguous. */ + + if (x1 > LC.x_end) { + if (x2 > LC.x_end) { + /* Hole. */ + + goto refill; + } + LC.x_end = x1; + LC.y_end = y1; + } else if (x1 < LC.x_start) { + if (x2 < LC.x_start) { + /* Hole. */ + + goto refill; + } + LC.x_start = x1; + LC.y_start = y1; + } + } + + if (x2 > LC.x_end) { + LC.x_end = x2; + LC.y_end = y2; + } else if (x2 < LC.x_start) { + LC.x_start = x2; + LC.y_start = y2; + } + LC.prev_x2 = x2; + break; + case VERTICAL: + if (x1 != LC.x_start || x2 != LC.x_start) + goto refill; + if (y1 != LC.y_end) { + /* Not contiguous, check for hole. */ + + if (y1 < LC.lc_min) { + if (y2 < LC.lc_min) + goto refill; + LC.lc_min = y1; + } else if (y1 > LC.lc_max) { + if (y2 > LC.lc_max) + goto refill; + LC.lc_max = y1; + } + } + + if (y2 < LC.lc_min) + LC.lc_min = y2; + else if (y2 > LC.lc_max) + LC.lc_max = y2; + LC.y_end = y2; + break; + } +} + /* * Add a point to the curve we're currently drawing. * Should be in between a gr_init() and a gr_end() - * expect when iplotting, very bad hack + * except when iplotting, very bad hack * Differences from old gr_point: * We save points here, instead of in lower levels. * Assume we are in right context @@ -327,7 +486,11 @@ void gr_point(struct dvec *dv, /* If it's a linear plot, ignore first point since we don't want to connect with oldx and oldy. */ if (np) - DevDrawLine(fromx, fromy, tox, toy, FALSE); +#ifdef LINE_COMPRESSION_CHECKS + drawLine(fromx, fromy, tox, toy, dv); +#else + drawLine(fromx, fromy, tox, toy); +#endif if ((tics = currentgraph->ticdata) != NULL) { for (; *tics < HUGE; tics++) @@ -348,7 +511,11 @@ void gr_point(struct dvec *dv, DatatoScreen(currentgraph, 0.0, currentgraph->datawindow.ymin, &dummy, &ymin); - DevDrawLine(tox, ymin, tox, toy, FALSE); +#ifdef LINE_COMPRESSION_CHECKS + drawLine(tox, ymin, tox, toy, dv); +#else + drawLine(tox, ymin, tox, toy); +#endif break; case PLOT_POINT: /* Here, gi_linestyle is the character used for the point. */ @@ -497,7 +664,15 @@ void drawlegend(GRAPH *graph, int plotno, struct dvec *dv) /* end one plot of a graph */ void gr_end(struct dvec *dv) { + LC_flush(); +#ifdef LINE_COMPRESSION_CHECKS + if (LC.dv && LC.dv != dv) + fprintf(cp_err, "LC: DV changed in gr_end()!\n"); + else + LC.dv = NULL; +#else NG_IGNORE(dv); +#endif DevUpdate(); } @@ -895,6 +1070,10 @@ static int iplot(struct plot *pl, int id) (isreal(v) ? v->v_realdata[len - 2] : realpart(v->v_compdata[len - 2])), len - 1); + LC_flush(); // Disable line compression here .. +#ifdef LINE_COMPRESSION_CHECKS + LC.dv = NULL; // ... and suppress warnings. +#endif } } }