From c0d038b9f2cb214ec010e74c99b1d5a1179e97ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Minisini?= Date: Mon, 12 Aug 2013 01:50:46 +0000 Subject: [PATCH] [DEVELOPMENT ENVIRONMENT] * NEW: ImageEditor: Invert the current selected shape reverses its points order. Selection offset has been implemented on top of the new gb.clipper component. Many bug fixes. [GB.CLIPPER] * NEW: This a new component based on the "Clipper" library. At the moment, it only allows to offset a group of polygons from a specified pixel delta. git-svn-id: svn://localhost/gambas/trunk@5788 867c0c6c-44f3-4631-809d-bfa615b0a4ec --- app/src/gambas3/.lang/.pot | 110 +- app/src/gambas3/.lang/fr.mo | Bin 114154 -> 114191 bytes app/src/gambas3/.lang/fr.po | 452 +- app/src/gambas3/.project | 1 + .../.src/Editor/Image/CImageSelection.class | 53 +- .../.src/Editor/Image/CImageShape.class | 17 + .../.src/Editor/Image/FImageEditor.class | 126 +- .../.src/Editor/Image/FImageEditor.form | 37 +- ...tion.class => FImageOffsetSelection.class} | 1 + ...ection.form => FImageOffsetSelection.form} | 4 +- .../.src/Editor/Image/FImageProperty.form | 1 + app/src/gambas3/img/draw/crop.png | Bin 224 -> 260 bytes app/src/gambas3/img/draw/duplicate.png | Bin 188 -> 224 bytes app/src/gambas3/img/draw/enlarge.png | Bin 232 -> 0 bytes app/src/gambas3/img/draw/invert.png | Bin 196 -> 225 bytes app/src/gambas3/img/draw/move.png | Bin 311 -> 406 bytes app/src/gambas3/img/draw/offset.png | Bin 0 -> 221 bytes main/configure.ac | 1 + main/lib/Makefile.am | 2 +- main/lib/clipper/LICENSE | 26 + main/lib/clipper/Makefile.am | 19 + main/lib/clipper/c_clipper.cpp | 160 + main/lib/clipper/c_clipper.h | 35 + main/lib/clipper/clipper.cpp | 3700 +++++++++++++++++ main/lib/clipper/clipper.hpp | 349 ++ main/lib/clipper/gb.clipper.component | 5 + main/lib/clipper/gb.geom.h | 1 + main/lib/clipper/main.cpp | 61 + main/lib/clipper/main.h | 36 + 29 files changed, 4855 insertions(+), 342 deletions(-) rename app/src/gambas3/.src/Editor/Image/{FImageResizeSelection.class => FImageOffsetSelection.class} (90%) rename app/src/gambas3/.src/Editor/Image/{FImageResizeSelection.form => FImageOffsetSelection.form} (90%) delete mode 100644 app/src/gambas3/img/draw/enlarge.png create mode 100644 app/src/gambas3/img/draw/offset.png create mode 100644 main/lib/clipper/LICENSE create mode 100644 main/lib/clipper/Makefile.am create mode 100644 main/lib/clipper/c_clipper.cpp create mode 100644 main/lib/clipper/c_clipper.h create mode 100644 main/lib/clipper/clipper.cpp create mode 100644 main/lib/clipper/clipper.hpp create mode 100644 main/lib/clipper/gb.clipper.component create mode 120000 main/lib/clipper/gb.geom.h create mode 100644 main/lib/clipper/main.cpp create mode 100644 main/lib/clipper/main.h diff --git a/app/src/gambas3/.lang/.pot b/app/src/gambas3/.lang/.pot index c9b09336c..09fc06454 100644 --- a/app/src/gambas3/.lang/.pot +++ b/app/src/gambas3/.lang/.pot @@ -547,7 +547,7 @@ msgstr "" #: FEditor.class:2373 FExportData.class:126 FFieldChooser.form:138 #: FFileProperty.class:140 FFontChooser.form:40 FForm.class:3132 #: FGotoLine.form:23 FHelpBrowser.form:61 FImageEditor.class:348 -#: FImageResize.form:49 FImageResizeSelection.form:33 FImageRotate.form:32 +#: FImageOffsetSelection.form:33 FImageResize.form:49 FImageRotate.form:32 #: FInfo.class:263 FList.form:121 FMain.class:212 FMakeInstall.class:350 #: FMenu.form:377 FNewConnection.form:255 FNewTable.form:86 #: FNewTranslation.form:21 FOpenProject.form:158 FOption.class:675 @@ -647,7 +647,7 @@ msgstr "" #: Design.module:428 FColorChooser.form:54 FConnectionEditor.class:452 #: FCreateFile.form:443 FFieldChooser.form:132 FFontChooser.form:34 -#: FGotoLine.form:17 FImageResize.form:103 FImageResizeSelection.form:27 +#: FGotoLine.form:17 FImageOffsetSelection.form:27 FImageResize.form:103 #: FImageRotate.form:26 FInfo.form:181 FList.form:115 FMain.class:1839 #: FMakeInstall.class:299 FMenu.form:372 FNewConnection.form:249 #: FNewTable.form:80 FNewTranslation.form:15 FOpenProject.form:152 @@ -719,31 +719,31 @@ msgid "Open" msgstr "" #: FConflict.form:53 FConnectionEditor.form:358 FEditor.form:144 -#: FForm.form:239 FImageEditor.form:110 FMenu.form:107 FOutput.form:58 +#: FForm.form:239 FImageEditor.form:112 FMenu.form:107 FOutput.form:58 #: FProjectVersion.form:234 FTextEditor.form:108 msgid "Cut" msgstr "" #: FConflict.form:59 FConnectionEditor.form:147 FEditor.form:150 -#: FForm.form:246 FImageEditor.form:118 FMenu.form:113 FOutput.form:65 +#: FForm.form:246 FImageEditor.form:120 FMenu.form:113 FOutput.form:65 #: FProjectVersion.form:240 FTextEditor.form:115 msgid "Copy" msgstr "" #: FConflict.form:65 FConnectionEditor.form:379 FEditor.form:157 -#: FForm.form:259 FImageEditor.form:125 FMenu.form:119 FOutput.form:72 +#: FForm.form:259 FImageEditor.form:127 FMenu.form:119 FOutput.form:72 #: FPasteTable.form:101 FProjectVersion.form:246 FTextEditor.form:122 msgid "Paste" msgstr "" #: FConflict.form:71 FConnectionEditor.form:386 FEditor.form:128 -#: FForm.form:222 FImageEditor.form:91 FOption.form:561 FOutput.form:41 +#: FForm.form:222 FImageEditor.form:93 FOption.form:561 FOutput.form:41 #: FProjectVersion.form:252 FTextEditor.form:91 msgid "Undo" msgstr "" #: FConflict.form:77 FConnectionEditor.form:393 FEditor.form:135 -#: FForm.form:229 FImageEditor.form:99 FOutput.form:48 +#: FForm.form:229 FImageEditor.form:101 FOutput.form:48 #: FProjectVersion.form:258 FTextEditor.form:98 msgid "Redo" msgstr "" @@ -788,7 +788,7 @@ msgid "Sol&ve" msgstr "" #: FConflict.form:184 FDebugInfo.form:270 FEditor.form:310 -#: FFileProperty.form:75 FForm.form:408 FImageEditor.form:270 +#: FFileProperty.form:75 FForm.form:408 FImageEditor.form:281 #: FImportTable.form:217 FProjectVersion.form:323 FSystemInfo.form:83 #: FTextEditor.form:244 FTips.form:83 msgid "Close" @@ -837,12 +837,12 @@ msgid "Connection editor" msgstr "" #: FConnectionEditor.form:100 FEditor.form:323 FForm.form:421 -#: FImageEditor.form:263 FMenu.class:68 FTextEditor.form:237 +#: FImageEditor.form:274 FMenu.class:68 FTextEditor.form:237 msgid "Save" msgstr "" #: FConnectionEditor.form:108 FEditor.form:317 FForm.form:415 -#: FHelpBrowser.form:67 FImageEditor.form:256 FTextEditor.form:231 +#: FHelpBrowser.form:67 FImageEditor.form:267 FTextEditor.form:231 #: FTranslate.class:650 msgid "Reload" msgstr "" @@ -1437,7 +1437,7 @@ msgstr "" msgid "Function" msgstr "" -#: FDebugInfo.class:55 FImageEditor.form:429 FImportTable.class:66 +#: FDebugInfo.class:55 FImageEditor.form:440 FImportTable.class:66 #: FOption.form:642 msgid "Line" msgstr "" @@ -1466,7 +1466,7 @@ msgstr "" msgid "Do you want to clear the expression list ?" msgstr "" -#: FDebugInfo.class:570 FImageEditor.form:511 FList.class:153 +#: FDebugInfo.class:570 FImageEditor.form:529 FList.class:153 #: FOption.class:675 FOutput.form:79 msgid "Clear" msgstr "" @@ -1503,7 +1503,7 @@ msgstr "" msgid "&Startup class" msgstr "" -#: FEditor.form:167 FImageEditor.form:135 FTextEditor.form:132 +#: FEditor.form:167 FImageEditor.form:137 FTextEditor.form:132 msgid "Select &All" msgstr "" @@ -2025,11 +2025,11 @@ msgstr "" msgid "Show help tree" msgstr "" -#: FHelpBrowser.form:85 FImageEditor.form:222 +#: FHelpBrowser.form:85 FImageEditor.form:233 msgid "Zoom in" msgstr "" -#: FHelpBrowser.form:91 FImageEditor.form:230 +#: FHelpBrowser.form:91 FImageEditor.form:241 msgid "Zoom out" msgstr "" @@ -2049,114 +2049,126 @@ msgstr "" msgid "Default language" msgstr "" -#: FImageEditor.form:141 +#: FImageEditor.form:143 msgid "Hide selection" msgstr "" -#: FImageEditor.form:148 +#: FImageEditor.form:150 msgid "Invert selection" msgstr "" -#: FImageEditor.form:156 +#: FImageEditor.form:158 msgid "Duplicate selection" msgstr "" -#: FImageEditor.form:164 +#: FImageEditor.form:166 FImageOffsetSelection.form:12 +msgid "Offset selection" +msgstr "" + +#: FImageEditor.form:175 msgid "Action" msgstr "" -#: FImageEditor.form:169 +#: FImageEditor.form:180 msgid "Crop" msgstr "" -#: FImageEditor.form:176 FImageProperty.form:289 +#: FImageEditor.form:187 FImageProperty.form:289 msgid "Horizontal flip" msgstr "" -#: FImageEditor.form:183 FImageProperty.form:283 +#: FImageEditor.form:194 FImageProperty.form:283 msgid "Vertical flip" msgstr "" -#: FImageEditor.form:190 FImageProperty.form:277 +#: FImageEditor.form:201 FImageProperty.form:277 msgid "Rotate counter-clockwise" msgstr "" -#: FImageEditor.form:197 FImageProperty.form:271 +#: FImageEditor.form:208 FImageProperty.form:271 msgid "Rotate clockwise" msgstr "" -#: FImageEditor.form:204 +#: FImageEditor.form:215 msgid "Resize..." msgstr "" -#: FImageEditor.form:211 +#: FImageEditor.form:222 msgid "Rotate..." msgstr "" -#: FImageEditor.form:217 +#: FImageEditor.form:228 msgid "Zoom" msgstr "" -#: FImageEditor.form:238 +#: FImageEditor.form:249 msgid "Zoom normal" msgstr "" -#: FImageEditor.form:245 +#: FImageEditor.form:256 msgid "Zoom fit" msgstr "" -#: FImageEditor.form:279 +#: FImageEditor.form:290 msgid "Image editor" msgstr "" -#: FImageEditor.form:379 +#: FImageEditor.form:390 msgid "Drawing grid" msgstr "" -#: FImageEditor.form:394 +#: FImageEditor.form:405 msgid "Move" msgstr "" -#: FImageEditor.form:405 +#: FImageEditor.form:416 msgid "Draw" msgstr "" -#: FImageEditor.form:415 +#: FImageEditor.form:426 msgid "Erase" msgstr "" -#: FImageEditor.form:439 +#: FImageEditor.form:450 msgid "Rectangle" msgstr "" -#: FImageEditor.form:449 +#: FImageEditor.form:460 msgid "Ellipse" msgstr "" -#: FImageEditor.form:461 +#: FImageEditor.form:472 msgid "Magic wand" msgstr "" -#: FImageEditor.form:471 +#: FImageEditor.form:482 msgid "Edit selection" msgstr "" -#: FImageEditor.form:497 +#: FImageEditor.form:515 msgid "Stroke" msgstr "" -#: FImageEditor.form:504 +#: FImageEditor.form:522 msgid "Fill" msgstr "" -#: FImageEditor.form:557 +#: FImageEditor.form:575 msgid "Resize or stretch image" msgstr "" -#: FImageEditor.form:566 FImageRotate.form:11 +#: FImageEditor.form:584 FImageRotate.form:11 msgid "Rotate image" msgstr "" +#: FImageOffsetSelection.form:22 FImageResize.form:127 +msgid "px" +msgstr "" + +#: FImageOffsetSelection.form:40 +msgid "Duplicate" +msgstr "" + #: FImageProperty.form:126 FReportBrushChooser.form:49 msgid "Color" msgstr "" @@ -2258,7 +2270,7 @@ msgstr "" msgid "Grid resolution" msgstr "" -#: FImageProperty.form:654 +#: FImageProperty.form:655 msgid "Subdivision" msgstr "" @@ -2318,18 +2330,6 @@ msgstr "" msgid "Ratio" msgstr "" -#: FImageResize.form:127 FImageResizeSelection.form:22 -msgid "px" -msgstr "" - -#: FImageResizeSelection.form:12 -msgid "Enlarge selection" -msgstr "" - -#: FImageResizeSelection.form:40 -msgid "Duplicate" -msgstr "" - #: FImageRotate.form:21 msgid "°" msgstr "" diff --git a/app/src/gambas3/.lang/fr.mo b/app/src/gambas3/.lang/fr.mo index f68a23be3edf810a3e795cccfc1b848be913a2e6..4e2c60a1676f878e3a47bbae1b868e8e3393554b 100644 GIT binary patch delta 21644 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 delta 21604 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 Bf>{6n diff --git a/app/src/gambas3/.lang/fr.po b/app/src/gambas3/.lang/fr.po index 1421d1686..7c99fc862 100644 --- a/app/src/gambas3/.lang/fr.po +++ b/app/src/gambas3/.lang/fr.po @@ -255,7 +255,7 @@ msgstr "Signet précédent" msgid "Next bookmark" msgstr "Signet suivant" -#: CClassInfo.class:557 FMain.form:1427 FProperty.form:23 +#: CClassInfo.class:557 FMain.form:1421 FProperty.form:23 msgid "Properties" msgstr "Propriétés" @@ -675,7 +675,7 @@ msgstr "Fournit" msgid "Requires" msgstr "Nécessite" -#: CModule.class:28 FCreateFile.form:150 FEditor.form:372 FForm.form:437 +#: CModule.class:28 FCreateFile.form:150 FEditor.form:372 FForm.form:431 msgid "Form" msgstr "Formulaire" @@ -891,7 +891,7 @@ msgstr "Système" msgid "Free" msgstr "Libre" -#: FConflict.class:24 FMain.form:714 +#: FConflict.class:24 FMain.form:713 msgid "&Edit" msgstr "&Éditer" @@ -1131,7 +1131,7 @@ msgstr "Désirez-vous vraiment supprimer les lignes sélectionnées ?" msgid "Connection editor" msgstr "Editeur de connexion" -#: FImageEditor.form:263 FMenu.class:68 FTextEditor.form:237 +#: FImageEditor.form:274 FMenu.class:68 FTextEditor.form:237 msgid "Save" msgstr "Enregistrer" @@ -1271,7 +1271,7 @@ msgstr "Impossible d'ajouter le fichier." msgid "New file" msgstr "Nouveau fichier" -#: FCreateFile.form:79 FMain.form:618 +#: FCreateFile.form:79 FMain.form:617 msgid "New" msgstr "Nouveau" @@ -1343,7 +1343,7 @@ msgstr "Veuillez saisir l'emplacement du référentiel." msgid "The project has been successfully created." msgstr "Le projet a été créé avec succès." -#: FCreateProject.form:66 FMain.form:971 +#: FCreateProject.form:66 FMain.form:966 msgid "New project" msgstr "Nouveau projet" @@ -1531,11 +1531,11 @@ msgstr "Variables statiques" msgid "Warnings" msgstr "Avertissements" -#: FDebugInfo.form:49 FMain.form:1416 +#: FDebugInfo.form:49 FMain.form:1410 msgid "Debug" msgstr "Débogage" -#: FDebugInfo.form:60 FMain.form:1112 FOption.form:346 FSearch.class:93 +#: FDebugInfo.form:60 FMain.form:1107 FOption.form:346 FSearch.class:93 msgid "Console" msgstr "Console" @@ -1647,11 +1647,11 @@ msgstr "Trouver la définition" msgid "Open form" msgstr "Ouvrir le formulaire" -#: FEditor.form:118 FForm.form:155 FMain.form:793 +#: FEditor.form:118 FForm.form:153 FMain.form:792 msgid "&Startup class" msgstr "Cla&sse de démarrage" -#: FEditor.form:167 FImageEditor.form:135 FTextEditor.form:132 +#: FEditor.form:167 FImageEditor.form:137 FTextEditor.form:132 msgid "Select &All" msgstr "&Tout sélectionner" @@ -1895,227 +1895,223 @@ msgstr "Composant introuvable pour le contrôle &1" msgid "The form has been modified.\n\nAll your changes will be lost." msgstr "Le formulaire a été modifié.\n\nTous vos changements seront perdus." -#: FForm.form:131 +#: FForm.form:129 msgid "Select" msgstr "Sélectionner" -#: FForm.form:136 +#: FForm.form:134 msgid "Event" msgstr "Evènement" -#: FForm.form:142 +#: FForm.form:140 msgid "Change into" msgstr "Transformer en" -#: FForm.form:148 FTextEditor.form:81 +#: FForm.form:146 FTextEditor.form:81 msgid "Open code" msgstr "Ouvrir le code" -#: FForm.form:164 +#: FForm.form:162 msgid "Show tab" msgstr "Afficher l'onglet" -#: FForm.form:172 +#: FForm.form:170 msgid "Move tab" msgstr "Déplacer l'onglet" -#: FForm.form:177 +#: FForm.form:175 msgid "&First" msgstr "&Début" -#: FForm.form:184 FSearch.form:198 FTips.form:71 +#: FForm.form:182 FSearch.form:198 FTips.form:71 msgid "&Previous" msgstr "&Précédent" -#: FForm.form:191 FSearch.form:192 FTips.form:77 +#: FForm.form:189 FSearch.form:192 FTips.form:77 msgid "&Next" msgstr "&Suivant" -#: FForm.form:198 +#: FForm.form:196 msgid "&Last" msgstr "&Fin" -#: FForm.form:209 +#: FForm.form:207 msgid "Select all" msgstr "Sélectionner tout" -#: FForm.form:215 +#: FForm.form:213 msgid "Unselect all" msgstr "Déselectionner tout" -#: FForm.form:255 +#: FForm.form:253 msgid "Copy at the same place" msgstr "Copier au même emplacement" -#: FForm.form:275 +#: FForm.form:273 msgid "Delete container only" msgstr "Supprimer le conteneur uniquement" -#: FForm.form:282 +#: FForm.form:280 msgid "Embed into a container" msgstr "Imbriquer dans un conteneur" -#: FForm.form:291 +#: FForm.form:289 msgid "Arrangement" msgstr "Disposition" -#: FForm.form:295 +#: FForm.form:293 msgid "Bring to foreground" msgstr "Au premier plan" -#: FForm.form:302 +#: FForm.form:300 msgid "Send to background" msgstr "A l'arrière-plan" -#: FForm.form:312 +#: FForm.form:310 msgid "Horizontal" msgstr "Horizontal" -#: FForm.form:319 +#: FForm.form:317 msgid "Rows" msgstr "Lignes" -#: FForm.form:325 +#: FForm.form:323 msgid "Vertical" msgstr "Vertical" -#: FForm.form:332 +#: FForm.form:330 msgid "Columns" msgstr "Colonnes" -#: FForm.form:341 +#: FForm.form:339 msgid "Center horizontally" msgstr "Centrer horizontalement" -#: FForm.form:347 +#: FForm.form:345 msgid "Center vertically" msgstr "Centrer verticalement" -#: FForm.form:353 +#: FForm.form:351 msgid "Alignment" msgstr "Alignement" -#: FForm.form:357 +#: FForm.form:355 msgid "Align to &left" msgstr "Aligner sur la &gauche" -#: FForm.form:363 +#: FForm.form:361 msgid "Align to &right" msgstr "Aligner sur la &droite" -#: FForm.form:369 +#: FForm.form:367 msgid "Align to &top" msgstr "Aligner sur le &haut" -#: FForm.form:375 +#: FForm.form:373 msgid "Align to &bottom" msgstr "Aligner sur le &bas" -#: FForm.form:384 +#: FForm.form:382 msgid "Same &width" msgstr "Même &largeur" -#: FForm.form:390 +#: FForm.form:388 msgid "Same &height" msgstr "Même hau&teur" -#: FForm.form:400 +#: FForm.form:398 msgid "Menu editor..." msgstr "Editeur de menu..." -#: FForm.form:458 FTextEditor.form:284 +#: FForm.form:453 FTextEditor.form:284 msgid "Code" msgstr "Code" -#: FForm.form:465 +#: FForm.form:460 msgid "Lock form" msgstr "Verrouiller le formulaire" -#: FForm.form:525 FMenu.form:65 +#: FForm.form:520 FMenu.form:65 msgid "Menu editor" msgstr "Editeur de menu" -#: FForm.form:532 +#: FForm.form:527 msgid "Toggle grid" msgstr "Bascule l'affichage de la grille" -#: FForm.form:559 +#: FForm.form:554 msgid "Align to top" msgstr "Aligner sur le haut" -#: FForm.form:566 +#: FForm.form:561 msgid "Align to bottom" msgstr "Aligner sur le bas" -#: FForm.form:573 +#: FForm.form:568 msgid "Align to left" msgstr "Aligner sur la gauche" -#: FForm.form:580 +#: FForm.form:575 msgid "Align to right" msgstr "Aligner sur la droite" -#: FForm.form:587 +#: FForm.form:582 msgid "Same width" msgstr "Même largeur" -#: FForm.form:594 +#: FForm.form:589 msgid "Same height" msgstr "Même hauteur" -#: FForm.form:651 +#: FForm.form:646 msgid "Move tab first" msgstr "Déplacer l'onglet au début" -#: FForm.form:658 +#: FForm.form:653 msgid "Move tab left" msgstr "Déplacer l'onglet à gauche" -#: FForm.form:665 +#: FForm.form:660 msgid "Move tab right" msgstr "Déplacer l'onglet à droite" -#: FForm.form:672 +#: FForm.form:667 msgid "Move tab last" msgstr "Déplacer l'onglet à la fin" -#: FForm.form:688 -msgid "Format" -msgstr "Formatage" - -#: FForm.form:692 FOption.form:225 +#: FForm.form:685 FOption.form:225 msgid "Bold" msgstr "Gras" -#: FForm.form:699 FOption.form:229 +#: FForm.form:692 FOption.form:229 msgid "Italic" msgstr "Italique" -#: FForm.form:706 FOption.form:233 +#: FForm.form:699 FOption.form:233 msgid "Underline" msgstr "Souligné" -#: FForm.form:713 +#: FForm.form:706 msgid "Bigger font" msgstr "Police plus grande" -#: FForm.form:720 +#: FForm.form:713 msgid "Smaller font" msgstr "Police plus petite" -#: FForm.form:727 FOption.form:261 +#: FForm.form:720 FOption.form:261 msgid "Default font" msgstr "Police par défaut" -#: FForm.form:734 FOption.form:577 MTheme.module:6 +#: FForm.form:727 FOption.form:577 MTheme.module:6 msgid "Background" msgstr "Arrière-plan" -#: FForm.form:741 +#: FForm.form:734 msgid "Foreground" msgstr "Avant-plan" -#: FFormStack.form:15 FMain.form:1454 +#: FFormStack.form:15 FMain.form:1448 msgid "Hierarchy" msgstr "Hiérarchie" @@ -2147,15 +2143,15 @@ msgstr "Aller à la ligne" msgid "Default language" msgstr "Langage par défaut" -#: FHelpBrowser.form:30 FMain.form:1130 +#: FHelpBrowser.form:30 FMain.form:1125 msgid "Help browser" msgstr "Navigateur d'aide" -#: FHelpBrowser.form:41 FMain.form:538 FProfile.form:48 +#: FHelpBrowser.form:41 FMain.form:537 FProfile.form:48 msgid "Go back" msgstr "Revenir en arrière" -#: FHelpBrowser.form:47 FMain.form:545 FProfile.form:54 +#: FHelpBrowser.form:47 FMain.form:544 FProfile.form:54 msgid "Go forward" msgstr "Aller en avant" @@ -2163,11 +2159,11 @@ msgstr "Aller en avant" msgid "Show help tree" msgstr "Afficher l'arborescence de l'aide" -#: FHelpBrowser.form:85 FImageEditor.form:222 +#: FHelpBrowser.form:85 FImageEditor.form:233 msgid "Zoom in" msgstr "Zoom avant" -#: FHelpBrowser.form:91 FImageEditor.form:230 +#: FHelpBrowser.form:91 FImageEditor.form:241 msgid "Zoom out" msgstr "Zoom arrière" @@ -2183,114 +2179,126 @@ msgstr "Imprimer" msgid "Modify documentation..." msgstr "Modifier la documentation..." -#: FImageEditor.form:141 +#: FImageEditor.form:143 msgid "Hide selection" msgstr "Cacher le sélection" -#: FImageEditor.form:148 +#: FImageEditor.form:150 msgid "Invert selection" msgstr "Inverser la sélection" -#: FImageEditor.form:156 +#: FImageEditor.form:158 msgid "Duplicate selection" msgstr "Dupliquer la sélection" -#: FImageEditor.form:164 +#: FImageEditor.form:166 FImageOffsetSelection.form:12 +msgid "Offset selection" +msgstr "Agrandir ou rétrécir la sélection" + +#: FImageEditor.form:175 msgid "Action" msgstr "Action" -#: FImageEditor.form:169 +#: FImageEditor.form:180 msgid "Crop" msgstr "Découper" -#: FImageEditor.form:176 FImageProperty.form:289 +#: FImageEditor.form:187 FImageProperty.form:289 msgid "Horizontal flip" msgstr "Miroir horizontal" -#: FImageEditor.form:183 FImageProperty.form:283 +#: FImageEditor.form:194 FImageProperty.form:283 msgid "Vertical flip" msgstr "Miroir vertical" -#: FImageEditor.form:190 FImageProperty.form:277 +#: FImageEditor.form:201 FImageProperty.form:277 msgid "Rotate counter-clockwise" msgstr "Rotation de 90° vers la gauche" -#: FImageEditor.form:197 FImageProperty.form:271 +#: FImageEditor.form:208 FImageProperty.form:271 msgid "Rotate clockwise" msgstr "Rotation de 90° vers la droite" -#: FImageEditor.form:204 +#: FImageEditor.form:215 msgid "Resize..." msgstr "Redimensionner..." -#: FImageEditor.form:211 +#: FImageEditor.form:222 msgid "Rotate..." msgstr "Rotation..." -#: FImageEditor.form:217 +#: FImageEditor.form:228 msgid "Zoom" msgstr "Zoom" -#: FImageEditor.form:238 +#: FImageEditor.form:249 msgid "Zoom normal" msgstr "Zoom normal" -#: FImageEditor.form:245 +#: FImageEditor.form:256 msgid "Zoom fit" msgstr "Zoom ajusté" -#: FImageEditor.form:280 +#: FImageEditor.form:290 msgid "Image editor" msgstr "Editeur d'image" -#: FImageEditor.form:380 +#: FImageEditor.form:390 msgid "Drawing grid" msgstr "Grille de dessin" -#: FImageEditor.form:395 +#: FImageEditor.form:405 msgid "Move" msgstr "Déplacer" -#: FImageEditor.form:406 +#: FImageEditor.form:416 msgid "Draw" msgstr "Dessiner" -#: FImageEditor.form:416 +#: FImageEditor.form:426 msgid "Erase" msgstr "Gommer" -#: FImageEditor.form:440 +#: FImageEditor.form:450 msgid "Rectangle" msgstr "Rectangle" -#: FImageEditor.form:450 +#: FImageEditor.form:460 msgid "Ellipse" msgstr "Ellipse" -#: FImageEditor.form:462 +#: FImageEditor.form:472 msgid "Magic wand" msgstr "" -#: FImageEditor.form:472 +#: FImageEditor.form:482 msgid "Edit selection" msgstr "Modifier la sélection" -#: FImageEditor.form:498 +#: FImageEditor.form:516 msgid "Stroke" msgstr "Tracer" -#: FImageEditor.form:505 +#: FImageEditor.form:523 msgid "Fill" msgstr "Remplir" -#: FImageEditor.form:558 +#: FImageEditor.form:576 msgid "Resize or stretch image" msgstr "Redimensionner ou étirer l'image" -#: FImageEditor.form:567 FImageRotate.form:11 +#: FImageEditor.form:585 FImageRotate.form:11 msgid "Rotate image" msgstr "Rotation de l'image" +#: FImageOffsetSelection.form:22 FImageResize.form:127 +msgid "px" +msgstr "px" + +#: FImageOffsetSelection.form:40 +msgid "Duplicate" +msgstr "Dupliquer" + #: FImageProperty.class:816 msgid "System clipboard" msgstr "Presse-papier système" @@ -2419,7 +2427,7 @@ msgstr "&Appliquer" msgid "Grid resolution" msgstr "Résolution de la grille" -#: FImageProperty.form:654 +#: FImageProperty.form:655 msgid "Subdivision" msgstr "Sous-divisions" @@ -2451,18 +2459,6 @@ msgstr "Ajouter une bordure" msgid "Ratio" msgstr "Rapport" -#: FImageResize.form:127 FImageResizeSelection.form:22 -msgid "px" -msgstr "px" - -#: FImageResizeSelection.form:12 -msgid "Enlarge selection" -msgstr "" - -#: FImageResizeSelection.form:40 -msgid "Duplicate" -msgstr "Dupliquer" - #: FImageRotate.form:21 msgid "°" msgstr "-" @@ -2651,11 +2647,11 @@ msgstr "&Descendre" msgid "Cl&ear" msgstr "&Effacer" -#: FList.form:92 FMain.form:753 FSystemInfo.form:77 +#: FList.form:92 FMain.form:752 FSystemInfo.form:77 msgid "&Copy" msgstr "&Copier" -#: FList.form:98 FMain.form:760 +#: FList.form:98 FMain.form:759 msgid "&Paste" msgstr "&Coller" @@ -2739,303 +2735,303 @@ msgstr "Choisissez un profilage" msgid "Profile for &1 project" msgstr "Profilage pour le projet &1" -#: FMain.form:223 +#: FMain.form:222 msgid "&File" msgstr "&Fichier" -#: FMain.form:227 +#: FMain.form:226 msgid "&New project..." msgstr "&Nouveau projet..." -#: FMain.form:234 +#: FMain.form:233 msgid "&Open project..." msgstr "&Ouvrir un projet..." -#: FMain.form:240 +#: FMain.form:239 msgid "Open &recent" msgstr "Ouvrir un projet &récent" -#: FMain.form:247 +#: FMain.form:246 msgid "Open &example" msgstr "Ouvrir un &exemple" -#: FMain.form:258 +#: FMain.form:257 msgid "&Save project" msgstr "&Enregistrer le projet" -#: FMain.form:266 +#: FMain.form:265 msgid "Save project &as..." msgstr "Enregistrer le projet &sous..." -#: FMain.form:277 +#: FMain.form:276 msgid "&Quit" msgstr "&Quitter" -#: FMain.form:284 +#: FMain.form:283 msgid "&Project" msgstr "&Projet" -#: FMain.form:289 +#: FMain.form:288 msgid "&Compile" msgstr "&Compiler" -#: FMain.form:296 +#: FMain.form:295 msgid "Compile &All" msgstr "&Tout Compiler" -#: FMain.form:306 +#: FMain.form:305 msgid "&Translate..." msgstr "&Traduire..." -#: FMain.form:315 +#: FMain.form:314 msgid "Make" msgstr "Générer" -#: FMain.form:319 +#: FMain.form:318 msgid "E&xecutable..." msgstr "E&xécutable..." -#: FMain.form:326 +#: FMain.form:325 msgid "&Source archive..." msgstr "Archive des fichiers &source..." -#: FMain.form:333 +#: FMain.form:332 msgid "&Installation package..." msgstr "Paquetage d'&installation..." -#: FMain.form:340 +#: FMain.form:339 msgid "Patch" msgstr "Patch" -#: FMain.form:343 +#: FMain.form:342 msgid "&Create..." msgstr "&Créer..." -#: FMain.form:348 +#: FMain.form:347 msgid "&Apply..." msgstr "&Appliquer..." -#: FMain.form:358 +#: FMain.form:357 msgid "&Clean up" msgstr "&Nettoyer" -#: FMain.form:364 +#: FMain.form:363 msgid "&Refresh" msgstr "&Rafraîchir" -#: FMain.form:370 +#: FMain.form:369 msgid "Put on &version control" msgstr "Mettre sous contrôle de &version" -#: FMain.form:380 +#: FMain.form:379 msgid "&Properties..." msgstr "&Propriétés..." -#: FMain.form:387 FPropertyComponent.form:44 +#: FMain.form:386 FPropertyComponent.form:44 msgid "Component properties" msgstr "Propriétés du composant" -#: FMain.form:395 +#: FMain.form:394 msgid "&Version control..." msgstr "Contrôle de &version..." -#: FMain.form:401 +#: FMain.form:400 msgid "&Debug" msgstr "&Débogage" -#: FMain.form:406 +#: FMain.form:405 msgid "&Run" msgstr "Déma&rrer" -#: FMain.form:413 +#: FMain.form:412 msgid "Use &terminal emulator" msgstr "Utiliser un émulateur de &terminal" -#: FMain.form:419 FProjectProperty.form:470 +#: FMain.form:418 FProjectProperty.form:470 msgid "Use embedded HTTP server" msgstr "Utiliser le serveur HTTP embarqué" -#: FMain.form:425 +#: FMain.form:424 msgid "Activate profilin&g" msgstr "Activer le profila&ge" -#: FMain.form:434 +#: FMain.form:433 msgid "&Pause" msgstr "&Pause" -#: FMain.form:441 +#: FMain.form:440 msgid "&Stop" msgstr "&Stop" -#: FMain.form:448 +#: FMain.form:447 msgid "St&ep" msgstr "P&as-à-pas" -#: FMain.form:455 +#: FMain.form:454 msgid "&Forward" msgstr "&Avancer" -#: FMain.form:462 +#: FMain.form:461 msgid "Finis&h" msgstr "&Terminer" -#: FMain.form:472 +#: FMain.form:471 msgid "&Open profile..." msgstr "&Ouvrir un profilage..." -#: FMain.form:478 +#: FMain.form:477 msgid "Clear &all breakpoints" msgstr "Effacer &tous les points d'arrêts" -#: FMain.form:483 +#: FMain.form:482 msgid "Close all &debug windows" msgstr "Fermer toutes les fenêtres de &débogage" -#: FMain.form:488 +#: FMain.form:487 msgid "&View" msgstr "&Affichage" -#: FMain.form:493 +#: FMain.form:492 msgid "Pro&ject" msgstr "Pro&jet" -#: FMain.form:502 +#: FMain.form:501 msgid "&Properties" msgstr "&Propriétés" -#: FMain.form:511 +#: FMain.form:510 msgid "&Console" msgstr "&Console" -#: FMain.form:521 +#: FMain.form:520 msgid "Status bar" msgstr "Barre d'état" -#: FMain.form:529 +#: FMain.form:528 msgid "Hide menubar" msgstr "Cacher la barre de menus" -#: FMain.form:552 +#: FMain.form:551 msgid "Close &all windows" msgstr "Fermer &toutes les fenêtres" -#: FMain.form:558 +#: FMain.form:557 msgid "&Tools" msgstr "&Outils" -#: FMain.form:562 +#: FMain.form:561 msgid "&Find..." msgstr "&Rechercher..." -#: FMain.form:568 +#: FMain.form:567 msgid "&Replace..." msgstr "&Remplacer..." -#: FMain.form:578 +#: FMain.form:577 msgid "&Browse project..." msgstr "&Parcourir le projet..." -#: FMain.form:585 +#: FMain.form:584 msgid "&Open a terminal..." msgstr "&Ouvrir un terminal..." -#: FMain.form:592 +#: FMain.form:591 msgid "Send project by &mail..." msgstr "Envoyer le projet par &courriel..." -#: FMain.form:601 +#: FMain.form:600 msgid "&Shortcuts..." msgstr "&Raccourcis..." -#: FMain.form:607 +#: FMain.form:606 msgid "&Preferences..." msgstr "&Préférences..." -#: FMain.form:622 +#: FMain.form:621 msgid "&Directory" msgstr "&Répertoire" -#: FMain.form:628 +#: FMain.form:627 msgid "&Project link..." msgstr "Lien vers un &projet..." -#: FMain.form:639 +#: FMain.form:638 msgid "&Module..." msgstr "&Module..." -#: FMain.form:647 +#: FMain.form:646 msgid "&Class..." msgstr "&Classe..." -#: FMain.form:655 +#: FMain.form:654 msgid "&Form..." msgstr "&Formulaire..." -#: FMain.form:663 +#: FMain.form:662 msgid "&WebPage..." msgstr "&Page Web..." -#: FMain.form:671 +#: FMain.form:670 msgid "&Report..." msgstr "&État..." -#: FMain.form:682 +#: FMain.form:681 msgid "&Image..." msgstr "&Image..." -#: FMain.form:689 +#: FMain.form:688 msgid "&HTML file..." msgstr "Fichiers &HTML..." -#: FMain.form:696 +#: FMain.form:695 msgid "&Style sheet..." msgstr "&Feuille de style..." -#: FMain.form:704 +#: FMain.form:703 msgid "&Other..." msgstr "&Autre..." -#: FMain.form:719 +#: FMain.form:718 msgid "Edit &code" msgstr "Editer le &code" -#: FMain.form:723 +#: FMain.form:722 msgid "Open with" msgstr "Ouvrir avec" -#: FMain.form:731 +#: FMain.form:730 msgid "Compress all" msgstr "Tout compresser" -#: FMain.form:737 +#: FMain.form:736 msgid "Uncompress all" msgstr "Tout décompresser" -#: FMain.form:746 +#: FMain.form:745 msgid "&Cut" msgstr "&Couper" -#: FMain.form:767 +#: FMain.form:766 msgid "&Rename..." msgstr "&Renommer..." -#: FMain.form:774 +#: FMain.form:773 msgid "&Delete..." msgstr "&Supprimer..." -#: FMain.form:784 +#: FMain.form:783 msgid "Copy file pat&h" msgstr "Copier le c&hemin du fichier" -#: FMain.form:802 +#: FMain.form:801 msgid "&Add to repository" msgstr "&Ajouter au référentiel" -#: FMain.form:827 +#: FMain.form:826 msgid "&New connection..." msgstr "&Nouvelle connexion..." -#: FMain.form:835 +#: FMain.form:834 msgid "&Open" msgstr "&Ouvrir" @@ -3043,151 +3039,151 @@ msgstr "&Ouvrir" msgid "&Remove" msgstr "&Supprimer" -#: FMain.form:895 +#: FMain.form:894 msgid "&?" msgstr "&?" -#: FMain.form:904 +#: FMain.form:903 msgid "&Help browser" msgstr "&Navigateur d'aide" -#: FMain.form:912 +#: FMain.form:911 msgid "&Tips of the day" msgstr "&Astuces du jour" -#: FMain.form:920 +#: FMain.form:919 msgid "&System informations..." msgstr "Informations &système..." -#: FMain.form:926 +#: FMain.form:925 msgid "&About Gambas..." msgstr "&À propos de Gambas..." -#: FMain.form:940 +#: FMain.form:939 msgid "Show exported classes" msgstr "Afficher les classes exportées" -#: FMain.form:945 +#: FMain.form:944 msgid "Show added files" msgstr "Afficher les fichier ajoutés" -#: FMain.form:951 +#: FMain.form:950 msgid "Show all" msgstr "Afficher tout" -#: FMain.form:978 +#: FMain.form:973 msgid "Open project" msgstr "Ouvrir un projet" -#: FMain.form:987 +#: FMain.form:982 msgid "Save project" msgstr "Enregistrer le projet" -#: FMain.form:994 FSaveProjectAs.form:18 +#: FMain.form:989 FSaveProjectAs.form:18 msgid "Save project as" msgstr "Enregistrer le projet sous" -#: FMain.form:1001 FProjectProperty.form:116 +#: FMain.form:996 FProjectProperty.form:116 msgid "Project properties" msgstr "Propriétés du projet" -#: FMain.form:1008 +#: FMain.form:1003 msgid "Project version control" msgstr "Contrôle de version du projet" -#: FMain.form:1022 +#: FMain.form:1017 msgid "Refresh project" msgstr "Rafraîchir le projet" -#: FMain.form:1029 FOption.form:203 +#: FMain.form:1024 FOption.form:203 msgid "Preferences" msgstr "Préférences" -#: FMain.form:1038 +#: FMain.form:1033 msgid "Shortcuts" msgstr "Raccourcis" -#: FMain.form:1049 FMakeExecutable.form:15 +#: FMain.form:1044 FMakeExecutable.form:15 msgid "Make executable" msgstr "Générer l'exécutable" -#: FMain.form:1056 +#: FMain.form:1051 msgid "Translate" msgstr "Traduire" -#: FMain.form:1064 +#: FMain.form:1059 msgid "Make source archive" msgstr "Générer une archive des sources" -#: FMain.form:1072 FMakeInstall.form:106 +#: FMain.form:1067 FMakeInstall.form:106 msgid "Make installation package" msgstr "Créer un paquetage d'installation" -#: FMain.form:1094 +#: FMain.form:1089 msgid "Properties sheet" msgstr "Feuille de propriétés" -#: FMain.form:1103 +#: FMain.form:1098 msgid "Toolbox" msgstr "Boîte à outils" -#: FMain.form:1121 FSearch.form:62 +#: FMain.form:1116 FSearch.form:62 msgid "Search" msgstr "Rechercher" -#: FMain.form:1138 +#: FMain.form:1133 msgid "Compile" msgstr "Compiler" -#: FMain.form:1145 +#: FMain.form:1140 msgid "Compile all" msgstr "Tout compiler" -#: FMain.form:1152 +#: FMain.form:1147 msgid "Run" msgstr "Démarrer" -#: FMain.form:1159 +#: FMain.form:1154 msgid "Pause" msgstr "Pause" -#: FMain.form:1166 +#: FMain.form:1161 msgid "Stop" msgstr "Arrêter" -#: FMain.form:1173 +#: FMain.form:1168 msgid "Step" msgstr "Pas-à-pas" -#: FMain.form:1180 +#: FMain.form:1175 msgid "Forward" msgstr "Avancer" -#: FMain.form:1187 +#: FMain.form:1182 msgid "Finish current function" msgstr "Terminer la fonction courante" -#: FMain.form:1195 +#: FMain.form:1190 msgid "Run until current line" msgstr "Exécuter jusqu'à la ligne courante" -#: FMain.form:1206 +#: FMain.form:1201 msgid "Browse project..." msgstr "Parcourir le projet..." -#: FMain.form:1231 +#: FMain.form:1226 msgid "Browse project" msgstr "Parcourir le projet" -#: FMain.form:1239 +#: FMain.form:1234 msgid "Open a terminal" msgstr "Ouvrir un terminal" -#: FMain.form:1247 +#: FMain.form:1242 msgid "Send project by mail" msgstr "Envoyer le projet par courriel" -#: FMain.form:1360 FProjectProperty.form:607 +#: FMain.form:1354 FProjectProperty.form:607 msgid "Reset filter" msgstr "Réinitialiser le filtre" diff --git a/app/src/gambas3/.project b/app/src/gambas3/.project index ec7deffd7..33e343e7e 100644 --- a/app/src/gambas3/.project +++ b/app/src/gambas3/.project @@ -8,6 +8,7 @@ VersionFile=1 Component=gb.image Component=gb.qt4 Component=gb.form +Component=gb.clipper Component=gb.db Component=gb.db.form Component=gb.debug diff --git a/app/src/gambas3/.src/Editor/Image/CImageSelection.class b/app/src/gambas3/.src/Editor/Image/CImageSelection.class index 4f7adc864..3f92efca2 100644 --- a/app/src/gambas3/.src/Editor/Image/CImageSelection.class +++ b/app/src/gambas3/.src/Editor/Image/CImageSelection.class @@ -219,7 +219,11 @@ End Public Sub Invert() - _Invert = Not _Invert + If _Current >= 0 Then + Shapes[_Current].Invert + Else + _Invert = Not _Invert + Endif End @@ -233,6 +237,7 @@ End Public Sub Clear() Shapes.Clear + _Magnets.Clear End @@ -708,3 +713,49 @@ Public Sub Duplicate() Next End + +Public Sub Offset(fOffset As Float, bDup As Boolean) + + Dim aPolygons As New PointF[][] + Dim hShape As CImageShape + Dim I As Integer + + If _Current < 0 Then + + For I = 0 To Shapes.Max + aPolygons.Add(Shapes[I].Points) + Next + + aPolygons = Clipper.OffsetPolygons(aPolygons, fOffset) + + If Not bDup Then Clear() + + For I = 0 To aPolygons.Max + hShape = New CImageShape + hShape.Points = aPolygons[I] + hShape.AddMagnetsFromExtent() + AddShape(hShape) + Next + + Else + + aPolygons.Add(Shapes[_Current].Points) + + aPolygons = Clipper.OffsetPolygons(aPolygons, fOffset) + + If aPolygons.Count Then + If Not bDup Then + Shapes[_Current].Points = aPolygons[0] + Shapes[_Current].AddMagnetsFromExtent() + Else + hShape = New CImageShape + hShape.Points = aPolygons[0] + hShape.AddMagnetsFromExtent() + AddShape(hShape) + _Current = Shapes.Max + Endif + Endif + + Endif + +End diff --git a/app/src/gambas3/.src/Editor/Image/CImageShape.class b/app/src/gambas3/.src/Editor/Image/CImageShape.class index 125ae916e..0df13a704 100644 --- a/app/src/gambas3/.src/Editor/Image/CImageShape.class +++ b/app/src/gambas3/.src/Editor/Image/CImageShape.class @@ -238,3 +238,20 @@ Public Sub SetPoint(iPoint As Integer, hPoint As PointF) _Extents = Null End + +Public Sub AddMagnetsFromExtent() + + Dim hExt As RectF = GetExtents() + + AddMagnet(PointF(hExt.X, hExt.Y)) + AddMagnet(PointF(hExt.X + hExt.W, hExt.Y)) + AddMagnet(PointF(hExt.X, hExt.Y + hExt.H)) + AddMagnet(PointF(hExt.X + hExt.W, hExt.Y + hExt.H)) + +End + +Public Sub Invert() + + Points.Reverse + +End diff --git a/app/src/gambas3/.src/Editor/Image/FImageEditor.class b/app/src/gambas3/.src/Editor/Image/FImageEditor.class index f76c168d9..61c22110f 100644 --- a/app/src/gambas3/.src/Editor/Image/FImageEditor.class +++ b/app/src/gambas3/.src/Editor/Image/FImageEditor.class @@ -660,7 +660,7 @@ Public Sub imvImage_MouseDown() If $iChangeAction = CHANGE_INSERT Then $vChangeIndex = $hSelect.InsertPoint($hLastPoint, $vChangeIndex) - $hLastPoint.X = -1000000 ' Avoid automatic undo at MouseUp + AddUndo($hSelect.Copy()) ' Avoid automatic undo at MouseUp $iChangeAction = CHANGE_POINT RefreshSelection Else If $iChangeAction = CHANGE_SELECT Then @@ -1002,11 +1002,11 @@ Public Sub imvImage_MouseMove() Endif If $iChangeAction = CHANGE_NOTHING Then - If Hyp(Abs($hChangeRect.X - $hCurrentPoint.X), Abs($hChangeRect.Y - $hCurrentPoint.Y)) < 16 Then + If Hyp(Abs($hChangeRect.X - $hCurrentPoint.X), Abs($hChangeRect.Y - $hCurrentPoint.Y)) < (16 / imvImage.Zoom) Then iMouse = Mouse.Cross $iChangeAction = CHANGE_ROTATE SetMagnet(Null) - Else If Hyp(Abs($hChangeRect.X + $hChangeRect.W - $hCurrentPoint.X), Abs($hChangeRect.Y + $hChangeRect.H - $hCurrentPoint.Y)) < 16 Then + Else If Hyp(Abs($hChangeRect.X + $hChangeRect.W - $hCurrentPoint.X), Abs($hChangeRect.Y + $hChangeRect.H - $hCurrentPoint.Y)) < (16 / imvImage.Zoom) Then iMouse = Mouse.Cross $iChangeAction = CHANGE_RESIZE SetMagnet(Null) @@ -1051,7 +1051,6 @@ Public Sub Form_KeyPress() $bCtrl = Key.Control $bAlt = Key.Alt UpdateTool - imvImage_MouseMove If $hSelect And If $hChangeRect Then If Key.Code = Key.Delete Or If Key.Code = Key.Backspace Then @@ -1067,6 +1066,8 @@ Public Sub Form_KeyPress() Endif Endif + imvImage_MouseMove + End Public Sub Form_KeyRelease() @@ -1139,40 +1140,55 @@ End Private Sub DrawTool() Dim I As Integer + Dim bDraw As Boolean Select Case $sTool Case "draw", "erase" - $hImage = $hSaveImage.Copy() - Paint.Begin($hImage) - - FImageProperty.PaintForStroke - 'Paint.Brush.Scale(Paint.LineWidth, Paint.LineWidth) - - If $sTool = "erase" Then - Paint.Operator = Paint.OperatorDestOut - Endif - - If $aStroke.Count = 2 Then - If Not Paint.AntiAlias Then - Paint.Rectangle($aStroke[0], $aStroke[1], 1, 1) - Paint.Fill - Else - Paint.Arc($aStroke[0], $aStroke[1], Paint.LineWidth / 2) - Paint.Fill + If $aStroke.Count Then + + $hImage = $hSaveImage.Copy() + Paint.Begin($hImage) + + FImageProperty.PaintForStroke + 'Paint.Brush.Scale(Paint.LineWidth, Paint.LineWidth) + + If $sTool = "erase" Then + Paint.Operator = Paint.OperatorDestOut Endif - Else - Paint.MoveTo($aStroke[0], $aStroke[1]) - For I = 2 To $aStroke.Max Step 2 - Paint.LineTo($aStroke[I], $aStroke[I + 1]) - Next - Paint.Stroke + + If Not Paint.AntiAlias Then + Paint.MoveTo(CInt($aStroke[0]), CInt($aStroke[1])) + For I = 2 To $aStroke.Max Step 2 + If CInt($aStroke[I]) = Paint.X And If CInt($aStroke[I + 1]) = Paint.Y Then Continue + Paint.LineTo(CInt($aStroke[I]), CInt($aStroke[I + 1])) + bDraw = True + Next + Paint.Stroke + If Not bDraw Then + Paint.Rectangle($aStroke[0], $aStroke[1], 1, 1) + Paint.Fill + Endif + Else + Paint.MoveTo($aStroke[0], $aStroke[1]) + For I = 2 To $aStroke.Max Step 2 + If CInt($aStroke[I]) = CInt(Paint.X) And If CInt($aStroke[I + 1]) = CInt(Paint.Y) Then Continue + Paint.LineTo($aStroke[I], $aStroke[I + 1]) + bDraw = True + Next + Paint.Stroke + If Not bDraw Then + Paint.Arc($aStroke[0], $aStroke[1], Paint.LineWidth / 2) + Paint.Fill + Endif + Endif + + Paint.End + Modify + Endif - Paint.End - Modify - Case "line", "rectangle" End Select @@ -1193,6 +1209,7 @@ Public Sub imvImage_Draw(hZoom As Image) Dim X As Integer Dim Y As Integer Dim iGrid As Integer + Dim W As Float If Project.ActiveForm = Me And If FImageProperty.HasBalance() Then bBalance = True @@ -1473,6 +1490,24 @@ Public Sub imvImage_Draw(hZoom As Image) Paint.Rectangle($hChangeRect.X + $hChangeRect.W + (0.5 - 6) / imvImage.Zoom, $hChangeRect.Y + $hChangeRect.H + (0.5 - 6) / imvImage.Zoom, 12 / imvImage.Zoom, 12 / imvImage.Zoom) Paint.Fill + If $iChangeAction = CHANGE_POINT Or If $iChangeAction = CHANGE_INSERT Then + + Paint.Background = Color.SetAlpha(Color.White, 128) + W = 3 / imvImage.Zoom + If $iChangeAction = CHANGE_INSERT Then + With ($hSelect.Shapes[$vChangeIndex[0]].Points[$vChangeIndex[1]] + $hSelect.Shapes[$vChangeIndex[0]].Points[$vChangeIndex[1] + 1]) / 2 + Paint.Rectangle(.X - W, .Y - W, W * 2, W * 2) + Paint.Fill + End With + Else + With $hSelect.Shapes[$vChangeIndex[0]].Points[$vChangeIndex[1]] + Paint.Rectangle(.X - W, .Y - W, W * 2, W * 2) + Paint.Fill + End With + Endif + + Endif + Paint.Restore Endif @@ -1946,10 +1981,10 @@ Public Sub RefreshSelection() imvImage.Refresh If $sTool = "change" Then - If $hSelect Then - $hChangeRect = $hSelect.GetExtents() + If Not $hSelect Or If $hSelect.IsVoid() Then + ClearSelection Else - $hChangeRect = Null + $hChangeRect = $hSelect.GetExtents() Endif Endif @@ -2049,17 +2084,6 @@ Public Sub panToolBar_Configure() End -Public Sub btnResizeSelection_Click() - - If Not $hSelect Then Return - If FImageResizeSelection.Run() Then Return - - AddUndo($hSelect.Copy()) - $hSelect.Enlarge(FImageResizeSelection.Size, FImageResizeSelection.Duplicate) - RefreshSelection - -End - Public Sub btnRotate_Click() If FImageRotate.Run(Me) Then @@ -2146,8 +2170,6 @@ End Public Sub btnDuplicate_Click() - Dim I As Integer - If $hSelect Then AddUndo($hSelect.Copy()) @@ -2157,3 +2179,15 @@ Public Sub btnDuplicate_Click() Endif End + +Public Sub btnOffset_Click() + + If Not $hSelect Then Return + + If FImageOffsetSelection.Run() Then Return + + AddUndo($hSelect.Copy()) + $hSelect.Offset(FImageOffsetSelection.Size, FImageOffsetSelection.Duplicate) + RefreshSelection + +End diff --git a/app/src/gambas3/.src/Editor/Image/FImageEditor.form b/app/src/gambas3/.src/Editor/Image/FImageEditor.form index 07c22f502..437ad97c6 100644 --- a/app/src/gambas3/.src/Editor/Image/FImageEditor.form +++ b/app/src/gambas3/.src/Editor/Image/FImageEditor.form @@ -65,6 +65,14 @@ Action = ".duplicate" Text = ("Duplicate selection") Picture = Picture["img/draw/duplicate.png"] + Shortcut = "Ctrl+D" + } + { mnuOffset Menu btnOffset + Name = "mnuOffset" + Action = ".offset" + Text = ("Offset selection") + Picture = Picture["img/draw/offset.png"] + Shortcut = "Ctrl+*" } { mnuSep4 Menu } @@ -304,7 +312,7 @@ } { btnRectangle ToolButton btnTool Name = "btnRectangle" - MoveScaled(70,0,4,4) + MoveScaled(69,0,4,4) Tag = "rectangle" ToolTip = ("Rectangle") Action = ".tool-rectangle" @@ -313,7 +321,7 @@ } { btnEllipse ToolButton btnTool Name = "btnEllipse" - MoveScaled(74,0,4,4) + MoveScaled(72,0,4,4) Tag = "ellipse" ToolTip = ("Ellipse") Action = ".tool-ellipse" @@ -322,7 +330,7 @@ } { btnMagic ToolButton btnTool Name = "btnMagic" - MoveScaled(78,0,4,4) + MoveScaled(75,0,4,4) Visible = False Enabled = False Tag = "magic" @@ -333,7 +341,7 @@ } { btnEditSelection ToolButton btnTool Name = "btnEditSelection" - MoveScaled(82,0,4,4) + MoveScaled(79,0,4,4) Tag = "change" ToolTip = ("Edit selection") Action = ".tool-change" @@ -341,17 +349,23 @@ Toggle = True } { btnInvert ToolButton - MoveScaled(87,0,4,4) + MoveScaled(83,0,4,4) ToolTip = ("Invert selection") Action = ".invert" Picture = Picture["img/draw/invert.png"] } { btnDuplicate ToolButton - MoveScaled(90,0,4,4) + MoveScaled(86,0,4,4) ToolTip = ("Duplicate selection") Action = ".duplicate" Picture = Picture["img/draw/duplicate.png"] } + { btnOffset ToolButton + MoveScaled(90,0,4,4) + ToolTip = ("Offset selection") + Action = ".offset" + Picture = Picture["img/draw/offset.png"] + } { Separator5 Separator MoveScaled(95,0,1,4) } @@ -480,7 +494,7 @@ } { Action duplicate Text = "Duplicate selection" - Shortcut = "" + Shortcut = "Ctrl+D" Picture = "img/draw/duplicate.png" } { Action fill @@ -510,6 +524,11 @@ Shortcut = "Ctrl+I" Picture = "img/draw/invert.png" } + { Action offset + Text = "Offset selection" + Shortcut = "Ctrl+*" + Picture = "img/draw/offset.png" + } { Action paste Text = "Paste" Shortcut = "Ctrl+V" @@ -636,7 +655,7 @@ { Toolbars { Toolbar image Text = "Image editor" - List = "save,reload,undo,redo,copy,cut,tool-paste,zoom-in,zoom,zoom-out,zoom-normal,zoom-fit,grid,tool-move,tool-draw,tool-erase,tool-line,tool-rectangle,tool-ellipse,tool-magic,tool-change,invert,duplicate,stroke,fill,clear,crop,flip-h,flip-v,rotate-r,rotate-l,resize,rotate" - Default = "save,reload,undo,redo,|,copy,cut,tool-paste,|,zoom-in,zoom,zoom-out,zoom-normal,zoom-fit,grid,|,tool-move,tool-draw,tool-erase,|,tool-line,tool-rectangle,tool-ellipse,tool-change,invert,duplicate,|,stroke,fill,clear,|,crop,flip-h,flip-v,rotate-r,rotate-l,resize,rotate" + List = "save,reload,undo,redo,copy,cut,tool-paste,zoom-in,zoom,zoom-out,zoom-normal,zoom-fit,grid,tool-move,tool-draw,tool-erase,tool-line,tool-rectangle,tool-ellipse,tool-magic,tool-change,invert,duplicate,offset,stroke,fill,clear,crop,flip-h,flip-v,rotate-r,rotate-l,resize,rotate" + Default = "save,reload,undo,redo,|,copy,cut,tool-paste,|,zoom-in,zoom,zoom-out,zoom-normal,zoom-fit,grid,|,tool-move,tool-draw,tool-erase,|,tool-line,tool-rectangle,tool-ellipse,tool-change,invert,duplicate,offset,|,stroke,fill,clear,|,crop,flip-h,flip-v,rotate-r,rotate-l,resize,rotate" } } diff --git a/app/src/gambas3/.src/Editor/Image/FImageResizeSelection.class b/app/src/gambas3/.src/Editor/Image/FImageOffsetSelection.class similarity index 90% rename from app/src/gambas3/.src/Editor/Image/FImageResizeSelection.class rename to app/src/gambas3/.src/Editor/Image/FImageOffsetSelection.class index 40e19eab4..d0ad0292d 100644 --- a/app/src/gambas3/.src/Editor/Image/FImageResizeSelection.class +++ b/app/src/gambas3/.src/Editor/Image/FImageOffsetSelection.class @@ -6,6 +6,7 @@ Static Public Duplicate As Boolean Public Sub Run() As Boolean txtSize.Value = Size + chkDuplicate.Value = Duplicate Return Not Me.ShowDialog() End diff --git a/app/src/gambas3/.src/Editor/Image/FImageResizeSelection.form b/app/src/gambas3/.src/Editor/Image/FImageOffsetSelection.form similarity index 90% rename from app/src/gambas3/.src/Editor/Image/FImageResizeSelection.form rename to app/src/gambas3/.src/Editor/Image/FImageOffsetSelection.form index 13ed4ee63..329f76dd3 100644 --- a/app/src/gambas3/.src/Editor/Image/FImageResizeSelection.form +++ b/app/src/gambas3/.src/Editor/Image/FImageOffsetSelection.form @@ -2,7 +2,7 @@ { Form Form MoveScaled(0,0,39,12) - Text = ("Enlarge selection") + Text = ("Offset selection") { txtSize SpinBox MoveScaled(1,1,10,4) MinValue = -256 @@ -24,7 +24,7 @@ } { chkDuplicate CheckBox MoveScaled(1,6,16,1) - Visible = False + AutoResize = True Text = ("Duplicate") } } diff --git a/app/src/gambas3/.src/Editor/Image/FImageProperty.form b/app/src/gambas3/.src/Editor/Image/FImageProperty.form index c5472a2bb..bcf75e6e9 100644 --- a/app/src/gambas3/.src/Editor/Image/FImageProperty.form +++ b/app/src/gambas3/.src/Editor/Image/FImageProperty.form @@ -458,6 +458,7 @@ MinValue = 16 MaxValue = 512 Step = 16 + Value = 64 } { Label13 Label MoveScaled(31,0,14,4) diff --git a/app/src/gambas3/img/draw/crop.png b/app/src/gambas3/img/draw/crop.png index 2430f1e0d03b3d972641ac1fe41547fccd91827c..d6dab12e374b12f934bf7759af8efd38b1716f3d 100644 GIT binary patch delta 195 zcmV;!06hQT0fYjONq?qEL_t(Ijir;p4Z|P|MZc?#!6AbnF&fS$ED|Y5>;ap&hb9uC zgv9APfsyU6k4?-h*zn%(N6hn#t2i?gm{~OBoTYC1f`U>CVvOI;8zdq!N@^h@s_!ZV zZSt)GyX;;v01QoR4eRkfdq^o^S(bxgJyag36j*Cvi~#@{syw(eIP1Ee`kS#L_t(Ijir-46~G`6gF|0sFdzgkScw&cu;Bxo(4k9*azAZW zUcQ7o2qYIiiev!E<S#L_t(IjlGiD4Z|=DL#d{Nc{5X^aWds6DSRkKg8+GdreGZe zjbz}U)(QY{cU);C4J{<&U)o_OpHaArMa0x~`N6C^VQR3tKl5nv;I1d{crc#O@_2*I z%veh4rk!thhooOk5+rr?=N@EH9$9%{qmY@pYB2G74!)Uq6&T$t$~!}u*%;Dy)%pMc N002ovPDHLkV1i`FNT>h+ delta 122 zcmV-=0EPeH0lWc_NpoIFL_t(Ijop&F4Ztu6M9-1;@@SklJ0>Gtl#dIOG*(^(NIawy zg=8QB$#U*10GQd-a_{{y+cMWjd=}K~f}~4kX+dl4sNH>m41OS}!1%*nBPnK)6pLhV c$K9s|J6FU%gONkG-~a#s07*qoM6N<$g6lmsq5uE@ diff --git a/app/src/gambas3/img/draw/enlarge.png b/app/src/gambas3/img/draw/enlarge.png deleted file mode 100644 index d4f56b6d45b0531ef204c98773d5c7c47110fd07..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 232 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf4nJ z@ErkR#;MwT(m=s3PZ!4!i{7)7y@ie_aInTNE7HGr_R8#oCylx_b?sGVom6|X;YFCv zjierSxg&fGiY@yeOu6JgVX}-8(J!z6);5BCVpIT$|a(jlI8 zs_(eN64safXx{x%sd1H_b7`*NjGL2|$gfz|I&(=o`@yM4nj5nwq;b1mnmbiCL;Pi4 a4dYj1n;iDQntGtC7(8A5T-G@yGywo0kW)ec diff --git a/app/src/gambas3/img/draw/invert.png b/app/src/gambas3/img/draw/invert.png index 6596001b6be99297b9f085ea58b6ec9a67b0f803..5c956f905458c6fff0551a6146c4607219959f79 100644 GIT binary patch delta 160 zcmV;R0AK&a0pS6VNq>V$L_t(Ijir;(4Z|P|1#U%j0~Ch}IiJ2=g~H*~TY6wHXrZedjL)eyVf`A4BN? O00000Is{z>% diff --git a/app/src/gambas3/img/draw/move.png b/app/src/gambas3/img/draw/move.png index 699db71d622db3ee363b11678f691146d67365a8..d7392a0cb8d544f5ba725e190e557081d5f1b852 100644 GIT binary patch delta 343 zcmV-d0jU1B0+s`iNPhu1Nkl=oqw2FMhOTv$F za5$7K>-Ab(*VSSIqP0*g7HPNJEEWrTy`EOAQmJr0pNXOBL|#z%Yyl pL*xe>4hLDSR!_D6unT`&=MN%n>GJB2>!|<$002ovPDHLkV1jm`q|pEX delta 247 zcmV39L=~=*uBI2Fjiip=)-=#U{%$)OO$YW*}k&jXeYOOzYGg~8KQPr*Qmob1O zM>7DVlwf8Mk%MP9b}x6QyN~7FIy#OGVERg{l~N!gYYWlY&Tu3-TMYnet-BC8+xbq7 ziT=qoQ$)PlpO3(15Z%4MjkkxYZV|DV+1f%yL`Dv+)4nJ z@ErkR#;MwT(m=s_PZ!4!i{7J?WBCp%@HEfe`n_rP!#h25bgCnx>s+i4E~)i*F+5?| zHKC-&LsQm$g_c*F?Ib&H&-z5c!tcc_#>@;Y0{d1mGAvy>=kwyXVZoL&5`_hiNi5e} zt+h&^U-Es1>FNCewgF467hKh8|M8p0t8r#&>GAvJiR|mX&WU_(t|I!`>Ws&Rb3mss Nc)I$ztaD0e0swblOaK4? literal 0 HcmV?d00001 diff --git a/main/configure.ac b/main/configure.ac index 9cead325b..1acce3025 100644 --- a/main/configure.ac +++ b/main/configure.ac @@ -113,6 +113,7 @@ lib/image.effect/Makefile \ lib/signal/Makefile \ lib/complex/Makefile \ lib/data/Makefile \ +lib/clipper/Makefile \ ]) AC_OUTPUT diff --git a/main/lib/Makefile.am b/main/lib/Makefile.am index 9f3b5034e..99b041885 100644 --- a/main/lib/Makefile.am +++ b/main/lib/Makefile.am @@ -1,5 +1,5 @@ SUBDIRS = . debug eval db compress vb option geom draw image gui gui.opengl \ - image.effect signal complex data + image.effect signal complex data clipper dist_gblib_DATA = gb.component gblib_DATA = gb.component diff --git a/main/lib/clipper/LICENSE b/main/lib/clipper/LICENSE new file mode 100644 index 000000000..3793cdd9f --- /dev/null +++ b/main/lib/clipper/LICENSE @@ -0,0 +1,26 @@ +The Clipper Library (including Delphi, C++ & C# source code, other accompanying +code, examples and documentation), hereafter called "the Software", has been +released under the following license, terms and conditions: + +Boost Software License - Version 1.0 - August 17th, 2003 +http://www.boost.org/LICENSE_1_0.txt + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the Software covered by this license to use, reproduce, +display, distribute, execute, and transmit the Software, and to prepare +derivative works of the Software, and to permit third-parties to whom the +Software is furnished to do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including the +above license grant, this restriction and the following disclaimer, must be +included in all copies of the Software, in whole or in part, and all derivative +works of the Software, unless such copies or derivative works are solely in the +form of machine-executable object code generated by a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL +THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY +DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/main/lib/clipper/Makefile.am b/main/lib/clipper/Makefile.am new file mode 100644 index 000000000..5a0b6e1ca --- /dev/null +++ b/main/lib/clipper/Makefile.am @@ -0,0 +1,19 @@ +COMPONENT = gb.clipper +include $(top_srcdir)/component.am + +noinst_LTLIBRARIES = libclipper.la +gblib_LTLIBRARIES = gb.clipper.la + +libclipper_la_LIBADD = +libclipper_la_LDFLAGS = -module @LD_FLAGS@ +libclipper_la_CXXFLAGS = -I$(top_srcdir)/share $(AM_CXXFLAGS_OPT) -fexceptions + +libclipper_la_SOURCES = \ + clipper.hpp clipper.cpp + +gb_clipper_la_LIBADD = libclipper.la +gb_clipper_la_LDFLAGS = -module @LD_FLAGS@ +gb_clipper_la_CXXFLAGS = -I$(top_srcdir)/share $(AM_CXXFLAGS) + +gb_clipper_la_SOURCES = \ + main.h main.cpp gb.geom.h c_clipper.cpp c_clipper.h diff --git a/main/lib/clipper/c_clipper.cpp b/main/lib/clipper/c_clipper.cpp new file mode 100644 index 000000000..e48a2b042 --- /dev/null +++ b/main/lib/clipper/c_clipper.cpp @@ -0,0 +1,160 @@ +/*************************************************************************** + + c_clipper.cpp + + (c) 2000-2013 Benoît Minisini + + 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, 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., 51 Franklin Street, Fifth Floor, Boston, + MA 02110-1301, USA. + +***************************************************************************/ + +#define __C_CLIPPER_CPP + +#include "main.h" +#include "gb.geom.h" +#include "c_clipper.h" +#include "clipper.hpp" + +using namespace ClipperLib; + +#define SCALE 1000000.0 + +static IntPoint to_point(GEOM_POINTF *point) +{ + return IntPoint(point->x * SCALE + 0.5, point->y * SCALE + 0.5); +} + +static GEOM_POINTF *from_point(IntPoint p) +{ + return GEOM.CreatePointF((double)p.X / SCALE, (double)p.Y / SCALE); +} + +/*static bool is_closed(GB_ARRAY p) +{ + if (!p) + return false; + + int count = GB.Array.Count(p); + if (count <= 1) + return false; + + GEOM_POINTF **ap = (GEOM_POINTF **)GB.Array.Get(p, 0); + return ap[0]->x == ap[count - 1]->x && ap[0]->y == ap[count - 1]->y; +}*/ + +static bool to_polygons(Polygons &polygons, GB_ARRAY array) +{ + int count; + GB_ARRAY a; + int i, j; + GEOM_POINTF **ap; + GEOM_POINTF *pp; + + if (GB.CheckObject(array)) + return true; + + count = GB.Array.Count(array); + if (count == 0) + return false; + + polygons.resize(count); + + for(i = 0; i < count; i++) + { + a = *(GB_ARRAY *)GB.Array.Get(array, i); + if (!a) + continue; + ap = (GEOM_POINTF **)GB.Array.Get(a, 0); + + for (j = 0; j < GB.Array.Count(a); j++) + { + pp = ap[j]; + if (!pp) + continue; + //fprintf(stderr, "<< %d %d: %g %g\n", i, j, pp->x, pp->y); + polygons[i].push_back(to_point(pp)); + } + } + + return false; +} + +static GB_ARRAY from_polygons(Polygons &polygons, GB_ARRAY array) +{ + GB_ARRAY a; + GB_ARRAY p; + uint i, j, n; + GEOM_POINTF *pp; + + GB.Array.New(&a, GB.FindClass("PointF[]"), polygons.size()); + for (i = 0; i < polygons.size(); i++) + { + n = polygons[i].size(); + GB.Array.New(&p, GB.FindClass("PointF"), n); + + for (j = 0; j < n; j++) + { + pp = from_point(polygons[i][j]); + //fprintf(stderr, ">> %d %d: %g %g\n", i, j, pp->x, pp->y); + *(GEOM_POINTF **)GB.Array.Get(p, j) = pp; + GB.Ref(pp); + } + + // Close polygon + pp = from_point(polygons[i][0]); + //fprintf(stderr, ">> %d %d: %g %g\n", i, j, pp->x, pp->y); + *(GEOM_POINTF **)GB.Array.Add(p) = pp; + GB.Ref(pp); + + *(GB_ARRAY *)GB.Array.Get(a, i) = p; + GB.Ref(p); + } + + return a; +} + +BEGIN_METHOD(Clipper_OffsetPolygons, GB_OBJECT polygons; GB_FLOAT delta; GB_INTEGER join; GB_FLOAT limit; GB_BOOLEAN do_not_fix) + + Polygons polygons; + Polygons result; + + if (to_polygons(polygons, VARG(polygons))) + return; + + SimplifyPolygons(polygons, result, pftNonZero); + polygons = result; + OffsetPolygons(polygons, result, VARG(delta) * SCALE, (JoinType)VARGOPT(join, jtSquare), VARGOPT(limit, 0.0), !VARGOPT(do_not_fix, false)); + + GB.ReturnObject(from_polygons(result, VARG(polygons))); + +END_METHOD + +GB_DESC ClipperDesc[] = +{ + GB_DECLARE_VIRTUAL("Clipper"), + + //void OffsetPolygons(const Polygons &in_polys, Polygons &out_polys, double delta, JoinType jointype = jtSquare, double limit = 0.0, bool autoFix = true); + + GB_CONSTANT("JoinMiter", "i", jtMiter), + GB_CONSTANT("JoinSquare", "i", jtSquare), + GB_CONSTANT("JoinRound", "i", jtRound), + + GB_STATIC_METHOD("OffsetPolygons", "PointF[][]", Clipper_OffsetPolygons, "(Polygons)PointF[][];(Delta)f[(Join)i(Limit)f(DoNotFix)b]"), + + GB_END_DECLARE +}; + + diff --git a/main/lib/clipper/c_clipper.h b/main/lib/clipper/c_clipper.h new file mode 100644 index 000000000..eded165d0 --- /dev/null +++ b/main/lib/clipper/c_clipper.h @@ -0,0 +1,35 @@ +/*************************************************************************** + + c_clipper.h + + (c) 2000-2013 Benoît Minisini + + 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, 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., 51 Franklin Street, Fifth Floor, Boston, + MA 02110-1301, USA. + +***************************************************************************/ + +#ifndef __C_CLIPPER_H +#define __C_CLIPPER_H + +#include "gambas.h" + +#ifndef __C_CLIPPER_CPP + +extern GB_DESC ClipperDesc[]; + +#endif + +#endif diff --git a/main/lib/clipper/clipper.cpp b/main/lib/clipper/clipper.cpp new file mode 100644 index 000000000..02a7b7085 --- /dev/null +++ b/main/lib/clipper/clipper.cpp @@ -0,0 +1,3700 @@ +/******************************************************************************* +* * +* Author : Angus Johnson * +* Version : 5.1.6 * +* Date : 23 May 2013 * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2013 * +* * +* License: * +* Use, modification & distribution is subject to Boost Software License Ver 1. * +* http://www.boost.org/LICENSE_1_0.txt * +* * +* Attributions: * +* The code in this library is an extension of Bala Vatti's clipping algorithm: * +* "A generic solution to polygon clipping" * +* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * +* http://portal.acm.org/citation.cfm?id=129906 * +* * +* Computer graphics and geometric modeling: implementation and algorithms * +* By Max K. Agoston * +* Springer; 1 edition (January 4, 2005) * +* http://books.google.com/books?q=vatti+clipping+agoston * +* * +* See also: * +* "Polygon Offsetting by Computing Winding Numbers" * +* Paper no. DETC2005-85513 pp. 565-575 * +* ASME 2005 International Design Engineering Technical Conferences * +* and Computers and Information in Engineering Conference (IDETC/CIE2005) * +* September 24-28, 2005 , Long Beach, California, USA * +* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * +* * +*******************************************************************************/ + +/******************************************************************************* +* * +* This is a translation of the Delphi Clipper library and the naming style * +* used has retained a Delphi flavour. * +* * +*******************************************************************************/ + +#include "clipper.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace ClipperLib { + +static long64 const loRange = 0x3FFFFFFF; +static long64 const hiRange = 0x3FFFFFFFFFFFFFFFLL; +static double const pi = 3.141592653589793238; +enum Direction { dRightToLeft, dLeftToRight }; + +#define HORIZONTAL (-1.0E+40) +#define TOLERANCE (1.0e-20) +#define NEAR_ZERO(val) (((val) > -TOLERANCE) && ((val) < TOLERANCE)) +#define NEAR_EQUAL(a, b) NEAR_ZERO((a) - (b)) + +const char coords_range_error[] = "Coordinate exceeds range bounds."; + +inline long64 Abs(long64 val) +{ + return val < 0 ? -val : val; +} + +//------------------------------------------------------------------------------ +// PolyTree methods ... +//------------------------------------------------------------------------------ + +void PolyTree::Clear() +{ + for (PolyNodes::size_type i = 0; i < AllNodes.size(); ++i) + delete AllNodes[i]; + AllNodes.resize(0); + Childs.resize(0); +} +//------------------------------------------------------------------------------ + +PolyNode* PolyTree::GetFirst() const +{ + if (!Childs.empty()) + return Childs[0]; + else + return 0; +} +//------------------------------------------------------------------------------ + +int PolyTree::Total() const +{ + return AllNodes.size(); +} + +//------------------------------------------------------------------------------ +// PolyNode methods ... +//------------------------------------------------------------------------------ + +PolyNode::PolyNode(): Childs(), Parent(0), Index(0) +{ +} +//------------------------------------------------------------------------------ + +int PolyNode::ChildCount() const +{ + return Childs.size(); +} +//------------------------------------------------------------------------------ + +void PolyNode::AddChild(PolyNode& child) +{ + unsigned cnt = Childs.size(); + Childs.push_back(&child); + child.Parent = this; + child.Index = cnt; +} +//------------------------------------------------------------------------------ + +PolyNode* PolyNode::GetNext() const +{ + if (!Childs.empty()) + return Childs[0]; + else + return GetNextSiblingUp(); +} +//------------------------------------------------------------------------------ + +PolyNode* PolyNode::GetNextSiblingUp() const +{ + if (!Parent) //protects against PolyTree.GetNextSiblingUp() + return 0; + else if (Index == Parent->Childs.size() - 1) + return Parent->GetNextSiblingUp(); + else + return Parent->Childs[Index + 1]; +} +//------------------------------------------------------------------------------ + +bool PolyNode::IsHole() const +{ + bool result = true; + PolyNode* node = Parent; + while (node) + { + result = !result; + node = node->Parent; + } + return result; +} + +//------------------------------------------------------------------------------ +// Int128 class (enables safe math on signed 64bit integers) +// eg Int128 val1((long64)9223372036854775807); //ie 2^63 -1 +// Int128 val2((long64)9223372036854775807); +// Int128 val3 = val1 * val2; +// val3.AsString => "85070591730234615847396907784232501249" (8.5e+37) +//------------------------------------------------------------------------------ + +class Int128 +{ + public: + + ulong64 lo; + long64 hi; + + Int128(long64 _lo = 0) + { + lo = (ulong64)_lo; + if (_lo < 0) hi = -1; else hi = 0; + } + + + Int128(const Int128 &val): lo(val.lo), hi(val.hi){} + + Int128(const long64& _hi, const ulong64& _lo): lo(_lo), hi(_hi){} + + long64 operator = (const long64 &val) + { + lo = (ulong64)val; + if (val < 0) hi = -1; else hi = 0; + return val; + } + + bool operator == (const Int128 &val) const + {return (hi == val.hi && lo == val.lo);} + + bool operator != (const Int128 &val) const + { return !(*this == val);} + + bool operator > (const Int128 &val) const + { + if (hi != val.hi) + return hi > val.hi; + else + return lo > val.lo; + } + + bool operator < (const Int128 &val) const + { + if (hi != val.hi) + return hi < val.hi; + else + return lo < val.lo; + } + + bool operator >= (const Int128 &val) const + { return !(*this < val);} + + bool operator <= (const Int128 &val) const + { return !(*this > val);} + + Int128& operator += (const Int128 &rhs) + { + hi += rhs.hi; + lo += rhs.lo; + if (lo < rhs.lo) hi++; + return *this; + } + + Int128 operator + (const Int128 &rhs) const + { + Int128 result(*this); + result+= rhs; + return result; + } + + Int128& operator -= (const Int128 &rhs) + { + *this += -rhs; + return *this; + } + + Int128 operator - (const Int128 &rhs) const + { + Int128 result(*this); + result -= rhs; + return result; + } + + Int128 operator-() const //unary negation + { + if (lo == 0) + return Int128(-hi,0); + else + return Int128(~hi,~lo +1); + } + + Int128 operator/ (const Int128 &rhs) const + { + if (rhs.lo == 0 && rhs.hi == 0) + throw "Int128 operator/: divide by zero"; + + bool negate = (rhs.hi < 0) != (hi < 0); + Int128 dividend = *this; + Int128 divisor = rhs; + if (dividend.hi < 0) dividend = -dividend; + if (divisor.hi < 0) divisor = -divisor; + + if (divisor < dividend) + { + Int128 result = Int128(0); + Int128 cntr = Int128(1); + while (divisor.hi >= 0 && !(divisor > dividend)) + { + divisor.hi <<= 1; + if ((long64)divisor.lo < 0) divisor.hi++; + divisor.lo <<= 1; + + cntr.hi <<= 1; + if ((long64)cntr.lo < 0) cntr.hi++; + cntr.lo <<= 1; + } + divisor.lo >>= 1; + if ((divisor.hi & 1) == 1) + divisor.lo |= 0x8000000000000000LL; + divisor.hi = (ulong64)divisor.hi >> 1; + + cntr.lo >>= 1; + if ((cntr.hi & 1) == 1) + cntr.lo |= 0x8000000000000000LL; + cntr.hi >>= 1; + + while (cntr.hi != 0 || cntr.lo != 0) + { + if (!(dividend < divisor)) + { + dividend -= divisor; + result.hi |= cntr.hi; + result.lo |= cntr.lo; + } + divisor.lo >>= 1; + if ((divisor.hi & 1) == 1) + divisor.lo |= 0x8000000000000000LL; + divisor.hi >>= 1; + + cntr.lo >>= 1; + if ((cntr.hi & 1) == 1) + cntr.lo |= 0x8000000000000000LL; + cntr.hi >>= 1; + } + if (negate) result = -result; + return result; + } + else if (rhs.hi == this->hi && rhs.lo == this->lo) + return Int128(1); + else + return Int128(0); + } + + double AsDouble() const + { + const double shift64 = 18446744073709551616.0; //2^64 + if (hi < 0) + { + if (lo == 0) return (double)hi * shift64; + else return -(double)(~lo + ~hi * shift64); + } + else + return (double)(lo + hi * shift64); + } +}; + +Int128 Int128Mul (long64 lhs, long64 rhs) +{ + bool negate = (lhs < 0) != (rhs < 0); + + if (lhs < 0) lhs = -lhs; + ulong64 int1Hi = ulong64(lhs) >> 32; + ulong64 int1Lo = ulong64(lhs & 0xFFFFFFFF); + + if (rhs < 0) rhs = -rhs; + ulong64 int2Hi = ulong64(rhs) >> 32; + ulong64 int2Lo = ulong64(rhs & 0xFFFFFFFF); + + //nb: see comments in clipper.pas + ulong64 a = int1Hi * int2Hi; + ulong64 b = int1Lo * int2Lo; + ulong64 c = int1Hi * int2Lo + int1Lo * int2Hi; + + Int128 tmp; + tmp.hi = long64(a + (c >> 32)); + tmp.lo = long64(c << 32); + tmp.lo += long64(b); + if (tmp.lo < b) tmp.hi++; + if (negate) tmp = -tmp; + return tmp; +} + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +bool FullRangeNeeded(const Polygon &pts) +{ + bool result = false; + for (Polygon::size_type i = 0; i < pts.size(); ++i) + { + if (Abs(pts[i].X) > hiRange || Abs(pts[i].Y) > hiRange) + throw coords_range_error; + else if (Abs(pts[i].X) > loRange || Abs(pts[i].Y) > loRange) + result = true; + } + return result; +} +//------------------------------------------------------------------------------ + +bool Orientation(const Polygon &poly) +{ + return Area(poly) >= 0; +} +//------------------------------------------------------------------------------ + +inline bool PointsEqual( const IntPoint &pt1, const IntPoint &pt2) +{ + return ( pt1.X == pt2.X && pt1.Y == pt2.Y ); +} +//------------------------------------------------------------------------------ + +double Area(const Polygon &poly) +{ + int highI = (int)poly.size() -1; + if (highI < 2) return 0; + + if (FullRangeNeeded(poly)) { + Int128 a; + a = Int128Mul(poly[highI].X + poly[0].X, poly[0].Y - poly[highI].Y); + for (int i = 1; i <= highI; ++i) + a += Int128Mul(poly[i - 1].X + poly[i].X, poly[i].Y - poly[i -1].Y); + return a.AsDouble() / 2; + } + else + { + double a; + a = ((double)poly[highI].X + poly[0].X) * ((double)poly[0].Y - poly[highI].Y); + for (int i = 1; i <= highI; ++i) + a += ((double)poly[i - 1].X + poly[i].X) * ((double)poly[i].Y - poly[i - 1].Y); + return a / 2; + } +} +//------------------------------------------------------------------------------ + +double Area(const OutRec &outRec, bool UseFullInt64Range) +{ + OutPt *op = outRec.pts; + if (!op) return 0; + if (UseFullInt64Range) { + Int128 a(0); + do { + a += Int128Mul(op->pt.X + op->prev->pt.X, op->prev->pt.Y - op->pt.Y); + op = op->next; + } while (op != outRec.pts); + return a.AsDouble() / 2; + } + else + { + double a = 0; + do { + a = a + (op->pt.X + op->prev->pt.X) * (op->prev->pt.Y - op->pt.Y); + op = op->next; + } while (op != outRec.pts); + return a / 2; + } +} +//------------------------------------------------------------------------------ + +bool PointIsVertex(const IntPoint &pt, OutPt *pp) +{ + OutPt *pp2 = pp; + do + { + if (PointsEqual(pp2->pt, pt)) return true; + pp2 = pp2->next; + } + while (pp2 != pp); + return false; +} +//------------------------------------------------------------------------------ + +bool PointOnLineSegment(const IntPoint pt, + const IntPoint linePt1, const IntPoint linePt2, bool UseFullInt64Range) +{ + if (UseFullInt64Range) + return ((pt.X == linePt1.X) && (pt.Y == linePt1.Y)) || + ((pt.X == linePt2.X) && (pt.Y == linePt2.Y)) || + (((pt.X > linePt1.X) == (pt.X < linePt2.X)) && + ((pt.Y > linePt1.Y) == (pt.Y < linePt2.Y)) && + ((Int128Mul((pt.X - linePt1.X), (linePt2.Y - linePt1.Y)) == + Int128Mul((linePt2.X - linePt1.X), (pt.Y - linePt1.Y))))); + else + return ((pt.X == linePt1.X) && (pt.Y == linePt1.Y)) || + ((pt.X == linePt2.X) && (pt.Y == linePt2.Y)) || + (((pt.X > linePt1.X) == (pt.X < linePt2.X)) && + ((pt.Y > linePt1.Y) == (pt.Y < linePt2.Y)) && + ((pt.X - linePt1.X) * (linePt2.Y - linePt1.Y) == + (linePt2.X - linePt1.X) * (pt.Y - linePt1.Y))); +} +//------------------------------------------------------------------------------ + +bool PointOnPolygon(const IntPoint pt, OutPt *pp, bool UseFullInt64Range) +{ + OutPt *pp2 = pp; + while (true) + { + if (PointOnLineSegment(pt, pp2->pt, pp2->next->pt, UseFullInt64Range)) + return true; + pp2 = pp2->next; + if (pp2 == pp) break; + } + return false; +} +//------------------------------------------------------------------------------ + +bool PointInPolygon(const IntPoint &pt, OutPt *pp, bool UseFullInt64Range) +{ + OutPt *pp2 = pp; + bool result = false; + if (UseFullInt64Range) { + do + { + if ((((pp2->pt.Y <= pt.Y) && (pt.Y < pp2->prev->pt.Y)) || + ((pp2->prev->pt.Y <= pt.Y) && (pt.Y < pp2->pt.Y))) && + Int128(pt.X - pp2->pt.X) < + Int128Mul(pp2->prev->pt.X - pp2->pt.X, pt.Y - pp2->pt.Y) / + Int128(pp2->prev->pt.Y - pp2->pt.Y)) + result = !result; + pp2 = pp2->next; + } + while (pp2 != pp); + } + else + { + do + { + if ((((pp2->pt.Y <= pt.Y) && (pt.Y < pp2->prev->pt.Y)) || + ((pp2->prev->pt.Y <= pt.Y) && (pt.Y < pp2->pt.Y))) && + (pt.X < (pp2->prev->pt.X - pp2->pt.X) * (pt.Y - pp2->pt.Y) / + (pp2->prev->pt.Y - pp2->pt.Y) + pp2->pt.X )) result = !result; + pp2 = pp2->next; + } + while (pp2 != pp); + } + return result; +} +//------------------------------------------------------------------------------ + +bool SlopesEqual(const TEdge &e1, const TEdge &e2, bool UseFullInt64Range) +{ + if (UseFullInt64Range) + return Int128Mul(e1.deltaY, e2.deltaX) == Int128Mul(e1.deltaX, e2.deltaY); + else return e1.deltaY * e2.deltaX == e1.deltaX * e2.deltaY; +} +//------------------------------------------------------------------------------ + +bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, + const IntPoint pt3, bool UseFullInt64Range) +{ + if (UseFullInt64Range) + return Int128Mul(pt1.Y-pt2.Y, pt2.X-pt3.X) == Int128Mul(pt1.X-pt2.X, pt2.Y-pt3.Y); + else return (pt1.Y-pt2.Y)*(pt2.X-pt3.X) == (pt1.X-pt2.X)*(pt2.Y-pt3.Y); +} +//------------------------------------------------------------------------------ + +bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, + const IntPoint pt3, const IntPoint pt4, bool UseFullInt64Range) +{ + if (UseFullInt64Range) + return Int128Mul(pt1.Y-pt2.Y, pt3.X-pt4.X) == Int128Mul(pt1.X-pt2.X, pt3.Y-pt4.Y); + else return (pt1.Y-pt2.Y)*(pt3.X-pt4.X) == (pt1.X-pt2.X)*(pt3.Y-pt4.Y); +} +//------------------------------------------------------------------------------ + +double GetDx(const IntPoint pt1, const IntPoint pt2) +{ + return (pt1.Y == pt2.Y) ? + HORIZONTAL : (double)(pt2.X - pt1.X) / (pt2.Y - pt1.Y); +} +//--------------------------------------------------------------------------- + +void SetDx(TEdge &e) +{ + e.deltaX = (e.xtop - e.xbot); + e.deltaY = (e.ytop - e.ybot); + + if (e.deltaY == 0) e.dx = HORIZONTAL; + else e.dx = (double)(e.deltaX) / e.deltaY; +} +//--------------------------------------------------------------------------- + +void SwapSides(TEdge &edge1, TEdge &edge2) +{ + EdgeSide side = edge1.side; + edge1.side = edge2.side; + edge2.side = side; +} +//------------------------------------------------------------------------------ + +void SwapPolyIndexes(TEdge &edge1, TEdge &edge2) +{ + int outIdx = edge1.outIdx; + edge1.outIdx = edge2.outIdx; + edge2.outIdx = outIdx; +} +//------------------------------------------------------------------------------ + +inline long64 Round(double val) +{ + return (val < 0) ? static_cast(val - 0.5) : static_cast(val + 0.5); +} +//------------------------------------------------------------------------------ + +long64 TopX(TEdge &edge, const long64 currentY) +{ + return ( currentY == edge.ytop ) ? + edge.xtop : edge.xbot + Round(edge.dx *(currentY - edge.ybot)); +} +//------------------------------------------------------------------------------ + +bool IntersectPoint(TEdge &edge1, TEdge &edge2, + IntPoint &ip, bool UseFullInt64Range) +{ + double b1, b2; + if (SlopesEqual(edge1, edge2, UseFullInt64Range)) + { + if (edge2.ybot > edge1.ybot) ip.Y = edge2.ybot; + else ip.Y = edge1.ybot; + return false; + } + else if (NEAR_ZERO(edge1.dx)) + { + ip.X = edge1.xbot; + if (NEAR_EQUAL(edge2.dx, HORIZONTAL)) + ip.Y = edge2.ybot; + else + { + b2 = edge2.ybot - (edge2.xbot / edge2.dx); + ip.Y = Round(ip.X / edge2.dx + b2); + } + } + else if (NEAR_ZERO(edge2.dx)) + { + ip.X = edge2.xbot; + if (NEAR_EQUAL(edge1.dx, HORIZONTAL)) + ip.Y = edge1.ybot; + else + { + b1 = edge1.ybot - (edge1.xbot / edge1.dx); + ip.Y = Round(ip.X / edge1.dx + b1); + } + } + else + { + b1 = edge1.xbot - edge1.ybot * edge1.dx; + b2 = edge2.xbot - edge2.ybot * edge2.dx; + double q = (b2-b1) / (edge1.dx - edge2.dx); + ip.Y = Round(q); + if (std::fabs(edge1.dx) < std::fabs(edge2.dx)) + ip.X = Round(edge1.dx * q + b1); + else + ip.X = Round(edge2.dx * q + b2); + } + + if (ip.Y < edge1.ytop || ip.Y < edge2.ytop) + { + if (edge1.ytop > edge2.ytop) + { + ip.X = edge1.xtop; + ip.Y = edge1.ytop; + return TopX(edge2, edge1.ytop) < edge1.xtop; + } + else + { + ip.X = edge2.xtop; + ip.Y = edge2.ytop; + return TopX(edge1, edge2.ytop) > edge2.xtop; + } + } + else + return true; +} +//------------------------------------------------------------------------------ + +void ReversePolyPtLinks(OutPt *pp) +{ + if (!pp) return; + OutPt *pp1, *pp2; + pp1 = pp; + do { + pp2 = pp1->next; + pp1->next = pp1->prev; + pp1->prev = pp2; + pp1 = pp2; + } while( pp1 != pp ); +} +//------------------------------------------------------------------------------ + +void DisposeOutPts(OutPt*& pp) +{ + if (pp == 0) return; + pp->prev->next = 0; + while( pp ) + { + OutPt *tmpPp = pp; + pp = pp->next; + delete tmpPp; + } +} +//------------------------------------------------------------------------------ + +void InitEdge(TEdge *e, TEdge *eNext, + TEdge *ePrev, const IntPoint &pt, PolyType polyType) +{ + std::memset(e, 0, sizeof(TEdge)); + e->next = eNext; + e->prev = ePrev; + e->xcurr = pt.X; + e->ycurr = pt.Y; + if (e->ycurr >= e->next->ycurr) + { + e->xbot = e->xcurr; + e->ybot = e->ycurr; + e->xtop = e->next->xcurr; + e->ytop = e->next->ycurr; + e->windDelta = 1; + } else + { + e->xtop = e->xcurr; + e->ytop = e->ycurr; + e->xbot = e->next->xcurr; + e->ybot = e->next->ycurr; + e->windDelta = -1; + } + SetDx(*e); + e->polyType = polyType; + e->outIdx = -1; +} +//------------------------------------------------------------------------------ + +inline void SwapX(TEdge &e) +{ + //swap horizontal edges' top and bottom x's so they follow the natural + //progression of the bounds - ie so their xbots will align with the + //adjoining lower edge. [Helpful in the ProcessHorizontal() method.] + e.xcurr = e.xtop; + e.xtop = e.xbot; + e.xbot = e.xcurr; +} +//------------------------------------------------------------------------------ + +void SwapPoints(IntPoint &pt1, IntPoint &pt2) +{ + IntPoint tmp = pt1; + pt1 = pt2; + pt2 = tmp; +} +//------------------------------------------------------------------------------ + +bool GetOverlapSegment(IntPoint pt1a, IntPoint pt1b, IntPoint pt2a, + IntPoint pt2b, IntPoint &pt1, IntPoint &pt2) +{ + //precondition: segments are colinear. + if (Abs(pt1a.X - pt1b.X) > Abs(pt1a.Y - pt1b.Y)) + { + if (pt1a.X > pt1b.X) SwapPoints(pt1a, pt1b); + if (pt2a.X > pt2b.X) SwapPoints(pt2a, pt2b); + if (pt1a.X > pt2a.X) pt1 = pt1a; else pt1 = pt2a; + if (pt1b.X < pt2b.X) pt2 = pt1b; else pt2 = pt2b; + return pt1.X < pt2.X; + } else + { + if (pt1a.Y < pt1b.Y) SwapPoints(pt1a, pt1b); + if (pt2a.Y < pt2b.Y) SwapPoints(pt2a, pt2b); + if (pt1a.Y < pt2a.Y) pt1 = pt1a; else pt1 = pt2a; + if (pt1b.Y > pt2b.Y) pt2 = pt1b; else pt2 = pt2b; + return pt1.Y > pt2.Y; + } +} +//------------------------------------------------------------------------------ + +bool FirstIsBottomPt(const OutPt* btmPt1, const OutPt* btmPt2) +{ + OutPt *p = btmPt1->prev; + while (PointsEqual(p->pt, btmPt1->pt) && (p != btmPt1)) p = p->prev; + double dx1p = std::fabs(GetDx(btmPt1->pt, p->pt)); + p = btmPt1->next; + while (PointsEqual(p->pt, btmPt1->pt) && (p != btmPt1)) p = p->next; + double dx1n = std::fabs(GetDx(btmPt1->pt, p->pt)); + + p = btmPt2->prev; + while (PointsEqual(p->pt, btmPt2->pt) && (p != btmPt2)) p = p->prev; + double dx2p = std::fabs(GetDx(btmPt2->pt, p->pt)); + p = btmPt2->next; + while (PointsEqual(p->pt, btmPt2->pt) && (p != btmPt2)) p = p->next; + double dx2n = std::fabs(GetDx(btmPt2->pt, p->pt)); + return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n); +} +//------------------------------------------------------------------------------ + +OutPt* GetBottomPt(OutPt *pp) +{ + OutPt* dups = 0; + OutPt* p = pp->next; + while (p != pp) + { + if (p->pt.Y > pp->pt.Y) + { + pp = p; + dups = 0; + } + else if (p->pt.Y == pp->pt.Y && p->pt.X <= pp->pt.X) + { + if (p->pt.X < pp->pt.X) + { + dups = 0; + pp = p; + } else + { + if (p->next != pp && p->prev != pp) dups = p; + } + } + p = p->next; + } + if (dups) + { + //there appears to be at least 2 vertices at bottomPt so ... + while (dups != p) + { + if (!FirstIsBottomPt(p, dups)) pp = dups; + dups = dups->next; + while (!PointsEqual(dups->pt, pp->pt)) dups = dups->next; + } + } + return pp; +} +//------------------------------------------------------------------------------ + +bool FindSegment(OutPt* &pp, bool UseFullInt64Range, + IntPoint &pt1, IntPoint &pt2) +{ + //outPt1 & outPt2 => the overlap segment (if the function returns true) + if (!pp) return false; + OutPt* pp2 = pp; + IntPoint pt1a = pt1, pt2a = pt2; + do + { + if (SlopesEqual(pt1a, pt2a, pp->pt, pp->prev->pt, UseFullInt64Range) && + SlopesEqual(pt1a, pt2a, pp->pt, UseFullInt64Range) && + GetOverlapSegment(pt1a, pt2a, pp->pt, pp->prev->pt, pt1, pt2)) + return true; + pp = pp->next; + } + while (pp != pp2); + return false; +} +//------------------------------------------------------------------------------ + +bool Pt3IsBetweenPt1AndPt2(const IntPoint pt1, + const IntPoint pt2, const IntPoint pt3) +{ + if (PointsEqual(pt1, pt3) || PointsEqual(pt2, pt3)) return true; + else if (pt1.X != pt2.X) return (pt1.X < pt3.X) == (pt3.X < pt2.X); + else return (pt1.Y < pt3.Y) == (pt3.Y < pt2.Y); +} +//------------------------------------------------------------------------------ + +OutPt* InsertPolyPtBetween(OutPt* p1, OutPt* p2, const IntPoint pt) +{ + if (p1 == p2) throw "JoinError"; + OutPt* result = new OutPt; + result->pt = pt; + if (p2 == p1->next) + { + p1->next = result; + p2->prev = result; + result->next = p2; + result->prev = p1; + } else + { + p2->next = result; + p1->prev = result; + result->next = p1; + result->prev = p2; + } + return result; +} + +//------------------------------------------------------------------------------ +// ClipperBase class methods ... +//------------------------------------------------------------------------------ + +ClipperBase::ClipperBase() //constructor +{ + m_MinimaList = 0; + m_CurrentLM = 0; + m_UseFullRange = true; +} +//------------------------------------------------------------------------------ + +ClipperBase::~ClipperBase() //destructor +{ + Clear(); +} +//------------------------------------------------------------------------------ + +void RangeTest(const IntPoint& pt, long64& maxrange) +{ + if (Abs(pt.X) > maxrange) + { + if (Abs(pt.X) > hiRange) + throw coords_range_error; + else maxrange = hiRange; + } + if (Abs(pt.Y) > maxrange) + { + if (Abs(pt.Y) > hiRange) + throw coords_range_error; + else maxrange = hiRange; + } +} +//------------------------------------------------------------------------------ + +bool ClipperBase::AddPolygon(const Polygon &pg, PolyType polyType) +{ + int len = (int)pg.size(); + if (len < 3) return false; + + long64 maxVal; + if (m_UseFullRange) maxVal = hiRange; else maxVal = loRange; + RangeTest(pg[0], maxVal); + + Polygon p(len); + p[0] = pg[0]; + int j = 0; + + for (int i = 0; i < len; ++i) + { + RangeTest(pg[i], maxVal); + + if (i == 0 || PointsEqual(p[j], pg[i])) continue; + else if (j > 0 && SlopesEqual(p[j-1], p[j], pg[i], m_UseFullRange)) + { + if (PointsEqual(p[j-1], pg[i])) j--; + } else j++; + p[j] = pg[i]; + } + if (j < 2) return false; + + len = j+1; + while (len > 2) + { + //nb: test for point equality before testing slopes ... + if (PointsEqual(p[j], p[0])) j--; + else if (PointsEqual(p[0], p[1]) || + SlopesEqual(p[j], p[0], p[1], m_UseFullRange)) + p[0] = p[j--]; + else if (SlopesEqual(p[j-1], p[j], p[0], m_UseFullRange)) j--; + else if (SlopesEqual(p[0], p[1], p[2], m_UseFullRange)) + { + for (int i = 2; i <= j; ++i) p[i-1] = p[i]; + j--; + } + else break; + len--; + } + if (len < 3) return false; + + //create a new edge array ... + TEdge *edges = new TEdge [len]; + m_edges.push_back(edges); + + //convert vertices to a double-linked-list of edges and initialize ... + edges[0].xcurr = p[0].X; + edges[0].ycurr = p[0].Y; + InitEdge(&edges[len-1], &edges[0], &edges[len-2], p[len-1], polyType); + for (int i = len-2; i > 0; --i) + InitEdge(&edges[i], &edges[i+1], &edges[i-1], p[i], polyType); + InitEdge(&edges[0], &edges[1], &edges[len-1], p[0], polyType); + + //reset xcurr & ycurr and find 'eHighest' (given the Y axis coordinates + //increase downward so the 'highest' edge will have the smallest ytop) ... + TEdge *e = &edges[0]; + TEdge *eHighest = e; + do + { + e->xcurr = e->xbot; + e->ycurr = e->ybot; + if (e->ytop < eHighest->ytop) eHighest = e; + e = e->next; + } + while ( e != &edges[0]); + + //make sure eHighest is positioned so the following loop works safely ... + if (eHighest->windDelta > 0) eHighest = eHighest->next; + if (NEAR_EQUAL(eHighest->dx, HORIZONTAL)) eHighest = eHighest->next; + + //finally insert each local minima ... + e = eHighest; + do { + e = AddBoundsToLML(e); + } + while( e != eHighest ); + return true; +} +//------------------------------------------------------------------------------ + +void ClipperBase::InsertLocalMinima(LocalMinima *newLm) +{ + if( ! m_MinimaList ) + { + m_MinimaList = newLm; + } + else if( newLm->Y >= m_MinimaList->Y ) + { + newLm->next = m_MinimaList; + m_MinimaList = newLm; + } else + { + LocalMinima* tmpLm = m_MinimaList; + while( tmpLm->next && ( newLm->Y < tmpLm->next->Y ) ) + tmpLm = tmpLm->next; + newLm->next = tmpLm->next; + tmpLm->next = newLm; + } +} +//------------------------------------------------------------------------------ + +TEdge* ClipperBase::AddBoundsToLML(TEdge *e) +{ + //Starting at the top of one bound we progress to the bottom where there's + //a local minima. We then go to the top of the next bound. These two bounds + //form the left and right (or right and left) bounds of the local minima. + e->nextInLML = 0; + e = e->next; + for (;;) + { + if (NEAR_EQUAL(e->dx, HORIZONTAL)) + { + //nb: proceed through horizontals when approaching from their right, + // but break on horizontal minima if approaching from their left. + // This ensures 'local minima' are always on the left of horizontals. + if (e->next->ytop < e->ytop && e->next->xbot > e->prev->xbot) break; + if (e->xtop != e->prev->xbot) SwapX(*e); + e->nextInLML = e->prev; + } + else if (e->ycurr == e->prev->ycurr) break; + else e->nextInLML = e->prev; + e = e->next; + } + + //e and e.prev are now at a local minima ... + LocalMinima* newLm = new LocalMinima; + newLm->next = 0; + newLm->Y = e->prev->ybot; + + if ( NEAR_EQUAL(e->dx, HORIZONTAL) ) //horizontal edges never start a left bound + { + if (e->xbot != e->prev->xbot) SwapX(*e); + newLm->leftBound = e->prev; + newLm->rightBound = e; + } else if (e->dx < e->prev->dx) + { + newLm->leftBound = e->prev; + newLm->rightBound = e; + } else + { + newLm->leftBound = e; + newLm->rightBound = e->prev; + } + newLm->leftBound->side = esLeft; + newLm->rightBound->side = esRight; + InsertLocalMinima( newLm ); + + for (;;) + { + if ( e->next->ytop == e->ytop && !NEAR_EQUAL(e->next->dx, HORIZONTAL) ) break; + e->nextInLML = e->next; + e = e->next; + if ( NEAR_EQUAL(e->dx, HORIZONTAL) && e->xbot != e->prev->xtop) SwapX(*e); + } + return e->next; +} +//------------------------------------------------------------------------------ + +bool ClipperBase::AddPolygons(const Polygons &ppg, PolyType polyType) +{ + bool result = false; + for (Polygons::size_type i = 0; i < ppg.size(); ++i) + if (AddPolygon(ppg[i], polyType)) result = true; + return result; +} +//------------------------------------------------------------------------------ + +void ClipperBase::Clear() +{ + DisposeLocalMinimaList(); + for (EdgeList::size_type i = 0; i < m_edges.size(); ++i) delete [] m_edges[i]; + m_edges.clear(); + m_UseFullRange = false; +} +//------------------------------------------------------------------------------ + +void ClipperBase::Reset() +{ + m_CurrentLM = m_MinimaList; + if( !m_CurrentLM ) return; //ie nothing to process + + //reset all edges ... + LocalMinima* lm = m_MinimaList; + while( lm ) + { + TEdge* e = lm->leftBound; + while( e ) + { + e->xcurr = e->xbot; + e->ycurr = e->ybot; + e->side = esLeft; + e->outIdx = -1; + e = e->nextInLML; + } + e = lm->rightBound; + while( e ) + { + e->xcurr = e->xbot; + e->ycurr = e->ybot; + e->side = esRight; + e->outIdx = -1; + e = e->nextInLML; + } + lm = lm->next; + } +} +//------------------------------------------------------------------------------ + +void ClipperBase::DisposeLocalMinimaList() +{ + while( m_MinimaList ) + { + LocalMinima* tmpLm = m_MinimaList->next; + delete m_MinimaList; + m_MinimaList = tmpLm; + } + m_CurrentLM = 0; +} +//------------------------------------------------------------------------------ + +void ClipperBase::PopLocalMinima() +{ + if( ! m_CurrentLM ) return; + m_CurrentLM = m_CurrentLM->next; +} +//------------------------------------------------------------------------------ + +IntRect ClipperBase::GetBounds() +{ + IntRect result; + LocalMinima* lm = m_MinimaList; + if (!lm) + { + result.left = result.top = result.right = result.bottom = 0; + return result; + } + result.left = lm->leftBound->xbot; + result.top = lm->leftBound->ybot; + result.right = lm->leftBound->xbot; + result.bottom = lm->leftBound->ybot; + while (lm) + { + if (lm->leftBound->ybot > result.bottom) + result.bottom = lm->leftBound->ybot; + TEdge* e = lm->leftBound; + for (;;) { + TEdge* bottomE = e; + while (e->nextInLML) + { + if (e->xbot < result.left) result.left = e->xbot; + if (e->xbot > result.right) result.right = e->xbot; + e = e->nextInLML; + } + if (e->xbot < result.left) result.left = e->xbot; + if (e->xbot > result.right) result.right = e->xbot; + if (e->xtop < result.left) result.left = e->xtop; + if (e->xtop > result.right) result.right = e->xtop; + if (e->ytop < result.top) result.top = e->ytop; + + if (bottomE == lm->leftBound) e = lm->rightBound; + else break; + } + lm = lm->next; + } + return result; +} + + +//------------------------------------------------------------------------------ +// TClipper methods ... +//------------------------------------------------------------------------------ + +Clipper::Clipper() : ClipperBase() //constructor +{ + m_Scanbeam = 0; + m_ActiveEdges = 0; + m_SortedEdges = 0; + m_IntersectNodes = 0; + m_ExecuteLocked = false; + m_UseFullRange = false; + m_ReverseOutput = false; + m_ForceSimple = false; +} +//------------------------------------------------------------------------------ + +Clipper::~Clipper() //destructor +{ + Clear(); + DisposeScanbeamList(); +} +//------------------------------------------------------------------------------ + +void Clipper::Clear() +{ + if (m_edges.empty()) return; //avoids problems with ClipperBase destructor + DisposeAllPolyPts(); + ClipperBase::Clear(); +} +//------------------------------------------------------------------------------ + +void Clipper::DisposeScanbeamList() +{ + while ( m_Scanbeam ) { + Scanbeam* sb2 = m_Scanbeam->next; + delete m_Scanbeam; + m_Scanbeam = sb2; + } +} +//------------------------------------------------------------------------------ + +void Clipper::Reset() +{ + ClipperBase::Reset(); + m_Scanbeam = 0; + m_ActiveEdges = 0; + m_SortedEdges = 0; + DisposeAllPolyPts(); + LocalMinima* lm = m_MinimaList; + while (lm) + { + InsertScanbeam(lm->Y); + lm = lm->next; + } +} +//------------------------------------------------------------------------------ + +bool Clipper::Execute(ClipType clipType, Polygons &solution, + PolyFillType subjFillType, PolyFillType clipFillType) +{ + if( m_ExecuteLocked ) return false; + m_ExecuteLocked = true; + solution.resize(0); + m_SubjFillType = subjFillType; + m_ClipFillType = clipFillType; + m_ClipType = clipType; + m_UsingPolyTree = false; + bool succeeded = ExecuteInternal(); + if (succeeded) BuildResult(solution); + m_ExecuteLocked = false; + return succeeded; +} +//------------------------------------------------------------------------------ + +bool Clipper::Execute(ClipType clipType, PolyTree& polytree, + PolyFillType subjFillType, PolyFillType clipFillType) +{ + if( m_ExecuteLocked ) return false; + m_ExecuteLocked = true; + m_SubjFillType = subjFillType; + m_ClipFillType = clipFillType; + m_ClipType = clipType; + m_UsingPolyTree = true; + bool succeeded = ExecuteInternal(); + if (succeeded) BuildResult2(polytree); + m_ExecuteLocked = false; + return succeeded; +} +//------------------------------------------------------------------------------ + +void Clipper::FixHoleLinkage(OutRec &outrec) +{ + //skip OutRecs that (a) contain outermost polygons or + //(b) already have the correct owner/child linkage ... + if (!outrec.FirstLeft || + (outrec.isHole != outrec.FirstLeft->isHole && + outrec.FirstLeft->pts)) return; + + OutRec* orfl = outrec.FirstLeft; + while (orfl && ((orfl->isHole == outrec.isHole) || !orfl->pts)) + orfl = orfl->FirstLeft; + outrec.FirstLeft = orfl; +} +//------------------------------------------------------------------------------ + +bool Clipper::ExecuteInternal() +{ + bool succeeded; + try { + Reset(); + if (!m_CurrentLM ) return true; + long64 botY = PopScanbeam(); + do { + InsertLocalMinimaIntoAEL(botY); + ClearHorzJoins(); + ProcessHorizontals(); + long64 topY = PopScanbeam(); + succeeded = ProcessIntersections(botY, topY); + if (!succeeded) break; + ProcessEdgesAtTopOfScanbeam(topY); + botY = topY; + } while(m_Scanbeam || m_CurrentLM); + } + catch(...) { + succeeded = false; + } + + if (succeeded) + { + //tidy up output polygons and fix orientations where necessary ... + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + OutRec *outRec = m_PolyOuts[i]; + if (!outRec->pts) continue; + FixupOutPolygon(*outRec); + if (!outRec->pts) continue; + + if ((outRec->isHole ^ m_ReverseOutput) == (Area(*outRec, m_UseFullRange) > 0)) + ReversePolyPtLinks(outRec->pts); + } + + if (!m_Joins.empty()) JoinCommonEdges(); + if (m_ForceSimple) DoSimplePolygons(); + } + + ClearJoins(); + ClearHorzJoins(); + return succeeded; +} +//------------------------------------------------------------------------------ + +void Clipper::InsertScanbeam(const long64 Y) +{ + if( !m_Scanbeam ) + { + m_Scanbeam = new Scanbeam; + m_Scanbeam->next = 0; + m_Scanbeam->Y = Y; + } + else if( Y > m_Scanbeam->Y ) + { + Scanbeam* newSb = new Scanbeam; + newSb->Y = Y; + newSb->next = m_Scanbeam; + m_Scanbeam = newSb; + } else + { + Scanbeam* sb2 = m_Scanbeam; + while( sb2->next && ( Y <= sb2->next->Y ) ) sb2 = sb2->next; + if( Y == sb2->Y ) return; //ie ignores duplicates + Scanbeam* newSb = new Scanbeam; + newSb->Y = Y; + newSb->next = sb2->next; + sb2->next = newSb; + } +} +//------------------------------------------------------------------------------ + +long64 Clipper::PopScanbeam() +{ + long64 Y = m_Scanbeam->Y; + Scanbeam* sb2 = m_Scanbeam; + m_Scanbeam = m_Scanbeam->next; + delete sb2; + return Y; +} +//------------------------------------------------------------------------------ + +void Clipper::DisposeAllPolyPts(){ + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + DisposeOutRec(i); + m_PolyOuts.clear(); +} +//------------------------------------------------------------------------------ + +void Clipper::DisposeOutRec(PolyOutList::size_type index) +{ + OutRec *outRec = m_PolyOuts[index]; + if (outRec->pts) DisposeOutPts(outRec->pts); + delete outRec; + m_PolyOuts[index] = 0; +} +//------------------------------------------------------------------------------ + +void Clipper::SetWindingCount(TEdge &edge) +{ + TEdge *e = edge.prevInAEL; + //find the edge of the same polytype that immediately preceeds 'edge' in AEL + while ( e && e->polyType != edge.polyType ) e = e->prevInAEL; + if ( !e ) + { + edge.windCnt = edge.windDelta; + edge.windCnt2 = 0; + e = m_ActiveEdges; //ie get ready to calc windCnt2 + } else if ( IsEvenOddFillType(edge) ) + { + //EvenOdd filling ... + edge.windCnt = 1; + edge.windCnt2 = e->windCnt2; + e = e->nextInAEL; //ie get ready to calc windCnt2 + } else + { + //nonZero, Positive or Negative filling ... + if ( e->windCnt * e->windDelta < 0 ) + { + if (Abs(e->windCnt) > 1) + { + if (e->windDelta * edge.windDelta < 0) edge.windCnt = e->windCnt; + else edge.windCnt = e->windCnt + edge.windDelta; + } else + edge.windCnt = e->windCnt + e->windDelta + edge.windDelta; + } else + { + if ( Abs(e->windCnt) > 1 && e->windDelta * edge.windDelta < 0) + edge.windCnt = e->windCnt; + else if ( e->windCnt + edge.windDelta == 0 ) + edge.windCnt = e->windCnt; + else edge.windCnt = e->windCnt + edge.windDelta; + } + edge.windCnt2 = e->windCnt2; + e = e->nextInAEL; //ie get ready to calc windCnt2 + } + + //update windCnt2 ... + if ( IsEvenOddAltFillType(edge) ) + { + //EvenOdd filling ... + while ( e != &edge ) + { + edge.windCnt2 = (edge.windCnt2 == 0) ? 1 : 0; + e = e->nextInAEL; + } + } else + { + //nonZero, Positive or Negative filling ... + while ( e != &edge ) + { + edge.windCnt2 += e->windDelta; + e = e->nextInAEL; + } + } +} +//------------------------------------------------------------------------------ + +bool Clipper::IsEvenOddFillType(const TEdge& edge) const +{ + if (edge.polyType == ptSubject) + return m_SubjFillType == pftEvenOdd; else + return m_ClipFillType == pftEvenOdd; +} +//------------------------------------------------------------------------------ + +bool Clipper::IsEvenOddAltFillType(const TEdge& edge) const +{ + if (edge.polyType == ptSubject) + return m_ClipFillType == pftEvenOdd; else + return m_SubjFillType == pftEvenOdd; +} +//------------------------------------------------------------------------------ + +bool Clipper::IsContributing(const TEdge& edge) const +{ + PolyFillType pft, pft2; + if (edge.polyType == ptSubject) + { + pft = m_SubjFillType; + pft2 = m_ClipFillType; + } else + { + pft = m_ClipFillType; + pft2 = m_SubjFillType; + } + + switch(pft) + { + case pftEvenOdd: + case pftNonZero: + if (Abs(edge.windCnt) != 1) return false; + break; + case pftPositive: + if (edge.windCnt != 1) return false; + break; + default: //pftNegative + if (edge.windCnt != -1) return false; + } + + switch(m_ClipType) + { + case ctIntersection: + switch(pft2) + { + case pftEvenOdd: + case pftNonZero: + return (edge.windCnt2 != 0); + case pftPositive: + return (edge.windCnt2 > 0); + default: + return (edge.windCnt2 < 0); + } + break; + case ctUnion: + switch(pft2) + { + case pftEvenOdd: + case pftNonZero: + return (edge.windCnt2 == 0); + case pftPositive: + return (edge.windCnt2 <= 0); + default: + return (edge.windCnt2 >= 0); + } + break; + case ctDifference: + if (edge.polyType == ptSubject) + switch(pft2) + { + case pftEvenOdd: + case pftNonZero: + return (edge.windCnt2 == 0); + case pftPositive: + return (edge.windCnt2 <= 0); + default: + return (edge.windCnt2 >= 0); + } + else + switch(pft2) + { + case pftEvenOdd: + case pftNonZero: + return (edge.windCnt2 != 0); + case pftPositive: + return (edge.windCnt2 > 0); + default: + return (edge.windCnt2 < 0); + } + break; + default: + return true; + } +} +//------------------------------------------------------------------------------ + +void Clipper::AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt) +{ + TEdge *e, *prevE; + if( NEAR_EQUAL(e2->dx, HORIZONTAL) || ( e1->dx > e2->dx ) ) + { + AddOutPt( e1, pt ); + e2->outIdx = e1->outIdx; + e1->side = esLeft; + e2->side = esRight; + e = e1; + if (e->prevInAEL == e2) + prevE = e2->prevInAEL; + else + prevE = e->prevInAEL; + } else + { + AddOutPt( e2, pt ); + e1->outIdx = e2->outIdx; + e1->side = esRight; + e2->side = esLeft; + e = e2; + if (e->prevInAEL == e1) + prevE = e1->prevInAEL; + else + prevE = e->prevInAEL; + } + if (prevE && prevE->outIdx >= 0 && + (TopX(*prevE, pt.Y) == TopX(*e, pt.Y)) && + SlopesEqual(*e, *prevE, m_UseFullRange)) + AddJoin(e, prevE, -1, -1); +} +//------------------------------------------------------------------------------ + +void Clipper::AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt) +{ + AddOutPt( e1, pt ); + if( e1->outIdx == e2->outIdx ) + { + e1->outIdx = -1; + e2->outIdx = -1; + } + else if (e1->outIdx < e2->outIdx) + AppendPolygon(e1, e2); + else + AppendPolygon(e2, e1); +} +//------------------------------------------------------------------------------ + +void Clipper::AddEdgeToSEL(TEdge *edge) +{ + //SEL pointers in PEdge are reused to build a list of horizontal edges. + //However, we don't need to worry about order with horizontal edge processing. + if( !m_SortedEdges ) + { + m_SortedEdges = edge; + edge->prevInSEL = 0; + edge->nextInSEL = 0; + } + else + { + edge->nextInSEL = m_SortedEdges; + edge->prevInSEL = 0; + m_SortedEdges->prevInSEL = edge; + m_SortedEdges = edge; + } +} +//------------------------------------------------------------------------------ + +void Clipper::CopyAELToSEL() +{ + TEdge* e = m_ActiveEdges; + m_SortedEdges = e; + while ( e ) + { + e->prevInSEL = e->prevInAEL; + e->nextInSEL = e->nextInAEL; + e = e->nextInAEL; + } +} +//------------------------------------------------------------------------------ + +void Clipper::AddJoin(TEdge *e1, TEdge *e2, int e1OutIdx, int e2OutIdx) +{ + JoinRec* jr = new JoinRec; + if (e1OutIdx >= 0) + jr->poly1Idx = e1OutIdx; else + jr->poly1Idx = e1->outIdx; + jr->pt1a = IntPoint(e1->xcurr, e1->ycurr); + jr->pt1b = IntPoint(e1->xtop, e1->ytop); + if (e2OutIdx >= 0) + jr->poly2Idx = e2OutIdx; else + jr->poly2Idx = e2->outIdx; + jr->pt2a = IntPoint(e2->xcurr, e2->ycurr); + jr->pt2b = IntPoint(e2->xtop, e2->ytop); + m_Joins.push_back(jr); +} +//------------------------------------------------------------------------------ + +void Clipper::ClearJoins() +{ + for (JoinList::size_type i = 0; i < m_Joins.size(); i++) + delete m_Joins[i]; + m_Joins.resize(0); +} +//------------------------------------------------------------------------------ + +void Clipper::AddHorzJoin(TEdge *e, int idx) +{ + HorzJoinRec* hj = new HorzJoinRec; + hj->edge = e; + hj->savedIdx = idx; + m_HorizJoins.push_back(hj); +} +//------------------------------------------------------------------------------ + +void Clipper::ClearHorzJoins() +{ + for (HorzJoinList::size_type i = 0; i < m_HorizJoins.size(); i++) + delete m_HorizJoins[i]; + m_HorizJoins.resize(0); +} +//------------------------------------------------------------------------------ + +void Clipper::InsertLocalMinimaIntoAEL(const long64 botY) +{ + while( m_CurrentLM && ( m_CurrentLM->Y == botY ) ) + { + TEdge* lb = m_CurrentLM->leftBound; + TEdge* rb = m_CurrentLM->rightBound; + + InsertEdgeIntoAEL( lb ); + InsertScanbeam( lb->ytop ); + InsertEdgeIntoAEL( rb ); + + if (IsEvenOddFillType(*lb)) + { + lb->windDelta = 1; + rb->windDelta = 1; + } + else + { + rb->windDelta = -lb->windDelta; + } + SetWindingCount( *lb ); + rb->windCnt = lb->windCnt; + rb->windCnt2 = lb->windCnt2; + + if( NEAR_EQUAL(rb->dx, HORIZONTAL) ) + { + //nb: only rightbounds can have a horizontal bottom edge + AddEdgeToSEL( rb ); + InsertScanbeam( rb->nextInLML->ytop ); + } + else + InsertScanbeam( rb->ytop ); + + if( IsContributing(*lb) ) + AddLocalMinPoly( lb, rb, IntPoint(lb->xcurr, m_CurrentLM->Y) ); + + //if any output polygons share an edge, they'll need joining later ... + if (rb->outIdx >= 0 && NEAR_EQUAL(rb->dx, HORIZONTAL)) + { + for (HorzJoinList::size_type i = 0; i < m_HorizJoins.size(); ++i) + { + IntPoint pt, pt2; //returned by GetOverlapSegment() but unused here. + HorzJoinRec* hj = m_HorizJoins[i]; + //if horizontals rb and hj.edge overlap, flag for joining later ... + if (GetOverlapSegment(IntPoint(hj->edge->xbot, hj->edge->ybot), + IntPoint(hj->edge->xtop, hj->edge->ytop), + IntPoint(rb->xbot, rb->ybot), + IntPoint(rb->xtop, rb->ytop), pt, pt2)) + AddJoin(hj->edge, rb, hj->savedIdx); + } + } + + if( lb->nextInAEL != rb ) + { + if (rb->outIdx >= 0 && rb->prevInAEL->outIdx >= 0 && + SlopesEqual(*rb->prevInAEL, *rb, m_UseFullRange)) + AddJoin(rb, rb->prevInAEL); + + TEdge* e = lb->nextInAEL; + IntPoint pt = IntPoint(lb->xcurr, lb->ycurr); + while( e != rb ) + { + if(!e) throw clipperException("InsertLocalMinimaIntoAEL: missing rightbound!"); + //nb: For calculating winding counts etc, IntersectEdges() assumes + //that param1 will be to the right of param2 ABOVE the intersection ... + IntersectEdges( rb , e , pt , ipNone); //order important here + e = e->nextInAEL; + } + } + PopLocalMinima(); + } +} +//------------------------------------------------------------------------------ + +void Clipper::DeleteFromAEL(TEdge *e) +{ + TEdge* AelPrev = e->prevInAEL; + TEdge* AelNext = e->nextInAEL; + if( !AelPrev && !AelNext && (e != m_ActiveEdges) ) return; //already deleted + if( AelPrev ) AelPrev->nextInAEL = AelNext; + else m_ActiveEdges = AelNext; + if( AelNext ) AelNext->prevInAEL = AelPrev; + e->nextInAEL = 0; + e->prevInAEL = 0; +} +//------------------------------------------------------------------------------ + +void Clipper::DeleteFromSEL(TEdge *e) +{ + TEdge* SelPrev = e->prevInSEL; + TEdge* SelNext = e->nextInSEL; + if( !SelPrev && !SelNext && (e != m_SortedEdges) ) return; //already deleted + if( SelPrev ) SelPrev->nextInSEL = SelNext; + else m_SortedEdges = SelNext; + if( SelNext ) SelNext->prevInSEL = SelPrev; + e->nextInSEL = 0; + e->prevInSEL = 0; +} +//------------------------------------------------------------------------------ + +void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, + const IntPoint &pt, const IntersectProtects protects) +{ + //e1 will be to the left of e2 BELOW the intersection. Therefore e1 is before + //e2 in AEL except when e1 is being inserted at the intersection point ... + bool e1stops = !(ipLeft & protects) && !e1->nextInLML && + e1->xtop == pt.X && e1->ytop == pt.Y; + bool e2stops = !(ipRight & protects) && !e2->nextInLML && + e2->xtop == pt.X && e2->ytop == pt.Y; + bool e1Contributing = ( e1->outIdx >= 0 ); + bool e2contributing = ( e2->outIdx >= 0 ); + + //update winding counts... + //assumes that e1 will be to the right of e2 ABOVE the intersection + if ( e1->polyType == e2->polyType ) + { + if ( IsEvenOddFillType( *e1) ) + { + int oldE1WindCnt = e1->windCnt; + e1->windCnt = e2->windCnt; + e2->windCnt = oldE1WindCnt; + } else + { + if (e1->windCnt + e2->windDelta == 0 ) e1->windCnt = -e1->windCnt; + else e1->windCnt += e2->windDelta; + if ( e2->windCnt - e1->windDelta == 0 ) e2->windCnt = -e2->windCnt; + else e2->windCnt -= e1->windDelta; + } + } else + { + if (!IsEvenOddFillType(*e2)) e1->windCnt2 += e2->windDelta; + else e1->windCnt2 = ( e1->windCnt2 == 0 ) ? 1 : 0; + if (!IsEvenOddFillType(*e1)) e2->windCnt2 -= e1->windDelta; + else e2->windCnt2 = ( e2->windCnt2 == 0 ) ? 1 : 0; + } + + PolyFillType e1FillType, e2FillType, e1FillType2, e2FillType2; + if (e1->polyType == ptSubject) + { + e1FillType = m_SubjFillType; + e1FillType2 = m_ClipFillType; + } else + { + e1FillType = m_ClipFillType; + e1FillType2 = m_SubjFillType; + } + if (e2->polyType == ptSubject) + { + e2FillType = m_SubjFillType; + e2FillType2 = m_ClipFillType; + } else + { + e2FillType = m_ClipFillType; + e2FillType2 = m_SubjFillType; + } + + long64 e1Wc, e2Wc; + switch (e1FillType) + { + case pftPositive: e1Wc = e1->windCnt; break; + case pftNegative: e1Wc = -e1->windCnt; break; + default: e1Wc = Abs(e1->windCnt); + } + switch(e2FillType) + { + case pftPositive: e2Wc = e2->windCnt; break; + case pftNegative: e2Wc = -e2->windCnt; break; + default: e2Wc = Abs(e2->windCnt); + } + + if ( e1Contributing && e2contributing ) + { + if ( e1stops || e2stops || + (e1Wc != 0 && e1Wc != 1) || (e2Wc != 0 && e2Wc != 1) || + (e1->polyType != e2->polyType && m_ClipType != ctXor) ) + AddLocalMaxPoly(e1, e2, pt); + else + { + AddOutPt(e1, pt); + AddOutPt(e2, pt); + SwapSides( *e1 , *e2 ); + SwapPolyIndexes( *e1 , *e2 ); + } + } + else if ( e1Contributing ) + { + if (e2Wc == 0 || e2Wc == 1) + { + AddOutPt(e1, pt); + SwapSides(*e1, *e2); + SwapPolyIndexes(*e1, *e2); + } + } + else if ( e2contributing ) + { + if (e1Wc == 0 || e1Wc == 1) + { + AddOutPt(e2, pt); + SwapSides(*e1, *e2); + SwapPolyIndexes(*e1, *e2); + } + } + else if ( (e1Wc == 0 || e1Wc == 1) && + (e2Wc == 0 || e2Wc == 1) && !e1stops && !e2stops ) + { + //neither edge is currently contributing ... + + long64 e1Wc2, e2Wc2; + switch (e1FillType2) + { + case pftPositive: e1Wc2 = e1->windCnt2; break; + case pftNegative : e1Wc2 = -e1->windCnt2; break; + default: e1Wc2 = Abs(e1->windCnt2); + } + switch (e2FillType2) + { + case pftPositive: e2Wc2 = e2->windCnt2; break; + case pftNegative: e2Wc2 = -e2->windCnt2; break; + default: e2Wc2 = Abs(e2->windCnt2); + } + + if (e1->polyType != e2->polyType) + AddLocalMinPoly(e1, e2, pt); + else if (e1Wc == 1 && e2Wc == 1) + switch( m_ClipType ) { + case ctIntersection: + if (e1Wc2 > 0 && e2Wc2 > 0) + AddLocalMinPoly(e1, e2, pt); + break; + case ctUnion: + if ( e1Wc2 <= 0 && e2Wc2 <= 0 ) + AddLocalMinPoly(e1, e2, pt); + break; + case ctDifference: + if (((e1->polyType == ptClip) && (e1Wc2 > 0) && (e2Wc2 > 0)) || + ((e1->polyType == ptSubject) && (e1Wc2 <= 0) && (e2Wc2 <= 0))) + AddLocalMinPoly(e1, e2, pt); + break; + case ctXor: + AddLocalMinPoly(e1, e2, pt); + } + else + SwapSides( *e1, *e2 ); + } + + if( (e1stops != e2stops) && + ( (e1stops && (e1->outIdx >= 0)) || (e2stops && (e2->outIdx >= 0)) ) ) + { + SwapSides( *e1, *e2 ); + SwapPolyIndexes( *e1, *e2 ); + } + + //finally, delete any non-contributing maxima edges ... + if( e1stops ) DeleteFromAEL( e1 ); + if( e2stops ) DeleteFromAEL( e2 ); +} +//------------------------------------------------------------------------------ + +void Clipper::SetHoleState(TEdge *e, OutRec *outrec) +{ + bool isHole = false; + TEdge *e2 = e->prevInAEL; + while (e2) + { + if (e2->outIdx >= 0) + { + isHole = !isHole; + if (! outrec->FirstLeft) + outrec->FirstLeft = m_PolyOuts[e2->outIdx]; + } + e2 = e2->prevInAEL; + } + if (isHole) outrec->isHole = true; +} +//------------------------------------------------------------------------------ + +OutRec* GetLowermostRec(OutRec *outRec1, OutRec *outRec2) +{ + //work out which polygon fragment has the correct hole state ... + if (!outRec1->bottomPt) + outRec1->bottomPt = GetBottomPt(outRec1->pts); + if (!outRec2->bottomPt) + outRec2->bottomPt = GetBottomPt(outRec2->pts); + OutPt *outPt1 = outRec1->bottomPt; + OutPt *outPt2 = outRec2->bottomPt; + if (outPt1->pt.Y > outPt2->pt.Y) return outRec1; + else if (outPt1->pt.Y < outPt2->pt.Y) return outRec2; + else if (outPt1->pt.X < outPt2->pt.X) return outRec1; + else if (outPt1->pt.X > outPt2->pt.X) return outRec2; + else if (outPt1->next == outPt1) return outRec2; + else if (outPt2->next == outPt2) return outRec1; + else if (FirstIsBottomPt(outPt1, outPt2)) return outRec1; + else return outRec2; +} +//------------------------------------------------------------------------------ + +bool Param1RightOfParam2(OutRec* outRec1, OutRec* outRec2) +{ + do + { + outRec1 = outRec1->FirstLeft; + if (outRec1 == outRec2) return true; + } while (outRec1); + return false; +} +//------------------------------------------------------------------------------ + +OutRec* Clipper::GetOutRec(int idx) +{ + OutRec* outrec = m_PolyOuts[idx]; + while (outrec != m_PolyOuts[outrec->idx]) + outrec = m_PolyOuts[outrec->idx]; + return outrec; +} +//------------------------------------------------------------------------------ + +void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) +{ + //get the start and ends of both output polygons ... + OutRec *outRec1 = m_PolyOuts[e1->outIdx]; + OutRec *outRec2 = m_PolyOuts[e2->outIdx]; + + OutRec *holeStateRec; + if (Param1RightOfParam2(outRec1, outRec2)) + holeStateRec = outRec2; + else if (Param1RightOfParam2(outRec2, outRec1)) + holeStateRec = outRec1; + else + holeStateRec = GetLowermostRec(outRec1, outRec2); + + OutPt* p1_lft = outRec1->pts; + OutPt* p1_rt = p1_lft->prev; + OutPt* p2_lft = outRec2->pts; + OutPt* p2_rt = p2_lft->prev; + + EdgeSide side; + //join e2 poly onto e1 poly and delete pointers to e2 ... + if( e1->side == esLeft ) + { + if( e2->side == esLeft ) + { + //z y x a b c + ReversePolyPtLinks(p2_lft); + p2_lft->next = p1_lft; + p1_lft->prev = p2_lft; + p1_rt->next = p2_rt; + p2_rt->prev = p1_rt; + outRec1->pts = p2_rt; + } else + { + //x y z a b c + p2_rt->next = p1_lft; + p1_lft->prev = p2_rt; + p2_lft->prev = p1_rt; + p1_rt->next = p2_lft; + outRec1->pts = p2_lft; + } + side = esLeft; + } else + { + if( e2->side == esRight ) + { + //a b c z y x + ReversePolyPtLinks(p2_lft); + p1_rt->next = p2_rt; + p2_rt->prev = p1_rt; + p2_lft->next = p1_lft; + p1_lft->prev = p2_lft; + } else + { + //a b c x y z + p1_rt->next = p2_lft; + p2_lft->prev = p1_rt; + p1_lft->prev = p2_rt; + p2_rt->next = p1_lft; + } + side = esRight; + } + + outRec1->bottomPt = 0; + if (holeStateRec == outRec2) + { + if (outRec2->FirstLeft != outRec1) + outRec1->FirstLeft = outRec2->FirstLeft; + outRec1->isHole = outRec2->isHole; + } + outRec2->pts = 0; + outRec2->bottomPt = 0; + + outRec2->FirstLeft = outRec1; + + int OKIdx = e1->outIdx; + int ObsoleteIdx = e2->outIdx; + + e1->outIdx = -1; //nb: safe because we only get here via AddLocalMaxPoly + e2->outIdx = -1; + + TEdge* e = m_ActiveEdges; + while( e ) + { + if( e->outIdx == ObsoleteIdx ) + { + e->outIdx = OKIdx; + e->side = side; + break; + } + e = e->nextInAEL; + } + + outRec2->idx = outRec1->idx; +} +//------------------------------------------------------------------------------ + +OutRec* Clipper::CreateOutRec() +{ + OutRec* result = new OutRec; + result->isHole = false; + result->FirstLeft = 0; + result->pts = 0; + result->bottomPt = 0; + result->polyNode = 0; + m_PolyOuts.push_back(result); + result->idx = (int)m_PolyOuts.size()-1; + return result; +} +//------------------------------------------------------------------------------ + +void Clipper::AddOutPt(TEdge *e, const IntPoint &pt) +{ + bool ToFront = (e->side == esLeft); + if( e->outIdx < 0 ) + { + OutRec *outRec = CreateOutRec(); + e->outIdx = outRec->idx; + OutPt* newOp = new OutPt; + outRec->pts = newOp; + newOp->pt = pt; + newOp->idx = outRec->idx; + newOp->next = newOp; + newOp->prev = newOp; + SetHoleState(e, outRec); + } else + { + OutRec *outRec = m_PolyOuts[e->outIdx]; + OutPt* op = outRec->pts; + if ((ToFront && PointsEqual(pt, op->pt)) || + (!ToFront && PointsEqual(pt, op->prev->pt))) return; + + OutPt* newOp = new OutPt; + newOp->pt = pt; + newOp->idx = outRec->idx; + newOp->next = op; + newOp->prev = op->prev; + newOp->prev->next = newOp; + op->prev = newOp; + if (ToFront) outRec->pts = newOp; + } +} +//------------------------------------------------------------------------------ + +void Clipper::ProcessHorizontals() +{ + TEdge* horzEdge = m_SortedEdges; + while( horzEdge ) + { + DeleteFromSEL( horzEdge ); + ProcessHorizontal( horzEdge ); + horzEdge = m_SortedEdges; + } +} +//------------------------------------------------------------------------------ + +bool Clipper::IsTopHorz(const long64 XPos) +{ + TEdge* e = m_SortedEdges; + while( e ) + { + if( ( XPos >= std::min(e->xcurr, e->xtop) ) && + ( XPos <= std::max(e->xcurr, e->xtop) ) ) return false; + e = e->nextInSEL; + } + return true; +} +//------------------------------------------------------------------------------ + +inline bool IsMinima(TEdge *e) +{ + return e && (e->prev->nextInLML != e) && (e->next->nextInLML != e); +} +//------------------------------------------------------------------------------ + +inline bool IsMaxima(TEdge *e, const long64 Y) +{ + return e && e->ytop == Y && !e->nextInLML; +} +//------------------------------------------------------------------------------ + +inline bool IsIntermediate(TEdge *e, const long64 Y) +{ + return e->ytop == Y && e->nextInLML; +} +//------------------------------------------------------------------------------ + +TEdge *GetMaximaPair(TEdge *e) +{ + if( !IsMaxima(e->next, e->ytop) || e->next->xtop != e->xtop ) + return e->prev; else + return e->next; +} +//------------------------------------------------------------------------------ + +void Clipper::SwapPositionsInAEL(TEdge *edge1, TEdge *edge2) +{ + if( edge1->nextInAEL == edge2 ) + { + TEdge* next = edge2->nextInAEL; + if( next ) next->prevInAEL = edge1; + TEdge* prev = edge1->prevInAEL; + if( prev ) prev->nextInAEL = edge2; + edge2->prevInAEL = prev; + edge2->nextInAEL = edge1; + edge1->prevInAEL = edge2; + edge1->nextInAEL = next; + } + else if( edge2->nextInAEL == edge1 ) + { + TEdge* next = edge1->nextInAEL; + if( next ) next->prevInAEL = edge2; + TEdge* prev = edge2->prevInAEL; + if( prev ) prev->nextInAEL = edge1; + edge1->prevInAEL = prev; + edge1->nextInAEL = edge2; + edge2->prevInAEL = edge1; + edge2->nextInAEL = next; + } + else + { + TEdge* next = edge1->nextInAEL; + TEdge* prev = edge1->prevInAEL; + edge1->nextInAEL = edge2->nextInAEL; + if( edge1->nextInAEL ) edge1->nextInAEL->prevInAEL = edge1; + edge1->prevInAEL = edge2->prevInAEL; + if( edge1->prevInAEL ) edge1->prevInAEL->nextInAEL = edge1; + edge2->nextInAEL = next; + if( edge2->nextInAEL ) edge2->nextInAEL->prevInAEL = edge2; + edge2->prevInAEL = prev; + if( edge2->prevInAEL ) edge2->prevInAEL->nextInAEL = edge2; + } + + if( !edge1->prevInAEL ) m_ActiveEdges = edge1; + else if( !edge2->prevInAEL ) m_ActiveEdges = edge2; +} +//------------------------------------------------------------------------------ + +void Clipper::SwapPositionsInSEL(TEdge *edge1, TEdge *edge2) +{ + if( !( edge1->nextInSEL ) && !( edge1->prevInSEL ) ) return; + if( !( edge2->nextInSEL ) && !( edge2->prevInSEL ) ) return; + + if( edge1->nextInSEL == edge2 ) + { + TEdge* next = edge2->nextInSEL; + if( next ) next->prevInSEL = edge1; + TEdge* prev = edge1->prevInSEL; + if( prev ) prev->nextInSEL = edge2; + edge2->prevInSEL = prev; + edge2->nextInSEL = edge1; + edge1->prevInSEL = edge2; + edge1->nextInSEL = next; + } + else if( edge2->nextInSEL == edge1 ) + { + TEdge* next = edge1->nextInSEL; + if( next ) next->prevInSEL = edge2; + TEdge* prev = edge2->prevInSEL; + if( prev ) prev->nextInSEL = edge1; + edge1->prevInSEL = prev; + edge1->nextInSEL = edge2; + edge2->prevInSEL = edge1; + edge2->nextInSEL = next; + } + else + { + TEdge* next = edge1->nextInSEL; + TEdge* prev = edge1->prevInSEL; + edge1->nextInSEL = edge2->nextInSEL; + if( edge1->nextInSEL ) edge1->nextInSEL->prevInSEL = edge1; + edge1->prevInSEL = edge2->prevInSEL; + if( edge1->prevInSEL ) edge1->prevInSEL->nextInSEL = edge1; + edge2->nextInSEL = next; + if( edge2->nextInSEL ) edge2->nextInSEL->prevInSEL = edge2; + edge2->prevInSEL = prev; + if( edge2->prevInSEL ) edge2->prevInSEL->nextInSEL = edge2; + } + + if( !edge1->prevInSEL ) m_SortedEdges = edge1; + else if( !edge2->prevInSEL ) m_SortedEdges = edge2; +} +//------------------------------------------------------------------------------ + +TEdge* GetNextInAEL(TEdge *e, Direction dir) +{ + return dir == dLeftToRight ? e->nextInAEL : e->prevInAEL; +} +//------------------------------------------------------------------------------ + +void Clipper::ProcessHorizontal(TEdge *horzEdge) +{ + Direction dir; + long64 horzLeft, horzRight; + + if( horzEdge->xcurr < horzEdge->xtop ) + { + horzLeft = horzEdge->xcurr; + horzRight = horzEdge->xtop; + dir = dLeftToRight; + } else + { + horzLeft = horzEdge->xtop; + horzRight = horzEdge->xcurr; + dir = dRightToLeft; + } + + TEdge* eMaxPair; + if( horzEdge->nextInLML ) eMaxPair = 0; + else eMaxPair = GetMaximaPair(horzEdge); + + TEdge* e = GetNextInAEL( horzEdge , dir ); + while( e ) + { + if ( e->xcurr == horzEdge->xtop && !eMaxPair ) + { + if (SlopesEqual(*e, *horzEdge->nextInLML, m_UseFullRange)) + { + //if output polygons share an edge, they'll need joining later ... + if (horzEdge->outIdx >= 0 && e->outIdx >= 0) + AddJoin(horzEdge->nextInLML, e, horzEdge->outIdx); + break; //we've reached the end of the horizontal line + } + else if (e->dx < horzEdge->nextInLML->dx) + //we really have got to the end of the intermediate horz edge so quit. + //nb: More -ve slopes follow more +ve slopes ABOVE the horizontal. + break; + } + + TEdge* eNext = GetNextInAEL( e, dir ); + + if (eMaxPair || + ((dir == dLeftToRight) && (e->xcurr < horzRight)) || + ((dir == dRightToLeft) && (e->xcurr > horzLeft))) + { + //so far we're still in range of the horizontal edge + if( e == eMaxPair ) + { + //horzEdge is evidently a maxima horizontal and we've arrived at its end. + if (dir == dLeftToRight) + IntersectEdges(horzEdge, e, IntPoint(e->xcurr, horzEdge->ycurr), ipNone); + else + IntersectEdges(e, horzEdge, IntPoint(e->xcurr, horzEdge->ycurr), ipNone); + if (eMaxPair->outIdx >= 0) throw clipperException("ProcessHorizontal error"); + return; + } + else if( NEAR_EQUAL(e->dx, HORIZONTAL) && !IsMinima(e) && !(e->xcurr > e->xtop) ) + { + //An overlapping horizontal edge. Overlapping horizontal edges are + //processed as if layered with the current horizontal edge (horizEdge) + //being infinitesimally lower that the next (e). Therfore, we + //intersect with e only if e.xcurr is within the bounds of horzEdge ... + if( dir == dLeftToRight ) + IntersectEdges( horzEdge , e, IntPoint(e->xcurr, horzEdge->ycurr), + (IsTopHorz( e->xcurr ))? ipLeft : ipBoth ); + else + IntersectEdges( e, horzEdge, IntPoint(e->xcurr, horzEdge->ycurr), + (IsTopHorz( e->xcurr ))? ipRight : ipBoth ); + } + else if( dir == dLeftToRight ) + { + IntersectEdges( horzEdge, e, IntPoint(e->xcurr, horzEdge->ycurr), + (IsTopHorz( e->xcurr ))? ipLeft : ipBoth ); + } + else + { + IntersectEdges( e, horzEdge, IntPoint(e->xcurr, horzEdge->ycurr), + (IsTopHorz( e->xcurr ))? ipRight : ipBoth ); + } + SwapPositionsInAEL( horzEdge, e ); + } + else if( (dir == dLeftToRight && e->xcurr >= horzRight) || + (dir == dRightToLeft && e->xcurr <= horzLeft) ) break; + e = eNext; + } //end while + + if( horzEdge->nextInLML ) + { + if( horzEdge->outIdx >= 0 ) + AddOutPt( horzEdge, IntPoint(horzEdge->xtop, horzEdge->ytop)); + UpdateEdgeIntoAEL( horzEdge ); + } + else + { + if ( horzEdge->outIdx >= 0 ) + IntersectEdges( horzEdge, eMaxPair, + IntPoint(horzEdge->xtop, horzEdge->ycurr), ipBoth); + if (eMaxPair->outIdx >= 0) throw clipperException("ProcessHorizontal error"); + DeleteFromAEL(eMaxPair); + DeleteFromAEL(horzEdge); + } +} +//------------------------------------------------------------------------------ + +void Clipper::UpdateEdgeIntoAEL(TEdge *&e) +{ + if( !e->nextInLML ) throw + clipperException("UpdateEdgeIntoAEL: invalid call"); + TEdge* AelPrev = e->prevInAEL; + TEdge* AelNext = e->nextInAEL; + e->nextInLML->outIdx = e->outIdx; + if( AelPrev ) AelPrev->nextInAEL = e->nextInLML; + else m_ActiveEdges = e->nextInLML; + if( AelNext ) AelNext->prevInAEL = e->nextInLML; + e->nextInLML->side = e->side; + e->nextInLML->windDelta = e->windDelta; + e->nextInLML->windCnt = e->windCnt; + e->nextInLML->windCnt2 = e->windCnt2; + e = e->nextInLML; + e->prevInAEL = AelPrev; + e->nextInAEL = AelNext; + if( !NEAR_EQUAL(e->dx, HORIZONTAL) ) InsertScanbeam( e->ytop ); +} +//------------------------------------------------------------------------------ + +bool Clipper::ProcessIntersections(const long64 botY, const long64 topY) +{ + if( !m_ActiveEdges ) return true; + try { + BuildIntersectList(botY, topY); + if (!m_IntersectNodes) return true; + if (!m_IntersectNodes->next || FixupIntersectionOrder()) ProcessIntersectList(); + else return false; + } + catch(...) { + m_SortedEdges = 0; + DisposeIntersectNodes(); + throw clipperException("ProcessIntersections error"); + } + m_SortedEdges = 0; + return true; +} +//------------------------------------------------------------------------------ + +void Clipper::DisposeIntersectNodes() +{ + while ( m_IntersectNodes ) + { + IntersectNode* iNode = m_IntersectNodes->next; + delete m_IntersectNodes; + m_IntersectNodes = iNode; + } +} +//------------------------------------------------------------------------------ + +void Clipper::BuildIntersectList(const long64 botY, const long64 topY) +{ + if ( !m_ActiveEdges ) return; + + //prepare for sorting ... + TEdge* e = m_ActiveEdges; + m_SortedEdges = e; + while( e ) + { + e->prevInSEL = e->prevInAEL; + e->nextInSEL = e->nextInAEL; + e->xcurr = TopX( *e, topY ); + e = e->nextInAEL; + } + + //bubblesort ... + bool isModified; + do + { + isModified = false; + e = m_SortedEdges; + while( e->nextInSEL ) + { + TEdge *eNext = e->nextInSEL; + IntPoint pt; + if(e->xcurr > eNext->xcurr) + { + if (!IntersectPoint(*e, *eNext, pt, m_UseFullRange) && e->xcurr > eNext->xcurr +1) + throw clipperException("Intersection error"); + if (pt.Y > botY) + { + pt.Y = botY; + pt.X = TopX(*e, pt.Y); + } + InsertIntersectNode( e, eNext, pt ); + SwapPositionsInSEL(e, eNext); + isModified = true; + } + else + e = eNext; + } + if( e->prevInSEL ) e->prevInSEL->nextInSEL = 0; + else break; + } + while ( isModified ); + m_SortedEdges = 0; //important +} +//------------------------------------------------------------------------------ + +void Clipper::InsertIntersectNode(TEdge *e1, TEdge *e2, const IntPoint &pt) +{ + IntersectNode* newNode = new IntersectNode; + newNode->edge1 = e1; + newNode->edge2 = e2; + newNode->pt = pt; + newNode->next = 0; + if( !m_IntersectNodes ) m_IntersectNodes = newNode; + else if(newNode->pt.Y > m_IntersectNodes->pt.Y ) + { + newNode->next = m_IntersectNodes; + m_IntersectNodes = newNode; + } + else + { + IntersectNode* iNode = m_IntersectNodes; + while(iNode->next && newNode->pt.Y <= iNode->next->pt.Y) + iNode = iNode->next; + newNode->next = iNode->next; + iNode->next = newNode; + } +} +//------------------------------------------------------------------------------ + +void Clipper::ProcessIntersectList() +{ + while( m_IntersectNodes ) + { + IntersectNode* iNode = m_IntersectNodes->next; + { + IntersectEdges( m_IntersectNodes->edge1 , + m_IntersectNodes->edge2 , m_IntersectNodes->pt, ipBoth ); + SwapPositionsInAEL( m_IntersectNodes->edge1 , m_IntersectNodes->edge2 ); + } + delete m_IntersectNodes; + m_IntersectNodes = iNode; + } +} +//------------------------------------------------------------------------------ + +void Clipper::DoMaxima(TEdge *e, long64 topY) +{ + TEdge* eMaxPair = GetMaximaPair(e); + long64 X = e->xtop; + TEdge* eNext = e->nextInAEL; + while( eNext != eMaxPair ) + { + if (!eNext) throw clipperException("DoMaxima error"); + IntersectEdges( e, eNext, IntPoint(X, topY), ipBoth ); + SwapPositionsInAEL(e, eNext); + eNext = e->nextInAEL; + } + if( e->outIdx < 0 && eMaxPair->outIdx < 0 ) + { + DeleteFromAEL( e ); + DeleteFromAEL( eMaxPair ); + } + else if( e->outIdx >= 0 && eMaxPair->outIdx >= 0 ) + { + IntersectEdges( e, eMaxPair, IntPoint(X, topY), ipNone ); + } + else throw clipperException("DoMaxima error"); +} +//------------------------------------------------------------------------------ + +void Clipper::ProcessEdgesAtTopOfScanbeam(const long64 topY) +{ + TEdge* e = m_ActiveEdges; + while( e ) + { + //1. process maxima, treating them as if they're 'bent' horizontal edges, + // but exclude maxima with horizontal edges. nb: e can't be a horizontal. + if( IsMaxima(e, topY) && !NEAR_EQUAL(GetMaximaPair(e)->dx, HORIZONTAL) ) + { + //'e' might be removed from AEL, as may any following edges so ... + TEdge* ePrev = e->prevInAEL; + DoMaxima(e, topY); + if( !ePrev ) e = m_ActiveEdges; + else e = ePrev->nextInAEL; + } + else + { + bool intermediateVert = IsIntermediate(e, topY); + //2. promote horizontal edges, otherwise update xcurr and ycurr ... + if (intermediateVert && NEAR_EQUAL(e->nextInLML->dx, HORIZONTAL) ) + { + if (e->outIdx >= 0) + { + AddOutPt(e, IntPoint(e->xtop, e->ytop)); + + for (HorzJoinList::size_type i = 0; i < m_HorizJoins.size(); ++i) + { + IntPoint pt, pt2; + HorzJoinRec* hj = m_HorizJoins[i]; + if (GetOverlapSegment(IntPoint(hj->edge->xbot, hj->edge->ybot), + IntPoint(hj->edge->xtop, hj->edge->ytop), + IntPoint(e->nextInLML->xbot, e->nextInLML->ybot), + IntPoint(e->nextInLML->xtop, e->nextInLML->ytop), pt, pt2)) + AddJoin(hj->edge, e->nextInLML, hj->savedIdx, e->outIdx); + } + + AddHorzJoin(e->nextInLML, e->outIdx); + } + UpdateEdgeIntoAEL(e); + AddEdgeToSEL(e); + } else + { + e->xcurr = TopX( *e, topY ); + e->ycurr = topY; + + if (m_ForceSimple && e->prevInAEL && + e->prevInAEL->xcurr == e->xcurr && + e->outIdx >= 0 && e->prevInAEL->outIdx >= 0) + { + if (intermediateVert) + AddOutPt(e->prevInAEL, IntPoint(e->xcurr, topY)); + else + AddOutPt(e, IntPoint(e->xcurr, topY)); + } + } + e = e->nextInAEL; + } + } + + //3. Process horizontals at the top of the scanbeam ... + ProcessHorizontals(); + + //4. Promote intermediate vertices ... + e = m_ActiveEdges; + while( e ) + { + if( IsIntermediate( e, topY ) ) + { + if( e->outIdx >= 0 ) AddOutPt(e, IntPoint(e->xtop,e->ytop)); + UpdateEdgeIntoAEL(e); + + //if output polygons share an edge, they'll need joining later ... + TEdge* ePrev = e->prevInAEL; + TEdge* eNext = e->nextInAEL; + if (ePrev && ePrev->xcurr == e->xbot && + ePrev->ycurr == e->ybot && e->outIdx >= 0 && + ePrev->outIdx >= 0 && ePrev->ycurr > ePrev->ytop && + SlopesEqual(*e, *ePrev, m_UseFullRange)) + { + AddOutPt(ePrev, IntPoint(e->xbot, e->ybot)); + AddJoin(e, ePrev); + } + else if (eNext && eNext->xcurr == e->xbot && + eNext->ycurr == e->ybot && e->outIdx >= 0 && + eNext->outIdx >= 0 && eNext->ycurr > eNext->ytop && + SlopesEqual(*e, *eNext, m_UseFullRange)) + { + AddOutPt(eNext, IntPoint(e->xbot, e->ybot)); + AddJoin(e, eNext); + } + } + e = e->nextInAEL; + } +} +//------------------------------------------------------------------------------ + +void Clipper::FixupOutPolygon(OutRec &outrec) +{ + //FixupOutPolygon() - removes duplicate points and simplifies consecutive + //parallel edges by removing the middle vertex. + OutPt *lastOK = 0; + outrec.bottomPt = 0; + OutPt *pp = outrec.pts; + + for (;;) + { + if (pp->prev == pp || pp->prev == pp->next ) + { + DisposeOutPts(pp); + outrec.pts = 0; + return; + } + //test for duplicate points and for same slope (cross-product) ... + if ( PointsEqual(pp->pt, pp->next->pt) || + SlopesEqual(pp->prev->pt, pp->pt, pp->next->pt, m_UseFullRange) ) + { + lastOK = 0; + OutPt *tmp = pp; + pp->prev->next = pp->next; + pp->next->prev = pp->prev; + pp = pp->prev; + delete tmp; + } + else if (pp == lastOK) break; + else + { + if (!lastOK) lastOK = pp; + pp = pp->next; + } + } + outrec.pts = pp; +} +//------------------------------------------------------------------------------ + +void Clipper::BuildResult(Polygons &polys) +{ + polys.reserve(m_PolyOuts.size()); + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + if (m_PolyOuts[i]->pts) + { + Polygon pg; + OutPt* p = m_PolyOuts[i]->pts; + do + { + pg.push_back(p->pt); + p = p->prev; + } while (p != m_PolyOuts[i]->pts); + if (pg.size() > 2) + polys.push_back(pg); + } + } +} +//------------------------------------------------------------------------------ + +int PointCount(OutPt *pts) +{ + if (!pts) return 0; + int result = 0; + OutPt* p = pts; + do + { + result++; + p = p->next; + } + while (p != pts); + return result; +} +//------------------------------------------------------------------------------ + +void Clipper::BuildResult2(PolyTree& polytree) +{ + polytree.Clear(); + polytree.AllNodes.reserve(m_PolyOuts.size()); + //add each output polygon/contour to polytree ... + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) + { + OutRec* outRec = m_PolyOuts[i]; + int cnt = PointCount(outRec->pts); + if (cnt < 3) continue; + FixHoleLinkage(*outRec); + PolyNode* pn = new PolyNode(); + //nb: polytree takes ownership of all the PolyNodes + polytree.AllNodes.push_back(pn); + outRec->polyNode = pn; + pn->Parent = 0; + pn->Index = 0; + pn->Contour.reserve(cnt); + OutPt *op = outRec->pts; + for (int j = 0; j < cnt; j++) + { + pn->Contour.push_back(op->pt); + op = op->prev; + } + } + + //fixup PolyNode links etc ... + polytree.Childs.reserve(m_PolyOuts.size()); + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) + { + OutRec* outRec = m_PolyOuts[i]; + if (!outRec->polyNode) continue; + if (outRec->FirstLeft) + outRec->FirstLeft->polyNode->AddChild(*outRec->polyNode); + else + polytree.AddChild(*outRec->polyNode); + } +} +//------------------------------------------------------------------------------ + +void SwapIntersectNodes(IntersectNode &int1, IntersectNode &int2) +{ + //just swap the contents (because fIntersectNodes is a single-linked-list) + IntersectNode inode = int1; //gets a copy of Int1 + int1.edge1 = int2.edge1; + int1.edge2 = int2.edge2; + int1.pt = int2.pt; + int2.edge1 = inode.edge1; + int2.edge2 = inode.edge2; + int2.pt = inode.pt; +} +//------------------------------------------------------------------------------ + +inline bool EdgesAdjacent(const IntersectNode &inode) +{ + return (inode.edge1->nextInSEL == inode.edge2) || + (inode.edge1->prevInSEL == inode.edge2); +} +//------------------------------------------------------------------------------ + +bool Clipper::FixupIntersectionOrder() +{ + //pre-condition: intersections are sorted bottom-most (then left-most) first. + //Now it's crucial that intersections are made only between adjacent edges, + //so to ensure this the order of intersections may need adjusting ... + IntersectNode *inode = m_IntersectNodes; + CopyAELToSEL(); + while (inode) + { + if (!EdgesAdjacent(*inode)) + { + IntersectNode *nextNode = inode->next; + while (nextNode && !EdgesAdjacent(*nextNode)) + nextNode = nextNode->next; + if (!nextNode) + return false; + SwapIntersectNodes(*inode, *nextNode); + } + SwapPositionsInSEL(inode->edge1, inode->edge2); + inode = inode->next; + } + return true; +} +//------------------------------------------------------------------------------ + +inline bool E2InsertsBeforeE1(TEdge &e1, TEdge &e2) +{ + if (e2.xcurr == e1.xcurr) + { + if (e2.ytop > e1.ytop) + return e2.xtop < TopX(e1, e2.ytop); + else return e1.xtop > TopX(e2, e1.ytop); + } + else return e2.xcurr < e1.xcurr; +} +//------------------------------------------------------------------------------ + +void Clipper::InsertEdgeIntoAEL(TEdge *edge) +{ + edge->prevInAEL = 0; + edge->nextInAEL = 0; + if( !m_ActiveEdges ) + { + m_ActiveEdges = edge; + } + else if( E2InsertsBeforeE1(*m_ActiveEdges, *edge) ) + { + edge->nextInAEL = m_ActiveEdges; + m_ActiveEdges->prevInAEL = edge; + m_ActiveEdges = edge; + } else + { + TEdge* e = m_ActiveEdges; + while( e->nextInAEL && !E2InsertsBeforeE1(*e->nextInAEL , *edge) ) + e = e->nextInAEL; + edge->nextInAEL = e->nextInAEL; + if( e->nextInAEL ) e->nextInAEL->prevInAEL = edge; + edge->prevInAEL = e; + e->nextInAEL = edge; + } +} +//---------------------------------------------------------------------- + +bool Clipper::JoinPoints(const JoinRec *j, OutPt *&p1, OutPt *&p2) +{ + OutRec *outRec1 = m_PolyOuts[j->poly1Idx]; + OutRec *outRec2 = m_PolyOuts[j->poly2Idx]; + if (!outRec1 || !outRec2) return false; + OutPt *pp1a = outRec1->pts; + OutPt *pp2a = outRec2->pts; + IntPoint pt1 = j->pt2a, pt2 = j->pt2b; + IntPoint pt3 = j->pt1a, pt4 = j->pt1b; + if (!FindSegment(pp1a, m_UseFullRange, pt1, pt2)) return false; + if (outRec1 == outRec2) + { + //we're searching the same polygon for overlapping segments so + //segment 2 mustn't be the same as segment 1 ... + pp2a = pp1a->next; + if (!FindSegment(pp2a, m_UseFullRange, pt3, pt4) || (pp2a == pp1a)) + return false; + } + else if (!FindSegment(pp2a, m_UseFullRange, pt3, pt4)) return false; + + if (!GetOverlapSegment(pt1, pt2, pt3, pt4, pt1, pt2)) return false; + + OutPt *p3, *p4, *prev = pp1a->prev; + //get p1 & p2 polypts - the overlap start & endpoints on poly1 + if (PointsEqual(pp1a->pt, pt1)) p1 = pp1a; + else if (PointsEqual(prev->pt, pt1)) p1 = prev; + else p1 = InsertPolyPtBetween(pp1a, prev, pt1); + + if (PointsEqual(pp1a->pt, pt2)) p2 = pp1a; + else if (PointsEqual(prev->pt, pt2)) p2 = prev; + else if ((p1 == pp1a) || (p1 == prev)) + p2 = InsertPolyPtBetween(pp1a, prev, pt2); + else if (Pt3IsBetweenPt1AndPt2(pp1a->pt, p1->pt, pt2)) + p2 = InsertPolyPtBetween(pp1a, p1, pt2); else + p2 = InsertPolyPtBetween(p1, prev, pt2); + + //get p3 & p4 polypts - the overlap start & endpoints on poly2 + prev = pp2a->prev; + if (PointsEqual(pp2a->pt, pt1)) p3 = pp2a; + else if (PointsEqual(prev->pt, pt1)) p3 = prev; + else p3 = InsertPolyPtBetween(pp2a, prev, pt1); + + if (PointsEqual(pp2a->pt, pt2)) p4 = pp2a; + else if (PointsEqual(prev->pt, pt2)) p4 = prev; + else if ((p3 == pp2a) || (p3 == prev)) + p4 = InsertPolyPtBetween(pp2a, prev, pt2); + else if (Pt3IsBetweenPt1AndPt2(pp2a->pt, p3->pt, pt2)) + p4 = InsertPolyPtBetween(pp2a, p3, pt2); else + p4 = InsertPolyPtBetween(p3, prev, pt2); + + //p1.pt == p3.pt and p2.pt == p4.pt so join p1 to p3 and p2 to p4 ... + if (p1->next == p2 && p3->prev == p4) + { + p1->next = p3; + p3->prev = p1; + p2->prev = p4; + p4->next = p2; + return true; + } + else if (p1->prev == p2 && p3->next == p4) + { + p1->prev = p3; + p3->next = p1; + p2->next = p4; + p4->prev = p2; + return true; + } + else + return false; //an orientation is probably wrong +} +//---------------------------------------------------------------------- + +void Clipper::FixupJoinRecs(JoinRec *j, OutPt *pt, unsigned startIdx) +{ + for (JoinList::size_type k = startIdx; k < m_Joins.size(); k++) + { + JoinRec* j2 = m_Joins[k]; + if (j2->poly1Idx == j->poly1Idx && PointIsVertex(j2->pt1a, pt)) + j2->poly1Idx = j->poly2Idx; + if (j2->poly2Idx == j->poly1Idx && PointIsVertex(j2->pt2a, pt)) + j2->poly2Idx = j->poly2Idx; + } +} +//---------------------------------------------------------------------- + +bool Poly2ContainsPoly1(OutPt* outPt1, OutPt* outPt2, bool UseFullInt64Range) +{ + OutPt* pt = outPt1; + //Because the polygons may be touching, we need to find a vertex that + //isn't touching the other polygon ... + if (PointOnPolygon(pt->pt, outPt2, UseFullInt64Range)) + { + pt = pt->next; + while (pt != outPt1 && PointOnPolygon(pt->pt, outPt2, UseFullInt64Range)) + pt = pt->next; + if (pt == outPt1) return true; + } + return PointInPolygon(pt->pt, outPt2, UseFullInt64Range); +} +//---------------------------------------------------------------------- + +void Clipper::FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec) +{ + + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + OutRec* outRec = m_PolyOuts[i]; + if (outRec->pts && outRec->FirstLeft == OldOutRec) + { + if (Poly2ContainsPoly1(outRec->pts, NewOutRec->pts, m_UseFullRange)) + outRec->FirstLeft = NewOutRec; + } + } +} +//---------------------------------------------------------------------- + +void Clipper::FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec) +{ + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + OutRec* outRec = m_PolyOuts[i]; + if (outRec->FirstLeft == OldOutRec) outRec->FirstLeft = NewOutRec; + } +} +//---------------------------------------------------------------------- + +void Clipper::JoinCommonEdges() +{ + for (JoinList::size_type i = 0; i < m_Joins.size(); i++) + { + JoinRec* j = m_Joins[i]; + + OutRec *outRec1 = GetOutRec(j->poly1Idx); + OutRec *outRec2 = GetOutRec(j->poly2Idx); + + if (!outRec1->pts || !outRec2->pts) continue; + + //get the polygon fragment with the correct hole state (FirstLeft) + //before calling JoinPoints() ... + OutRec *holeStateRec; + if (outRec1 == outRec2) holeStateRec = outRec1; + else if (Param1RightOfParam2(outRec1, outRec2)) holeStateRec = outRec2; + else if (Param1RightOfParam2(outRec2, outRec1)) holeStateRec = outRec1; + else holeStateRec = GetLowermostRec(outRec1, outRec2); + + OutPt *p1, *p2; + if (!JoinPoints(j, p1, p2)) continue; + + if (outRec1 == outRec2) + { + //instead of joining two polygons, we've just created a new one by + //splitting one polygon into two. + outRec1->pts = p1; + outRec1->bottomPt = 0; + outRec2 = CreateOutRec(); + outRec2->pts = p2; + + if (Poly2ContainsPoly1(outRec2->pts, outRec1->pts, m_UseFullRange)) + { + //outRec2 is contained by outRec1 ... + outRec2->isHole = !outRec1->isHole; + outRec2->FirstLeft = outRec1; + + FixupJoinRecs(j, p2, i+1); + + //fixup FirstLeft pointers that may need reassigning to OutRec1 + if (m_UsingPolyTree) FixupFirstLefts2(outRec2, outRec1); + + FixupOutPolygon(*outRec1); //nb: do this BEFORE testing orientation + FixupOutPolygon(*outRec2); // but AFTER calling FixupJoinRecs() + + + if ((outRec2->isHole ^ m_ReverseOutput) == (Area(*outRec2, m_UseFullRange) > 0)) + ReversePolyPtLinks(outRec2->pts); + + } else if (Poly2ContainsPoly1(outRec1->pts, outRec2->pts, m_UseFullRange)) + { + //outRec1 is contained by outRec2 ... + outRec2->isHole = outRec1->isHole; + outRec1->isHole = !outRec2->isHole; + outRec2->FirstLeft = outRec1->FirstLeft; + outRec1->FirstLeft = outRec2; + + FixupJoinRecs(j, p2, i+1); + + //fixup FirstLeft pointers that may need reassigning to OutRec1 + if (m_UsingPolyTree) FixupFirstLefts2(outRec1, outRec2); + + FixupOutPolygon(*outRec1); //nb: do this BEFORE testing orientation + FixupOutPolygon(*outRec2); // but AFTER calling FixupJoinRecs() + + if ((outRec1->isHole ^ m_ReverseOutput) == (Area(*outRec1, m_UseFullRange) > 0)) + ReversePolyPtLinks(outRec1->pts); + } + else + { + //the 2 polygons are completely separate ... + outRec2->isHole = outRec1->isHole; + outRec2->FirstLeft = outRec1->FirstLeft; + + FixupJoinRecs(j, p2, i+1); + + //fixup FirstLeft pointers that may need reassigning to OutRec2 + if (m_UsingPolyTree) FixupFirstLefts1(outRec1, outRec2); + + FixupOutPolygon(*outRec1); //nb: do this BEFORE testing orientation + FixupOutPolygon(*outRec2); // but AFTER calling FixupJoinRecs() + } + + } else + { + //joined 2 polygons together ... + + //cleanup redundant edges ... + FixupOutPolygon(*outRec1); + + outRec2->pts = 0; + outRec2->bottomPt = 0; + outRec2->idx = outRec1->idx; + + outRec1->isHole = holeStateRec->isHole; + if (holeStateRec == outRec2) + outRec1->FirstLeft = outRec2->FirstLeft; + outRec2->FirstLeft = outRec1; + + //fixup FirstLeft pointers that may need reassigning to OutRec1 + if (m_UsingPolyTree) FixupFirstLefts2(outRec2, outRec1); + } + } +} +//------------------------------------------------------------------------------ + +inline void UpdateOutPtIdxs(OutRec& outrec) +{ + OutPt* op = outrec.pts; + do + { + op->idx = outrec.idx; + op = op->prev; + } + while(op != outrec.pts); +} +//------------------------------------------------------------------------------ + +void Clipper::DoSimplePolygons() +{ + PolyOutList::size_type i = 0; + while (i < m_PolyOuts.size()) + { + OutRec* outrec = m_PolyOuts[i++]; + OutPt* op = outrec->pts; + if (!op) continue; + do //for each Pt in Polygon until duplicate found do ... + { + OutPt* op2 = op->next; + while (op2 != outrec->pts) + { + if (PointsEqual(op->pt, op2->pt) && op2->next != op && op2->prev != op) + { + //split the polygon into two ... + OutPt* op3 = op->prev; + OutPt* op4 = op2->prev; + op->prev = op4; + op4->next = op; + op2->prev = op3; + op3->next = op2; + + outrec->pts = op; + OutRec* outrec2 = CreateOutRec(); + outrec2->pts = op2; + UpdateOutPtIdxs(*outrec2); + if (Poly2ContainsPoly1(outrec2->pts, outrec->pts, m_UseFullRange)) + { + //OutRec2 is contained by OutRec1 ... + outrec2->isHole = !outrec->isHole; + outrec2->FirstLeft = outrec; + } + else + if (Poly2ContainsPoly1(outrec->pts, outrec2->pts, m_UseFullRange)) + { + //OutRec1 is contained by OutRec2 ... + outrec2->isHole = outrec->isHole; + outrec->isHole = !outrec2->isHole; + outrec2->FirstLeft = outrec->FirstLeft; + outrec->FirstLeft = outrec2; + } else + { + //the 2 polygons are separate ... + outrec2->isHole = outrec->isHole; + outrec2->FirstLeft = outrec->FirstLeft; + } + op2 = op; //ie get ready for the next iteration + } + op2 = op2->next; + } + op = op->next; + } + while (op != outrec->pts); + } +} +//------------------------------------------------------------------------------ + +void ReversePolygon(Polygon& p) +{ + std::reverse(p.begin(), p.end()); +} +//------------------------------------------------------------------------------ + +void ReversePolygons(Polygons& p) +{ + for (Polygons::size_type i = 0; i < p.size(); ++i) + ReversePolygon(p[i]); +} + +//------------------------------------------------------------------------------ +// OffsetPolygon functions ... +//------------------------------------------------------------------------------ + +struct DoublePoint +{ + double X; + double Y; + DoublePoint(double x = 0, double y = 0) : X(x), Y(y) {} +}; +//------------------------------------------------------------------------------ + +Polygon BuildArc(const IntPoint &pt, + const double a1, const double a2, const double r, double limit) +{ + //see notes in clipper.pas regarding steps + double arcFrac = std::fabs(a2 - a1) / (2 * pi); + int steps = (int)(arcFrac * pi / std::acos(1 - limit / std::fabs(r))); + if (steps < 2) steps = 2; + else if (steps > (int)(222.0 * arcFrac)) steps = (int)(222.0 * arcFrac); + + double x = std::cos(a1); + double y = std::sin(a1); + double c = std::cos((a2 - a1) / steps); + double s = std::sin((a2 - a1) / steps); + Polygon result(steps +1); + for (int i = 0; i <= steps; ++i) + { + result[i].X = pt.X + Round(x * r); + result[i].Y = pt.Y + Round(y * r); + double x2 = x; + x = x * c - s * y; //cross product + y = x2 * s + y * c; //dot product + } + return result; +} +//------------------------------------------------------------------------------ + +DoublePoint GetUnitNormal(const IntPoint &pt1, const IntPoint &pt2) +{ + if(pt2.X == pt1.X && pt2.Y == pt1.Y) + return DoublePoint(0, 0); + + double dx = (double)(pt2.X - pt1.X); + double dy = (double)(pt2.Y - pt1.Y); + double f = 1 *1.0/ std::sqrt( dx*dx + dy*dy ); + dx *= f; + dy *= f; + return DoublePoint(dy, -dx); +} + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +class OffsetBuilder +{ +private: + const Polygons& m_p; + Polygon* m_curr_poly; + std::vector normals; + double m_delta, m_rmin, m_r; + size_t m_i, m_j, m_k; + static const int buffLength = 128; + +public: + +OffsetBuilder(const Polygons& in_polys, Polygons& out_polys, + bool isPolygon, double delta, JoinType jointype, EndType endtype, double limit): m_p(in_polys) +{ + //precondition: &out_polys != &in_polys + + if (NEAR_ZERO(delta)) {out_polys = in_polys; return;} + m_rmin = 0.5; + m_delta = delta; + if (jointype == jtMiter) + { + if (limit > 2) m_rmin = 2.0 / (limit * limit); + limit = 0.25; //just in case endtype == etRound + } + else + { + if (limit <= 0) limit = 0.25; + else if (limit > std::fabs(delta)) limit = std::fabs(delta); + } + + double deltaSq = delta*delta; + out_polys.clear(); + out_polys.resize(m_p.size()); + for (m_i = 0; m_i < m_p.size(); m_i++) + { + size_t len = m_p[m_i].size(); + + if (len == 0 || (len < 3 && delta <= 0)) + continue; + else if (len == 1) + { + out_polys[m_i] = BuildArc(m_p[m_i][0], 0, 2*pi, delta, limit); + continue; + } + + bool forceClose = PointsEqual(m_p[m_i][0], m_p[m_i][len -1]); + if (forceClose) len--; + + //build normals ... + normals.clear(); + normals.resize(len); + for (m_j = 0; m_j < len -1; ++m_j) + normals[m_j] = GetUnitNormal(m_p[m_i][m_j], m_p[m_i][m_j +1]); + if (isPolygon || forceClose) + normals[len-1] = GetUnitNormal(m_p[m_i][len-1], m_p[m_i][0]); + else //is open polyline + normals[len-1] = normals[len-2]; + + m_curr_poly = &out_polys[m_i]; + m_curr_poly->reserve(len); + + if (isPolygon || forceClose) + { + m_k = len -1; + for (m_j = 0; m_j < len; ++m_j) + OffsetPoint(jointype, limit); + + if (!isPolygon) + { + size_t j = out_polys.size(); + out_polys.resize(j+1); + m_curr_poly = &out_polys[j]; + m_curr_poly->reserve(len); + m_delta = -m_delta; + + m_k = len -1; + for (m_j = 0; m_j < len; ++m_j) + OffsetPoint(jointype, limit); + m_delta = -m_delta; + ReversePolygon(*m_curr_poly); + } + } + else //is open polyline + { + //offset the polyline going forward ... + m_k = 0; + for (m_j = 1; m_j < len -1; ++m_j) + OffsetPoint(jointype, limit); + + //handle the end (butt, round or square) ... + IntPoint pt1; + if (endtype == etButt) + { + m_j = len - 1; + pt1 = IntPoint(Round(m_p[m_i][m_j].X + normals[m_j].X * m_delta), + Round(m_p[m_i][m_j].Y + normals[m_j].Y * m_delta)); + AddPoint(pt1); + pt1 = IntPoint(Round(m_p[m_i][m_j].X - normals[m_j].X * m_delta), + Round(m_p[m_i][m_j].Y - normals[m_j].Y * m_delta)); + AddPoint(pt1); + } + else + { + m_j = len - 1; + m_k = len - 2; + normals[m_j].X = -normals[m_j].X; + normals[m_j].Y = -normals[m_j].Y; + if (endtype == etSquare) DoSquare(); + else DoRound(limit); + } + + //re-build Normals ... + for (int j = len - 1; j > 0; --j) + { + normals[j].X = -normals[j - 1].X; + normals[j].Y = -normals[j - 1].Y; + } + normals[0].X = -normals[1].X; + normals[0].Y = -normals[1].Y; + + //offset the polyline going backward ... + m_k = len -1; + for (m_j = m_k - 1; m_j > 0; --m_j) + OffsetPoint(jointype, limit); + + //finally handle the start (butt, round or square) ... + if (endtype == etButt) + { + pt1 = IntPoint(Round(m_p[m_i][0].X - normals[0].X * m_delta), + Round(m_p[m_i][0].Y - normals[0].Y * m_delta)); + AddPoint(pt1); + pt1 = IntPoint(Round(m_p[m_i][0].X + normals[0].X * m_delta), + Round(m_p[m_i][0].Y + normals[0].Y * m_delta)); + AddPoint(pt1); + } else + { + m_k = 1; + if (endtype == etSquare) DoSquare(); + else DoRound(limit); + } + } + } + + //and clean up untidy corners using Clipper ... + Clipper clpr; + clpr.AddPolygons(out_polys, ptSubject); + if (delta > 0) + { + if (!clpr.Execute(ctUnion, out_polys, pftPositive, pftPositive)) + out_polys.clear(); + } + else + { + IntRect r = clpr.GetBounds(); + Polygon outer(4); + outer[0] = IntPoint(r.left - 10, r.bottom + 10); + outer[1] = IntPoint(r.right + 10, r.bottom + 10); + outer[2] = IntPoint(r.right + 10, r.top - 10); + outer[3] = IntPoint(r.left - 10, r.top - 10); + + clpr.AddPolygon(outer, ptSubject); + clpr.ReverseSolution(true); + if (clpr.Execute(ctUnion, out_polys, pftNegative, pftNegative)) + out_polys.erase(out_polys.begin()); + else + out_polys.clear(); + } +} +//------------------------------------------------------------------------------ + +private: + +void OffsetPoint(JoinType jointype, double limit) +{ + switch (jointype) + { + case jtMiter: + { + m_r = 1 + (normals[m_j].X*normals[m_k].X + + normals[m_j].Y*normals[m_k].Y); + if (m_r >= m_rmin) DoMiter(); else DoSquare(); + break; + } + case jtSquare: DoSquare(); break; + case jtRound: DoRound(limit); break; + } + m_k = m_j; +} +//------------------------------------------------------------------------------ + +void AddPoint(const IntPoint& pt) +{ + if (m_curr_poly->size() == m_curr_poly->capacity()) + m_curr_poly->reserve(m_curr_poly->capacity() + buffLength); + m_curr_poly->push_back(pt); +} +//------------------------------------------------------------------------------ + +void DoSquare() +{ + IntPoint pt1 = IntPoint(Round(m_p[m_i][m_j].X + normals[m_k].X * m_delta), + Round(m_p[m_i][m_j].Y + normals[m_k].Y * m_delta)); + IntPoint pt2 = IntPoint(Round(m_p[m_i][m_j].X + normals[m_j].X * m_delta), + Round(m_p[m_i][m_j].Y + normals[m_j].Y * m_delta)); + if ((normals[m_k].X * normals[m_j].Y - normals[m_j].X * normals[m_k].Y) * m_delta >= 0) + { + double a1 = std::atan2(normals[m_k].Y, normals[m_k].X); + double a2 = std::atan2(-normals[m_j].Y, -normals[m_j].X); + a1 = std::fabs(a2 - a1); + if (a1 > pi) a1 = pi * 2 - a1; + double dx = std::tan((pi - a1) / 4) * std::fabs(m_delta); + pt1 = IntPoint((long64)(pt1.X -normals[m_k].Y * dx), (long64)(pt1.Y + normals[m_k].X * dx)); + AddPoint(pt1); + pt2 = IntPoint((long64)(pt2.X + normals[m_j].Y * dx), (long64)(pt2.Y -normals[m_j].X * dx)); + AddPoint(pt2); + } + else + { + AddPoint(pt1); + AddPoint(m_p[m_i][m_j]); + AddPoint(pt2); + } +} +//------------------------------------------------------------------------------ + +void DoMiter() +{ + if ((normals[m_k].X * normals[m_j].Y - normals[m_j].X * normals[m_k].Y) * m_delta >= 0) + { + double q = m_delta / m_r; + AddPoint(IntPoint(Round(m_p[m_i][m_j].X + (normals[m_k].X + normals[m_j].X) * q), + Round(m_p[m_i][m_j].Y + (normals[m_k].Y + normals[m_j].Y) * q))); + } + else + { + IntPoint pt1 = IntPoint(Round(m_p[m_i][m_j].X + normals[m_k].X * m_delta), + Round(m_p[m_i][m_j].Y + normals[m_k].Y * m_delta)); + IntPoint pt2 = IntPoint(Round(m_p[m_i][m_j].X + normals[m_j].X * m_delta), + Round(m_p[m_i][m_j].Y + normals[m_j].Y * m_delta)); + AddPoint(pt1); + AddPoint(m_p[m_i][m_j]); + AddPoint(pt2); + } +} +//------------------------------------------------------------------------------ + +void DoRound(double limit) +{ + IntPoint pt1 = IntPoint(Round(m_p[m_i][m_j].X + normals[m_k].X * m_delta), + Round(m_p[m_i][m_j].Y + normals[m_k].Y * m_delta)); + IntPoint pt2 = IntPoint(Round(m_p[m_i][m_j].X + normals[m_j].X * m_delta), + Round(m_p[m_i][m_j].Y + normals[m_j].Y * m_delta)); + AddPoint(pt1); + //round off reflex angles (ie > 180 deg) unless almost flat (ie < ~10deg). + if ((normals[m_k].X*normals[m_j].Y - normals[m_j].X*normals[m_k].Y) * m_delta >= 0) + { + if (normals[m_j].X * normals[m_k].X + normals[m_j].Y * normals[m_k].Y < 0.985) + { + double a1 = std::atan2(normals[m_k].Y, normals[m_k].X); + double a2 = std::atan2(normals[m_j].Y, normals[m_j].X); + if (m_delta > 0 && a2 < a1) a2 += pi *2; + else if (m_delta < 0 && a2 > a1) a2 -= pi *2; + Polygon arc = BuildArc(m_p[m_i][m_j], a1, a2, m_delta, limit); + for (Polygon::size_type m = 0; m < arc.size(); m++) + AddPoint(arc[m]); + } + } + else + AddPoint(m_p[m_i][m_j]); + AddPoint(pt2); +} +//-------------------------------------------------------------------------- + +}; //end PolyOffsetBuilder + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +bool UpdateBotPt(const IntPoint &pt, IntPoint &botPt) +{ + if (pt.Y > botPt.Y || (pt.Y == botPt.Y && pt.X < botPt.X)) + { + botPt = pt; + return true; + } + else return false; +} +//-------------------------------------------------------------------------- + +void OffsetPolygons(const Polygons &in_polys, Polygons &out_polys, + double delta, JoinType jointype, double limit, bool autoFix) +{ + if (!autoFix && &in_polys != &out_polys) + { + OffsetBuilder(in_polys, out_polys, true, delta, jointype, etClosed, limit); + return; + } + + Polygons inPolys = Polygons(in_polys); + out_polys.clear(); + + //ChecksInput - fixes polygon orientation if necessary and removes + //duplicate vertices. Can be set false when you're sure that polygon + //orientation is correct and that there are no duplicate vertices. + if (autoFix) + { + size_t polyCount = inPolys.size(), botPoly = 0; + while (botPoly < polyCount && inPolys[botPoly].empty()) botPoly++; + if (botPoly == polyCount) return; + + //botPt: used to find the lowermost (in inverted Y-axis) & leftmost point + //This point (on m_p[botPoly]) must be on an outer polygon ring and if + //its orientation is false (counterclockwise) then assume all polygons + //need reversing ... + IntPoint botPt = inPolys[botPoly][0]; + for (size_t i = botPoly; i < polyCount; ++i) + { + if (inPolys[i].size() < 3) { inPolys[i].clear(); continue; } + if (UpdateBotPt(inPolys[i][0], botPt)) botPoly = i; + Polygon::iterator it = inPolys[i].begin() +1; + while (it != inPolys[i].end()) + { + if (PointsEqual(*it, *(it -1))) + it = inPolys[i].erase(it); + else + { + if (UpdateBotPt(*it, botPt)) botPoly = i; + ++it; + } + } + } + if (!Orientation(inPolys[botPoly])) + ReversePolygons(inPolys); + } + OffsetBuilder(inPolys, out_polys, true, delta, jointype, etClosed, limit); +} +//------------------------------------------------------------------------------ + +void OffsetPolyLines(const Polygons &in_lines, Polygons &out_lines, + double delta, JoinType jointype, EndType endtype, + double limit, bool autoFix) +{ + if (!autoFix && endtype != etClosed && &in_lines != &out_lines) + { + OffsetBuilder(in_lines, out_lines, false, delta, jointype, endtype, limit); + return; + } + + Polygons inLines = Polygons(in_lines); + if (autoFix) + for (size_t i = 0; i < inLines.size(); ++i) + { + if (inLines[i].size() < 2) { inLines[i].clear(); continue; } + Polygon::iterator it = inLines[i].begin() +1; + while (it != inLines[i].end()) + { + if (PointsEqual(*it, *(it -1))) + it = inLines[i].erase(it); + else + ++it; + } + } + + if (endtype == etClosed) + { + size_t sz = inLines.size(); + inLines.resize(sz * 2); + for (size_t i = 0; i < sz; ++i) + { + inLines[sz+i] = inLines[i]; + ReversePolygon(inLines[sz+i]); + } + OffsetBuilder(inLines, out_lines, true, delta, jointype, endtype, limit); + } + else + OffsetBuilder(inLines, out_lines, false, delta, jointype, endtype, limit); +} +//------------------------------------------------------------------------------ + +void SimplifyPolygon(const Polygon &in_poly, Polygons &out_polys, PolyFillType fillType) +{ + Clipper c; + c.ForceSimple(true); + c.AddPolygon(in_poly, ptSubject); + c.Execute(ctUnion, out_polys, fillType, fillType); +} +//------------------------------------------------------------------------------ + +void SimplifyPolygons(const Polygons &in_polys, Polygons &out_polys, PolyFillType fillType) +{ + Clipper c; + c.ForceSimple(true); + c.AddPolygons(in_polys, ptSubject); + c.Execute(ctUnion, out_polys, fillType, fillType); +} +//------------------------------------------------------------------------------ + +void SimplifyPolygons(Polygons &polys, PolyFillType fillType) +{ + SimplifyPolygons(polys, polys, fillType); +} +//------------------------------------------------------------------------------ + +inline double DistanceSqrd(const IntPoint& pt1, const IntPoint& pt2) +{ + double dx = ((double)pt1.X - pt2.X); + double dy = ((double)pt1.Y - pt2.Y); + return (dx*dx + dy*dy); +} +//------------------------------------------------------------------------------ + +DoublePoint ClosestPointOnLine(const IntPoint& pt, const IntPoint& linePt1, const IntPoint& linePt2) +{ + double dx = ((double)linePt2.X - linePt1.X); + double dy = ((double)linePt2.Y - linePt1.Y); + if (dx == 0 && dy == 0) + return DoublePoint((double)linePt1.X, (double)linePt1.Y); + double q = ((pt.X-linePt1.X)*dx + (pt.Y-linePt1.Y)*dy) / (dx*dx + dy*dy); + return DoublePoint( + (1-q)*linePt1.X + q*linePt2.X, + (1-q)*linePt1.Y + q*linePt2.Y); +} +//------------------------------------------------------------------------------ + +bool SlopesNearColinear(const IntPoint& pt1, + const IntPoint& pt2, const IntPoint& pt3, double distSqrd) +{ + if (DistanceSqrd(pt1, pt2) > DistanceSqrd(pt1, pt3)) return false; + DoublePoint cpol = ClosestPointOnLine(pt2, pt1, pt3); + double dx = pt2.X - cpol.X; + double dy = pt2.Y - cpol.Y; + return (dx*dx + dy*dy) < distSqrd; +} +//------------------------------------------------------------------------------ + +bool PointsAreClose(IntPoint pt1, IntPoint pt2, double distSqrd) +{ + double dx = (double)pt1.X - pt2.X; + double dy = (double)pt1.Y - pt2.Y; + return ((dx * dx) + (dy * dy) <= distSqrd); +} +//------------------------------------------------------------------------------ + +void CleanPolygon(const Polygon& in_poly, Polygon& out_poly, double distance) +{ + //distance = proximity in units/pixels below which vertices + //will be stripped. Default ~= sqrt(2). + int highI = in_poly.size() -1; + double distSqrd = distance * distance; + while (highI > 0 && PointsAreClose(in_poly[highI], in_poly[0], distSqrd)) highI--; + if (highI < 2) { out_poly.clear(); return; } + + if (&in_poly != &out_poly) + out_poly.resize(highI + 1); + + IntPoint pt = in_poly[highI]; + int i = 0, k = 0; + for (;;) + { + while (i < highI && PointsAreClose(pt, in_poly[i+1], distSqrd)) i+=2; + int i2 = i; + while (i < highI && (PointsAreClose(in_poly[i], in_poly[i+1], distSqrd) || + SlopesNearColinear(pt, in_poly[i], in_poly[i+1], distSqrd))) i++; + if (i >= highI) break; + else if (i != i2) continue; + pt = in_poly[i++]; + out_poly[k++] = pt; + } + if (i <= highI) out_poly[k++] = in_poly[i]; + if (k > 2 && SlopesNearColinear(out_poly[k -2], out_poly[k -1], out_poly[0], distSqrd)) k--; + if (k < 3) out_poly.clear(); + else if (k <= highI) out_poly.resize(k); +} +//------------------------------------------------------------------------------ + +void CleanPolygons(const Polygons& in_polys, Polygons& out_polys, double distance) +{ + for (Polygons::size_type i = 0; i < in_polys.size(); ++i) + CleanPolygon(in_polys[i], out_polys[i], distance); +} +//------------------------------------------------------------------------------ + +void AddPolyNodeToPolygons(const PolyNode& polynode, Polygons& polygons) +{ + if (!polynode.Contour.empty()) + polygons.push_back(polynode.Contour); + for (int i = 0; i < polynode.ChildCount(); ++i) + AddPolyNodeToPolygons(*polynode.Childs[i], polygons); +} +//------------------------------------------------------------------------------ + +void PolyTreeToPolygons(const PolyTree& polytree, Polygons& polygons) +{ + polygons.resize(0); + polygons.reserve(polytree.Total()); + AddPolyNodeToPolygons(polytree, polygons); +} +//------------------------------------------------------------------------------ + +std::ostream& operator <<(std::ostream &s, IntPoint& p) +{ + s << p.X << ' ' << p.Y << "\n"; + return s; +} +//------------------------------------------------------------------------------ + +std::ostream& operator <<(std::ostream &s, Polygon &p) +{ + for (Polygon::size_type i = 0; i < p.size(); i++) + s << p[i]; + s << "\n"; + return s; +} +//------------------------------------------------------------------------------ + +std::ostream& operator <<(std::ostream &s, Polygons &p) +{ + for (Polygons::size_type i = 0; i < p.size(); i++) + s << p[i]; + s << "\n"; + return s; +} +//------------------------------------------------------------------------------ + +} //ClipperLib namespace diff --git a/main/lib/clipper/clipper.hpp b/main/lib/clipper/clipper.hpp new file mode 100644 index 000000000..e313cfaa5 --- /dev/null +++ b/main/lib/clipper/clipper.hpp @@ -0,0 +1,349 @@ +/******************************************************************************* +* * +* Author : Angus Johnson * +* Version : 5.1.6 * +* Date : 23 May 2013 * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2013 * +* * +* License: * +* Use, modification & distribution is subject to Boost Software License Ver 1. * +* http://www.boost.org/LICENSE_1_0.txt * +* * +* Attributions: * +* The code in this library is an extension of Bala Vatti's clipping algorithm: * +* "A generic solution to polygon clipping" * +* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * +* http://portal.acm.org/citation.cfm?id=129906 * +* * +* Computer graphics and geometric modeling: implementation and algorithms * +* By Max K. Agoston * +* Springer; 1 edition (January 4, 2005) * +* http://books.google.com/books?q=vatti+clipping+agoston * +* * +* See also: * +* "Polygon Offsetting by Computing Winding Numbers" * +* Paper no. DETC2005-85513 pp. 565-575 * +* ASME 2005 International Design Engineering Technical Conferences * +* and Computers and Information in Engineering Conference (IDETC/CIE2005) * +* September 24-28, 2005 , Long Beach, California, USA * +* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * +* * +*******************************************************************************/ + +#ifndef clipper_hpp +#define clipper_hpp + +#include +#include +#include +#include +#include + +namespace ClipperLib { + +enum ClipType { ctIntersection, ctUnion, ctDifference, ctXor }; +enum PolyType { ptSubject, ptClip }; +//By far the most widely used winding rules for polygon filling are +//EvenOdd & NonZero (GDI, GDI+, XLib, OpenGL, Cairo, AGG, Quartz, SVG, Gr32) +//Others rules include Positive, Negative and ABS_GTR_EQ_TWO (only in OpenGL) +//see http://glprogramming.com/red/chapter11.html +enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative }; + +typedef signed long long long64; +typedef unsigned long long ulong64; + +struct IntPoint { +public: + long64 X; + long64 Y; + IntPoint(long64 x = 0, long64 y = 0): X(x), Y(y) {}; + friend std::ostream& operator <<(std::ostream &s, IntPoint &p); +}; + +typedef std::vector< IntPoint > Polygon; +typedef std::vector< Polygon > Polygons; + + +std::ostream& operator <<(std::ostream &s, Polygon &p); +std::ostream& operator <<(std::ostream &s, Polygons &p); + +class PolyNode; +typedef std::vector< PolyNode* > PolyNodes; + +class PolyNode +{ +public: + PolyNode(); + Polygon Contour; + PolyNodes Childs; + PolyNode* Parent; + PolyNode* GetNext() const; + bool IsHole() const; + int ChildCount() const; +private: + PolyNode* GetNextSiblingUp() const; + unsigned Index; //node index in Parent.Childs + void AddChild(PolyNode& child); + friend class Clipper; //to access Index +}; + +class PolyTree: public PolyNode +{ +public: + ~PolyTree(){Clear();}; + PolyNode* GetFirst() const; + void Clear(); + int Total() const; +private: + PolyNodes AllNodes; + friend class Clipper; //to access AllNodes +}; + +enum JoinType { jtSquare, jtRound, jtMiter }; +enum EndType { etClosed, etButt, etSquare, etRound}; + +bool Orientation(const Polygon &poly); +double Area(const Polygon &poly); + +void OffsetPolygons(const Polygons &in_polys, Polygons &out_polys, + double delta, JoinType jointype = jtSquare, double limit = 0, bool autoFix = true); + +void OffsetPolyLines(const Polygons &in_lines, Polygons &out_lines, + double delta, JoinType jointype = jtSquare, EndType endtype = etSquare, double limit = 0, bool autoFix = true); + +void SimplifyPolygon(const Polygon &in_poly, Polygons &out_polys, PolyFillType fillType = pftEvenOdd); +void SimplifyPolygons(const Polygons &in_polys, Polygons &out_polys, PolyFillType fillType = pftEvenOdd); +void SimplifyPolygons(Polygons &polys, PolyFillType fillType = pftEvenOdd); + +void CleanPolygon(const Polygon& in_poly, Polygon& out_poly, double distance = 1.415); +void CleanPolygons(const Polygons& in_polys, Polygons& out_polys, double distance = 1.415); + +void PolyTreeToPolygons(const PolyTree& polytree, Polygons& polygons); + +void ReversePolygon(Polygon& p); +void ReversePolygons(Polygons& p); + +//used internally ... +enum EdgeSide { esLeft = 1, esRight = 2}; +enum IntersectProtects { ipNone = 0, ipLeft = 1, ipRight = 2, ipBoth = 3 }; +//inline IntersectProtects operator|(IntersectProtects a, IntersectProtects b) +//{return static_cast(static_cast(a) | static_cast(b));} + +struct TEdge { + long64 xbot; + long64 ybot; + long64 xcurr; + long64 ycurr; + long64 xtop; + long64 ytop; + double dx; + long64 deltaX; + long64 deltaY; + PolyType polyType; + EdgeSide side; + int windDelta; //1 or -1 depending on winding direction + int windCnt; + int windCnt2; //winding count of the opposite polytype + int outIdx; + TEdge *next; + TEdge *prev; + TEdge *nextInLML; + TEdge *nextInAEL; + TEdge *prevInAEL; + TEdge *nextInSEL; + TEdge *prevInSEL; +}; + +struct IntersectNode { + TEdge *edge1; + TEdge *edge2; + IntPoint pt; + IntersectNode *next; +}; + +struct LocalMinima { + long64 Y; + TEdge *leftBound; + TEdge *rightBound; + LocalMinima *next; +}; + +struct Scanbeam { + long64 Y; + Scanbeam *next; +}; + +struct OutPt; //forward declaration + +struct OutRec { + int idx; + bool isHole; + OutRec *FirstLeft; //see comments in clipper.pas + PolyNode *polyNode; + OutPt *pts; + OutPt *bottomPt; +}; + +struct OutPt { + int idx; + IntPoint pt; + OutPt *next; + OutPt *prev; +}; + +struct JoinRec { + IntPoint pt1a; + IntPoint pt1b; + int poly1Idx; + IntPoint pt2a; + IntPoint pt2b; + int poly2Idx; +}; + +struct HorzJoinRec { + TEdge *edge; + int savedIdx; +}; + +struct IntRect { long64 left; long64 top; long64 right; long64 bottom; }; + +typedef std::vector < OutRec* > PolyOutList; +typedef std::vector < TEdge* > EdgeList; +typedef std::vector < JoinRec* > JoinList; +typedef std::vector < HorzJoinRec* > HorzJoinList; + +//ClipperBase is the ancestor to the Clipper class. It should not be +//instantiated directly. This class simply abstracts the conversion of sets of +//polygon coordinates into edge objects that are stored in a LocalMinima list. +class ClipperBase +{ +public: + ClipperBase(); + virtual ~ClipperBase(); + bool AddPolygon(const Polygon &pg, PolyType polyType); + bool AddPolygons( const Polygons &ppg, PolyType polyType); + virtual void Clear(); + IntRect GetBounds(); +protected: + void DisposeLocalMinimaList(); + TEdge* AddBoundsToLML(TEdge *e); + void PopLocalMinima(); + virtual void Reset(); + void InsertLocalMinima(LocalMinima *newLm); + LocalMinima *m_CurrentLM; + LocalMinima *m_MinimaList; + bool m_UseFullRange; + EdgeList m_edges; +}; + +class Clipper : public virtual ClipperBase +{ +public: + Clipper(); + ~Clipper(); + bool Execute(ClipType clipType, + Polygons &solution, + PolyFillType subjFillType = pftEvenOdd, + PolyFillType clipFillType = pftEvenOdd); + bool Execute(ClipType clipType, + PolyTree &polytree, + PolyFillType subjFillType = pftEvenOdd, + PolyFillType clipFillType = pftEvenOdd); + void Clear(); + bool ReverseSolution() {return m_ReverseOutput;}; + void ReverseSolution(bool value) {m_ReverseOutput = value;}; + bool ForceSimple() {return m_ForceSimple;}; + void ForceSimple(bool value) {m_ForceSimple = value;}; +protected: + void Reset(); + virtual bool ExecuteInternal(); +private: + PolyOutList m_PolyOuts; + JoinList m_Joins; + HorzJoinList m_HorizJoins; + ClipType m_ClipType; + Scanbeam *m_Scanbeam; + TEdge *m_ActiveEdges; + TEdge *m_SortedEdges; + IntersectNode *m_IntersectNodes; + bool m_ExecuteLocked; + PolyFillType m_ClipFillType; + PolyFillType m_SubjFillType; + bool m_ReverseOutput; + bool m_UsingPolyTree; + bool m_ForceSimple; + void DisposeScanbeamList(); + void SetWindingCount(TEdge& edge); + bool IsEvenOddFillType(const TEdge& edge) const; + bool IsEvenOddAltFillType(const TEdge& edge) const; + void InsertScanbeam(const long64 Y); + long64 PopScanbeam(); + void InsertLocalMinimaIntoAEL(const long64 botY); + void InsertEdgeIntoAEL(TEdge *edge); + void AddEdgeToSEL(TEdge *edge); + void CopyAELToSEL(); + void DeleteFromSEL(TEdge *e); + void DeleteFromAEL(TEdge *e); + void UpdateEdgeIntoAEL(TEdge *&e); + void SwapPositionsInSEL(TEdge *edge1, TEdge *edge2); + bool IsContributing(const TEdge& edge) const; + bool IsTopHorz(const long64 XPos); + void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2); + void DoMaxima(TEdge *e, long64 topY); + void ProcessHorizontals(); + void ProcessHorizontal(TEdge *horzEdge); + void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); + void AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); + OutRec* GetOutRec(int idx); + void AppendPolygon(TEdge *e1, TEdge *e2); + void IntersectEdges(TEdge *e1, TEdge *e2, + const IntPoint &pt, const IntersectProtects protects); + OutRec* CreateOutRec(); + void AddOutPt(TEdge *e, const IntPoint &pt); + void DisposeAllPolyPts(); + void DisposeOutRec(PolyOutList::size_type index); + bool ProcessIntersections(const long64 botY, const long64 topY); + void InsertIntersectNode(TEdge *e1, TEdge *e2, const IntPoint &pt); + void BuildIntersectList(const long64 botY, const long64 topY); + void ProcessIntersectList(); + void ProcessEdgesAtTopOfScanbeam(const long64 topY); + void BuildResult(Polygons& polys); + void BuildResult2(PolyTree& polytree); + void SetHoleState(TEdge *e, OutRec *outrec); + void DisposeIntersectNodes(); + bool FixupIntersectionOrder(); + void FixupOutPolygon(OutRec &outrec); + bool IsHole(TEdge *e); + void FixHoleLinkage(OutRec &outrec); + void AddJoin(TEdge *e1, TEdge *e2, int e1OutIdx = -1, int e2OutIdx = -1); + void ClearJoins(); + void AddHorzJoin(TEdge *e, int idx); + void ClearHorzJoins(); + bool JoinPoints(const JoinRec *j, OutPt *&p1, OutPt *&p2); + void FixupJoinRecs(JoinRec *j, OutPt *pt, unsigned startIdx); + void JoinCommonEdges(); + void DoSimplePolygons(); + void FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec); + void FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec); +}; + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +class clipperException : public std::exception +{ + public: + clipperException(const char* description): m_descr(description) {} + virtual ~clipperException() throw() {} + virtual const char* what() const throw() {return m_descr.c_str();} + private: + std::string m_descr; +}; +//------------------------------------------------------------------------------ + +} //ClipperLib namespace + +#endif //clipper_hpp + + diff --git a/main/lib/clipper/gb.clipper.component b/main/lib/clipper/gb.clipper.component new file mode 100644 index 000000000..ac550d9f3 --- /dev/null +++ b/main/lib/clipper/gb.clipper.component @@ -0,0 +1,5 @@ +[Component] +Key=gb.image +Name=Image buffer support +Author=Benoît Minisini +State=Experimental diff --git a/main/lib/clipper/gb.geom.h b/main/lib/clipper/gb.geom.h new file mode 120000 index 000000000..2ae286cfb --- /dev/null +++ b/main/lib/clipper/gb.geom.h @@ -0,0 +1 @@ +../geom/gb.geom.h \ No newline at end of file diff --git a/main/lib/clipper/main.cpp b/main/lib/clipper/main.cpp new file mode 100644 index 000000000..c151c5e19 --- /dev/null +++ b/main/lib/clipper/main.cpp @@ -0,0 +1,61 @@ +/*************************************************************************** + + main.cpp + + (c) 2000-2013 Benoît Minisini + + 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, 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., 51 Franklin Street, Fifth Floor, Boston, + MA 02110-1301, USA. + +***************************************************************************/ + +#define __MAIN_CPP + +#include +#include +#include +#include +#include + +#include "gb_common.h" +#include "c_clipper.h" +#include "main.h" + +extern "C" { + +GB_INTERFACE GB EXPORT; +GEOM_INTERFACE GEOM EXPORT; + +GB_DESC *GB_CLASSES [] EXPORT = +{ + ClipperDesc, + NULL +}; + +const char *GB_INCLUDE EXPORT = "gb.geom"; + +int EXPORT GB_INIT(void) +{ + GB.Component.Load("gb.geom"); + GB.GetInterface("gb.geom", GEOM_INTERFACE_VERSION, &GEOM); + return 0; +} + + +void EXPORT GB_EXIT() +{ +} + +} diff --git a/main/lib/clipper/main.h b/main/lib/clipper/main.h new file mode 100644 index 000000000..d1cdf0818 --- /dev/null +++ b/main/lib/clipper/main.h @@ -0,0 +1,36 @@ +/*************************************************************************** + + main.h + + (c) 2000-2013 Benoît Minisini + + 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, 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., 51 Franklin Street, Fifth Floor, Boston, + MA 02110-1301, USA. + +***************************************************************************/ + +#ifndef __MAIN_H +#define __MAIN_H + +#include "gambas.h" +#include "gb.geom.h" +#include "gb_common.h" + +#ifndef __MAIN_C +extern "C" GB_INTERFACE GB; +extern "C" GEOM_INTERFACE GEOM; +#endif + +#endif /* __MAIN_H */