KoPathPointInsertCommand::KoPathPointInsertCommand(const QList<KoPathPointData> & pointDataList, qreal insertPosition, QUndoCommand *parent) : QUndoCommand(parent) , m_deletePoints(true) { if (insertPosition < 0) insertPosition = 0; if (insertPosition > 1) insertPosition = 1; //TODO the list needs to be sorted QList<KoPathPointData>::const_iterator it(pointDataList.begin()); for (; it != pointDataList.end(); ++it) { KoPathShape * pathShape = it->pathShape; KoPathSegment segment = pathShape->segmentByIndex(it->pointIndex); // should not happen but to be sure if (! segment.isValid()) continue; m_pointDataList.append(*it); QPair<KoPathSegment, KoPathSegment> splitSegments = segment.splitAt( insertPosition ); KoPathPoint * split1 = splitSegments.first.second(); KoPathPoint * split2 = splitSegments.second.first(); KoPathPoint * splitPoint = new KoPathPoint( pathShape, split1->point() ); if( split1->activeControlPoint1() ) splitPoint->setControlPoint1(split1->controlPoint1()); if( split2->activeControlPoint2() ) splitPoint->setControlPoint2(split2->controlPoint2()); m_points.append(splitPoint); QPointF cp1 = splitSegments.first.first()->controlPoint2(); QPointF cp2 = splitSegments.second.second()->controlPoint1(); m_controlPoints.append(QPair<QPointF, QPointF>(cp1, cp2)); } }
void KoPathSegmentTypeCommand::initialize(const QList<KoPathPointData> & pointDataList) { QList<KoPathPointData>::const_iterator it(pointDataList.begin()); for (; it != pointDataList.end(); ++it) { KoPathSegment segment = it->pathShape->segmentByIndex(it->pointIndex); if (segment.isValid()) { if (m_segmentType == Curve) { // don not change segment if already a curve if (segment.first()->activeControlPoint2() || segment.second()->activeControlPoint1()) continue; } else { // do not change segment if already a line if (! segment.first()->activeControlPoint2() && ! segment.second()->activeControlPoint1()) continue; } m_pointDataList.append(*it); SegmentTypeData segmentData; KoPathShape * pathShape = segment.first()->parent(); // we are changing a curve to a line -> save control point positions if (m_segmentType == Line) { segmentData.m_controlPoint2 = pathShape->shapeToDocument(segment.first()->controlPoint2()); segmentData.m_controlPoint1 = pathShape->shapeToDocument(segment.second()->controlPoint1()); } // save point properties segmentData.m_properties2 = segment.first()->properties(); segmentData.m_properties1 = segment.second()->properties(); m_segmentData.append(segmentData); } } if (m_segmentType == Curve) { setText(i18nc("(qtundo-format)", "Change segments to curves")); } else { setText(i18nc("(qtundo-format)", "Change segments to lines")); } }
void KoPathSegmentChangeStrategy::handleMouseMove(const QPointF &mouseLocation, Qt::KeyboardModifiers modifiers) { m_tool->canvas()->updateCanvas(m_tool->canvas()->snapGuide()->boundingRect()); QPointF snappedPosition = m_tool->canvas()->snapGuide()->snap(mouseLocation, modifiers); m_tool->canvas()->updateCanvas(m_tool->canvas()->snapGuide()->boundingRect()); QPointF localPos = m_path->documentToShape(snappedPosition); if (m_segment.degree() == 1) { // line segment is converted to a curve KoPathSegmentTypeCommand cmd(m_pointData1, KoPathSegmentTypeCommand::Curve); cmd.redo(); } QPointF move1, move2; if (m_segment.degree() == 2) { // interpolate quadratic segment between segment start, mouse position and segment end KoPathSegment ipol = KoPathSegment::interpolate(m_segment.first()->point(), localPos, m_segment.second()->point(), m_segmentParam); if (ipol.isValid()) { move1 = move2 = ipol.controlPoints()[1] - m_segment.controlPoints()[1]; } } else if (m_segment.degree() == 3) { /* * method from inkscape, original method and idea borrowed from Simon Budig * <*****@*****.**> and the GIMP * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative() * * feel good is an arbitrary parameter that distributes the delta between handles * if t of the drag point is less than 1/6 distance form the endpoint only * the corresponding handle is adjusted. This matches the behavior in GIMP */ const qreal t = m_segmentParam; qreal feel_good; if (t <= 1.0 / 6.0) feel_good = 0; else if (t <= 0.5) feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2; else if (t <= 5.0 / 6.0) feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5; else feel_good = 1; QPointF lastLocalPos = m_path->documentToShape(m_lastPosition); QPointF delta = localPos - lastLocalPos; move2 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta; move1 = (feel_good/(3*t*t*(1-t))) * delta; } m_path->update(); if(m_segment.first()->activeControlPoint2()) { KoPathControlPointMoveCommand cmd(m_pointData1, move2, KoPathPoint::ControlPoint2); cmd.redo(); } if(m_segment.second()->activeControlPoint1()) { KoPathControlPointMoveCommand cmd(m_pointData2, move1, KoPathPoint::ControlPoint1); cmd.redo(); } m_path->normalize(); m_path->update(); m_ctrlPoint1Move += move1; m_ctrlPoint2Move += move2; // save last mouse position m_lastPosition = mouseLocation; }
void RoundCornersCommand::roundPath() { /* * This algorithm is worked out by <kudling AT kde DOT org> to produce similar results as * the "round corners" algorithms found in other applications. Neither code nor * algorithms from any 3rd party is used though. * * We want to replace all corners with round corners having "radius" m_radius. * The algorithm doesn't really produce circular arcs, but that's ok since * the algorithm achieves nice looking results and is generic enough to be applied * to all kind of paths. * Note also, that this algorithm doesn't touch smooth joins (in the sense of * KoPathPoint::isSmooth() ). * * We'll manipulate the input path for bookkeeping purposes and construct a new * temporary path in parallel. We finally replace the input path with the new path. * * * Without restricting generality, let's assume the input path is closed and * contains segments which build a rectangle. * * 2 * O------------O * | | Numbers reflect the segments' order * 3| |1 in the path. We neglect the "begin" * | | segment here. * O------------O * 0 * * There are three unique steps to process. The second step is processed * many times in a loop. * * 1) Begin * ----- * Split the first segment of the input path (called "path[0]" here) * at parameter t * * t = path[0]->param( m_radius ) * * and move newPath to this new knot. If current segment is too small * (smaller than 2 * m_radius), we always set t = 0.5 here and in the further * steps as well. * * path: new path: * * 2 * O------------O * | | * 3 | | 1 The current segment is marked with "#"s. * | | * O##O#########O ...O * 0 0 * * 2) Loop * ---- * The loop step is iterated over all segments. After each appliance the index n * is incremented and the loop step is reapplied until no untouched segment is left. * * Split the current segment path[n] of the input path at parameter t * * t = path[n]->param( path[n]->length() - m_radius ) * * and add the first subsegment of the curent segment to newPath. * * path: new path: * * 2 * O------------O * | | * 3 | | 1 * | | * O--O######O##O O------O... * 0 0 * * Now make the second next segment (the original path[1] segment in our example) * the current one. Split it at parameter t * * t = path[n]->param( m_radius ) * * path: new path: * * 2 * O------------O * | # * 3 | O 1 * | # * O--O------O--O O------O... * 0 0 * * Make the first subsegment of the current segment the current one. * * path: new path: * * 2 * O------------O * | | * 3 | O 1 O * | # /.1 * O--O------O--O O------O... * 0 0 * * 3) End * --- * * path: new path: * * 2 4 * O--O------O--O 5 .O------O. 3 * | | / \ * 3 O O 1 6 O O 2 * | | 7 .\ / * O--O------O--O ...O------O. 1 * 0 0 */ // TODO: not sure if we should only touch flat segment joins as the original algorithm m_path->clear(); int subpathCount = m_copy->subpathCount(); for( int subpathIndex = 0; subpathIndex < subpathCount; ++subpathIndex ) { int pointCount = m_copy->pointCountSubpath( subpathIndex ); if( ! pointCount ) continue; // check if we have sufficient number of points if( pointCount < 3 ) { // copy the only segment KoPathSegment s = m_copy->segmentByIndex( KoPathPointIndex( subpathIndex, 0 ) ); m_path->moveTo( m_copy->pointByIndex( KoPathPointIndex( subpathIndex, 0 ) )->point() ); addSegment( m_path, s ); continue; } KoPathSegment prevSeg = m_copy->segmentByIndex( KoPathPointIndex( subpathIndex, pointCount-1 ) ); KoPathSegment nextSeg = m_copy->segmentByIndex( KoPathPointIndex( subpathIndex, 0 ) ); KoPathSegment lastSeg; KoPathPoint * currPoint = nextSeg.first(); KoPathPoint * firstPoint = 0; KoPathPoint * lastPoint = 0; // check if first path point is a smooth join with the closing segment bool firstPointIsCorner = m_copy->isClosedSubpath( subpathIndex ) && ! currPoint->isSmooth( prevSeg.first(), nextSeg.second() ); // Begin: take care of the first path point if( firstPointIsCorner ) { // split the previous segment at length - radius qreal prevLength = prevSeg.length(); qreal prevSplit = prevLength > m_radius ? prevSeg.paramAtLength( prevLength-m_radius ) : 0.5; QPair<KoPathSegment,KoPathSegment> prevParts = prevSeg.splitAt( prevSplit ); // split the next segment at radius qreal nextLength = nextSeg.length(); qreal nextSplit = nextLength > m_radius ? nextSeg.paramAtLength( m_radius ) : 0.5; QPair<KoPathSegment,KoPathSegment> nextParts = nextSeg.splitAt( nextSplit ); // calculate smooth tangents QPointF P0 = prevParts.first.second()->point(); QPointF P3 = nextParts.first.second()->point(); qreal tangentLength1 = 0.5 * QLineF( P0, currPoint->point() ).length(); qreal tangentLength2 = 0.5 * QLineF( P3, currPoint->point() ).length(); QPointF P1 = P0 - tangentLength1 * tangentAtEnd( prevParts.first ); QPointF P2 = P3 - tangentLength2 * tangentAtStart( nextParts.second ); // start the subpath firstPoint = m_path->moveTo( prevParts.second.first()->point() ); // connect the split points with curve // TODO: shall we create a correct arc? m_path->curveTo( P1, P2, P3 ); prevSeg = nextParts.second; lastSeg = prevParts.first; } else { firstPoint = m_path->moveTo( currPoint->point() ); prevSeg = nextSeg; } // Loop: for( int pointIndex = 1; pointIndex < pointCount; ++pointIndex ) { nextSeg = m_copy->segmentByIndex( KoPathPointIndex( subpathIndex, pointIndex ) ); if( ! nextSeg.isValid() ) break; currPoint = nextSeg.first(); if( ! currPoint ) continue; if( currPoint->isSmooth( prevSeg.first(), nextSeg.second() ) ) { // the current point has a smooth join, so we can add the previous segment // to our new path addSegment( m_path, prevSeg ); prevSeg = nextSeg; } else { // split the previous segment at length - radius qreal prevLength = prevSeg.length(); qreal prevSplit = prevLength > m_radius ? prevSeg.paramAtLength( prevLength-m_radius ) : 0.5; QPair<KoPathSegment,KoPathSegment> prevParts = prevSeg.splitAt( prevSplit ); // add the remaining part up to the split point of the pervious segment lastPoint = addSegment( m_path, prevParts.first ); // split the next segment at radius qreal nextLength = nextSeg.length(); qreal nextSplit = nextLength > m_radius ? nextSeg.paramAtLength( m_radius ) : 0.5; QPair<KoPathSegment,KoPathSegment> nextParts = nextSeg.splitAt( nextSplit ); // calculate smooth tangents QPointF P0 = prevParts.first.second()->point(); QPointF P3 = nextParts.first.second()->point(); qreal tangentLength1 = 0.5 * QLineF( P0, currPoint->point() ).length(); qreal tangentLength2 = 0.5 * QLineF( P3, currPoint->point() ).length(); QPointF P1 = P0 - tangentLength1 * tangentAtEnd( prevParts.first ); QPointF P2 = P3 - tangentLength2 * tangentAtStart( nextParts.second ); // connect the split points with curve // TODO: shall we create a correct arc? lastPoint = m_path->curveTo( P1, P2, P3 ); prevSeg = nextParts.second; } } // End: take care of the last path point if( firstPointIsCorner ) { // construct the closing segment lastPoint->setProperty( KoPathPoint::CloseSubpath ); firstPoint->setProperty( KoPathPoint::CloseSubpath ); switch( lastSeg.degree() ) { case 1: lastPoint->removeControlPoint2(); firstPoint->removeControlPoint1(); break; case 2: if( lastSeg.first()->activeControlPoint2() ) { lastPoint->setControlPoint2( lastSeg.first()->controlPoint2() ); firstPoint->removeControlPoint1(); } else { lastPoint->removeControlPoint2(); firstPoint->setControlPoint1( lastSeg.second()->controlPoint1() ); } break; case 3: lastPoint->setControlPoint2( lastSeg.first()->controlPoint2() ); firstPoint->setControlPoint1( lastSeg.second()->controlPoint1() ); break; } } else { // add the last remaining segment addSegment( m_path, prevSeg ); } } }