karbon

roundcornersplugin.cc

00001 /* This file is part of the KDE project
00002    Copyright (C) 2002, The Karbon Developers
00003 
00004    This library is free software; you can redistribute it and/or
00005    modify it under the terms of the GNU Library General Public
00006    License as published by the Free Software Foundation; either
00007    version 2 of the License, or (at your option) any later version.
00008 
00009    This library is distributed in the hope that it will be useful,
00010    but WITHOUT ANY WARRANTY; without even the implied warranty of
00011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012    Library General Public License for more details.
00013 
00014    You should have received a copy of the GNU Library General Public License
00015    along with this library; see the file COPYING.LIB.  If not, write to
00016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00017  * Boston, MA 02110-1301, USA.
00018 */
00019 
00020 #include "roundcornersplugin.h"
00021 #include <karbon_view.h>
00022 #include <karbon_part.h>
00023 #include <core/vpath.h>
00024 #include <core/vsegment.h>
00025 #include <kgenericfactory.h>
00026 #include <kdebug.h>
00027 #include <qgroupbox.h>
00028 #include <qlabel.h>
00029 
00030 #include <knuminput.h>
00031 
00032 typedef KGenericFactory<VRoundCornersPlugin, KarbonView> VRoundCornersPluginFactory;
00033 K_EXPORT_COMPONENT_FACTORY( karbon_roundcornersplugin, VRoundCornersPluginFactory( "karbonroundcornersplugin" ) )
00034 
00035 VRoundCornersPlugin::VRoundCornersPlugin( KarbonView *parent, const char* name, const QStringList & ) : Plugin( parent, name )
00036 {
00037     new KAction(
00038         i18n( "&Round Corners..." ), "14_roundcorners", 0, this,
00039         SLOT( slotRoundCorners() ), actionCollection(), "path_round_corners" );
00040 
00041     m_roundCornersDlg = new VRoundCornersDlg();
00042     m_roundCornersDlg->setRadius( 10.0 );
00043 }
00044 
00045 VRoundCornersPlugin::~VRoundCornersPlugin()
00046 {
00047 }
00048 
00049 void
00050 VRoundCornersPlugin::slotRoundCorners()
00051 {
00052     KarbonPart *part = ((KarbonView *)parent())->part();
00053     if( part && m_roundCornersDlg->exec() )
00054         part->addCommand( new VRoundCornersCmd( &part->document(), m_roundCornersDlg->radius() ), true );
00055 }
00056 
00057 
00058 VRoundCornersDlg::VRoundCornersDlg( QWidget* parent, const char* name )
00059     : KDialogBase( parent, name, true,  i18n( "Polygonize" ), Ok | Cancel  )
00060 {
00061     // add input:
00062     QGroupBox* group = new QGroupBox( 2, Qt::Horizontal, i18n( "Properties" ), this );
00063 
00064     new QLabel( i18n( "Round corners:" ), group );
00065     m_radius = new KDoubleNumInput( group );
00066     group->setMinimumWidth( 300 );
00067 
00068     // signals and slots:
00069     connect( this, SIGNAL( okClicked() ), this, SLOT( accept() ) );
00070     connect( this, SIGNAL( cancelClicked() ), this, SLOT( reject() ) );
00071 
00072     setMainWidget( group );
00073     setFixedSize( baseSize() );
00074 }
00075 
00076 double
00077 VRoundCornersDlg::radius() const
00078 {
00079     return m_radius->value();
00080 }
00081 
00082 void
00083 VRoundCornersDlg::setRadius( double value )
00084 {
00085     m_radius->setValue(value);
00086 }
00087 
00088 
00089 
00090 VRoundCornersCmd::VRoundCornersCmd( VDocument* doc, double radius )
00091     : VReplacingCmd( doc, i18n( "Round Corners" ) )
00092 {
00093     // Set members.
00094     m_radius = radius > 0.0 ? radius : 1.0;
00095 }
00096 
00097 void
00098 VRoundCornersCmd::visitVSubpath( VSubpath& path )
00099 {
00100     // Optimize and avoid a crash.
00101     if( path.isEmpty() )
00102         return;
00103 
00104     // Note: we modiy segments from path. that doesn't hurt, since we
00105     // replace "path" with the temporary path "newPath" afterwards.
00106 
00107     VSubpath newPath( 0L );
00108 
00109     path.first();
00110     // Skip "begin".
00111     path.next();
00112 
00113     /* This algorithm is worked out by <kudling AT kde DOT org> to produce similar results as
00114      * the "round corners" algorithms found in other applications. Neither code nor
00115      * algorithms from any 3rd party is used though.
00116      *
00117      * We want to replace all corners with round corners having "radius" m_radius.
00118      * The algorithm doesn't really produce circular arcs, but that's ok since
00119      * the algorithm achieves nice looking results and is generic enough to be applied
00120      * to all kind of paths.
00121      * Note also, that this algorithm doesn't touch smooth joins (in the sense of
00122      * VSegment::isSmooth() ).
00123      *
00124      * We'll manipulate the input path for bookkeeping purposes and construct a new
00125      * temporary path in parallel. We finally replace the input path with the new path.
00126      *
00127      *
00128      * Without restricting generality, let's assume the input path is closed and
00129      * contains segments which build a rectangle.
00130      *
00131      *           2
00132      *    O------------O
00133      *    |            |        Numbers reflect the segments' order
00134      *   3|            |1       in the path. We neglect the "begin"
00135      *    |            |        segment here.
00136      *    O------------O
00137      *           0
00138      *
00139      * There are three unique steps to process. The second step is processed
00140      * many times in a loop.
00141      *
00142      * 1) Begin
00143      *    -----
00144      *    Split the first segment of the input path (called "path[0]" here)
00145      *    at parameter t
00146      *
00147      *        t = path[0]->param( m_radius )
00148      *
00149      *    and move newPath to this new knot. If current segment is too small
00150      *    (smaller than 2 * m_radius), we always set t = 0.5 here and in the further
00151      *    steps as well.
00152      *
00153      *    path:                 new path:
00154      *
00155      *           2
00156      *    O------------O
00157      *    |            |
00158      *  3 |            | 1                    The current segment is marked with "#"s.
00159      *    |            |
00160      *    O##O#########O        ...O
00161      *           0                     0
00162      *
00163      * 2) Loop
00164      *    ----
00165      *    The loop step is iterated over all segments. After each appliance the index n
00166      *    is incremented and the loop step is reapplied until no untouched segment is left.
00167      *
00168      *    Split the current segment path[n] of the input path at parameter t
00169      *
00170      *        t = path[n]->param( path[n]->length() - m_radius )
00171      *
00172      *    and add the first subsegment of the curent segment to newPath.
00173      *
00174      *    path:                 new path:
00175      *
00176      *           2
00177      *    O------------O
00178      *    |            |
00179      *  3 |            | 1
00180      *    |            |
00181      *    O--O######O##O           O------O...
00182      *           0                     0
00183      *
00184      *    Now make the second next segment (the original path[1] segment in our example)
00185      *    the current one. Split it at parameter t
00186      *
00187      *        t = path[n]->param( m_radius )
00188      *
00189      *    path:                 new path:
00190      *
00191      *           2
00192      *    O------------O
00193      *    |            #
00194      *  3 |            O 1
00195      *    |            #
00196      *    O--O------O--O           O------O...
00197      *           0                     0
00198      *
00199      *    Make the first subsegment of the current segment the current one.
00200      *
00201      *    path:                 new path:
00202      *
00203      *           2
00204      *    O------------O
00205      *    |            |
00206      *  3 |            O 1                   O
00207      *    |            #                    /.1
00208      *    O--O------O--O           O------O...
00209      *           0                     0
00210      *
00211      * 3) End
00212      *    ---
00213      *
00214      *    path:                 new path:
00215      *
00216      *           2                     4
00217      *    O--O------O--O        5 .O------O. 3
00218      *    |            |         /          \
00219      *  3 O            O 1    6 O            O 2
00220      *    |            |      7 .\          /
00221      *    O--O------O--O        ...O------O. 1
00222      *           0                     0
00223      */
00224 
00225     double length;
00226     double param;
00227 
00228     // "Begin" step.
00229     // =============
00230 
00231     // Convert flat beziers to lines.
00232     if( path.current()->isFlat() )
00233         path.current()->setDegree( 1 );
00234 
00235     if( path.getLast()->isFlat() )
00236         path.getLast()->setDegree( 1 );
00237 
00238     if(
00239         path.isClosed() &&
00240         // Don't touch smooth joins.
00241         !path.getLast()->isSmooth( *path.current() ) )
00242     {
00243         length = path.current()->length();
00244 
00245         param = length > 2 * m_radius
00246             ? path.current()->lengthParam( m_radius )
00247             : 0.5;
00248 
00249 
00250         path.insert(
00251             path.current()->splitAt( param ) );
00252 
00253         newPath.moveTo(
00254             path.current()->knot() );
00255 
00256         path.next();
00257 
00258 
00259         if( !success() )
00260             setSuccess();
00261     }
00262     else
00263     {
00264         newPath.moveTo(
00265             path.current()->prev()->knot() );
00266     }
00267 
00268 
00269     // "Loop" step.
00270     // ============
00271 
00272     while(
00273         path.current() &&
00274         path.current()->next() )
00275     {
00276         // Convert flat beziers to lines.
00277         if( path.current()->isFlat() )
00278             path.current()->setDegree( 1 );
00279 
00280         if( path.current()->next()->isFlat() )
00281             path.current()->next()->setDegree( 1 );
00282 
00283 
00284         // Don't touch smooth joins.
00285         if( path.current()->isSmooth() )
00286         {
00287             newPath.append( path.current()->clone() );
00288             path.next();
00289             continue;
00290         }
00291 
00292 
00293         // Split the current segment at param( m_radius ) counting
00294         // from the end.
00295         length = path.current()->length();
00296 
00297         // If the current segment is too short to be split, just don't split it
00298         // because it was split already a t=0.5 during the former step.
00299         if( length > m_radius )
00300         {
00301             param = path.current()->lengthParam( length - m_radius );
00302 
00303             path.insert(
00304                 path.current()->splitAt( param ) );
00305             newPath.append(
00306                 path.current()->clone() );
00307 
00308             path.next();
00309         }
00310 
00311 
00312         // Jump to the next untouched segment.
00313         path.next();
00314 
00315 
00316         // Split the current segment at param( m_radius ).
00317         length = path.current()->length();
00318 
00319         param = length > 2 * m_radius
00320             ? path.current()->lengthParam( m_radius )
00321             : 0.5;
00322 
00323         path.insert(
00324             path.current()->splitAt( param ) );
00325 
00326 
00327         // Round corner.
00328         newPath.curveTo(
00329             path.current()->prev()->pointAt( 0.5 ),
00330             path.current()->pointAt( 0.5 ),
00331             path.current()->knot() );
00332 
00333 
00334         if( !success() )
00335             setSuccess();
00336 
00337         path.next();
00338     }
00339 
00340 
00341     // "End" step.
00342     // ===========
00343 
00344     if( path.isClosed() )
00345     {
00346         // Convert flat beziers to lines.
00347         if( path.current()->isFlat() )
00348             path.current()->setDegree( 1 );
00349 
00350         if( path.getFirst()->next()->isFlat() )
00351             path.getFirst()->next()->setDegree( 1 );
00352 
00353         // Don't touch smooth joins.
00354         if( !path.current()->isSmooth( *path.getFirst()->next() ) )
00355         {
00356             length = path.current()->length();
00357 
00358             // If the current segment is too short to be split, just don't split it
00359             // because it was split already at t=0.5 during the former step.
00360             if( length > m_radius )
00361             {
00362                 param = path.current()->lengthParam( length - m_radius );
00363 
00364                 path.insert(
00365                     path.current()->splitAt( param ) );
00366                 newPath.append(
00367                     path.current()->clone() );
00368 
00369                 path.next();
00370             }
00371 
00372 
00373             path.first();
00374             path.next();
00375 
00376             // Round corner.
00377             newPath.curveTo(
00378                 path.getLast()->pointAt( 0.5 ),
00379                 path.current()->pointAt( 0.5 ),
00380                 path.current()->knot() );
00381 
00382 
00383             if( !success() )
00384                 setSuccess();
00385         }
00386         else
00387             newPath.append( path.current()->clone() );
00388 
00389         newPath.close();
00390     }
00391     else
00392         newPath.append( path.current()->clone() );
00393 
00394 
00395     path = newPath;
00396 
00397     // Invalidate bounding box once.
00398     path.invalidateBoundingBox();
00399 }
00400 
00401 #include "roundcornersplugin.moc"
00402 
KDE Home | KDE Accessibility Home | Description of Access Keys