karbon

roundcornersplugin.cpp
1/* This file is part of the KDE project
2 Copyright (C) 2002, The Karbon Developers
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License as published by the Free Software Foundation; either
7 version 2 of the License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18*/
19
20#include "roundcornersplugin.h"
21#include <karbon_view.h>
22#include <karbon_part.h>
23#include <core/vpath.h>
24#include <core/vsegment.h>
25#include <kgenericfactory.h>
26#include <kdebug.h>
27#include <tqgroupbox.h>
28#include <tqlabel.h>
29
30#include <knuminput.h>
31
32typedef KGenericFactory<VRoundCornersPlugin, KarbonView> VRoundCornersPluginFactory;
33K_EXPORT_COMPONENT_FACTORY( karbon_roundcornersplugin, VRoundCornersPluginFactory( "karbonroundcornersplugin" ) )
34
35VRoundCornersPlugin::VRoundCornersPlugin( KarbonView *parent, const char* name, const TQStringList & ) : Plugin( parent, name )
36{
37 new TDEAction(
38 i18n( "&Round Corners..." ), "14_roundcorners", 0, this,
39 TQ_SLOT( slotRoundCorners() ), actionCollection(), "path_round_corners" );
40
41 m_roundCornersDlg = new VRoundCornersDlg();
42 m_roundCornersDlg->setRadius( 10.0 );
43}
44
45VRoundCornersPlugin::~VRoundCornersPlugin()
46{
47}
48
49void
50VRoundCornersPlugin::slotRoundCorners()
51{
52 KarbonPart *part = ((KarbonView *)parent())->part();
53 if( part && m_roundCornersDlg->exec() )
54 part->addCommand( new VRoundCornersCmd( &part->document(), m_roundCornersDlg->radius() ), true );
55}
56
57
58VRoundCornersDlg::VRoundCornersDlg( TQWidget* parent, const char* name )
59 : KDialogBase( parent, name, true, i18n( "Polygonize" ), Ok | Cancel )
60{
61 // add input:
62 TQGroupBox* group = new TQGroupBox( 2, TQt::Horizontal, i18n( "Properties" ), this );
63
64 new TQLabel( i18n( "Round corners:" ), group );
65 m_radius = new KDoubleNumInput( group );
66 group->setMinimumWidth( 300 );
67
68 // signals and slots:
69 connect( this, TQ_SIGNAL( okClicked() ), this, TQ_SLOT( accept() ) );
70 connect( this, TQ_SIGNAL( cancelClicked() ), this, TQ_SLOT( reject() ) );
71
72 setMainWidget( group );
73 setFixedSize( baseSize() );
74}
75
76double
77VRoundCornersDlg::radius() const
78{
79 return m_radius->value();
80}
81
82void
83VRoundCornersDlg::setRadius( double value )
84{
85 m_radius->setValue(value);
86}
87
88
89
90VRoundCornersCmd::VRoundCornersCmd( VDocument* doc, double radius )
91 : VReplacingCmd( doc, i18n( "Round Corners" ) )
92{
93 // Set members.
94 m_radius = radius > 0.0 ? radius : 1.0;
95}
96
97void
98VRoundCornersCmd::visitVSubpath( VSubpath& path )
99{
100 // Optimize and avoid a crash.
101 if( path.isEmpty() )
102 return;
103
104 // Note: we modiy segments from path. that doesn't hurt, since we
105 // replace "path" with the temporary path "newPath" afterwards.
106
107 VSubpath newPath( 0L );
108
109 path.first();
110 // Skip "begin".
111 path.next();
112
113 /* This algorithm is worked out by <kudling AT kde DOT org> to produce similar results as
114 * the "round corners" algorithms found in other applications. Neither code nor
115 * algorithms from any 3rd party is used though.
116 *
117 * We want to replace all corners with round corners having "radius" m_radius.
118 * The algorithm doesn't really produce circular arcs, but that's ok since
119 * the algorithm achieves nice looking results and is generic enough to be applied
120 * to all kind of paths.
121 * Note also, that this algorithm doesn't touch smooth joins (in the sense of
122 * VSegment::isSmooth() ).
123 *
124 * We'll manipulate the input path for bookkeeping purposes and construct a new
125 * temporary path in parallel. We finally replace the input path with the new path.
126 *
127 *
128 * Without restricting generality, let's assume the input path is closed and
129 * contains segments which build a rectangle.
130 *
131 * 2
132 * O------------O
133 * | | Numbers reflect the segments' order
134 * 3| |1 in the path. We neglect the "begin"
135 * | | segment here.
136 * O------------O
137 * 0
138 *
139 * There are three unique steps to process. The second step is processed
140 * many times in a loop.
141 *
142 * 1) Begin
143 * -----
144 * Split the first segment of the input path (called "path[0]" here)
145 * at parameter t
146 *
147 * t = path[0]->param( m_radius )
148 *
149 * and move newPath to this new knot. If current segment is too small
150 * (smaller than 2 * m_radius), we always set t = 0.5 here and in the further
151 * steps as well.
152 *
153 * path: new path:
154 *
155 * 2
156 * O------------O
157 * | |
158 * 3 | | 1 The current segment is marked with "#"s.
159 * | |
160 * O##O#########O ...O
161 * 0 0
162 *
163 * 2) Loop
164 * ----
165 * The loop step is iterated over all segments. After each appliance the index n
166 * is incremented and the loop step is reapplied until no untouched segment is left.
167 *
168 * Split the current segment path[n] of the input path at parameter t
169 *
170 * t = path[n]->param( path[n]->length() - m_radius )
171 *
172 * and add the first subsegment of the curent segment to newPath.
173 *
174 * path: new path:
175 *
176 * 2
177 * O------------O
178 * | |
179 * 3 | | 1
180 * | |
181 * O--O######O##O O------O...
182 * 0 0
183 *
184 * Now make the second next segment (the original path[1] segment in our example)
185 * the current one. Split it at parameter t
186 *
187 * t = path[n]->param( m_radius )
188 *
189 * path: new path:
190 *
191 * 2
192 * O------------O
193 * | #
194 * 3 | O 1
195 * | #
196 * O--O------O--O O------O...
197 * 0 0
198 *
199 * Make the first subsegment of the current segment the current one.
200 *
201 * path: new path:
202 *
203 * 2
204 * O------------O
205 * | |
206 * 3 | O 1 O
207 * | # /.1
208 * O--O------O--O O------O...
209 * 0 0
210 *
211 * 3) End
212 * ---
213 *
214 * path: new path:
215 *
216 * 2 4
217 * O--O------O--O 5 .O------O. 3
218 * | | / \
219 * 3 O O 1 6 O O 2
220 * | | 7 .\ /
221 * O--O------O--O ...O------O. 1
222 * 0 0
223 */
224
225 double length;
226 double param;
227
228 // "Begin" step.
229 // =============
230
231 // Convert flat beziers to lines.
232 if( path.current()->isFlat() )
233 path.current()->setDegree( 1 );
234
235 if( path.getLast()->isFlat() )
236 path.getLast()->setDegree( 1 );
237
238 if(
239 path.isClosed() &&
240 // Don't touch smooth joins.
241 !path.getLast()->isSmooth( *path.current() ) )
242 {
243 length = path.current()->length();
244
245 param = length > 2 * m_radius
246 ? path.current()->lengthParam( m_radius )
247 : 0.5;
248
249
250 path.insert(
251 path.current()->splitAt( param ) );
252
253 newPath.moveTo(
254 path.current()->knot() );
255
256 path.next();
257
258
259 if( !success() )
260 setSuccess();
261 }
262 else
263 {
264 newPath.moveTo(
265 path.current()->prev()->knot() );
266 }
267
268
269 // "Loop" step.
270 // ============
271
272 while(
273 path.current() &&
274 path.current()->next() )
275 {
276 // Convert flat beziers to lines.
277 if( path.current()->isFlat() )
278 path.current()->setDegree( 1 );
279
280 if( path.current()->next()->isFlat() )
281 path.current()->next()->setDegree( 1 );
282
283
284 // Don't touch smooth joins.
285 if( path.current()->isSmooth() )
286 {
287 newPath.append( path.current()->clone() );
288 path.next();
289 continue;
290 }
291
292
293 // Split the current segment at param( m_radius ) counting
294 // from the end.
295 length = path.current()->length();
296
297 // If the current segment is too short to be split, just don't split it
298 // because it was split already a t=0.5 during the former step.
299 if( length > m_radius )
300 {
301 param = path.current()->lengthParam( length - m_radius );
302
303 path.insert(
304 path.current()->splitAt( param ) );
305 newPath.append(
306 path.current()->clone() );
307
308 path.next();
309 }
310
311
312 // Jump to the next untouched segment.
313 path.next();
314
315
316 // Split the current segment at param( m_radius ).
317 length = path.current()->length();
318
319 param = length > 2 * m_radius
320 ? path.current()->lengthParam( m_radius )
321 : 0.5;
322
323 path.insert(
324 path.current()->splitAt( param ) );
325
326
327 // Round corner.
328 newPath.curveTo(
329 path.current()->prev()->pointAt( 0.5 ),
330 path.current()->pointAt( 0.5 ),
331 path.current()->knot() );
332
333
334 if( !success() )
335 setSuccess();
336
337 path.next();
338 }
339
340
341 // "End" step.
342 // ===========
343
344 if( path.isClosed() )
345 {
346 // Convert flat beziers to lines.
347 if( path.current()->isFlat() )
348 path.current()->setDegree( 1 );
349
350 if( path.getFirst()->next()->isFlat() )
351 path.getFirst()->next()->setDegree( 1 );
352
353 // Don't touch smooth joins.
354 if( !path.current()->isSmooth( *path.getFirst()->next() ) )
355 {
356 length = path.current()->length();
357
358 // If the current segment is too short to be split, just don't split it
359 // because it was split already at t=0.5 during the former step.
360 if( length > m_radius )
361 {
362 param = path.current()->lengthParam( length - m_radius );
363
364 path.insert(
365 path.current()->splitAt( param ) );
366 newPath.append(
367 path.current()->clone() );
368
369 path.next();
370 }
371
372
373 path.first();
374 path.next();
375
376 // Round corner.
377 newPath.curveTo(
378 path.getLast()->pointAt( 0.5 ),
379 path.current()->pointAt( 0.5 ),
380 path.current()->knot() );
381
382
383 if( !success() )
384 setSuccess();
385 }
386 else
387 newPath.append( path.current()->clone() );
388
389 newPath.close();
390 }
391 else
392 newPath.append( path.current()->clone() );
393
394
395 path = newPath;
396
397 // Invalidate bounding box once.
399}
400
401#include "roundcornersplugin.moc"
402
void addCommand(VCommand *cmd, bool repaint=false)
insert a command into the undo/redo-history:
All non-visual, static doc info is in here.
Definition vdocument.h:53
void invalidateBoundingBox()
Invalidates the bounding box, so it has to be recalculated.
Definition vobject.h:127
VReplacingCmd is a generic command.
double length(double t=1.0) const
Calculates the arclength from p0 to the point parametrized by 0 <= t <= 1.
Definition vsegment.cpp:291
double lengthParam(double len) const
Calculates the parameter of a point located at arclength len.
Definition vsegment.cpp:396
VSegment * next() const
Returns a pointer to the next not deleted segment, if stored in a VSubpath.
bool isFlat(double flatness=VGlobal::flatnessTolerance) const
Returns true if the segment is flat.
Definition vsegment.cpp:139
VSegment * clone() const
Returns a pointer to a copy of this segment.
void setDegree(unsigned short deg)
Sets the segment's degree and thus resizes the array of node data.
Definition vsegment.cpp:72
bool isSmooth(const VSegment &next) const
Calculates wether the tangent at the knot is exactly parallel to the tangent at p0 of the next segmen...
Definition vsegment.cpp:700
VSegment * splitAt(double t)
Splits the segment at parameter 0 <= t <= 1.
Definition vsegment.cpp:857
VSegment * prev() const
Returns a pointer to the previous not deleted segment, if stored in a VSubpath.
KoPoint pointAt(double t) const
Calculates the point on this segment at parameter 0 <= t <= 1.
Definition vsegment.cpp:165
const KoPoint & knot() const
Returns the knot.
Definition vsegment.h:135
VSubpath provides a sophisticated list of VSegment.
Definition vpath.h:43
bool isEmpty() const
Returns true if the current path is "emtpy".
Definition vpath.h:100
bool success() const
Return if the operation was a success or not.
Definition vvisitor.h:125
void setSuccess(bool success=true)
Set the success property.
Definition vvisitor.h:142