Index: gfig.c =================================================================== RCS file: /cvs/gnome/gimp/plug-ins/gfig/gfig.c,v retrieving revision 1.64 diff -p -b -B -u -d -r1.64 gfig.c --- gfig.c 2000/09/21 21:28:44 1.64 +++ gfig.c 2000/11/18 15:52:14 @@ -94,6 +94,8 @@ extern void * gdk_root_parent; #include "pix_data.h" +#include + /***** Magic numbers *****/ #define PREVIEW_SIZE 400 @@ -243,6 +245,7 @@ typedef enum STAR, SPIRAL, BEZIER, + FFT, MOVE_OBJ, MOVE_POINT, COPY_OBJ, @@ -486,6 +488,7 @@ typedef struct Dobject static Dobject *obj_creating; /* Object we are creating */ static Dobject *tmp_line; /* Needed when drawing lines */ static Dobject *tmp_bezier; /* Neeed when drawing bezier curves */ +static Dobject *last_fft; /* Neeed when drawing fft figures */ typedef struct DAllObjs { @@ -553,6 +556,32 @@ static GFigObj *gfig_obj_for_menu; /* static GtkWidget *save_menu_item; static GtkWidget *save_button; +static GtkWidget *fft_window = NULL; /* FFT dialog window */ + +#define MAX_FFT_IMP 5 /* at most this many input impulses */ + +typedef struct Impulse { + gint position; /* position of impulse */ + gint magnitude; /* impulses are complex; here's the size... */ + gint angle; /* ...and the angle */ +} Impulse; + +typedef struct FFTdata { + gint bits; /* size of FFT array is 2^fft_bits */ + Impulse imp[MAX_FFT_IMP]; /* input impulses */ +} FFTdata; + +FFTdata curFFT = +{ + 6, /* default size is 64 */ + { + {12, 1, -180}, + {51, 2, 30}, + } +}; + +typedef __complex__ double complex; +typedef gdouble scalar; /* Don't up just like BIGGG source files? */ @@ -622,6 +651,15 @@ static void d_update_bezier static void d_bezier_start (GdkPoint *pnt, gint shift_down); static void d_bezier_end (GdkPoint *pnt, gint shift_down); +static Dobject * d_load_fft (FILE *from); +static void d_draw_fft (Dobject *obj); +static void d_paint_fft (Dobject *obj); +static Dobject * d_copy_fft (Dobject * obj); +static Dobject * d_new_fft (gint x, gint y); +static void d_update_fft (GdkPoint *pnt); +static void d_fft_start (GdkPoint *pnt, gint shift_down); +static void d_fft_end (GdkPoint *pnt, gint shift_down); + static void new_obj_2edit (GFigObj *obj); static Dobject * d_new_ellipse (gint x, gint y); static Dobject * d_load_ellipse (FILE *from); @@ -1116,6 +1154,10 @@ gfig_load_objs (GFigObj *gfig, { obj = d_load_bezier (fp); } + else if (!strcmp (load_buf, "")) + { + obj = d_load_fft (fp); + } else if (!strcmp (load_buf, "")) { obj = d_load_arc (fp); @@ -2048,6 +2090,141 @@ bezier_dialog (void) gtk_widget_show (window); } +static void +fft_adjustment_update (GtkAdjustment *adjustment, + gpointer data) +{ + gint *val; + + // patterned after gimp_int_adjustment_update + + val = (gint *) data; + + g_return_if_fail (val != NULL); + + *val = (gint) (adjustment->value + 0.5); + +#if 0 + if (val == &fft_order) + { + /* refresh the dialog if the order changes */ + + fft_redo_dialog = 1; + +// $$$ +{ + if (doit) + { + gfig_run = TRUE; + gtk_widget_destroy (GTK_WIDGET (data)); + } +} + +// $$$ + gtk_widget_destroy (fft_window); + + fft_dialog (); + } +#endif + + d_update_fft (0); +} + +static void +do_fft_knob ( + GtkWidget *table, + gint col, + gint row, + gchar *text, + gint scale_size, + gint *value, + gint min_val, + gint max_val, + gint step_inc, + gint page_inc) +{ + + GtkObject *size_data; + + size_data = gimp_scale_entry_new (GTK_TABLE (table), col, row, + text, scale_size, 0, + *value, min_val, max_val, 1, 8, 0, + TRUE, 0, 0, + NULL, NULL); + gtk_signal_connect (GTK_OBJECT (size_data), "value_changed", + GTK_SIGNAL_FUNC (fft_adjustment_update), + value); +} + +static void do_fft_impulse_knobs (GtkWidget *table, gint nr) +{ + char buf[30]; + + sprintf (buf, _("Impulse %d position:"), nr); + + do_fft_knob (table, 0, nr + 1, buf, 512, &curFFT.imp[nr].position, + 0, (1 << curFFT.bits) - 1, 1, 8); + + do_fft_knob (table, 4, nr + 1, _("Size:"), 0, &curFFT.imp[nr].magnitude, + 0, 20, 1, 5); + + do_fft_knob (table, 8, nr + 1, _("Angle:"), 0, &curFFT.imp[nr].angle, + -180, 180, 1, 30); +} + +static void +fft_dialog (void) +{ + static GtkWidget *window = NULL; + GtkWidget *vbox; + GtkWidget *table; + gint i; + + if (window) + { + gdk_window_raise (window->window); + return; + } + + window = gimp_dialog_new (_("FFT Settings"), "gfig", + gimp_standard_help_func, "filters/gfig.html", + GTK_WIN_POS_MOUSE, + FALSE, FALSE, FALSE, + + _("Close"), gtk_widget_destroy, + NULL, 1, NULL, TRUE, TRUE, + + NULL); + + gtk_signal_connect (GTK_OBJECT (window), "destroy", + GTK_SIGNAL_FUNC (gtk_widget_destroyed), + &window); + + vbox = gtk_vbox_new (FALSE, 2); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 4); + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->vbox), vbox, + FALSE, FALSE, 0); + gtk_widget_show (vbox); + + + table = gtk_table_new (2 + MAX_FFT_IMP, 12, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 4); + gtk_table_set_row_spacings (GTK_TABLE (table), 2); + gtk_container_set_border_width (GTK_CONTAINER (table), 6); + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->vbox), table, + FALSE, FALSE, 0); + gtk_widget_show (table); + + do_fft_knob (table, 0, 0, _("FFT bits (2-12):"), 20, &curFFT.bits, 2, 12, 1, 1); + + for (i = 0; i < MAX_FFT_IMP; i++) { + do_fft_impulse_knobs (table, i); + } + + gtk_widget_show (window); + fft_window = window; +} + static gint poly_button_press (GtkWidget *widget, GdkEventButton *event, @@ -2095,6 +2272,17 @@ bezier_button_press (GtkWidget *wid return FALSE; } +static gint +fft_button_press (GtkWidget *widget, + GdkEventButton *event, + gpointer data) +{ + if ((event->type == GDK_2BUTTON_PRESS) && + (event->button == 1)) + fft_dialog (); + return FALSE; +} + static GtkWidget * draw_buttons (GtkWidget *ww) { @@ -2170,6 +2358,18 @@ draw_buttons (GtkWidget *ww) _("Create bezier curve. " "Shift + Button ends object creation."), NULL); + button = but_with_pix (fft_xpm, &group, FFT); + gtk_box_pack_start (GTK_BOX (vbox), button, TRUE, TRUE, 0); + gtk_widget_show (button); + gtk_signal_connect (GTK_OBJECT (button), "button_press_event", + GTK_SIGNAL_FUNC (fft_button_press), + NULL); + + gimp_help_set_help_data (button, + _("Create FFT curve. " + "Control + Button inserts a zero. " + "Shift + Button ends object creation."), NULL); + button = but_with_pix (move_obj_xpm, &group, MOVE_OBJ); gtk_container_add (GTK_CONTAINER (vbox), button); gtk_widget_show (button); @@ -5861,6 +6061,7 @@ toggle_obj_type (GtkWidget *widget, case STAR: case SPIRAL: case BEZIER: + case FFT: default: ctype = GDK_CROSSHAIR; break; @@ -6105,6 +6306,10 @@ static void free_one_obj (Dobject *obj) { d_delete_dobjpoints (obj->points); + + if (obj->type == FFT) + g_free (obj->type_data); + g_free (obj); } @@ -10849,7 +11053,611 @@ d_bezier_end (GdkPoint *pnt, gint shift_ } } +/************* FFT harmongraph generator ********************/ + +static complex c (scalar a, scalar b) +{ + complex z; + + __real__ z = a; + __imag__ z = b; + + return z; +} + +static scalar cmod (complex z) +{ + return pow (__real__ z * __real__ z + __imag__ z * __imag__ z, 0.5); +} + +static scalar carg (complex z) +{ + scalar sign = __imag__ z < 0.0 ? -1.0 : 1.0; + + if (__imag__ z == 0.0) return 0.0; + + return sign * acos (__real__ z / cmod (z)); +} + +static complex cpolar (complex z) +{ + complex z2; + + __real__ z2 = cmod (z); + __imag__ z2 = carg (z); + + return z2; +} + +static complex crect (complex z) +{ + complex z2; + + __real__ z2 = __real__ z * cos (__imag__ z); + __imag__ z2 = __real__ z * sin (__imag__ z); + + return z2; +} + +static void +do_fft_bit_reversal (complex *f, gint N) +{ + gint i, j, m = N >> 1; + + f--; + + j = 1; + for (i = 1; i <= N; i++) + { + if (i < j) + { + complex temp = f[j]; + f[j] = f[i]; + f[i] = temp; + } + if (m < j) + { + while (m < j) + { + j -= m; + m = (m + 1) >> 1; + } + } + j += m; + } +} + +static void +do_fft_butterflies (complex *f, gint N) +{ + gint mmax, step, index, m, i, j; + scalar theta, sign = -1.0; /* sign = 1.0 for inverse fft */ + complex w, temp; + + f--; + + mmax = 1; + while (N > mmax) + { + step = mmax << 1; + for (m = 1; m <= mmax; m++) + { + theta = G_PI * sign * (scalar)(m - 1) / (scalar)mmax; + __real__ w = cos (theta); + __imag__ w = sin (theta); + for (i = 1; i <= (N - m) / step + 1; i++) + { + index = m + (i - 1) * step; + j = index + mmax; + temp = w * f[j]; + f[j] = f[index] - temp; + f[index] = f[index] + temp; + } + } + mmax = step; + } +} + +static void +do_fft (complex *f, gint N) +{ + do_fft_bit_reversal (f, N); + do_fft_butterflies (f, N); +} + +/* + * For the FFT object, the type_data field points to an FFTdata + * structure. The first point in the object's points array specifies + * the center point to which the FFT output will be translated. + * The second point specifies the overall radius and rotation, + * relative to the center point. + */ + +static void +ComputeFFT (complex center, complex outer, FFTdata *FFTdp) +{ + gint N = 1 << FFTdp->bits; + complex impulse[N]; + Impulse *impp = FFTdp->imp; + gint i; + complex disp; + scalar result_size, scale; + complex xform[3]; + + /* + * disp is the vector difference between the outer and center + * control points. We'll need it in polar form: the magnitude + * (modulus) will be used to normalize the output, and the + * angle (argument) will be used to rotate the output. + */ + + disp = outer - center; + disp = cpolar (disp); + + printf ("ComputeFFT: bits=%d N=%d trans=(%g,%g) disp=(%g@%g)\n", FFTdp->bits, N, + __real__ center, __imag__ center, __real__ disp, __imag__ disp); + + /* fill in the input array */ + + memset(impulse, 0, sizeof impulse); + + for (i = 0; i < MAX_FFT_IMP; i++) + { + int index = impp[i].position; /* index */ + complex imp = crect (c (impp[i].magnitude, impp[i].angle * G_PI / 180.0)); + + if (index >= 0 && index < N) + impulse[index] = imp; + } + + /* do the FFT */ + + do_fft (impulse, N); + + /* + * Scan the result and find its size so it can be normalized to + * the size specified by the outer control point. + */ + + result_size = 1e-60; + for (i = 0; i < N; i++) + { + scalar modulus = cmod (impulse[i]); + if (modulus > result_size) + result_size = modulus; + } + + scale = __real__ disp / 1.4 / result_size; + + /* precompute the output transformation matrix + * + * rotate: + * cos(a) -sin(a) 0 + * sin(a) cos(a) 0 + * 0 0 1 + * scale: + * sx 0 0 + * 0 sy 0 + * 0 0 1 + * translate: + * 0 0 0 + * 0 0 0 + * tx ty 1 + * + * For our case, rotate and scale first, then translate: + * s*cos(a) -s*sin(a) 0 (xform[0]) + * s*sin(a) s*cos(a) 0 (xform[1]) + * tx ty 1 (xform[2]) + * + * Since we can throw out the third column, we'll cheat + * by using complex types. + */ + + __imag__ disp = - __imag__ disp; /* swap the spin */ + __real__ xform[0] = __imag__ xform[1] = scale * cos (__imag__ disp); + __real__ xform[1] = -(__imag__ xform[0] = -scale * sin (__imag__ disp)); + xform[2] = center; + + /* transform and render each point */ + + for (i = 0; i < N; i++) + { + complex imp = impulse[i]; + complex pt = imp * xform[0] + imp * xform[1] + xform[2]; + + if (i > 0) + { + fp_pnt_add (__real__ impulse[i-1], __imag__ impulse[i-1], + __real__ pt, __imag__ pt); + } + if (i == N - 1) + { + /* close the figure */ + fp_pnt_add (__real__ pt, __imag__ pt, + __real__ impulse[0], __imag__ impulse[0]); + } + + impulse[i] = pt; /* save the transformed pt for next iter */ + } +} + +static void +d_save_fft (Dobject *obj, + FILE *to) +{ + DobjPoints *spnt; + + spnt = obj->points; + + if (!spnt) + return; /* End-of-line */ + + fprintf (to, "\n"); + +// $$$ to implement + + while (spnt) + { + fprintf (to, "%d %d\n", + (gint)spnt->pnt.x, + (gint)spnt->pnt.y); + spnt = spnt->next; + } + + fprintf (to, "\n"); + fprintf (to, "%d\n\n", (gint) obj->type_data); + fprintf (to, "\n"); +} + +/* Load an FFT from the specified stream */ + +static Dobject * +d_load_fft (FILE *from) +{ + Dobject *new_obj = NULL; + gint xpnt; + gint ypnt; + gchar buf[MAX_LOAD_LINE]; + +// $$$ to implement + +#ifdef DEBUG + printf ("Load bezier called\n"); +#endif /* DEBUG */ + + while (get_line (buf, MAX_LOAD_LINE, from, 0)) + { + if (sscanf (buf, "%d %d", &xpnt, &ypnt) != 2) + { + /* Must be the end */ + if (!strcmp ("", buf)) + { + gint nsides = 3; + /* Number of sides - data item */ + if ( !new_obj) + { + g_message ("[%d] Internal load error while loading bezier " + "(extra area)", line_no); + return NULL; + } + get_line (buf, MAX_LOAD_LINE, from, 0); + if (sscanf (buf, "%d", &nsides) != 1) + { + g_message ("[%d] Internal load error while loading bezier " + "(extra area scanf)", line_no); + return NULL; + } + new_obj->type_data = (gpointer) nsides; + get_line (buf, MAX_LOAD_LINE, from, 0); + if (strcmp ("", buf)) + { + g_message ("[%d] Internal load error while loading bezier", + line_no); + return NULL; + } + /* Go around and read the last line */ + continue; + } + else if (strcmp ("", buf)) + { + g_message ("[%d] Internal load error while loading bezier", + line_no); + return NULL; + } + return new_obj; + } + + if (!new_obj) + new_obj = d_new_fft (xpnt, ypnt); + else + d_pnt_add_line (new_obj, xpnt, ypnt, -1); + } + + return new_obj; +} + +static void +d_fft_result (void) +{ + gint i, x0, y0, x1, y1; + + g_assert ((fp_pnt_cnt % 4) == 0); + +//printf ("d_fft_result: %d pts, drawing_pic=%d\n", fp_pnt_cnt, drawing_pic); + + for (i = 0 ; i < fp_pnt_cnt; i += 4) + { + x0 = (gint) fp_pnt_pnts[i]; + y0 = (gint) fp_pnt_pnts[i + 1]; + x1 = (gint) fp_pnt_pnts[i + 2]; + y1 = (gint) fp_pnt_pnts[i + 3]; + + if (drawing_pic) + { + gdk_draw_line (pic_preview->window, + pic_preview->style->black_gc, + adjust_pic_coords ((gint) x0, + preview_width), + adjust_pic_coords ((gint) y0, + preview_height), + adjust_pic_coords ((gint) x1, + preview_width), + adjust_pic_coords ((gint) y1, + preview_height)); + } + else + { + gdk_draw_line (gfig_preview->window, + gfig_gc, + gfig_scale_x ((gint) x0), + gfig_scale_y ((gint) y0), + gfig_scale_x ((gint) x1), + gfig_scale_y ((gint) y1)); + } + } +} + +static void +d_draw_fft (Dobject *obj) +{ + DobjPoints * spnt; + complex center, outer; + DobjPoints * center_pnt; + + center_pnt = spnt = obj->points; + + if (!spnt) + return; /* no control pts! */ + + __real__ center = spnt->pnt.x; + __imag__ center = spnt->pnt.y; + + spnt = spnt->next; + if (!spnt) + return; /* hum.... */ + + __real__ outer = spnt->pnt.x; + __imag__ outer = spnt->pnt.y; + + draw_sqr (¢er_pnt->pnt); + draw_sqr (&spnt->pnt); + + fp_pnt_start (); + ComputeFFT (center, outer, (FFTdata *) obj->type_data); + +//gdk_gc_set_function (gfig_gc, GDK_CLEAR); + d_fft_result (); +//gdk_gc_set_function (gfig_gc, GDK_INVERT); +} + +static void +d_paint_fft (Dobject *obj) +{ + DobjPoints * spnt; + gdouble *line_pnts; + gint i; + complex center, outer; + + spnt = obj->points; + + if (!spnt) + return; /* no control pts! */ + + __real__ center = spnt->pnt.x; + __imag__ center = spnt->pnt.y; + spnt = spnt->next; + if (!spnt) + return; /* hum.... */ + + __real__ outer = spnt->pnt.x; + __imag__ outer = spnt->pnt.y; + + spnt = spnt->next; + + fp_pnt_start (); + ComputeFFT (center, outer, (FFTdata *) obj->type_data); + line_pnts = d_bz_get_array (&i); + + /* Scale before drawing */ + if (selvals.scaletoimage) + scale_to_original_xy (&line_pnts[0], i / 2); + else + scale_to_xy (&line_pnts[0], i / 2); + + /* One go */ + if (selvals.painttype == PAINT_BRUSH_TYPE) + { + gfig_paint (selvals.brshtype, + gfig_drawable, + i, line_pnts); + } + else + { + gimp_free_select (gfig_image, + i, line_pnts, + selopt.type, + selopt.antia, + selopt.feather, + selopt.feather_radius); + } +} + +static Dobject * +d_copy_fft (Dobject * obj) +{ + Dobject *np; + FFTdata *FFTdp, *FFTnew; + +#if DEBUG + printf ("Copy FFT\n"); +#endif /*DEBUG*/ + if (!obj) + return (NULL); + + g_assert (obj->type == FFT); + + FFTdp = (FFTdata *) obj->type_data; + if (!FFTdp) + return (NULL); + + np = d_new_fft (obj->points->pnt.x, obj->points->pnt.y); + + np->points->next = d_copy_dobjpoints (obj->points->next); + + FFTnew = (FFTdata *) g_new0 (FFTdata, 1); + *FFTnew = *FFTdp; + + np->type_data = (gpointer) FFTnew; + +#if DEBUG + printf ("Done FFT copy\n"); +#endif /*DEBUG*/ + return np; +} + +static Dobject * +d_new_fft (gint x, gint y) +{ + Dobject *nobj; + DobjPoints *npnt; + FFTdata *FFTnew; + + /* Get new object and starting point */ + + /* Start point */ + npnt = g_new0 (DobjPoints, 1); + +#if DEBUG + printf ("New FFT start at (%x,%x)\n", x, y); +#endif /* DEBUG */ + npnt->pnt.x = x; + npnt->pnt.y = y; + + nobj = g_new0 (Dobject, 1); + + nobj->type = FFT; + + FFTnew = (FFTdata *) g_new0 (FFTdata, 1); + *FFTnew = curFFT; + nobj->type_data = (gpointer)FFTnew; + + nobj->points = npnt; + nobj->drawfunc = d_draw_fft; + nobj->loadfunc = d_load_fft; + nobj->savefunc = d_save_fft; + nobj->paintfunc = d_paint_fft; + nobj->copyfunc = d_copy_fft; + + return (nobj); +} + +static void +d_update_fft (GdkPoint *pnt) +{ + DobjPoints *center_pnt, *outer_pnt; + Dobject *obj_cur = obj_creating; + gint saved_cnt_pnt = selvals.opts.showcontrol; + + /* Undraw last one then draw new one */ + + if (!obj_cur) + obj_cur = last_fft; + + if (!obj_cur) + return; + + center_pnt = obj_cur->points; + + if (!center_pnt) + return; /* No points */ + + if ((outer_pnt = center_pnt->next)) + { + /* Undraw */ + + draw_circle (&outer_pnt->pnt); + +// gdk_gc_set_function (gfig_gc, GDK_SET); + selvals.opts.showcontrol = 0; + d_draw_fft (obj_cur); +// d_fft_result (); +// gdk_gc_set_function (gfig_gc, GDK_INVERT); + + if (pnt) + { + outer_pnt->pnt.x = pnt->x; + outer_pnt->pnt.y = pnt->y; + } + } + else if (pnt) + { + /* First edge point */ + d_pnt_add_line (obj_cur, pnt->x, pnt->y, -1); + outer_pnt = center_pnt->next; + } + else + { + return; /* pointless */ + } + + /* update the impulse control points */ + + *(FFTdata *) obj_cur->type_data = curFFT; + + /* draw it */ + + selvals.opts.showcontrol = 0; + d_draw_fft (obj_cur); + selvals.opts.showcontrol = saved_cnt_pnt; + + draw_circle (&outer_pnt->pnt); +} + +/* first point is center + * next defines the radius + */ + +static void +d_fft_start (GdkPoint *pnt, + gint shift_down) +{ + gint16 x, y; + /* First is center point */ + last_fft = obj_creating = d_new_fft (x = pnt->x, y = pnt->y); +} + +static void +d_fft_end (GdkPoint *pnt, + gint shift_down) +{ + draw_circle (pnt); + add_to_all_obj (current_obj, obj_creating); + obj_creating = NULL; +} + +/************** end FFT **********************/ + /* copy objs */ static DAllObjs * copy_all_objs (DAllObjs *objs) @@ -11089,6 +11896,7 @@ object_operation (GdkPoint *to_pnt, case STAR: case SPIRAL: case BEZIER: + case FFT: do_move_obj (operation_obj, to_pnt); break; default: @@ -11108,6 +11916,7 @@ object_operation (GdkPoint *to_pnt, case STAR: case SPIRAL: case BEZIER: + case FFT: do_move_obj_pnt (operation_obj, to_pnt); break; default: @@ -11177,6 +11986,10 @@ object_start (GdkPoint *pnt, draw_sqr (pnt); d_bezier_start (pnt, shift_down); break; + case FFT: + draw_sqr (pnt); + d_fft_start (pnt, shift_down); + break; default: /* Internal error */ break; @@ -11225,6 +12038,10 @@ object_end (GdkPoint *pnt, case BEZIER: d_bezier_end (pnt, shift_down); break; + case FFT: + draw_sqr (pnt); + d_fft_end (pnt, shift_down); + break; default: /* Internal error */ break; @@ -11267,6 +12084,9 @@ object_update (GdkPoint *pnt) break; case BEZIER: d_update_bezier (pnt); + break; + case FFT: + d_update_fft (pnt); break; default: /* Internal error */ Index: pix_data.h =================================================================== RCS file: /cvs/gnome/gimp/plug-ins/gfig/pix_data.h,v retrieving revision 1.4 diff -p -b -B -u -d -r1.4 pix_data.h --- pix_data.h 1998/03/20 02:39:19 1.4 +++ pix_data.h 2000/11/18 15:52:14 @@ -475,6 +475,39 @@ static char * bezier_xpm[] = { /* XPM */ +static char * fft_xpm[] = { +"24 24 4 1", +" s None c None", +". c red", +"X c white", +"o c black", +" ", +" ... o ... ", +" .X. o o .X. ", +" ... o o ... ", +" o o ", +" o ", +" ooo ... ooo ", +" o o.X.o o ", +" ooo ... ooo ", +" o ", +" o o ", +" ... o o ", +" .X. o o ", +" ... o ", +" ", +" ", +" ", +" oooo ooooooooo ", +" o o o ", +" ooo ooo o ", +" o o o ", +" o o o ", +" o o o ", +" "}; + + +/* XPM */ static char * rulers_comp_xpm[] = { "74 85 192 2", " c None",