Merge branch 'master' into xcircuit-3.10
[xcircuit.git] / graphic.c
blob76f4c65311648682bb02bcb3b03048d4e1bc931f
1 /*----------------------------------------------------------------------*/
2 /* graphic.c --- xcircuit routines handling rendered graphic elements */
3 /* Copyright (c) 2005 Tim Edwards, MultiGiG, Inc. */
4 /*----------------------------------------------------------------------*/
6 /*----------------------------------------------------------------------*/
7 /* written by Tim Edwards, 7/11/05 */
8 /*----------------------------------------------------------------------*/
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <math.h>
15 #ifndef _MSC_VER
16 #include <X11/Intrinsic.h>
17 #include <X11/StringDefs.h>
18 #endif
20 #ifdef TCL_WRAPPER
21 #include <tk.h>
22 #endif
24 /*----------------------------------------------------------------------*/
25 /* Local includes */
26 /*----------------------------------------------------------------------*/
28 #include "xcircuit.h"
29 #include "colordefs.h"
31 /*----------------------------------------------------------------------*/
32 /* Function prototype declarations */
33 /*----------------------------------------------------------------------*/
34 #include "prototypes.h"
36 /*----------------------------------------------------------------------*/
37 /* Global Variable definitions */
38 /*----------------------------------------------------------------------*/
40 extern Display *dpy;
41 extern Globaldata xobjs;
42 extern XCWindowData *areawin;
43 extern colorindex *colorlist;
44 extern int number_colors;
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 /*----------------------------------------------------------------------*/
52 void count_graphics(objectptr thisobj, short *glist)
54 genericptr *ge;
55 graphicptr gp;
56 Imagedata *iptr;
57 int i;
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]++;
69 else if (IS_OBJINST(*ge)) {
70 count_graphics(TOOBJINST(ge)->thisobject, glist);
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 /*----------------------------------------------------------------------*/
82 short *collect_graphics(short *pagelist)
84 short *glist;
85 int i;
87 glist = (short *)malloc(xobjs.images * sizeof(short));
89 for (i = 0; i < xobjs.images; i++) glist[i] = 0;
91 for (i = 0; i < xobjs.pages; i++)
92 if (pagelist[i] > 0)
93 count_graphics(xobjs.pagelist[i]->pageinst->thisobject, glist);
95 return glist;
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 /*----------------------------------------------------------------------*/
108 #ifndef HAVE_CAIRO
109 Boolean transform_graphic(graphicptr gp)
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;
118 tscale = UTopScale();
119 scale = gp->scale * tscale;
120 rotation = gp->rotation + UTopRotation();
122 if (rotation >= 360.0) rotation -= 360.0;
123 else if (rotation < 0.0) rotation += 360.0;
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;
129 cosr = cos(RADFAC * rotation);
130 sinr = sin(RADFAC * rotation);
131 c = (int)(8192 * cosr / scale);
132 s = (int)(8192 * sinr / scale);
134 /* Determine the necessary width and height of the pixmap */
135 /* that fits the rotated and scaled image. */
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;
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++;
150 /* Check whether this instance is going to be off-screen, */
151 /* to avoid excessive computation. */
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);
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;
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;
170 XDestroyImage(gp->target);
172 if (gp->clipmask != (Pixmap)NULL) XFreePixmap(dpy, gp->clipmask);
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);
179 if (gp->target->data == (char *)NULL) {
180 XDestroyImage(gp->target);
181 gp->target = (XImage *)NULL;
182 gp->clipmask = (Pixmap)NULL;
183 return FALSE;
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);
194 XSetForeground(dpy, cmgc, 1);
195 XFillRectangle(dpy, gp->clipmask, cmgc, 0, 0, twidth, theight);
196 XSetForeground(dpy, cmgc, 0);
198 else
199 gp->clipmask = (Pixmap)NULL;
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;
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);
218 gp->tscale = scale;
219 gp->trot = rotation;
220 return TRUE;
222 #endif /* HAVE_CAIRO */
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 /*----------------------------------------------------------------------*/
229 #ifndef HAVE_CAIRO
230 void UDrawGraphic(graphicptr gp)
232 XPoint ppt;
234 /* transform to current scale and rotation, if necessary */
235 if (transform_graphic(gp) == FALSE) return; /* Graphic off-screen */
237 /* transform to current position */
238 UTransformbyCTM(DCTM, &(gp->position), &ppt, 1);
240 /* user_to_window(gp->position, &ppt); */
242 ppt.x -= (gp->target->width >> 1);
243 ppt.y -= (gp->target->height >> 1);
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);
255 else {
256 XSetClipOrigin(dpy, areawin->gc, ppt.x, ppt.y);
257 XSetClipMask(dpy, areawin->gc, gp->clipmask);
261 XPutImage(dpy, areawin->window, areawin->gc,
262 gp->target, 0, 0, ppt.x, ppt.y, gp->target->width,
263 gp->target->height);
265 if (gp->clipmask != (Pixmap)NULL) {
266 if (areawin->clipped <= 0) {
267 XSetClipMask(dpy, areawin->gc, None);
268 XSetClipOrigin(dpy, areawin->gc, 0, 0);
272 #endif /* HAVE_CAIRO */
274 /*----------------------------------------------------------------------*/
275 /* Allocate space for a new graphic source image of size width x height */
276 /*----------------------------------------------------------------------*/
278 Imagedata *addnewimage(char *name, int width, int height)
280 Imagedata *iptr;
281 int screen = DefaultScreen(dpy);
283 /* Create the image and store in the global list of images */
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));
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);
301 return iptr;
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 /*----------------------------------------------------------------------*/
313 graphicptr new_graphic(objinstptr destinst, char *filename, int px, int py)
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];
323 locdestinst = (destinst == NULL) ? areawin->topinstance : destinst;
324 destobject = locdestinst->thisobject;
326 /* Check the existing list of images. If there is a match, */
327 /* re-use the source; don't load the file again. */
329 for (i = 0; i < xobjs.images; i++) {
330 iptr = xobjs.imagelist + i;
331 if (!strcmp(iptr->filename, filename)) {
332 break;
335 if (i == xobjs.images) {
337 fg = fopen(filename, "r");
338 if (fg == NULL) return NULL;
340 /* This ONLY handles binary ppm files with max data = 255 */
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;
350 else fgets(buf, 127, fg);
352 if ((nr <= 0) || strncmp(id, "P6", 2)) return NULL;
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;
362 else fgets(buf, 127, fg);
364 if (width <= 0) return NULL;
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;
374 else fgets(buf, 127, fg);
376 if (height <= 0) return NULL;
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;
386 else fgets(buf, 127, fg);
388 if (imax != 255) return NULL;
390 while (1) {
391 fread(&c, 1, 1, fg);
392 if (c == '\n') break;
393 else if (c == '\0') return NULL;
396 iptr = addnewimage(filename, width, height);
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);
409 iptr->refcount++;
410 NEW_GRAPHIC(gp, destobject);
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 */
426 calcbboxvalues(locdestinst, (genericptr *)gp);
427 updatepagebounds(destobject);
428 incr_changes(destobject);
430 register_for_undo(XCF_Graphic, UNDO_DONE, areawin->topinstance, *gp);
432 return *gp;
435 /*----------------------------------------------------------------------*/
436 /* Create a gradient field graphic */
437 /* For now, gradient field is linear white-to-black size 100x100. */
438 /*----------------------------------------------------------------------*/
440 graphicptr gradient_field(objinstptr destinst, int px, int py, int c1, int c2)
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];
450 locdestinst = (destinst == NULL) ? areawin->topinstance : destinst;
451 destobject = locdestinst->thisobject;
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;
458 /* Create name "gradientXX" */
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;
469 sprintf(id, "gradient%02d", y);
471 width = height = 100; /* Fixed size, at least for now */
473 iptr = addnewimage(id, width, height);
475 r = (colorlist[c1].color.red >> 8);
476 g = (colorlist[c1].color.green >> 8);
477 b = (colorlist[c1].color.blue >> 8);
479 rd = (colorlist[c2].color.red >> 8) - r;
480 gd = (colorlist[c2].color.green >> 8) - g;
481 bd = (colorlist[c2].color.blue >> 8) - b;
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)));
491 iptr->refcount++;
492 NEW_GRAPHIC(gp, destobject);
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 */
508 calcbboxvalues(locdestinst, (genericptr *)gp);
509 updatepagebounds(destobject);
510 incr_changes(destobject);
512 register_for_undo(XCF_Graphic, UNDO_DONE, areawin->topinstance, *gp);
514 return *gp;
518 /*----------------------------------------------------------------------*/
519 /* Free memory associated with the XImage structure for a graphic. */
520 /*----------------------------------------------------------------------*/
522 void freeimage(xcImage *source)
524 int i, j;
525 Imagedata *iptr;
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);
535 /* Remove this from the list of images */
537 for (j = i; j < xobjs.images - 1; j++)
538 *(xobjs.imagelist + j) = *(xobjs.imagelist + j + 1);
539 xobjs.images--;
541 break;
546 /*----------------------------------------------------------------------*/
547 /* Free memory allocated by a graphicptr structure. */
548 /*----------------------------------------------------------------------*/
550 void freegraphic(graphicptr gp)
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;
559 XDestroyImage(gp->target);
561 if (gp->clipmask != (Pixmap)NULL) XFreePixmap(dpy, gp->clipmask);
562 #endif /* HAVE_CAIRO */
563 freeimage(gp->source);
566 /*----------------------------------------------------------------------*/
567 /* A light wrapper around XImage, to a generalized xcImage */
568 /*----------------------------------------------------------------------*/
570 #ifndef HAVE_CAIRO
571 xcImage *xcImageCreate(int width, int height)
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;
580 void xcImageDestroy(xcImage *img)
582 free(img->data);
583 img->data = NULL;
584 XDestroyImage(img);
587 int xcImageGetWidth(xcImage *img)
589 return img->width;
592 int xcImageGetHeight(xcImage *img)
594 return img->height;
597 void xcImagePutPixel(xcImage *img, int x, int y, u_char r, u_char g, u_char b)
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);
610 void xcImageGetPixel(xcImage *img, int x, int y, u_char *r, u_char *g,
611 u_char *b)
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];
622 #endif /* HAVE_CAIRO */