3
$Id: GBezier.cc,v 1.47 2001/05/21 13:12:48 neundorf Exp $
4
This file is part of KIllustrator.
5
Copyright (C) 1998 Kai-Uwe Sattler (kus@iti.cs.uni-magdeburg.de)
7
This program is free software; you can redistribute it and/or modify
8
it under the terms of the GNU Library General Public License as
10
the Free Software Foundation; either version 2 of the License, or
11
(at your option) any later version.
13
This program is distributed in the hope that it will be useful,
14
but WITHOUT ANY WARRANTY; without even the implied warranty of
15
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
GNU General Public License for more details.
18
You should have received a copy of the GNU Library General Public License
19
along with this program; if not, write to the Free Software
20
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25
#if defined(__FreeBSD__) || defined(__NetBSD__)
37
#include <qpointarray.h>
41
#include "GDocument.h"
44
static bool bezier_segment_part_contains (int x0, int y0, int x1,int y1,
49
if(pp.x () <= QMAX(x0,x1) + 5 && pp.x() >= QMIN(x0,x1) - 5 &&
50
pp.y () <= QMAX(y0,y1) + 5 && pp.y() >= QMIN(y0,y1) - 5) {
62
if (x0 - 5 <= pp.x () && pp.x () <= x1 + 5) {
63
if (abs (int (x0 - x1)) < 5) {
64
if ((y0 <= pp.y () && pp.y () <= y1) ||
65
(y1 <= pp.y () && pp.y () <= y0)) {
71
m = (float)(y1 - y0) / (float)(x1 - x0);
72
n = (float)y0 - m * (float)x0;
75
xp = ((float) pp.y () - n) / m;
76
if (xp - 5 <= pp.x () && pp.x () <= xp + 5) {
82
yp = m * (float) pp.x () + n;
83
if (yp - 5 <= pp.y () && pp.y () <= yp + 5) {
93
bool GBezier::bezier_segment_contains (const Coord& p0, const Coord& p1,
94
const Coord& p2, const Coord& p3,
97
/** every bezier_segment contains 1/DELTA lines, we have to compute the
99
to check later in bezier_segment_part_contains, if the Coord c on a
109
for(t = 0; t < 1.01; t += DELTA) {
117
x1 = (int) (th3* p0.x() +
121
y1 = (int) (th3* p0.y() +
125
if (bezier_segment_part_contains(x0, y0, x1, y1, c)) return true;
131
GBezier::GBezier (GDocument *doc )
134
wSegment = 0; closed = false;
137
GBezier::GBezier (GDocument *doc, const QDomElement &element)
138
:GPolyline (doc, element.namedItem("polyline").toElement())
142
closed=(element.attribute("closed").toInt()==1);
147
GBezier::GBezier (const GBezier& obj) : GPolyline (obj) {
150
ppoints = obj.ppoints;
154
void GBezier::setPoint (int idx, const Coord& p) {
155
Coord np = p.transform (iMatrix);
157
points.at (idx)->x (np.x ());
158
points.at (idx)->y (np.y());
159
if (! isEndPoint (idx))
160
updateBasePoint (cPoint (idx));
167
void GBezier::movePoint (int idx, float dx, float dy, bool ctrlPressed) {
168
float x = points.at (idx)->x ();
169
float y = points.at (idx)->y ();
171
int cidx = -1; // >= 0, if the curve is closed and we move
172
// the first or last point
174
ndx = dx * iMatrix.m11 () + dy * iMatrix.m21 ();
175
ndy = dy * iMatrix.m22 () + dx * iMatrix.m12 ();
177
points.at (idx)->x (x + ndx);
178
points.at (idx)->y (y + ndy);
180
int n = points.count ();
182
cidx = n - (3 - idx);
183
points.at (cidx)->x (x + ndx);
184
points.at (cidx)->y (y + ndy);
186
else if (idx + 3 >= (int) points.count ()) {
187
cidx = 3 - (n - idx);
188
points.at (cidx)->x (x + ndx);
189
points.at (cidx)->y (y + ndy);
192
if (isEndPoint (idx) && !ctrlPressed) {
193
points.at (idx - 1)->x (points.at (idx - 1)->x () + ndx);
194
points.at (idx - 1)->y (points.at (idx - 1)->y () + ndy);
195
points.at (idx + 1)->x (points.at (idx + 1)->x () + ndx);
196
points.at (idx + 1)->y (points.at (idx + 1)->y () + ndy);
198
points.at (cidx - 1)->x (points.at (cidx - 1)->x () + ndx);
199
points.at (cidx - 1)->y (points.at (cidx - 1)->y () + ndy);
200
points.at (cidx + 1)->x (points.at (cidx + 1)->x () + ndx);
201
points.at (cidx + 1)->y (points.at (cidx + 1)->y () + ndy);
204
else if(!ctrlPressed) {
205
updateBasePoint (cPoint (idx));
207
updateBasePoint (cPoint (cidx));
213
QString GBezier::typeName () const
215
return i18n("Bezier curve");
218
void GBezier::draw (QPainter& p, bool withBasePoints, bool outline, bool) {
229
p.setWorldMatrix (tmpMatrix, true);
230
unsigned num = points.count ();
232
float w = outlineInfo.width == 0 ? 1.0 : outlineInfo.width;
235
// (xAngle/RAD_FACTOR) doesnt work!?
237
sdx = qRound (w*sArrow->length () * cos (1/(RAD_FACTOR/(sAngle))));
238
sdy = qRound (w*sArrow->length () * sin (1/(RAD_FACTOR/(sAngle))));
242
edx = qRound (w*eArrow->length () * cos (1/(RAD_FACTOR/(eAngle))));
243
edy = qRound (w*eArrow->length () * sin (1/(RAD_FACTOR/(eAngle))));
247
for (unsigned int i = 1; i + 3 < num; i += 3) {
248
if (points.at (i + 1)->x () == FLT_MAX ||
249
points.at (i + 2)->x () == FLT_MAX) {
250
p.drawLine (points.at (i)->x (), points.at (i)->y (),
251
points.at (i + 3)->x (), points.at (i + 3)->y ());
254
p.drawBezier (points,i);
260
if (! workInProgress () && !outline) {
264
if (gradientFill ()) {
265
//if (! gShape.valid ())
266
updateGradientShape (p);
270
p.drawPolygon (ppoints);
273
// p.drawPolyline (ppoints);
274
for (unsigned int i = 1; i + 3 < num; i += 3) {
275
if (points.at (i + 1)->x () == FLT_MAX ||
276
points.at (i + 2)->x () == FLT_MAX) {
277
p.drawLine (qRound (points.at (i)->x () + ((i==1) ? sdx : 0)),
278
qRound (points.at (i)->y () + ((i==1) ? sdy : 0)),
279
qRound (points.at (i + 3)->x () + ((i==num-2) ? edx : 0)),
280
qRound (points.at (i + 3)->y () + ((i==num-2) ? edy : 0)));
283
QPointArray bpoints (4);
284
bpoints.setPoint (0, qRound (points.at (i)->x () + ((i==1) ? sdx : 0)),
285
qRound (points.at (i)->y () + ((i==1) ? sdy : 0)));
286
bpoints.setPoint (1, qRound (points.at (i + 1)->x ()),
287
qRound (points.at (i + 1)->y ()));
288
bpoints.setPoint (2, qRound (points.at (i + 2)->x ()),
289
qRound (points.at (i + 2)->y ()));
290
bpoints.setPoint (3, qRound (points.at (i + 3)->x () + ((i==num-5) ? edx : 0)),
291
qRound (points.at (i + 3)->y () + ((i==num-5) ? edy : 0)));
292
p.drawQuadBezier (bpoints);
298
p.setClipping (false);
301
Coord pp = points.at (1)->transform (tmpMatrix);
302
sArrow->draw (p, pp, outlineInfo.color,
303
outlineInfo.width, sAngle);
306
Coord pp = points.at (num - 2)->transform (tmpMatrix);
307
eArrow->draw (p, pp, outlineInfo.color,
308
outlineInfo.width, eAngle);
313
drawHelpLinesForWorkingSegment (p);
316
void GBezier::drawHelpLines (QPainter& p) {
317
unsigned int i, num = points.count ();
320
for (i = 0; i < num; i++) {
321
Coord c = points.at (i)->transform (tmpMatrix);
326
Painter::drawRect (p, c.x () - 2.0, c.y () - 2.0, 4, 4);
329
QPen pen (blue, 1, DotLine);
331
for (i = 0; i + 2 < num; i += 3) {
332
if (points.at (i)->x () == FLT_MAX ||
333
points.at (i + 2)->x () == FLT_MAX)
336
Coord c = points.at (i + 1)->transform (tmpMatrix);
337
Coord c1 = points.at (i)->transform (tmpMatrix);
338
Coord c2 = points.at (i + 2)->transform (tmpMatrix);
339
Painter::drawLine (p, c1.x (), c1.y (), c.x (), c.y ());
340
Painter::drawLine (p, c.x (), c.y (), c2.x (), c2.y ());
345
void GBezier::drawHelpLinesForWorkingSegment (QPainter& p) {
348
QPen pen1 (blue, 1, DotLine);
351
for (int i = wSegment * 3; i <= (wSegment + 1) * 3; i += 3) {
352
if (i + 2 >= (int) points.count () ||
353
points.at (i)->x () == FLT_MAX ||
354
points.at (i + 2)->x () == FLT_MAX) {
359
Coord c = points.at (i + 1)->transform (tmpMatrix);
360
Coord c1 = points.at (i)->transform (tmpMatrix);
361
Coord c2 = points.at (i + 2)->transform (tmpMatrix);
362
Painter::drawLine (p, c1.x (), c1.y (), c.x (), c.y ());
363
Painter::drawLine (p, c.x (), c.y (), c2.x (), c2.y ());
365
Painter::drawRect (p, c1.x () - 2, c1.y () - 2, 4, 4);
366
Painter::drawRect (p, c2.x () - 2, c2.y () - 2, 4, 4);
371
bool GBezier::contains (const Coord& p) {
372
if (rbox.contains (p)) {
373
Coord pc = p.transform (iMatrix);
375
for (unsigned int i = 1; i + 3 < points.count (); i += 3) {
376
// detect the containing curve segment
378
Coord p = *(points.at (i));
384
for (unsigned int j = i + 1; j < i + 4; j++) {
385
Coord pn = *(points.at (j));
386
r.left (QMIN(pn.x (), r.left ()));
387
r.top (QMIN(pn.y (), r.top ()));
388
r.right (QMAX(pn.x (), r.right ()));
389
r.bottom (QMAX(pn.y (), r.bottom ()));
391
if (r.contains (pc)) {
392
if (bezier_segment_contains (*(points.at (i)), *(points.at (i + 1)),
393
*(points.at (i + 2)),
394
*(points.at (i + 3)), pc))
402
GObject* GBezier::copy () {
403
return new GBezier (*this);
406
/*GObject* GBezier::create (GDocument *doc, const QDomElement &element)
408
return new GBezier (doc, element);
411
void GBezier::initBasePoint (int idx) {
412
Coord epoint = *(points.at (idx + 1));
413
float dx = epoint.x ();
414
float dy = epoint.y ();
415
points.at (idx)->x (2 * dx - points.at (idx + 2)->x ());
416
points.at (idx)->y (2 * dy - points.at (idx + 2)->y ());
417
updateRegion (false);
420
void GBezier::updateBasePoint (int idx) {
421
int eidx = (idx == 0 ? 1 : (cPoint (idx) < idx ? idx - 1 : idx + 1));
422
if (idx < 0 || eidx < 0)
425
Coord epoint = *(points.at (eidx));
426
if (points.at (cPoint (idx))->x () == FLT_MAX)
429
float dx = epoint.x ();
430
float dy = epoint.y ();
431
points.at (idx)->x (2 * dx - points.at (cPoint (idx))->x ());
432
points.at (idx)->y (2 * dy - points.at (cPoint (idx))->y ());
437
void GBezier::setWorkingSegment (int seg) {
439
updateRegion (false);
442
void GBezier::calcBoundingBox () {
444
unsigned int num = points.count ();
445
Coord p = points.at (0)->transform (tmpMatrix);
453
for (unsigned int i = 1; i < num; i++) {
454
Coord p = points.at (i)->transform (tmpMatrix);
456
if (p.x () != FLT_MAX && p.y () != FLT_MAX) {
457
r.left (QMIN(p.x (), r.left ()));
458
r.top (QMIN(p.y (), r.top ()));
459
r.right (QMAX(p.x (), r.right ()));
460
r.bottom (QMAX(p.y (), r.bottom ()));
464
float w = outlineInfo.width == 0 ? 1.0 : outlineInfo.width;
466
if (sArrow != 0L && num > 2) {
467
Coord p1 = points.at (0)->transform (tmpMatrix);
468
Coord p2 = points.at (2)->transform (tmpMatrix);
469
sAngle = calcArrowAngle (p1, p2, 0);
470
Rect sr = sArrow->boundingBox (p1, w, sAngle);
473
if (eArrow != 0L && num >= 3) {
474
Coord p1 = points.at (num - 3)->transform (tmpMatrix);
475
Coord p2 = points.at (num - 1)->transform (tmpMatrix);
476
eAngle = calcArrowAngle (p1, p2, 1);
477
Rect er = eArrow->boundingBox (p2, w, eAngle);
481
r.enlarge (2); // for the help lines
482
// updateBoundingBox (r);
483
rbox = r.normalize ();
484
QRect pbox = ppoints.boundingRect ();
485
box = Rect (pbox.x (), pbox.y (), pbox.width (), pbox.height ());
486
box = box.transform (tmpMatrix);
489
void GBezier::removePoint (int idx, bool update) {
490
if (points.count () > 6) {
491
points.remove (idx - 1);
492
points.remove (idx - 1);
493
points.remove (idx - 1);
499
void GBezier::insertPoint (int idx, const Coord& p, bool update) {
500
Coord p0 (p.x () - 20, p.y () - 20);
501
Coord p1 (p.x () + 20, p.y () + 20);
502
addPoint (idx, p0, false);
503
addPoint (idx + 1, p, false);
504
addPoint (idx + 2, p1, update);
507
int GBezier::containingSegment (float xpos, float ypos) {
508
Coord p (xpos, ypos);
509
Coord pc = p.transform (iMatrix);
512
for (unsigned int i = 1; i + 3 < points.count (); i += 3) {
513
// detect the containing curve segment
515
Coord p = *(points.at (i));
521
for (unsigned int j = i + 1; j < i + 4; j++) {
522
Coord pn = *(points.at (j));
523
r.left (QMIN(pn.x (), r.left ()));
524
r.top (QMIN(pn.y (), r.top ()));
525
r.right (QMAX(pn.x (), r.right ()));
526
r.bottom (QMAX(pn.y (), r.bottom ()));
528
if (r.contains (pc)) {
529
if (bezier_segment_contains (*(points.at (i)), *(points.at (i + 1)),
530
*(points.at (i + 2)),
531
*(points.at (i + 3)), pc)) {
540
int GBezier::cPoint (int idx) {
542
return idx + (isEndPoint (idx - 1) ? -2 : 2);
544
return idx + (isEndPoint (idx + 1) ? 2 : -2);
547
QDomElement GBezier::writeToXml (QDomDocument &document) {
549
QDomElement bezier=document.createElement("bezier");
550
bezier.setAttribute ("closed", (int) closed);
551
bezier.appendChild(GPolyline::writeToXml(document));
555
bool GBezier::findNearestPoint (const Coord& p, float max_dist,
556
float& dist, int& pidx, bool all) {
557
float dx, dy, min_dist = max_dist + 1, d;
560
Coord np = p.transform (iMatrix);
563
while (i <= points.count () - 2) {
564
dx = points.at (i)->x () - np.x ();
565
dy = points.at (i)->y () - np.y ();
566
d = sqrt (dx * dx + dy * dy);
568
if (d < max_dist && d < min_dist) {
573
if (! all && i == 1) {
574
// test only first and last point
575
i = points.count () - 2;
583
void GBezier::computePPoints () {
584
unsigned int i, num = points.count ();
585
unsigned int idx = 0;
587
ppoints.resize (num);
588
for (i = 1; i + 3 < num; i += 3) {
589
if (points.at (i + 1)->x () == FLT_MAX ||
590
points.at (i + 2)->x () == FLT_MAX) {
591
if (ppoints.size () < idx + 2)
592
ppoints.resize (ppoints.size () + 2);
593
ppoints.setPoint (idx++, qRound (points.at (i)->x ()), qRound (points.at (i)->y ()));
594
ppoints.setPoint (idx++, qRound (points.at (i+3)->x ()), qRound (points.at (i+3)->y ()));
597
idx = createPolyline (i, idx);
599
ppoints.resize (idx);
602
void GBezier::setClosed (bool flag) {
603
if (flag && points.count () < 6)
608
// Point #n-2 := Point #0
609
// Point #n-1 := Point #1
610
// Point #n := Point #2
611
unsigned int n = points.count () - 1;
612
*points.at (n) = *points.at (2);
613
*points.at (n-1) = *points.at (1);
614
*points.at (n-2) = *points.at (0);
619
int GBezier::createPolyline (int index, int pidx) {
624
double x0 = points.at (index)->x();
625
double y0 = points.at (index)->y();
626
double x1 = points.at (index+1)->x();
627
double y1 = points.at (index+1)->y();
628
double x2 = points.at (index+2)->x();
629
double y2 = points.at (index+2)->y();
630
double x3 = points.at (index+3)->x();
631
double y3 = points.at (index+3)->y();
633
if (ppoints.size () - pidx < ((points.count ())/ DELTA / 3))
634
ppoints.resize ((static_cast<int>(points.count () /DELTA / 3)) + pidx);
636
for (t = 0; t < 1.01; t += DELTA) {
642
x4 = (int) (th3 * x0 + 3. * t * th2 * x1 + 3. *t2 *th *x2 + t3 * x3);
643
y4 = (int) (th3 * y0 + 3. * t * th2 * y1 + 3. * t2 * th * y2 + t3 * y3);
644
ppoints.setPoint (pidx, x4, y4);
647
ppoints.resize (pidx);
651
void GBezier::updateGradientShape (QPainter& p) {
652
// define the rectangular box for the gradient pixmap
653
// (in object coordinate system)
654
gShape.setBox (calcEnvelope ());
656
// define the clipping region
657
QWMatrix matrix = p.worldMatrix ();
658
gShape.setRegion (QRegion (matrix.map (ppoints)));
660
// update the gradient information
661
gShape.setGradient (fillInfo.gradient);
663
// and create a new gradient pixmap
664
gShape.updatePixmap ();
667
void GBezier::getPath (QValueList<Coord>& path) {
668
unsigned int num = ppoints.size ();
669
for (unsigned int i = 0; i < num; i++) {
670
const QPoint& p = ppoints.point (i);
671
Coord pi (p.x (), p.y ());
672
path.append(pi.transform (tMatrix));
676
bool GBezier::intersects (const Rect& r) {
677
return r.intersects (rbox);
678
// return GObject::intersects (r);
681
bool GBezier::splitAt (unsigned int idx, GObject*& obj1, GObject*& obj2) {
684
if (isEndPoint (idx)) {
686
GBezier* other = new GBezier (*this);
687
other->closed = false;
688
other->removeAllPoints ();
689
unsigned int i, num = points.count ();
690
for (i = idx - 1; i < num; i++)
691
other->points.append (new Coord (*points.at (i)));
692
for (i = 0; i <= idx + 1; i++)
693
other->points.append (new Coord (*points.at (i)));
694
other->calcBoundingBox ();
699
else if (idx > 1 && idx < points.count () - 1) {
700
GBezier* other1 = (GBezier *) this->copy ();
701
unsigned int i, num = points.count ();
702
for (i = idx + 2; i < num; i++)
703
other1->points.remove (idx + 2);
704
other1->calcBoundingBox ();
706
GBezier* other2 = (GBezier *) this->copy ();
707
for (i = 0; i < idx - 1; i++)
708
other2->points.remove ((unsigned int) 0);
709
other2->calcBoundingBox ();
718
GCurve* GBezier::convertToCurve () const {
719
unsigned int nsegs = (points.count () - 3) / 3;
720
GCurve* curve = new GCurve (m_gdoc);
721
curve->setOutlineInfo (outlineInfo);
722
QListIterator<Coord> it (points);
724
Coord p1 = it.current ()->transform (tmpMatrix); ++it;
725
for (unsigned int i = 0; i < nsegs; i++) {
726
Coord p2 = it.current ()->transform (tmpMatrix); ++it;
727
Coord p3 = it.current ()->transform (tmpMatrix); ++it;
728
Coord p4 = it.current ()->transform (tmpMatrix); ++it;
729
curve->addBezierSegment (p1, p2, p3, p4);
733
curve->setClosed (closed);
737
#include <GBezier.moc>