src/lib/pscmGfx.c 1.29
1.29 2010/06/05 19:29:53 braney
add support for 32-bit color (make USE_PNG have global consequence)
Index: src/lib/pscmGfx.c
===================================================================
RCS file: /projects/compbio/cvsroot/kent/src/lib/pscmGfx.c,v
retrieving revision 1.28
retrieving revision 1.29
diff -b -B -U 1000000 -r1.28 -r1.29
--- src/lib/pscmGfx.c 31 Mar 2010 00:07:43 -0000 1.28
+++ src/lib/pscmGfx.c 5 Jun 2010 19:29:53 -0000 1.29
@@ -1,779 +1,779 @@
/* pscmGfx - routines for making postScript output seem a
* lot like 256 color bitmap output.
*
* This file is copyright 2002 Jim Kent, but license is hereby
* granted for all use - public, private or commercial. */
#include <math.h>
#ifdef MACHTYPE_sparc
#include <ieeefp.h>
int isinf(double x) { return !finite(x) && x==x; }
#endif
#include "common.h"
#include "hash.h"
#include "memgfx.h"
#include "gfxPoly.h"
#include "colHash.h"
#include "psGfx.h"
#include "pscmGfx.h"
#include "gemfont.h"
#include "vGfx.h"
#include "vGfxPrivate.h"
static char const rcsid[] = "$Id$";
static struct pscmGfx *boxPscm; /* Used to keep from drawing the same box again
* and again with no other calls between. This
* ends up cutting down the file size by 5x
* in the whole chromosome case of the browser. */
void pscmSetHint(struct pscmGfx *pscm, char *hint, char *value)
/* set a hint */
{
if (!value) return;
if (sameString(value,""))
{
hashRemove(pscm->hints, hint);
}
struct hashEl *el = hashLookup(pscm->hints, hint);
if (el)
{
freeMem(el->val);
el->val = cloneString(value);
}
else
{
hashAdd(pscm->hints, hint, cloneString(value));
}
}
char *pscmGetHint(struct pscmGfx *pscm, char *hint)
/* get a hint */
{
return hashOptionalVal(pscm->hints, hint, "");
}
int pscmGetFontPixelHeight(struct pscmGfx *pscm, MgFont *font)
/* How high in pixels is font? */
{
return font_cel_height(font);
}
int pscmGetFontStringWidth(struct pscmGfx *pscm, MgFont *font, char *string)
/* How wide is a string? */
{
return fnstring_width(font, (unsigned char *)string, strlen(string));
}
void pscmSetClip(struct pscmGfx *pscm, int x, int y, int width, int height)
/* Set clipping rectangle. */
{
double x2 = x + width;
double y2 = y + height;
pscm->clipMinX = x;
pscm->clipMinY = y;
pscm->clipMaxX = x2; /* one beyond actual last pixel */
pscm->clipMaxY = y2;
/* adjust to pixel-centered coordinates */
x2 -= 1;
y2 -= 1;
double x1 = x;
double y1 = y;
/* pad a half-pixel all the way around the box */
x1 -= 0.5;
y1 -= 0.5;
x2 += 0.5;
y2 += 0.5;
psClipRect(pscm->ps, x1, y1, x2-x1, y2-y1);
}
void pscmUnclip(struct pscmGfx *pscm)
/* Set clipping rect to cover full thing. */
{
pscmSetClip(pscm, 0, 0, pscm->ps->userWidth, pscm->ps->userHeight);
}
static Color pscmClosestColor(struct pscmGfx *pscm,
unsigned char r, unsigned char g, unsigned char b)
/* Returns closest color in color map to r,g,b */
{
struct rgbColor *c = pscm->colorMap;
int closestDist = 0x7fffffff;
int closestIx = -1;
int dist, dif;
int i;
for (i=0; i<pscm->colorsUsed; ++i)
{
dif = c->r - r;
dist = dif*dif;
dif = c->g - g;
dist += dif*dif;
dif = c->b - b;
dist += dif*dif;
if (dist < closestDist)
{
closestDist = dist;
closestIx = i;
}
++c;
}
return closestIx;
}
static Color pscmAddColor(struct pscmGfx *pscm,
unsigned char r, unsigned char g, unsigned char b)
/* Adds color to end of color map if there's room. */
{
int colIx = pscm->colorsUsed;
struct rgbColor *c = pscm->colorMap + pscm->colorsUsed;
c->r = r;
c->g = g;
c->b = b;
pscm->colorsUsed += 1;
colHashAdd(pscm->colorHash, r, g, b, colIx);;
return (Color)colIx;
}
int pscmFindColorIx(struct pscmGfx *pscm, int r, int g, int b)
/* Returns closest color in color map to rgb values. If it doesn't
* already exist in color map and there's room, it will create
* exact color in map. */
{
struct colHashEl *che;
if (r>255||g>255||b>255)
errAbort("RGB values out of range (0-255). r:%d g:%d b:%d", r, g, b);
if ((che = colHashLookup(pscm->colorHash, r, g, b)) != NULL)
return che->ix;
if (pscm->colorsUsed < 256)
return pscmAddColor(pscm, r, g, b);
return pscmClosestColor(pscm, r, g, b);
}
struct rgbColor pscmColorIxToRgb(struct pscmGfx *pscm, int colorIx)
/* Return rgb value at color index. */
{
return pscm->colorMap[colorIx];
}
static void pscmSetDefaultColorMap(struct pscmGfx *pscm)
/* Set up default color map for a memGfx. */
{
/* Note dependency in order here and in MG_WHITE, MG_BLACK, etc. */
int i;
for (i=0; i<ArraySize(mgFixedColors); ++i)
{
struct rgbColor *c = &mgFixedColors[i];
pscmFindColorIx(pscm, c->r, c->g, c->b);
}
}
struct pscmGfx *pscmOpen(int width, int height, char *file)
/* Return new pscmGfx. */
{
struct pscmGfx *pscm;
AllocVar(pscm);
pscm->ps = psOpen(file, width, height, 72.0 * 7.5, 0, 0);
psTranslate(pscm->ps,0.5,0.5); /* translate all coordinates to pixel centers */
pscm->colorHash = colHashNew();
pscmSetDefaultColorMap(pscm);
pscm->clipMinX = pscm->clipMinY = 0;
pscm->clipMaxX = width;
pscm->clipMaxY = height;
pscm->hints = hashNew(6);
return pscm;
}
void pscmClose(struct pscmGfx **pPscm)
/* Finish writing out and free structure. */
{
struct pscmGfx *pscm = *pPscm;
if (pscm != NULL)
{
psClose(&pscm->ps);
colHashFree(&pscm->colorHash);
freez(pPscm);
}
}
void pscmSetColor(struct pscmGfx *pscm, int colorIx)
/* Set color to index. */
{
struct rgbColor *col = pscm->colorMap + colorIx;
if (colorIx != pscm->curColor)
{
psSetColor(pscm->ps, col->r, col->g, col->b);
pscm->curColor = colorIx;
}
}
void pscmBoxToPs(struct pscmGfx *pscm, int x, int y,
int width, int height)
/* adjust coordinates for PS */
{
/* Do some clipping here to make the postScript
* easier to edit in illustrator. */
double x2 = x + width;
double y2 = y + height;
if (x < pscm->clipMinX) x = pscm->clipMinX;
if (y < pscm->clipMinY) y = pscm->clipMinY;
if (x2 > pscm->clipMaxX) x2 = pscm->clipMaxX;
if (y2 > pscm->clipMaxY) y2 = pscm->clipMaxY;
/* adjust to pixel-centered coordinates */
x2 -= 1;
y2 -= 1;
double x1 = x;
double y1 = y;
/* pad a half-pixel all the way around the box */
x1 -= 0.5;
y1 -= 0.5;
x2 += 0.5;
y2 += 0.5;
psDrawBox(pscm->ps, x1, y1, x2-x1, y2-y1);
}
void pscmBox(struct pscmGfx *pscm, int x, int y,
int width, int height, int color)
/* Draw a box. */
{
/* When viewing whole chromosomes the browser tends
* to draw the same little vertical tick over and
* over again. This tries to remove the worst of
* the redundancy anyway. */
static int lx, ly, lw, lh, lc=-1;
if (x != lx || y != ly || width != lw || height != lh || color != lc ||
pscm != boxPscm)
{
pscmSetColor(pscm, color);
pscmBoxToPs(pscm, x, y, width, height);
lx = x;
ly = y;
lw = width;
lh = height;
lc = color;
boxPscm = pscm;
}
}
void pscmDot(struct pscmGfx *pscm, int x, int y, int color)
/* Set a dot. */
{
pscmBox(pscm, x, y, 1, 1, color);
}
-static void pscmVerticalSmear(struct pscmGfx *pscm,
+static void pscmVerticalSmear8(struct pscmGfx *pscm,
int xOff, int yOff, int width, int height,
unsigned char *dots, boolean zeroClear)
/* Put a series of one 'pixel' width vertical lines. */
{
int x, i;
struct psGfx *ps = pscm->ps;
Color c;
for (i=0; i<width; ++i)
{
x = xOff + i;
c = dots[i];
if (c != 0 || !zeroClear)
{
pscmSetColor(pscm, c);
psDrawBox(ps, x, yOff, 1, height);
}
}
}
static void pscmSetFont(struct pscmGfx *pscm, MgFont *font)
/* Set font. */
{
/* For now we basically still get the sizing info from the old
* gem fonts. I'm not sure how to get sizing info out of
* PostScript. We'll try and arrange it so that the PostScript
* fonts match the gem fonts more or less. */
void *v = font;
if (v != pscm->curFont)
{
psTimesFont(pscm->ps, font->psHeight);
pscm->curFont = v;
}
}
void pscmText(struct pscmGfx *pscm, int x, int y, int color,
MgFont *font, char *text)
/* Draw a line of text with upper left corner x,y. */
{
pscmSetColor(pscm, color);
pscmSetFont(pscm, font);
psTextAt(pscm->ps, x, y, text);
boxPscm = NULL;
}
void pscmTextRight(struct pscmGfx *pscm, int x, int y, int width, int height,
int color, MgFont *font, char *text)
/* Draw a line of text right justified in box defined by x/y/width/height */
{
pscmSetColor(pscm, color);
pscmSetFont(pscm, font);
psTextRight(pscm->ps, x, y, width, height, text);
boxPscm = NULL;
}
void pscmTextCentered(struct pscmGfx *pscm, int x, int y,
int width, int height, int color, MgFont *font, char *text)
/* Draw a line of text centered in box defined by x/y/width/height */
{
pscmSetColor(pscm, color);
pscmSetFont(pscm, font);
psTextCentered(pscm->ps, x, y, width, height, text);
boxPscm = NULL;
}
void pscmFillUnder(struct pscmGfx *pscm, int x1, int y1, int x2, int y2,
int bottom, Color color)
/* Draw a 4 sided filled figure that has line x1/y1 to x2/y2 at
* it's top, a horizontal line at bottom at it's bottom, and
* vertical lines from the bottom to y1 on the left and bottom to
* y2 on the right. */
{
pscmSetColor(pscm, color);
psFillUnder(pscm->ps, x1, y1, x2, y2, bottom);
boxPscm = NULL;
}
void psPolyFindExtremes(struct gfxPoly *poly,
int *pMinX, int *pMaxX,
int *pMinY, int *pMaxY)
/* find min and max of x and y */
{
struct gfxPoint *p = poly->ptList;
int minX, maxX, minY, maxY;
minX = maxX = p->x;
minY = maxY = p->y;
for (;;)
{
p = p->next;
if (p == poly->ptList)
break;
if (minX > p->x) minX = p->x;
if (minY > p->y) minY = p->y;
if (maxX < p->x) maxX = p->x;
if (maxY < p->y) maxY = p->y;
}
*pMinX = minX;
*pMaxX = maxX;
*pMinY = minY;
*pMaxY = maxY;
}
double gfxSlope(struct gfxPoint *q, struct gfxPoint *p)
/* determine slope from two gfxPoints */
{
double dx = p->x - q->x;
double dy = p->y - q->y;
return dy/dx;
}
boolean colinearPoly(struct gfxPoly *poly)
/* determine if points are co-linear */
{
if (poly->ptCount < 2) return TRUE;
struct gfxPoint *q = poly->ptList, *p=q->next;
double m0 = gfxSlope(q,p);
for (;;)
{
p = p->next;
if (p == poly->ptList)
break;
double m1 = gfxSlope(q,p);
if (!(isinf(m0) && isinf(m1)) && (m1 != m0))
return FALSE;
}
return TRUE;
}
double fatPixel(double c, double center, double fat)
/* fatten coordinate by scaling */
{
return center+((c-center)*fat);
}
void pscmPolyFatten(struct psPoly *psPoly,
int minX, int maxX, int minY, int maxY)
/* Fatten polygon by finding the center and then
* scaling by 0.5 pixel from the center in all directions.
* Caller assures dx and dy will NOT be zero. */
{
int dx = maxX - minX;
int dy = maxY - minY;
double centerX = (maxX + minX) / 2.0;
double centerY = (maxY + minY) / 2.0;
double fatX = (dx + 1.0)/dx;
double fatY = (dy + 1.0)/dy;
struct psPoint *p = psPoly->ptList;
for (;;)
{
p->x = fatPixel(p->x, centerX, fatX);
p->y = fatPixel(p->y, centerY, fatY);
p = p->next;
if (p == psPoly->ptList)
break;
}
}
void findLineParams(double x1, double y1, double x2, double y2, double *m, double *b)
/* Return parameters slope m and y-intercept b of equation for line from x1,y1 to x2,y2,
* vertical lines return infinite slope with b = x value instead */
{
double dx = x2 - x1;
double dy = y2 - y1;
*m = dy/dx;
if (dx == 0)
*b = x1;
else
*b = y1 - (*m)*x1;
}
double bDeltaForParallelLine(double d, double m)
/* find delta-b value for parallel line at distance d for line slope m */
{
return d * sqrt(1 + m*m); /* do not call with m == infinity */
}
void adjustBForParallelLine(boolean cw, double m,
double x1, double y1,
double x2, double y2,
double *b)
/* adjust the b value for parallel line at distance d for line slope m
* cw if clockwise */
{
if (isinf(m)) /* handle vertical lines */
{
if ((cw && (y2 < y1)) || (!cw && (y2 > y1)))
*b += 1; /* b holds x-value rather than y-intercept */
else
*b -= 1;
}
else
{
/* Y axis is increasing downwards */
double bDelta = bDeltaForParallelLine(1.0, m);
if ((cw && (x2 > x1)) || (!cw && (x2 < x1)))
*b += bDelta;
else
*b -= bDelta;
}
}
void findLinesIntersection(boolean cw, double m0, double b0, double m1, double b1,
double px, double py, boolean posDeltaX, boolean posDeltaY,
double *ix, double *iy)
/* find intersection between two lines */
{
/* colinear vert */
if ((isinf(m0) && isinf(m1)) && (b0 == b1))
{
if (cw ^ posDeltaY)
*ix = px+1;
else
*ix = px-1;
*iy = py;
}
/* colinear horiz */
else if (((m0==0)&&(m1==0)) && (b0 == b1))
{
if (cw ^ posDeltaX)
*iy = py+1;
else
*iy = py-1;
*ix = px;
}
/* colinear */
else if ((m0 == m1) && (b0 == b1))
{
/* inner point shifted 1 pixel away from the point,
* moving perpendicular to m0 towards the center */
double dx, dy;
double m = -1/m0;
dx = sqrt(1/(1+m*m));
dy = m * dx;
if (!(cw ^ posDeltaX))
{
*ix = px + dx;
*iy = py + dy;
}
else
{
*ix = px - dx;
*iy = py - dy;
}
}
/* non-colinear */
else
{
if (isinf(m0) && isinf(m1))
{ /* should be handled earlier by colinear vert lines above */
errAbort("pscmGfx: m0 and m1 both inf, shouldn't get here");
}
else if (!isinf(m0) && isinf(m1))
{
*ix = b1;
*iy = m0*(*ix)+b0;
}
else if (isinf(m0) && !isinf(m1))
{
*ix = b0;
*iy = m1*(*ix)+b1;
}
else if (!isinf(m0) && !isinf(m1))
{
*ix = -(b1-b0)/(m1-m0);
*iy = m0*(*ix)+b0;
}
}
}
boolean pscmIsClockwise(struct psPoly *psPoly)
/* determine if polygon points are in clockwise order or not
* using cross-product sum*/
{
struct psPoint *p = psPoly->ptList,*q;
double x0, y0;
double x1, y1;
double x2, y2;
double crossProd = 0;
x1 = p->x;
y1 = p->y;
p = p->next;
x2 = p->x;
y2 = p->y;
p = p->next;
q = p;
for (;;)
{
x0 = x1;
y0 = y1;
x1 = x2;
y1 = y2;
x2 = p->x;
y2 = p->y;
crossProd += ((x1-x0)*(y2-y1) - (y1-y0)*(x2-x1));
p = p->next;
if (p == q)
break;
}
return (crossProd > 0);
}
void pscmPolyTrapStrokeOutline(struct pscmGfx *pscm, struct psPoly *psPoly)
/* Stroke a fattened polygon using trapezoids.
* Make each stroke-segment of the poly be made of a 4-sided trapezoidal figure
* whose inner edge is parallel 1 pixel away and the points are found
* by finding the intersections of these parallel lines. */
{
struct psPoint *p = psPoly->ptList,*q;
double px0, py0; /* outer points */
double px1, py1;
double m0,b0;
double m1,b1;
double ix0,iy0; /* inner points */
double ix1,iy1;
boolean cw = pscmIsClockwise(psPoly);
px1 = p->x;
py1 = p->y;
p = p->next;
findLineParams(px1, py1, p->x, p->y, &m1, &b1);
adjustBForParallelLine(cw, m1, px1, py1, p->x, p->y, &b1);
px0 = px1;
py0 = py1;
px1 = p->x;
py1 = p->y;
p = p->next;
m0 = m1;
b0 = b1;
findLineParams(px1, py1, p->x, p->y, &m1, &b1);
adjustBForParallelLine(cw, m1, px1, py1, p->x, p->y, &b1);
findLinesIntersection(cw,m0,b0,m1,b1,px1,py1,
(px1<px0) ^ (m0 < 0),
(py1>py0),
&ix1,&iy1);
px0 = px1;
py0 = py1;
px1 = p->x;
py1 = p->y;
p = p->next;
q = p;
for (;;)
{
m0 = m1;
b0 = b1;
ix0 = ix1;
iy0 = iy1;
findLineParams(px1, py1, p->x, p->y, &m1, &b1);
adjustBForParallelLine(cw, m1, px1, py1, p->x, p->y, &b1);
findLinesIntersection(cw,m0,b0,m1,b1,px1,py1,
(px1<px0) ^ (m0 < 0),
(py1>py0),
&ix1,&iy1);
struct psPoly *inner = psPolyNew();
psPolyAddPoint(inner,px0, py0);
psPolyAddPoint(inner,px1, py1);
psPolyAddPoint(inner,ix1, iy1);
psPolyAddPoint(inner,ix0, iy0);
psDrawPoly(pscm->ps, inner, TRUE);
psPolyFree(&inner);
px0 = px1;
py0 = py1;
px1 = p->x;
py1 = p->y;
p = p->next;
if (p == q)
break;
}
}
void pscmDrawPoly(struct pscmGfx *pscm, struct gfxPoly *poly, Color color,
boolean filled)
/* Draw a polygon, possibly filled, in color. */
{
struct gfxPoint *p = poly->ptList;
struct psPoly *psPoly = psPolyNew();
int minX, maxX, minY, maxY;
if (poly->ptCount < 1) /* nothing to do */
{
return;
}
psPolyFindExtremes(poly, &minX, &maxX, &minY, &maxY);
/* check for co-linear polygon, render as line instead */
if (colinearPoly(poly))
{
pscmLine(pscm, minX, minY, maxX, maxY, color);
return;
}
pscmSetColor(pscm, color);
/* convert pixel coords to real values */
for (;;)
{
psPolyAddPoint(psPoly,p->x, p->y);
p = p->next;
if (p == poly->ptList)
break;
}
boolean fat = sameString(pscmGetHint(pscm,"fat"),"on");
if (fat)
pscmPolyFatten(psPoly, minX, maxX, minY, maxY);
if (fat && !filled)
pscmPolyTrapStrokeOutline(pscm,psPoly);
else
psDrawPoly(pscm->ps, psPoly, filled);
psPolyFree(&psPoly);
}
void pscmFatLine(struct pscmGfx *pscm, double x1, double y1, double x2, double y2)
/* Draw a line from x1/y1 to x2/y2 by making a filled polygon.
* This also avoids some problems with stroke-width variation
* from different postscript implementations. */
{
struct psPoly *psPoly = psPolyNew();
double cX = (x1+x2)/2.0;
double cY = (y1+y2)/2.0;
double fX;
double fY;
/* fatten by lengthing line by 0.5 at each end */
fX = fY = 1.0+0.5*sqrt(0.5);
/* expand the length of the line by a half-pixel on each end */
x1 = fatPixel(x1,cX,fX);
x2 = fatPixel(x2,cX,fX);
y1 = fatPixel(y1,cY,fY);
y2 = fatPixel(y2,cY,fY);
/* calculate 4 corners {h,i,j,k} of the rectangle covered */
double m = (y2 - y1)/(x2 - x1);
m = -1/m; /* rotate slope 90 degrees */
double ddX = sqrt( (0.5*0.5) / (m*m+1) );
double ddY = m * ddX;
double hX = x1-ddX, hY = y1-ddY;
double iX = x1+ddX, iY = y1+ddY;
double jX = x2+ddX, jY = y2+ddY;
double kX = x2-ddX, kY = y2-ddY;
psPolyAddPoint(psPoly,hX,hY);
psPolyAddPoint(psPoly,iX,iY);
psPolyAddPoint(psPoly,jX,jY);
psPolyAddPoint(psPoly,kX,kY);
psDrawPoly(pscm->ps, psPoly, TRUE);
psPolyFree(&psPoly);
}
void pscmLine(struct pscmGfx *pscm,
int x1, int y1, int x2, int y2, int color)
/* Draw a line from one point to another. */
{
pscmSetColor(pscm, color);
boolean fat = sameString(pscmGetHint(pscm,"fat"),"on");
if (fat)
pscmFatLine(pscm, x1, y1, x2, y2);
else
psDrawLine(pscm->ps, x1, y1, x2, y2);
boxPscm = NULL;
}
struct vGfx *vgOpenPostScript(int width, int height, char *fileName)
/* Open up something that will someday be a PostScript file. */
{
struct vGfx *vg = vgHalfInit(width, height);
vg->data = pscmOpen(width, height, fileName);
vg->close = (vg_close)pscmClose;
vg->dot = (vg_dot)pscmDot;
vg->box = (vg_box)pscmBox;
vg->line = (vg_line)pscmLine;
vg->text = (vg_text)pscmText;
vg->textRight = (vg_textRight)pscmTextRight;
vg->textCentered = (vg_textCentered)pscmTextCentered;
vg->findColorIx = (vg_findColorIx)pscmFindColorIx;
vg->colorIxToRgb = (vg_colorIxToRgb)pscmColorIxToRgb;
vg->setClip = (vg_setClip)pscmSetClip;
vg->unclip = (vg_unclip)pscmUnclip;
-vg->verticalSmear = (vg_verticalSmear)pscmVerticalSmear;
+vg->verticalSmear8 = (vg_verticalSmear8)pscmVerticalSmear8;
vg->fillUnder = (vg_fillUnder)pscmFillUnder;
vg->drawPoly = (vg_drawPoly)pscmDrawPoly;
vg->setHint = (vg_setHint)pscmSetHint;
vg->getHint = (vg_getHint)pscmGetHint;
vg->getFontPixelHeight = (vg_getFontPixelHeight)pscmGetFontPixelHeight;
vg->getFontStringWidth = (vg_getFontStringWidth)pscmGetFontStringWidth;
return vg;
}