/* * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * This is a plug-in for GIMP. * * Generates images containing vector type drawings. * * Copyright (C) 1997 Andy Thomas alt@picnic.demon.co.uk * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "config.h" #include #include #include #include #include "gfig.h" #include "gfig-dobject.h" #include "gfig-arc.h" #include "libgimp/stdplugins-intl.h" static gdouble dist (gdouble x1, gdouble y1, gdouble x2, gdouble y2); static void mid_point (gdouble x1, gdouble y1, gdouble x2, gdouble y2, gdouble *mx, gdouble *my); static gdouble line_grad (gdouble x1, gdouble y1, gdouble x2, gdouble y2); static gdouble line_cons (gdouble x, gdouble y, gdouble lgrad); static void line_definition (gdouble x1, gdouble y1, gdouble x2, gdouble y2, gdouble *lgrad, gdouble *lconst); static void arc_details (GdkPoint *vert_a, GdkPoint *vert_b, GdkPoint *vert_c, GdkPoint *center_pnt, gdouble *radius); static gdouble arc_angle (GdkPoint *pnt, GdkPoint *center); static void arc_drawing_details (GfigObject *obj, gdouble *minang, GdkPoint *center_pnt, gdouble *arcang, gdouble *radius, gboolean draw_cnts, gboolean do_scale); static void d_draw_arc (GfigObject *obj, cairo_t *cr); static void d_paint_arc (GfigObject *obj); static GfigObject *d_copy_arc (GfigObject *obj); static void d_update_arc_line (GdkPoint *pnt); static void d_update_arc (GdkPoint *pnt); static void d_arc_line_start (GdkPoint *pnt, gboolean shift_down); static void d_arc_line_end (GimpGfig *gfig, GdkPoint *pnt, gboolean shift_down); /* Distance between two points. */ static gdouble dist (gdouble x1, gdouble y1, gdouble x2, gdouble y2) { double s1 = x1 - x2; double s2 = y1 - y2; return sqrt (s1 * s1 + s2 * s2); } /* Mid point of line returned */ static void mid_point (gdouble x1, gdouble y1, gdouble x2, gdouble y2, gdouble *mx, gdouble *my) { *mx = (x1 + x2) / 2.0; *my = (y1 + y2) / 2.0; } /* Careful about infinite grads */ static gdouble line_grad (gdouble x1, gdouble y1, gdouble x2, gdouble y2) { double dx, dy; dx = x1 - x2; dy = y1 - y2; return (dx == 0.0) ? 0.0 : dy / dx; } /* Constant of line that goes through x, y with grad lgrad */ static gdouble line_cons (gdouble x, gdouble y, gdouble lgrad) { return y - lgrad * x; } /* Get grad & const for perpend. line to given points */ static void line_definition (gdouble x1, gdouble y1, gdouble x2, gdouble y2, gdouble *lgrad, gdouble *lconst) { double grad1; double midx, midy; grad1 = line_grad (x1, y1, x2, y2); if (grad1 == 0.0) { #ifdef DEBUG printf ("Infinite grad....\n"); #endif /* DEBUG */ return; } mid_point (x1, y1, x2, y2, &midx, &midy); /* Invert grad for perpen gradient */ *lgrad = -1.0 / grad1; *lconst = line_cons (midx, midy,*lgrad); } /* Arch details * Given three points get arc radius and the co-ords * of center point. */ static void arc_details (GdkPoint *vert_a, GdkPoint *vert_b, GdkPoint *vert_c, GdkPoint *center_pnt, gdouble *radius) { /* Only vertices are in whole numbers - everything else is in doubles */ double ax, ay; double bx, by; double cx, cy; double len_a, len_b, len_c; double sum_sides2; double area; double circumcircle_R; double line1_grad = 0, line1_const = 0; double line2_grad = 0, line2_const = 0; double inter_x = 0.0, inter_y = 0.0; int got_x = 0, got_y = 0; ax = (double) (vert_a->x); ay = (double) (vert_a->y); bx = (double) (vert_b->x); by = (double) (vert_b->y); cx = (double) (vert_c->x); cy = (double) (vert_c->y); len_a = dist (ax, ay, bx, by); len_b = dist (bx, by, cx, cy); len_c = dist (cx, cy, ax, ay); sum_sides2 = (fabs (len_a) + fabs (len_b) + fabs (len_c))/2; /* Area */ area = sqrt (sum_sides2 * (sum_sides2 - len_a) * (sum_sides2 - len_b) * (sum_sides2 - len_c)); /* Circumcircle */ circumcircle_R = len_a * len_b * len_c / (4 * area); *radius = circumcircle_R; /* Deal with exceptions - I hate exceptions */ if (ax == bx || ax == cx || cx == bx) { /* vert line -> mid point gives inter_x */ if (ax == bx && bx == cx) { /* Straight line */ double miny = ay; double maxy = ay; if (by > maxy) maxy = by; if (by < miny) miny = by; if (cy > maxy) maxy = cy; if (cy < miny) miny = cy; inter_y = (maxy - miny) / 2 + miny; } else if (ax == bx) { inter_y = (ay - by) / 2 + by; } else if (bx == cx) { inter_y = (by - cy) / 2 + cy; } else { inter_y = (cy - ay) / 2 + ay; } got_y = 1; } if (ay == by || by == cy || ay == cy) { /* Horz line -> midpoint gives inter_y */ if (ay == by && by == cy) { /* Straight line */ double minx = ax; double maxx = ax; if (bx > maxx) maxx = bx; if (bx < minx) minx = bx; if (cx > maxx) maxx = cx; if (cx < minx) minx = cx; inter_x = (maxx - minx) / 2 + minx; } else if (ay == by) { inter_x = (ax - bx) / 2 + bx; } else if (by == cy) { inter_x = (bx - cx) / 2 + cx; } else { inter_x = (cx - ax) / 2 + ax; } got_x = 1; } if (!got_x || !got_y) { /* At least two of the lines are not parallel to the axis */ /*first line */ if (ax != bx && ay != by) line_definition (ax, ay, bx, by, &line1_grad, &line1_const); else line_definition (ax, ay, cx, cy, &line1_grad, &line1_const); /* second line */ if (bx != cx && by != cy) line_definition (bx, by, cx, cy, &line2_grad, &line2_const); else line_definition (ax, ay, cx, cy, &line2_grad, &line2_const); } /* Intersection point */ if (!got_x) inter_x = (line2_const - line1_const) / (line1_grad - line2_grad); if (!got_y) inter_y = line1_grad * inter_x + line1_const; center_pnt->x = (gint) inter_x; center_pnt->y = (gint) inter_y; } static gdouble arc_angle (GdkPoint *pnt, GdkPoint *center) { /* Get angle (in degrees) of point given origin of center */ gint16 shift_x; gint16 shift_y; gdouble offset_angle; shift_x = pnt->x - center->x; shift_y = -pnt->y + center->y; offset_angle = atan2 (shift_y, shift_x); if (offset_angle < 0) offset_angle += 2.0 * G_PI; return offset_angle * 360 / (2.0 * G_PI); } static void arc_drawing_details (GfigObject *obj, gdouble *minang, GdkPoint *center_pnt, gdouble *arcang, gdouble *radius, gboolean draw_cnts, gboolean do_scale) { DobjPoints *pnt1 = NULL; DobjPoints *pnt2 = NULL; DobjPoints *pnt3 = NULL; DobjPoints dpnts[3]; gdouble ang1, ang2, ang3; gdouble maxang; pnt1 = obj->points; if (!pnt1) return; /* Not fully drawn */ pnt2 = pnt1->next; if (!pnt2) return; /* Not fully drawn */ pnt3 = pnt2->next; if (!pnt3) return; /* Still not fully drawn */ if (do_scale) { /* Adjust pnts for scaling */ /* Warning struct copies here! and casting to double <-> int */ /* Too complex fix me - to much hacking */ gdouble xy[2]; int j; dpnts[0] = *pnt1; dpnts[1] = *pnt2; dpnts[2] = *pnt3; pnt1 = &dpnts[0]; pnt2 = &dpnts[1]; pnt3 = &dpnts[2]; for (j = 0 ; j < 3; j++) { xy[0] = dpnts[j].pnt.x; xy[1] = dpnts[j].pnt.y; if (selvals.scaletoimage) scale_to_original_xy (&xy[0], 1); else scale_to_xy (&xy[0], 1); dpnts[j].pnt.x = xy[0]; dpnts[j].pnt.y = xy[1]; } } arc_details (&pnt1->pnt, &pnt2->pnt, &pnt3->pnt, center_pnt, radius); ang1 = arc_angle (&pnt1->pnt, center_pnt); ang2 = arc_angle (&pnt2->pnt, center_pnt); ang3 = arc_angle (&pnt3->pnt, center_pnt); /* Find min/max angle */ maxang = ang1; if (ang3 > maxang) maxang = ang3; *minang = ang1; if (ang3 < *minang) *minang = ang3; if (ang2 > *minang && ang2 < maxang) *arcang = maxang - *minang; else *arcang = maxang - *minang - 360; } static void d_draw_arc (GfigObject *obj, cairo_t *cr) { DobjPoints *pnt1, *pnt2, *pnt3; GdkPoint center_pnt; gdouble radius, minang, arcang; g_assert (obj != NULL); if (!obj) return; pnt1 = obj->points; pnt2 = pnt1 ? pnt1->next : NULL; pnt3 = pnt2 ? pnt2->next : NULL; if (! pnt3) return; draw_sqr (&pnt1->pnt, obj == gfig_context->selected_obj, cr); draw_sqr (&pnt2->pnt, obj == gfig_context->selected_obj, cr); draw_sqr (&pnt3->pnt, obj == gfig_context->selected_obj, cr); arc_drawing_details (obj, &minang, ¢er_pnt, &arcang, &radius, TRUE, FALSE); gfig_draw_arc (center_pnt.x, center_pnt.y, radius, radius, -minang, -(minang + arcang), cr); } static void d_paint_arc (GfigObject *obj) { /* first point center */ /* Next point is radius */ gdouble *line_pnts; gint seg_count = 0; gint i = 0; gdouble ang_grid; gdouble ang_loop; gdouble radius; gint loop; GdkPoint last_pnt = { 0, 0 }; gboolean first = TRUE; GdkPoint center_pnt; gdouble minang, arcang; g_assert (obj != NULL); if (!obj) return; /* No cnt pnts & must scale */ arc_drawing_details (obj, &minang, ¢er_pnt, &arcang, &radius, FALSE, TRUE); seg_count = 360; /* Should make a smoth-ish curve */ /* +3 because we MIGHT do pie selection */ line_pnts = g_new0 (gdouble, 2 * seg_count + 3); /* Lines */ ang_grid = 2.0 * G_PI / 360.0; if (arcang < 0.0) { /* Swap - since we always draw anti-clock wise */ minang += arcang; arcang = -arcang; } minang = minang * (2.0 * G_PI / 360.0); /* min ang is in degrees - need in rads */ for (loop = 0 ; loop < abs ((gint)arcang) ; loop++) { gdouble lx, ly; GdkPoint calc_pnt; ang_loop = (gdouble)loop * ang_grid + minang; lx = radius * cos (ang_loop); ly = -radius * sin (ang_loop); /* y grows down screen and angs measured from x clockwise */ calc_pnt.x = RINT (lx + center_pnt.x); calc_pnt.y = RINT (ly + center_pnt.y); /* Miss out duped pnts */ if (!first) { if (calc_pnt.x == last_pnt.x && calc_pnt.y == last_pnt.y) { continue; } } line_pnts[i++] = calc_pnt.x; line_pnts[i++] = calc_pnt.y; last_pnt = calc_pnt; if (first) { first = FALSE; } } /* One go */ if (obj->style.paint_type == PAINT_BRUSH_TYPE) { gfig_paint (selvals.brshtype, gfig_context->drawable, i, line_pnts); } g_free (line_pnts); } static GfigObject * d_copy_arc (GfigObject *obj) { GfigObject *nc; g_assert (obj->type == ARC); nc = d_new_object (ARC, obj->points->pnt.x, obj->points->pnt.y); nc->points->next = d_copy_dobjpoints (obj->points->next); return nc; } void d_arc_object_class_init (void) { GfigObjectClass *class = &dobj_class[ARC]; class->type = ARC; class->name = "ARC"; class->drawfunc = d_draw_arc; class->paintfunc = d_paint_arc; class->copyfunc = d_copy_arc; class->update = d_update_arc; } /* Update end point of line */ static void d_update_arc_line (GdkPoint *pnt) { DobjPoints *spnt, *epnt; /* Get last but one segment and undraw it - * Then draw new segment in. * always dealing with the static object. */ /* Get start of segments */ spnt = obj_creating->points; if (!spnt) return; /* No points */ if ((epnt = spnt->next)) { g_free (epnt); } epnt = new_dobjpoint (pnt->x, pnt->y); spnt->next = epnt; } static void d_update_arc (GdkPoint *pnt) { DobjPoints *pnt1 = NULL; DobjPoints *pnt2 = NULL; DobjPoints *pnt3 = NULL; /* First two points as line only become arch when third * point is placed on canvas. */ pnt1 = obj_creating->points; if (!pnt1 || !(pnt2 = pnt1->next) || !(pnt3 = pnt2->next)) { d_update_arc_line (pnt); return; /* Not fully drawn */ } /* Update a real curve */ /* Nothing to be done ... */ } static void d_arc_line_start (GdkPoint *pnt, gboolean shift_down) { if (!obj_creating || !shift_down) { /* Must delete obj_creating if we have one */ obj_creating = d_new_object (LINE, pnt->x, pnt->y); } else { /* Contniuation */ d_update_arc_line (pnt); } } void d_arc_start (GdkPoint *pnt, gboolean shift_down) { /* Draw lines to start with -- then convert to an arc */ d_arc_line_start (pnt, TRUE); /* TRUE means multiple pointed line */ } static void d_arc_line_end (GimpGfig *gfig, GdkPoint *pnt, gboolean shift_down) { if (shift_down) { if (tmp_line) { GdkPoint tmp_pnt = *pnt; if (need_to_scale) { tmp_pnt.x = pnt->x * scale_x_factor; tmp_pnt.y = pnt->y * scale_y_factor; } d_pnt_add_line (tmp_line, tmp_pnt.x, tmp_pnt.y, -1); free_one_obj (obj_creating); /* Must free obj_creating */ } else { tmp_line = obj_creating; add_to_all_obj (gfig, gfig_context->current_obj, obj_creating); } obj_creating = d_new_object (LINE, pnt->x, pnt->y); } else { if (tmp_line) { GdkPoint tmp_pnt = *pnt; if (need_to_scale) { tmp_pnt.x = pnt->x * scale_x_factor; tmp_pnt.y = pnt->y * scale_y_factor; } d_pnt_add_line (tmp_line, tmp_pnt.x, tmp_pnt.y, -1); free_one_obj (obj_creating); /* Must free obj_creating */ } else { add_to_all_obj (gfig, gfig_context->current_obj, obj_creating); } obj_creating = NULL; tmp_line = NULL; } /*gtk_widget_queue_draw (gfig_context->preview);*/ } void d_arc_end (GimpGfig *gfig, GdkPoint *pnt, gboolean shift_down) { /* Under control point */ if (!tmp_line || !tmp_line->points || !tmp_line->points->next) { /* No arc created - yet. Must have three points */ d_arc_line_end (gfig, pnt, TRUE); } else { /* Complete arc */ /* Convert to an arc ... */ tmp_line->type = ARC; tmp_line->class = &dobj_class[ARC]; d_arc_line_end (gfig, pnt, FALSE); if (need_to_scale) { selvals.scaletoimage = 0; } gtk_widget_queue_draw (gfig_context->preview); if (need_to_scale) { selvals.scaletoimage = 1; } } }