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