1 /*----------------------------------------------------------------------*/
2 /* graphic.c --- xcircuit routines handling rendered graphic elements	*/
3 /* Copyright (c) 2005  Tim Edwards, MultiGiG, Inc.			*/
4 /*----------------------------------------------------------------------*/
5 
6 /*----------------------------------------------------------------------*/
7 /*	written by Tim Edwards, 7/11/05					*/
8 /*----------------------------------------------------------------------*/
9 
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <math.h>
14 
15 #ifndef _MSC_VER
16 #include <X11/Intrinsic.h>
17 #include <X11/StringDefs.h>
18 #endif
19 
20 #ifdef TCL_WRAPPER
21 #include <tk.h>
22 #endif
23 
24 /*----------------------------------------------------------------------*/
25 /* Local includes							*/
26 /*----------------------------------------------------------------------*/
27 
28 #include "xcircuit.h"
29 #include "colordefs.h"
30 
31 /*----------------------------------------------------------------------*/
32 /* Function prototype declarations					*/
33 /*----------------------------------------------------------------------*/
34 #include "prototypes.h"
35 
36 /*----------------------------------------------------------------------*/
37 /* Global Variable definitions						*/
38 /*----------------------------------------------------------------------*/
39 
40 extern Display  *dpy;
41 extern Globaldata xobjs;
42 extern XCWindowData *areawin;
43 extern colorindex *colorlist;
44 extern int number_colors;
45 
46 /*----------------------------------------------------------------------*/
47 /* Recursive search for graphic images in an object.			*/
48 /* Updates list "glist" when any image is found in an object or its	*/
49 /* descendents.								*/
50 /*----------------------------------------------------------------------*/
51 
count_graphics(objectptr thisobj,short * glist)52 void count_graphics(objectptr thisobj, short *glist)
53 {
54    genericptr *ge;
55    graphicptr gp;
56    Imagedata *iptr;
57    int i;
58 
59    for (ge = thisobj->plist; ge < thisobj->plist + thisobj->parts; ge++) {
60       if (IS_GRAPHIC(*ge)) {
61 	 gp = TOGRAPHIC(ge);
62          for (i = 0; i < xobjs.images; i++) {
63 	    iptr = xobjs.imagelist + i;
64             if (iptr->image == gp->source) {
65 	       glist[i]++;
66 	    }
67 	 }
68       }
69       else if (IS_OBJINST(*ge)) {
70 	 count_graphics(TOOBJINST(ge)->thisobject, glist);
71       }
72    }
73 }
74 
75 /*----------------------------------------------------------------------*/
76 /* Given a list of pages, return a list of indices into the graphics	*/
77 /* buffer area of each graphic used on any of the indicated pages.	*/
78 /* The returned list is allocated and it is the responsibility of the	*/
79 /* calling routine to free it.						*/
80 /*----------------------------------------------------------------------*/
81 
collect_graphics(short * pagelist)82 short *collect_graphics(short *pagelist)
83 {
84    short *glist;
85    int i;
86 
87    glist = (short *)malloc(xobjs.images * sizeof(short));
88 
89    for (i = 0; i < xobjs.images; i++) glist[i] = 0;
90 
91    for (i = 0; i < xobjs.pages; i++)
92       if (pagelist[i] > 0)
93 	 count_graphics(xobjs.pagelist[i]->pageinst->thisobject, glist);
94 
95    return glist;
96 }
97 
98 /*----------------------------------------------------------------------*/
99 /* Generate the target view of the indicated graphic image, combining	*/
100 /* the image's scale and rotation and the zoom factor of the current	*/
101 /* view.								*/
102 /*									*/
103 /* If the graphic is at the wrong scale or rotation but is not redrawn	*/
104 /* because it is outside the screen viewing area, return FALSE. 	*/
105 /* Otherwise, return TRUE.						*/
106 /*----------------------------------------------------------------------*/
107 
108 #ifndef HAVE_CAIRO
transform_graphic(graphicptr gp)109 Boolean transform_graphic(graphicptr gp)
110 {
111     int width, height, twidth, theight;
112     float scale, tscale, rotation, crot;
113     double cosr, sinr;
114     int x, y, c, s, hw, hh, thw, thh, xorig, yorig, xc, yc;
115     int screen = DefaultScreen(dpy);
116     static GC cmgc = (GC)NULL;
117 
118     tscale = UTopScale();
119     scale = gp->scale * tscale;
120     rotation = gp->rotation + UTopRotation();
121 
122     if (rotation >= 360.0) rotation -= 360.0;
123     else if (rotation < 0.0) rotation += 360.0;
124 
125     /* Check if the top-level rotation and scale match the	*/
126     /* saved target image.  If so, then we're done.		*/
127     if ((rotation == gp->trot) && (scale == gp->tscale)) return TRUE;
128 
129     cosr = cos(RADFAC * rotation);
130     sinr = sin(RADFAC * rotation);
131     c = (int)(8192 * cosr / scale);
132     s = (int)(8192 * sinr / scale);
133 
134     /* Determine the necessary width and height of the pixmap	*/
135     /* that fits the rotated and scaled image.			*/
136 
137     crot = rotation;
138     if (crot > 90.0 && crot < 180.0) crot = 180.0 - crot;
139     if (crot > 270.0 && crot < 360.0) crot = 360.0 - crot;
140     cosr = cos(RADFAC * crot);
141     sinr = sin(RADFAC * crot);
142     width = gp->source->width * scale;
143     height = gp->source->height * scale;
144 
145     twidth = (int)(fabs(width * cosr + height * sinr));
146     theight = (int)(fabs(width * sinr + height * cosr));
147     if (twidth & 1) twidth++;
148     if (theight & 1) theight++;
149 
150     /* Check whether this instance is going to be off-screen,	*/
151     /* to avoid excessive computation.				*/
152 
153     UTopOffset(&xc, &yc);
154     xc += (int)((float)gp->position.x * tscale);
155     yc = areawin->height - yc;
156     yc += (int)((float)gp->position.y * tscale);
157 
158     if (xc - (twidth >> 1) > areawin->width) return FALSE;
159     if (xc + (twidth >> 1) < 0) return FALSE;
160     if (yc - (theight >> 1) > areawin->height) return FALSE;
161     if (yc + (theight >> 1) < 0) return FALSE;
162 
163     /* Generate the new target image */
164     if (gp->target != NULL) {
165        if (gp->target->data != NULL) {
166 	  /* Free data first, because we used our own malloc() */
167 	  free(gp->target->data);
168 	  gp->target->data = NULL;
169        }
170        XDestroyImage(gp->target);
171     }
172     if (gp->clipmask != (Pixmap)NULL) XFreePixmap(dpy, gp->clipmask);
173 
174     gp->target = XCreateImage(dpy, DefaultVisual(dpy, screen),
175 			DefaultDepth(dpy, screen), ZPixmap,
176 			0, 0, twidth, theight, 8, 0);
177     gp->target->data = (char *)malloc(theight * gp->target->bytes_per_line);
178 
179     if (gp->target->data == (char *)NULL) {
180        XDestroyImage(gp->target);
181        gp->target = (XImage *)NULL;
182        gp->clipmask = (Pixmap)NULL;
183        return FALSE;
184     }
185 
186     if (rotation != 0.0) {
187        gp->clipmask = XCreatePixmap(dpy, areawin->window, twidth, theight, 1);
188        if (cmgc == (GC)NULL) {
189           XGCValues values;
190           values.foreground = 0;
191           values.background = 0;
192           cmgc = XCreateGC(dpy, gp->clipmask, GCForeground | GCBackground, &values);
193        }
194        XSetForeground(dpy, cmgc, 1);
195        XFillRectangle(dpy, gp->clipmask, cmgc, 0, 0, twidth, theight);
196        XSetForeground(dpy, cmgc, 0);
197     }
198     else
199        gp->clipmask = (Pixmap)NULL;
200 
201     hh = gp->source->height >> 1;
202     hw = gp->source->width >> 1;
203     thh = theight >> 1;
204     thw = twidth >> 1;
205     for (y = -thh; y < thh; y++) {
206 	for (x = -thw; x < thw; x++) {
207 	    xorig = ((x * c + y * s) >> 13) + hw;
208 	    yorig = ((-x * s + y * c) >> 13) + hh;
209 
210 	    if ((xorig >= 0) && (yorig >= 0) &&
211 			(xorig < gp->source->width) && (yorig < gp->source->height))
212 	       XPutPixel(gp->target, x + thw, y + thh,
213 			XGetPixel(gp->source, xorig, yorig));
214 	    else if (gp->clipmask)
215 	       XDrawPoint(dpy, gp->clipmask, cmgc, x + thw, y + thh);
216 	}
217     }
218     gp->tscale = scale;
219     gp->trot = rotation;
220     return TRUE;
221 }
222 #endif /* HAVE_CAIRO */
223 
224 /*----------------------------------------------------------------------*/
225 /* Draw a graphic image by copying from the image to the window.	*/
226 /* Image is centered on the center point of the graphic image.		*/
227 /*----------------------------------------------------------------------*/
228 
229 #ifndef HAVE_CAIRO
UDrawGraphic(graphicptr gp)230 void UDrawGraphic(graphicptr gp)
231 {
232     XPoint ppt;
233 
234     /* transform to current scale and rotation, if necessary */
235     if (transform_graphic(gp) == FALSE) return;  /* Graphic off-screen */
236 
237     /* transform to current position */
238     UTransformbyCTM(DCTM, &(gp->position), &ppt, 1);
239 
240     /* user_to_window(gp->position, &ppt); */
241 
242     ppt.x -= (gp->target->width >> 1);
243     ppt.y -= (gp->target->height >> 1);
244 
245     if (gp->clipmask != (Pixmap)NULL) {
246        if (areawin->clipped > 0) {
247 	  /* Clipmask areawin->clipmask already applied to window. */
248 	  /* Modify existing clipmask with the graphic's.	   */
249 	  XSetFunction(dpy, areawin->cmgc, GXand);
250 	  XCopyArea(dpy, gp->clipmask, areawin->clipmask, areawin->cmgc,
251 		0, 0, gp->target->width, gp->target->height, ppt.x, ppt.y);
252           XSetClipMask(dpy, areawin->gc, areawin->clipmask);
253 	  XSetFunction(dpy, areawin->cmgc, GXcopy);
254        }
255        else {
256           XSetClipOrigin(dpy, areawin->gc, ppt.x, ppt.y);
257           XSetClipMask(dpy, areawin->gc, gp->clipmask);
258        }
259     }
260 
261     XPutImage(dpy, areawin->window, areawin->gc,
262 		gp->target, 0, 0, ppt.x, ppt.y, gp->target->width,
263 		gp->target->height);
264 
265     if (gp->clipmask != (Pixmap)NULL) {
266        if (areawin->clipped <= 0) {
267 	  XSetClipMask(dpy, areawin->gc, None);
268           XSetClipOrigin(dpy, areawin->gc, 0, 0);
269        }
270     }
271 }
272 #endif /* HAVE_CAIRO */
273 
274 /*----------------------------------------------------------------------*/
275 /* Allocate space for a new graphic source image of size width x height	*/
276 /*----------------------------------------------------------------------*/
277 
addnewimage(char * name,int width,int height)278 Imagedata *addnewimage(char *name, int width, int height)
279 {
280    Imagedata *iptr;
281    int screen = DefaultScreen(dpy);
282 
283    /* Create the image and store in the global list of images */
284 
285    xobjs.images++;
286    if (xobjs.imagelist)
287       xobjs.imagelist = (Imagedata *)realloc(xobjs.imagelist,
288 		xobjs.images * sizeof(Imagedata));
289    else
290       xobjs.imagelist = (Imagedata *)malloc(sizeof(Imagedata));
291 
292    /* Save the image source in a file */
293    iptr = xobjs.imagelist + xobjs.images - 1;
294    if (name)
295       iptr->filename = strdup(name);
296    else
297       iptr->filename = NULL;	/* must be filled in later! */
298    iptr->refcount = 0;		/* no calls yet */
299    iptr->image = xcImageCreate(width, height);
300 
301    return iptr;
302 }
303 
304 /*----------------------------------------------------------------------*/
305 /* Create a new graphic image from a PPM file, and position it at the	*/
306 /* indicated (px, py) coordinate in user space.				*/
307 /*									*/
308 /* This should be expanded to incorporate more PPM formats.  Also, it	*/
309 /* needs to be combined with the render.c routines to transform		*/
310 /* PostScript graphics into an internal XImage for faster rendering.	*/
311 /*----------------------------------------------------------------------*/
312 
new_graphic(objinstptr destinst,char * filename,int px,int py)313 graphicptr new_graphic(objinstptr destinst, char *filename, int px, int py)
314 {
315     graphicptr *gp;
316     objectptr destobject;
317     objinstptr locdestinst;
318     Imagedata *iptr;
319     FILE *fg;
320     int nr, width, height, imax, x, y, i; /* nf, (jdk) */
321     char id[5], c, buf[128];
322 
323     locdestinst = (destinst == NULL) ? areawin->topinstance : destinst;
324     destobject = locdestinst->thisobject;
325 
326     /* Check the existing list of images.  If there is a match,	*/
327     /* re-use the source; don't load the file again.		*/
328 
329     for (i = 0; i < xobjs.images; i++) {
330        iptr = xobjs.imagelist + i;
331        if (!strcmp(iptr->filename, filename)) {
332 	  break;
333        }
334     }
335     if (i == xobjs.images) {
336 
337        fg = fopen(filename, "r");
338        if (fg == NULL) return NULL;
339 
340        /* This ONLY handles binary ppm files with max data = 255 */
341 
342        while (1) {
343           nr = fscanf(fg, " %s", buf);
344 	  if (nr <= 0) return NULL;
345 	  if (buf[0] != '#') {
346 	     if (sscanf(buf, "%s", id) <= 0)
347 		return NULL;
348 	     break;
349 	  }
350 	  else fgets(buf, 127, fg);
351        }
352        if ((nr <= 0) || strncmp(id, "P6", 2)) return NULL;
353 
354        while (1) {
355           nr = fscanf(fg, " %s", buf);
356 	  if (nr <= 0) return NULL;
357 	  if (buf[0] != '#') {
358 	     if (sscanf(buf, "%d", &width) <= 0)
359 		return NULL;
360 	     break;
361 	  }
362 	  else fgets(buf, 127, fg);
363        }
364        if (width <= 0) return NULL;
365 
366        while (1) {
367           nr = fscanf(fg, " %s", buf);
368 	  if (nr <= 0) return NULL;
369 	  if (buf[0] != '#') {
370 	     if (sscanf(buf, "%d", &height) <= 0)
371 		return NULL;
372 	     break;
373 	  }
374 	  else fgets(buf, 127, fg);
375        }
376        if (height <= 0) return NULL;
377 
378        while (1) {
379           nr = fscanf(fg, " %s", buf);
380 	  if (nr <= 0) return NULL;
381 	  if (buf[0] != '#') {
382 	     if (sscanf(buf, "%d", &imax) <= 0)
383 		return NULL;
384 	     break;
385 	  }
386 	  else fgets(buf, 127, fg);
387        }
388        if (imax != 255) return NULL;
389 
390        while (1) {
391           fread(&c, 1, 1, fg);
392           if (c == '\n') break;
393           else if (c == '\0') return NULL;
394        }
395 
396        iptr = addnewimage(filename, width, height);
397 
398        /* Read the image data from the PPM file */
399        for (y = 0; y < height; y++)
400           for (x = 0; x < width; x++) {
401 	     u_char r, g, b;
402 	     fread(&r, 1, 1, fg);
403 	     fread(&g, 1, 1, fg);
404 	     fread(&b, 1, 1, fg);
405 	     xcImagePutPixel(iptr->image, x, y, r, g, b);
406           }
407     }
408 
409     iptr->refcount++;
410     NEW_GRAPHIC(gp, destobject);
411 
412     (*gp)->scale = 1.0;
413     (*gp)->position.x = px;
414     (*gp)->position.y = py;
415     (*gp)->rotation = 0.0;
416     (*gp)->color = DEFAULTCOLOR;
417     (*gp)->passed = NULL;
418     (*gp)->source = iptr->image;
419 #ifndef HAVE_CAIRO
420     (*gp)->target = NULL;
421     (*gp)->trot = 0.0;
422     (*gp)->tscale = 0;
423     (*gp)->clipmask = (Pixmap)NULL;
424 #endif /* HAVE_CAIRO */
425 
426     calcbboxvalues(locdestinst, (genericptr *)gp);
427     updatepagebounds(destobject);
428     incr_changes(destobject);
429 
430     register_for_undo(XCF_Graphic, UNDO_DONE, areawin->topinstance, *gp);
431 
432     return *gp;
433 }
434 
435 /*----------------------------------------------------------------------*/
436 /* Create a gradient field graphic					*/
437 /* For now, gradient field is linear white-to-black size 100x100.	*/
438 /*----------------------------------------------------------------------*/
439 
gradient_field(objinstptr destinst,int px,int py,int c1,int c2)440 graphicptr gradient_field(objinstptr destinst, int px, int py, int c1, int c2)
441 {
442     graphicptr *gp;
443     objectptr destobject;
444     objinstptr locdestinst;
445     Imagedata *iptr;
446     int width, height, imax, x, y, i;
447     int r, g, b, rd, gd, bd;
448     char id[11];
449 
450     locdestinst = (destinst == NULL) ? areawin->topinstance : destinst;
451     destobject = locdestinst->thisobject;
452 
453     if (c1 < 0) c1 = 0;
454     if (c1 >= number_colors) c1 = 1;
455     if (c2 < 0) c2 = 0;
456     if (c2 >= number_colors) c2 = 1;
457 
458     /* Create name "gradientXX"	*/
459 
460     y = 0;
461     for (i = 0; i < xobjs.images; i++) {
462        iptr = xobjs.imagelist + i;
463        if (!strncmp(iptr->filename, "gradient", 8)) {
464 	  if (sscanf(iptr->filename + 8, "%2d", &x) == 1)
465 	     if (x >= y)
466 		y = x + 1;
467        }
468     }
469     sprintf(id, "gradient%02d", y);
470 
471     width = height = 100;	/* Fixed size, at least for now */
472 
473     iptr = addnewimage(id, width, height);
474 
475     r = (colorlist[c1].color.red >> 8);
476     g = (colorlist[c1].color.green >> 8);
477     b = (colorlist[c1].color.blue >> 8);
478 
479     rd = (colorlist[c2].color.red >> 8) - r;
480     gd = (colorlist[c2].color.green >> 8) - g;
481     bd = (colorlist[c2].color.blue >> 8) - b;
482 
483     for (y = 0; y < height; y++)
484       for (x = 0; x < width; x++) {
485 	 xcImagePutPixel(iptr->image, x, y,
486 	       r + ((y * rd) / (height - 1)),
487 	       g + ((y * gd) / (height - 1)),
488 	       b + ((y * bd) / (height - 1)));
489       }
490 
491     iptr->refcount++;
492     NEW_GRAPHIC(gp, destobject);
493 
494     (*gp)->scale = 1.0;
495     (*gp)->position.x = px;
496     (*gp)->position.y = py;
497     (*gp)->rotation = 0.0;
498     (*gp)->color = DEFAULTCOLOR;
499     (*gp)->passed = NULL;
500     (*gp)->source = iptr->image;
501 #ifndef HAVE_CAIRO
502     (*gp)->target = NULL;
503     (*gp)->trot = 0.0;
504     (*gp)->tscale = 0;
505     (*gp)->clipmask = (Pixmap)NULL;
506 #endif /* HAVE_CAIRO */
507 
508     calcbboxvalues(locdestinst, (genericptr *)gp);
509     updatepagebounds(destobject);
510     incr_changes(destobject);
511 
512     register_for_undo(XCF_Graphic, UNDO_DONE, areawin->topinstance, *gp);
513 
514     return *gp;
515 }
516 
517 
518 /*----------------------------------------------------------------------*/
519 /* Free memory associated with the XImage structure for a graphic.	*/
520 /*----------------------------------------------------------------------*/
521 
freeimage(xcImage * source)522 void freeimage(xcImage *source)
523 {
524    int i, j;
525    Imagedata *iptr;
526 
527    for (i = 0; i < xobjs.images; i++) {
528       iptr = xobjs.imagelist + i;
529       if (iptr->image == source) {
530 	 iptr->refcount--;
531 	 if (iptr->refcount <= 0) {
532 	    xcImageDestroy(iptr->image);
533 	    free(iptr->filename);
534 
535 	    /* Remove this from the list of images */
536 
537 	    for (j = i; j < xobjs.images - 1; j++)
538 	       *(xobjs.imagelist + j) = *(xobjs.imagelist + j + 1);
539 	    xobjs.images--;
540 	 }
541 	 break;
542       }
543    }
544 }
545 
546 /*----------------------------------------------------------------------*/
547 /* Free memory allocated by a graphicptr structure.			*/
548 /*----------------------------------------------------------------------*/
549 
freegraphic(graphicptr gp)550 void freegraphic(graphicptr gp)
551 {
552 #ifndef HAVE_CAIRO
553    if (gp->target != NULL) {
554       if (gp->target->data != NULL) {
555 	 /* Free data first, because we used our own malloc() */
556 	 free(gp->target->data);
557 	 gp->target->data = NULL;
558       }
559       XDestroyImage(gp->target);
560    }
561    if (gp->clipmask != (Pixmap)NULL) XFreePixmap(dpy, gp->clipmask);
562 #endif /* HAVE_CAIRO */
563    freeimage(gp->source);
564 }
565 
566 /*----------------------------------------------------------------------*/
567 /* A light wrapper around XImage, to a generalized xcImage              */
568 /*----------------------------------------------------------------------*/
569 
570 #ifndef HAVE_CAIRO
xcImageCreate(int width,int height)571 xcImage *xcImageCreate(int width, int height)
572 {
573    int screen = DefaultScreen(dpy);
574    xcImage *img = XCreateImage(dpy, DefaultVisual(dpy, screen),
575 	 DefaultDepth(dpy, screen), ZPixmap, 0, NULL, width, height, 8, 0);
576    img->data = (char *) malloc(height * img->bytes_per_line);
577    return img;
578 }
579 
xcImageDestroy(xcImage * img)580 void xcImageDestroy(xcImage *img)
581 {
582    free(img->data);
583    img->data = NULL;
584    XDestroyImage(img);
585 }
586 
xcImageGetWidth(xcImage * img)587 int xcImageGetWidth(xcImage *img)
588 {
589    return img->width;
590 }
591 
xcImageGetHeight(xcImage * img)592 int xcImageGetHeight(xcImage *img)
593 {
594    return img->height;
595 }
596 
xcImagePutPixel(xcImage * img,int x,int y,u_char r,u_char g,u_char b)597 void xcImagePutPixel(xcImage *img, int x, int y, u_char r, u_char g, u_char b)
598 {
599    union {
600       u_char b[4];
601       u_long i;
602    } pixel;
603    pixel.b[3] = 0;
604    pixel.b[2] = r;
605    pixel.b[1] = g;
606    pixel.b[0] = b;
607    XPutPixel(img, x, y, pixel.i);
608 }
609 
xcImageGetPixel(xcImage * img,int x,int y,u_char * r,u_char * g,u_char * b)610 void xcImageGetPixel(xcImage *img, int x, int y, u_char *r, u_char *g,
611       u_char *b)
612 {
613    union {
614       u_char b[4];
615       u_long i;
616    } pixel;
617    pixel.i = XGetPixel(img, x, y);
618    *r = pixel.b[2];
619    *g = pixel.b[1];
620    *b = pixel.b[0];
621 }
622 #endif /* HAVE_CAIRO */
623 
624