gimp/plug-ins/gfig/gfig-arc.c

621 lines
13 KiB
C

/*
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* This is a plug-in for the 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 2 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*/
#include "config.h"
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <gtk/gtk.h>
#include <libgimp/gimp.h>
#include "gfig.h"
#include "gfig-dobject.h"
#include "gfig-line.h"
#include "libgimp/stdplugins-intl.h"
/* Distance between two lines */
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, line1_const;
double line2_grad, line2_const;
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);
#ifdef DEBUG
printf ("Vertices (%f,%f), (%f,%f), (%f,%f)\n", ax, ay, bx, by, cx, cy);
#endif /* DEBUG */
len_a = dist (ax, ay, bx, by);
len_b = dist (bx, by, cx, cy);
len_c = dist (cx, cy, ax, ay);
#ifdef DEBUG
printf ("len_a = %f, len_b = %f, len_c = %f\n", len_a, len_b, len_c);
#endif /* DEBUG */
sum_sides2 = (fabs (len_a) + fabs (len_b) + fabs (len_c))/2;
#ifdef DEBUG
printf ("Sum sides / 2 = %f\n", sum_sides2);
#endif /* DEBUG */
/* Area */
area = sqrt (sum_sides2*(sum_sides2 - len_a)*(sum_sides2 - len_b)*(sum_sides2 - len_c));
#ifdef DEBUG
printf ("Area of triangle = %f\n", area);
#endif /* DEBUG */
/* Circumcircle */
circumcircle_R = len_a*len_b*len_c/(4*area);
*radius = circumcircle_R;
#ifdef DEBUG
printf ("Circumcircle radius = %f\n", circumcircle_R);
#endif /* DEBUG */
/* 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 (ax == bx && bx == cx)
{
/* 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 = /*rint*/((line2_const - line1_const)/(line1_grad - line2_grad));
if (!got_y)
inter_y = /*rint*/((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 degress) 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*G_PI;
return offset_angle * 360 / (2*G_PI);
}
static void
arc_drawing_details (Dobject *obj,
gdouble *minang,
GdkPoint *center_pnt,
gdouble *arcang,
gdouble *radius,
gint draw_cnts,
gint 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 (draw_cnts)
{
draw_sqr (&pnt1->pnt);
draw_sqr (&pnt2->pnt);
draw_sqr (&pnt3->pnt);
}
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 (Dobject * obj)
{
GdkPoint center_pnt;
gdouble radius, minang, arcang;
g_assert (obj != NULL);
if (!obj)
return;
arc_drawing_details (obj, &minang, &center_pnt, &arcang, &radius, TRUE,
FALSE);
gfig_draw_arc (center_pnt.x, center_pnt.y, radius, radius, minang, arcang);
}
static void
d_paint_arc (Dobject *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 first_pnt, last_pnt;
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, &center_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*G_PI/(gdouble)360;
if (arcang < 0.0)
{
/* Swap - since we always draw anti-clock wise */
minang += arcang;
arcang = -arcang;
}
minang = minang * (2*G_PI/360); /* 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_pnt = calc_pnt;
first = FALSE;
}
}
/* Reverse line if approp */
if (selvals.reverselines)
reverse_pairs_list (&line_pnts[0], i/2);
/* One go */
if (selvals.painttype == PAINT_BRUSH_TYPE)
{
gfig_paint (selvals.brshtype,
gfig_context->drawable_id,
i, line_pnts);
}
else
{
if (selopt.as_pie)
{
/* Add center point - cause a pie like selection... */
line_pnts[i++] = center_pnt.x;
line_pnts[i++] = center_pnt.y;
}
gimp_free_select (gfig_context->image_id,
i, line_pnts,
selopt.type,
selopt.antia,
selopt.feather,
selopt.feather_radius);
}
g_free (line_pnts);
}
static Dobject *
d_copy_arc (Dobject * obj)
{
Dobject *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 ()
{
DobjClass *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;
}
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_line (pnt);
return; /* Not fully drawn */
}
/* Update a real curve */
/* Nothing to be done ... */
}
void
d_arc_start (GdkPoint *pnt,
gint shift_down)
{
/* Draw lines to start with -- then convert to an arc */
if (!tmp_line)
draw_sqr (pnt);
d_line_start (pnt, TRUE); /* TRUE means multiple pointed line */
}
void
d_arc_end (GdkPoint *pnt,
gint shift_down)
{
/* Under contrl point */
if (!tmp_line ||
!tmp_line->points ||
!tmp_line->points->next)
{
/* No arc created - yet. Must have three points */
d_line_end (pnt, TRUE);
}
else
{
/* Complete arc */
/* Convert to an arc ... */
tmp_line->type = ARC;
tmp_line->class = &dobj_class[ARC];
d_line_end (pnt, FALSE);
/*d_draw_line (newarc); Should undraw line */
if (need_to_scale)
{
selvals.scaletoimage = 0;
}
/*d_draw_arc (newarc);*/
gtk_widget_queue_draw (gfig_context->preview);
if (need_to_scale)
{
selvals.scaletoimage = 1;
}
}
}