diff --git a/open-anpr-core/pom.xml b/open-anpr-core/pom.xml
index 8a6c1bb..11b5318 100644
--- a/open-anpr-core/pom.xml
+++ b/open-anpr-core/pom.xml
@@ -11,5 +11,30 @@
open-anpr-core
+
+
+ org.openpnp
+ opencv
+
+
+ com.microsoft.onnxruntime
+ onnxruntime
+
+
+
+ com.alibaba
+ fastjson
+
+
+
+ org.apache.commons
+ commons-collections4
+
+
+
+ org.apache.commons
+ commons-math3
+
+
\ No newline at end of file
diff --git a/open-anpr-core/src/main/java/com/visual/open/anpr/core/base/BaseOnnxInfer.java b/open-anpr-core/src/main/java/com/visual/open/anpr/core/base/BaseOnnxInfer.java
new file mode 100755
index 0000000..d5bdde0
--- /dev/null
+++ b/open-anpr-core/src/main/java/com/visual/open/anpr/core/base/BaseOnnxInfer.java
@@ -0,0 +1,110 @@
+package com.visual.open.anpr.core.base;
+
+import ai.onnxruntime.OrtEnvironment;
+import ai.onnxruntime.OrtLoggingLevel;
+import ai.onnxruntime.OrtSession;
+
+public abstract class BaseOnnxInfer extends OpenCVLoader{
+
+ private OrtEnvironment env;
+ private String[] inputNames;
+ private OrtSession[] sessions;
+
+ /**
+ * 构造函数
+ * @param modelPath
+ * @param threads
+ */
+ public BaseOnnxInfer(String modelPath, int threads){
+ try {
+ this.env = OrtEnvironment.getEnvironment();
+ OrtSession.SessionOptions opts = new OrtSession.SessionOptions();
+ opts.setInterOpNumThreads(threads);
+ opts.setSessionLogLevel(OrtLoggingLevel.ORT_LOGGING_LEVEL_ERROR);
+ opts.setOptimizationLevel(OrtSession.SessionOptions.OptLevel.BASIC_OPT);
+ this.sessions = new OrtSession[]{env.createSession(modelPath, opts)};
+ this.inputNames = new String[]{this.sessions[0].getInputNames().iterator().next()};
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * 构造函数
+ * @param modelPaths
+ * @param threads
+ */
+ public BaseOnnxInfer(String[] modelPaths, int threads){
+ try {
+ OrtEnvironment env = OrtEnvironment.getEnvironment();
+ OrtSession.SessionOptions opts = new OrtSession.SessionOptions();
+ opts.setInterOpNumThreads(threads);
+ opts.setSessionLogLevel(OrtLoggingLevel.ORT_LOGGING_LEVEL_ERROR);
+ opts.setOptimizationLevel(OrtSession.SessionOptions.OptLevel.BASIC_OPT);
+ this.inputNames = new String[modelPaths.length];
+ this.sessions = new OrtSession[modelPaths.length];
+ for(int i=0; i< modelPaths.length; i++){
+ OrtSession session = env.createSession(modelPaths[i], opts);
+ this.sessions[i] = session;
+ this.inputNames[i] = session.getInputNames().iterator().next();
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * 获取环境信息
+ * @return
+ */
+ public OrtEnvironment getEnv() {
+ return env;
+ }
+
+ /**
+ * 获取输入端的名称
+ * @return
+ */
+ public String getInputName() {
+ return inputNames[0];
+ }
+
+ /**
+ * 获取session
+ * @return
+ */
+ public OrtSession getSession() {
+ return sessions[0];
+ }
+
+ /**
+ * 获取输入端的名称
+ * @return
+ */
+ public String[] getInputNames() {
+ return inputNames;
+ }
+
+ /**
+ * 获取session
+ * @return
+ */
+ public OrtSession[] getSessions() {
+ return sessions;
+ }
+
+ /**
+ * 关闭服务
+ */
+ public void close(){
+ try {
+ if(sessions != null){
+ for(OrtSession session : sessions){
+ session.close();
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/open-anpr-core/src/main/java/com/visual/open/anpr/core/base/OpenCVLoader.java b/open-anpr-core/src/main/java/com/visual/open/anpr/core/base/OpenCVLoader.java
new file mode 100755
index 0000000..eaf1a95
--- /dev/null
+++ b/open-anpr-core/src/main/java/com/visual/open/anpr/core/base/OpenCVLoader.java
@@ -0,0 +1,8 @@
+package com.visual.open.anpr.core.base;
+
+public abstract class OpenCVLoader {
+
+ //静态加载动态链接库
+ static{ nu.pattern.OpenCV.loadShared(); }
+
+}
diff --git a/open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/Clipper.java b/open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/Clipper.java
new file mode 100644
index 0000000..9a6b9d6
--- /dev/null
+++ b/open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/Clipper.java
@@ -0,0 +1,54 @@
+package com.visual.open.anpr.core.clipper;
+
+import com.visual.open.anpr.core.clipper.Point.LongPoint;
+
+public interface Clipper {
+ enum ClipType {
+ INTERSECTION, UNION, DIFFERENCE, XOR
+ }
+
+ enum Direction {
+ RIGHT_TO_LEFT, LEFT_TO_RIGHT
+ }
+
+ enum EndType {
+ CLOSED_POLYGON, CLOSED_LINE, OPEN_BUTT, OPEN_SQUARE, OPEN_ROUND
+ }
+
+ enum JoinType {
+ SQUARE, ROUND, MITER
+ }
+
+ enum PolyFillType {
+ EVEN_ODD, NON_ZERO, POSITIVE, NEGATIVE
+ }
+
+ enum PolyType {
+ SUBJECT, CLIP
+ }
+
+ interface ZFillCallback {
+ void zFill(LongPoint bot1, LongPoint top1, LongPoint bot2, LongPoint top2, LongPoint pt);
+ }
+
+ //InitOptions that can be passed to the constructor ...
+ int REVERSE_SOLUTION = 1;
+
+ int STRICTLY_SIMPLE = 2;
+
+ int PRESERVE_COLINEAR = 4;
+
+ boolean addPath(Path pg, PolyType polyType, boolean Closed);
+
+ boolean addPaths(Paths ppg, PolyType polyType, boolean closed);
+
+ void clear();
+
+ boolean execute(ClipType clipType, Paths solution);
+
+ boolean execute(ClipType clipType, Paths solution, PolyFillType subjFillType, PolyFillType clipFillType);
+
+ boolean execute(ClipType clipType, PolyTree polytree);
+
+ boolean execute(ClipType clipType, PolyTree polytree, PolyFillType subjFillType, PolyFillType clipFillType);
+}
diff --git a/open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/ClipperBase.java b/open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/ClipperBase.java
new file mode 100644
index 0000000..5296a2c
--- /dev/null
+++ b/open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/ClipperBase.java
@@ -0,0 +1,691 @@
+package com.visual.open.anpr.core.clipper;
+
+import com.visual.open.anpr.core.clipper.Path.OutRec;
+import com.visual.open.anpr.core.clipper.Point.LongPoint;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Logger;
+
+public abstract class ClipperBase implements Clipper {
+ protected class LocalMinima {
+ long y;
+ Edge leftBound;
+ Edge rightBound;
+ LocalMinima next;
+ }
+
+ protected class Scanbeam {
+ long y;
+ Scanbeam next;
+ }
+
+ protected class Maxima {
+ long x;
+ Maxima next;
+ Maxima prev;
+ }
+
+ private static void initEdge(Edge e, Edge eNext, Edge ePrev, LongPoint pt ) {
+ e.next = eNext;
+ e.prev = ePrev;
+ e.setCurrent( new LongPoint( pt ) );
+ e.outIdx = Edge.UNASSIGNED;
+ }
+
+ private static void initEdge2(Edge e, PolyType polyType ) {
+ if (e.getCurrent().getY() >= e.next.getCurrent().getY()) {
+ e.setBot( new LongPoint( e.getCurrent() ) );
+ e.setTop( new LongPoint( e.next.getCurrent() ) );
+ }
+ else {
+ e.setTop( new LongPoint( e.getCurrent() ) );
+ e.setBot( new LongPoint( e.next.getCurrent() ) );
+ }
+ e.updateDeltaX();
+ e.polyTyp = polyType;
+ }
+
+ private static void rangeTest( LongPoint Pt ) {
+
+ if (Pt.getX() > LOW_RANGE || Pt.getY() > LOW_RANGE || -Pt.getX() > LOW_RANGE || -Pt.getY() > LOW_RANGE) {
+ if (Pt.getX() > HI_RANGE || Pt.getY() > HI_RANGE || -Pt.getX() > HI_RANGE || -Pt.getY() > HI_RANGE) {
+ throw new IllegalStateException( "Coordinate outside allowed range" );
+ }
+ }
+ }
+
+ private static Edge removeEdge(Edge e ) {
+ //removes e from double_linked_list (but without removing from memory)
+ e.prev.next = e.next;
+ e.next.prev = e.prev;
+ final Edge result = e.next;
+ e.prev = null; //flag as removed (see ClipperBase.Clear)
+ return result;
+ }
+
+ private final static long LOW_RANGE = 0x3FFFFFFF;
+
+ private final static long HI_RANGE = 0x3FFFFFFFFFFFFFFFL;
+
+ protected LocalMinima minimaList;
+
+ protected LocalMinima currentLM;
+
+ protected Scanbeam scanbeam;
+
+ protected final List polyOuts = new ArrayList<>();
+
+ protected Edge activeEdges;
+
+ protected boolean hasOpenPaths;
+
+ protected final boolean preserveCollinear;
+
+ private final static Logger LOGGER = Logger.getLogger( Clipper.class.getName() );
+
+ protected ClipperBase( boolean preserveCollinear ) //constructor (nb: no external instantiation)
+ {
+ this.preserveCollinear = preserveCollinear;
+ minimaList = null;
+ currentLM = null;
+ hasOpenPaths = false;
+ }
+
+ @Override
+ public boolean addPath(Path pg, PolyType polyType, boolean Closed ) {
+
+ if (!Closed && polyType == PolyType.CLIP) {
+ throw new IllegalStateException( "AddPath: Open paths must be subject." );
+ }
+
+ int highI = pg.size() - 1;
+ if (Closed) {
+ while (highI > 0 && pg.get( highI ).equals( pg.get( 0 ) )) {
+ --highI;
+ }
+ }
+ while (highI > 0 && pg.get( highI ).equals( pg.get( highI - 1 ) )) {
+ --highI;
+ }
+ if (Closed && highI < 2 || !Closed && highI < 1) {
+ return false;
+ }
+
+ //create a new edge array ...
+ final List edges = new ArrayList<>( highI + 1 );
+ for (int i = 0; i <= highI; i++) {
+ edges.add( new Edge() );
+ }
+
+ boolean IsFlat = true;
+
+ //1. Basic (first) edge initialization ...
+ edges.get( 1 ).setCurrent( new LongPoint( pg.get( 1 ) ) );
+ rangeTest( pg.get( 0 ) );
+ rangeTest( pg.get( highI ) );
+ initEdge( edges.get( 0 ), edges.get( 1 ), edges.get( highI ), pg.get( 0 ) );
+ initEdge( edges.get( highI ), edges.get( 0 ), edges.get( highI - 1 ), pg.get( highI ) );
+ for (int i = highI - 1; i >= 1; --i) {
+ rangeTest( pg.get( i ) );
+ initEdge( edges.get( i ), edges.get( i + 1 ), edges.get( i - 1 ), pg.get( i ) );
+ }
+ Edge eStart = edges.get( 0 );
+
+ //2. Remove duplicate vertices, and (when closed) collinear edges ...
+ Edge e = eStart, eLoopStop = eStart;
+ for (;;) {
+ //nb: allows matching start and end points when not Closed ...
+ if (e.getCurrent().equals( e.next.getCurrent() ) && (Closed || !e.next.equals( eStart ))) {
+ if (e == e.next) {
+ break;
+ }
+ if (e == eStart) {
+ eStart = e.next;
+ }
+ e = removeEdge( e );
+ eLoopStop = e;
+ continue;
+ }
+ if (e.prev == e.next) {
+ break; //only two vertices
+ }
+ else if (Closed && Point.slopesEqual( e.prev.getCurrent(), e.getCurrent(), e.next.getCurrent() )
+ && (!isPreserveCollinear() || !Point.isPt2BetweenPt1AndPt3( e.prev.getCurrent(), e.getCurrent(), e.next.getCurrent() ))) {
+ //Collinear edges are allowed for open paths but in closed paths
+ //the default is to merge adjacent collinear edges into a single edge.
+ //However, if the PreserveCollinear property is enabled, only overlapping
+ //collinear edges (ie spikes) will be removed from closed paths.
+ if (e == eStart) {
+ eStart = e.next;
+ }
+ e = removeEdge( e );
+ e = e.prev;
+ eLoopStop = e;
+ continue;
+ }
+ e = e.next;
+ if (e == eLoopStop || !Closed && e.next == eStart) {
+ break;
+ }
+ }
+
+ if (!Closed && e == e.next || Closed && e.prev == e.next) {
+ return false;
+ }
+
+ if (!Closed) {
+ hasOpenPaths = true;
+ eStart.prev.outIdx = Edge.SKIP;
+ }
+
+ //3. Do second stage of edge initialization ...
+ e = eStart;
+ do {
+ initEdge2( e, polyType );
+ e = e.next;
+ if (IsFlat && e.getCurrent().getY() != eStart.getCurrent().getY()) {
+ IsFlat = false;
+ }
+ }
+ while (e != eStart);
+
+ //4. Finally, add edge bounds to LocalMinima list ...
+
+ //Totally flat paths must be handled differently when adding them
+ //to LocalMinima list to avoid endless loops etc ...
+ if (IsFlat) {
+ if (Closed) {
+ return false;
+ }
+ e.prev.outIdx = Edge.SKIP;
+ final LocalMinima locMin = new LocalMinima();
+ locMin.next = null;
+ locMin.y = e.getBot().getY();
+ locMin.leftBound = null;
+ locMin.rightBound = e;
+ locMin.rightBound.side = Edge.Side.RIGHT;
+ locMin.rightBound.windDelta = 0;
+ for ( ; ; ) {
+ if (e.getBot().getX() != e.prev.getTop().getX()) {
+ e.reverseHorizontal();
+ }
+ if (e.next.outIdx == Edge.SKIP) break;
+ e.nextInLML = e.next;
+ e = e.next;
+ }
+ insertLocalMinima( locMin );
+ return true;
+ }
+
+ boolean leftBoundIsForward;
+ Edge EMin = null;
+
+ //workaround to avoid an endless loop in the while loop below when
+ //open paths have matching start and end points ...
+ if (e.prev.getBot().equals( e.prev.getTop() )) {
+ e = e.next;
+ }
+
+ for (;;) {
+ e = e.findNextLocMin();
+ if (e == EMin) {
+ break;
+ }
+ else if (EMin == null) {
+ EMin = e;
+ }
+
+ //E and E.Prev now share a local minima (left aligned if horizontal).
+ //Compare their slopes to find which starts which bound ...
+ final LocalMinima locMin = new LocalMinima();
+ locMin.next = null;
+ locMin.y = e.getBot().getY();
+ if (e.deltaX < e.prev.deltaX) {
+ locMin.leftBound = e.prev;
+ locMin.rightBound = e;
+ leftBoundIsForward = false; //Q.nextInLML = Q.prev
+ }
+ else {
+ locMin.leftBound = e;
+ locMin.rightBound = e.prev;
+ leftBoundIsForward = true; //Q.nextInLML = Q.next
+ }
+ locMin.leftBound.side = Edge.Side.LEFT;
+ locMin.rightBound.side = Edge.Side.RIGHT;
+
+ if (!Closed) {
+ locMin.leftBound.windDelta = 0;
+ }
+ else if (locMin.leftBound.next == locMin.rightBound) {
+ locMin.leftBound.windDelta = -1;
+ }
+ else {
+ locMin.leftBound.windDelta = 1;
+ }
+ locMin.rightBound.windDelta = -locMin.leftBound.windDelta;
+
+ e = processBound( locMin.leftBound, leftBoundIsForward );
+ if (e.outIdx == Edge.SKIP) {
+ e = processBound( e, leftBoundIsForward );
+ }
+
+ Edge E2 = processBound( locMin.rightBound, !leftBoundIsForward );
+ if (E2.outIdx == Edge.SKIP) {
+ E2 = processBound( E2, !leftBoundIsForward );
+ }
+
+ if (locMin.leftBound.outIdx == Edge.SKIP) {
+ locMin.leftBound = null;
+ }
+ else if (locMin.rightBound.outIdx == Edge.SKIP) {
+ locMin.rightBound = null;
+ }
+ insertLocalMinima( locMin );
+ if (!leftBoundIsForward) {
+ e = E2;
+ }
+ }
+ return true;
+
+ }
+
+ @Override
+ public boolean addPaths(Paths paths, PolyType polyType, boolean closed ) {
+ boolean result = false;
+ for (Path path : paths) {
+ if (addPath(path, polyType, closed)) {
+ result = true;
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public void clear() {
+ disposeLocalMinimaList();
+ hasOpenPaths = false;
+ }
+
+ private void disposeLocalMinimaList() {
+ while (minimaList != null) {
+ final LocalMinima tmpLm = minimaList.next;
+ minimaList = null;
+ minimaList = tmpLm;
+ }
+ currentLM = null;
+ }
+
+ private void insertLocalMinima( LocalMinima newLm ) {
+ if (minimaList == null) {
+ minimaList = newLm;
+ }
+ else if (newLm.y >= minimaList.y) {
+ newLm.next = minimaList;
+ minimaList = newLm;
+ }
+ else {
+ LocalMinima tmpLm = minimaList;
+ while (tmpLm.next != null && newLm.y < tmpLm.next.y) {
+ tmpLm = tmpLm.next;
+ }
+ newLm.next = tmpLm.next;
+ tmpLm.next = newLm;
+ }
+ }
+ private boolean isPreserveCollinear() {
+ return preserveCollinear;
+ }
+
+ protected boolean popLocalMinima( long y, LocalMinima[] current ) {
+ LOGGER.entering( ClipperBase.class.getName(), "popLocalMinima" );
+ current[0] = currentLM;
+ if (currentLM != null && currentLM.y == y) {
+ currentLM = currentLM.next;
+ return true;
+ }
+ return false;
+ }
+
+ private Edge processBound(Edge e, boolean LeftBoundIsForward ) {
+ Edge EStart, result = e;
+ Edge Horz;
+
+ if (result.outIdx == Edge.SKIP) {
+ //check if there are edges beyond the skip edge in the bound and if so
+ //create another LocMin and calling ProcessBound once more ...
+ e = result;
+ if (LeftBoundIsForward) {
+ while (e.getTop().getY() == e.next.getBot().getY()) {
+ e = e.next;
+ }
+ while (e != result && e.deltaX == Edge.HORIZONTAL) {
+ e = e.prev;
+ }
+ }
+ else {
+ while (e.getTop().getY() == e.prev.getBot().getY()) {
+ e = e.prev;
+ }
+ while (e != result && e.deltaX == Edge.HORIZONTAL) {
+ e = e.next;
+ }
+ }
+ if (e == result) {
+ if (LeftBoundIsForward) {
+ result = e.next;
+ }
+ else {
+ result = e.prev;
+ }
+ }
+ else {
+ //there are more edges in the bound beyond result starting with E
+ if (LeftBoundIsForward) {
+ e = result.next;
+ }
+ else {
+ e = result.prev;
+ }
+ final LocalMinima locMin = new LocalMinima();
+ locMin.next = null;
+ locMin.y = e.getBot().getY();
+ locMin.leftBound = null;
+ locMin.rightBound = e;
+ e.windDelta = 0;
+ result = processBound( e, LeftBoundIsForward );
+ insertLocalMinima( locMin );
+ }
+ return result;
+ }
+
+ if (e.deltaX == Edge.HORIZONTAL) {
+ //We need to be careful with open paths because this may not be a
+ //true local minima (ie E may be following a skip edge).
+ //Also, consecutive horz. edges may start heading left before going right.
+ if (LeftBoundIsForward) {
+ EStart = e.prev;
+ }
+ else {
+ EStart = e.next;
+ }
+ if (EStart.deltaX == Edge.HORIZONTAL) //ie an adjoining horizontal skip edge
+ {
+ if (EStart.getBot().getX() != e.getBot().getX() && EStart.getTop().getX() != e.getBot().getX()) {
+ e.reverseHorizontal();
+ }
+ }
+ else if (EStart.getBot().getX() != e.getBot().getX()) {
+ e.reverseHorizontal();
+ }
+ }
+
+ EStart = e;
+ if (LeftBoundIsForward) {
+ while (result.getTop().getY() == result.next.getBot().getY() && result.next.outIdx != Edge.SKIP) {
+ result = result.next;
+ }
+ if (result.deltaX == Edge.HORIZONTAL && result.next.outIdx != Edge.SKIP) {
+ //nb: at the top of a bound, horizontals are added to the bound
+ //only when the preceding edge attaches to the horizontal's left vertex
+ //unless a Skip edge is encountered when that becomes the top divide
+ Horz = result;
+ while (Horz.prev.deltaX == Edge.HORIZONTAL) {
+ Horz = Horz.prev;
+ }
+ if (Horz.prev.getTop().getX() > result.next.getTop().getX()) {
+ result = Horz.prev;
+ }
+ }
+ while (e != result) {
+ e.nextInLML = e.next;
+ if (e.deltaX == Edge.HORIZONTAL && e != EStart && e.getBot().getX() != e.prev.getTop().getX()) {
+ e.reverseHorizontal();
+ }
+ e = e.next;
+ }
+ if (e.deltaX == Edge.HORIZONTAL && e != EStart && e.getBot().getX() != e.prev.getTop().getX()) {
+ e.reverseHorizontal();
+ }
+ result = result.next; //move to the edge just beyond current bound
+ }
+ else {
+ while (result.getTop().getY() == result.prev.getBot().getY() && result.prev.outIdx != Edge.SKIP) {
+ result = result.prev;
+ }
+ if (result.deltaX == Edge.HORIZONTAL && result.prev.outIdx != Edge.SKIP) {
+ Horz = result;
+ while (Horz.next.deltaX == Edge.HORIZONTAL) {
+ Horz = Horz.next;
+ }
+ if (Horz.next.getTop().getX() == result.prev.getTop().getX() ||
+ Horz.next.getTop().getX() > result.prev.getTop().getX()) {
+ result = Horz.next;
+ }
+ }
+
+ while (e != result) {
+ e.nextInLML = e.prev;
+ if (e.deltaX == Edge.HORIZONTAL && e != EStart && e.getBot().getX() != e.next.getTop().getX()) {
+ e.reverseHorizontal();
+ }
+ e = e.prev;
+ }
+ if (e.deltaX == Edge.HORIZONTAL && e != EStart && e.getBot().getX() != e.next.getTop().getX()) {
+ e.reverseHorizontal();
+ }
+ result = result.prev; //move to the edge just beyond current bound
+ }
+ return result;
+ }
+
+ protected void reset() {
+ currentLM = minimaList;
+ if (currentLM == null) {
+ return; //ie nothing to process
+ }
+
+ //reset all edges ...
+ scanbeam = null;
+ LocalMinima lm = minimaList;
+ while (lm != null) {
+ insertScanbeam(lm.y);
+ Edge e = lm.leftBound;
+ if (e != null) {
+ e.setCurrent( new LongPoint( e.getBot() ) );
+ e.outIdx = Edge.UNASSIGNED;
+ }
+ e = lm.rightBound;
+ if (e != null) {
+ e.setCurrent( new LongPoint( e.getBot() ) );
+ e.outIdx = Edge.UNASSIGNED;
+ }
+ lm = lm.next;
+ }
+ activeEdges = null;
+ }
+
+ protected void insertScanbeam( long y ) {
+ LOGGER.entering( ClipperBase.class.getName(), "insertScanbeam" );
+
+ //single-linked list: sorted descending, ignoring dups.
+ if (scanbeam == null) {
+ scanbeam = new Scanbeam();
+ scanbeam.next = null;
+ scanbeam.y = y;
+ }
+ else if (y > scanbeam.y) {
+ final Scanbeam newSb = new Scanbeam();
+ newSb.y = y;
+ newSb.next = scanbeam;
+ scanbeam = newSb;
+ }
+ else {
+ Scanbeam sb2 = scanbeam;
+ while (sb2.next != null && (y <= sb2.next.y)) {
+ sb2 = sb2.next;
+ }
+ if (y == sb2.y) {
+ return; //ie ignores duplicates
+ }
+ final Scanbeam newSb = new Scanbeam();
+ newSb.y = y;
+ newSb.next = sb2.next;
+ sb2.next = newSb;
+ }
+ }
+
+ protected boolean popScanbeam( long[] y ) {
+ if (scanbeam == null) {
+ y[0] = 0;
+ return false;
+ }
+ y[0] = scanbeam.y;
+ scanbeam = scanbeam.next;
+ return true;
+ }
+
+ protected final boolean localMinimaPending() {
+ return currentLM != null;
+ }
+
+ protected OutRec createOutRec() {
+ OutRec result = new OutRec();
+ result.Idx = Edge.UNASSIGNED;
+ result.isHole = false;
+ result.isOpen = false;
+ result.firstLeft = null;
+ result.setPoints( null );
+ result.bottomPt = null;
+ result.polyNode = null;
+ polyOuts.add( result );
+ result.Idx = polyOuts.size() - 1;
+ return result;
+ }
+
+ protected void disposeOutRec( int index ) {
+ OutRec outRec = polyOuts.get( index );
+ outRec.setPoints( null );
+ outRec = null;
+ polyOuts.set( index, null );
+ }
+
+ protected void updateEdgeIntoAEL( Edge e ) {
+ if (e.nextInLML == null) {
+ throw new IllegalStateException("UpdateEdgeIntoAEL: invalid call");
+ }
+ final Edge aelPrev = e.prevInAEL;
+ final Edge aelNext = e.nextInAEL;
+ e.nextInLML.outIdx = e.outIdx;
+ if (aelPrev != null) {
+ aelPrev.nextInAEL = e.nextInLML;
+ }
+ else {
+ activeEdges = e.nextInLML;
+ }
+ if (aelNext != null) {
+ 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.setCurrent(e.getBot());
+ e.prevInAEL = aelPrev;
+ e.nextInAEL = aelNext;
+ if (e.isHorizontal()) {
+ insertScanbeam(e.getTop().getY());
+ }
+ }
+
+ protected void swapPositionsInAEL(Edge edge1, Edge edge2 ) {
+ LOGGER.entering( ClipperBase.class.getName(), "swapPositionsInAEL" );
+
+ //check that one or other edge hasn't already been removed from AEL ...
+ if (edge1.nextInAEL == edge1.prevInAEL || edge2.nextInAEL == edge2.prevInAEL) {
+ return;
+ }
+
+ if (edge1.nextInAEL == edge2) {
+ final Edge next = edge2.nextInAEL;
+ if (next != null) {
+ next.prevInAEL = edge1;
+ }
+ final Edge prev = edge1.prevInAEL;
+ if (prev != null) {
+ prev.nextInAEL = edge2;
+ }
+ edge2.prevInAEL = prev;
+ edge2.nextInAEL = edge1;
+ edge1.prevInAEL = edge2;
+ edge1.nextInAEL = next;
+ }
+ else if (edge2.nextInAEL == edge1) {
+ final Edge next = edge1.nextInAEL;
+ if (next != null) {
+ next.prevInAEL = edge2;
+ }
+ final Edge prev = edge2.prevInAEL;
+ if (prev != null) {
+ prev.nextInAEL = edge1;
+ }
+ edge1.prevInAEL = prev;
+ edge1.nextInAEL = edge2;
+ edge2.prevInAEL = edge1;
+ edge2.nextInAEL = next;
+ }
+ else {
+ final Edge next = edge1.nextInAEL;
+ final Edge prev = edge1.prevInAEL;
+ edge1.nextInAEL = edge2.nextInAEL;
+ if (edge1.nextInAEL != null) {
+ edge1.nextInAEL.prevInAEL = edge1;
+ }
+ edge1.prevInAEL = edge2.prevInAEL;
+ if (edge1.prevInAEL != null) {
+ edge1.prevInAEL.nextInAEL = edge1;
+ }
+ edge2.nextInAEL = next;
+ if (edge2.nextInAEL != null) {
+ edge2.nextInAEL.prevInAEL = edge2;
+ }
+ edge2.prevInAEL = prev;
+ if (edge2.prevInAEL != null) {
+ edge2.prevInAEL.nextInAEL = edge2;
+ }
+ }
+
+ if (edge1.prevInAEL == null) {
+ activeEdges = edge1;
+ }
+ else if (edge2.prevInAEL == null) {
+ activeEdges = edge2;
+ }
+
+ LOGGER.exiting( ClipperBase.class.getName(), "swapPositionsInAEL" );
+ }
+
+ protected void deleteFromAEL( Edge e ) {
+ LOGGER.entering( ClipperBase.class.getName(), "deleteFromAEL" );
+
+ Edge aelPrev = e.prevInAEL;
+ Edge aelNext = e.nextInAEL;
+ if (aelPrev == null && aelNext == null && (e != activeEdges)) {
+ return; //already deleted
+ }
+ if (aelPrev != null) {
+ aelPrev.nextInAEL = aelNext;
+ }
+ else {
+ activeEdges = aelNext;
+ }
+ if (aelNext != null) {
+ aelNext.prevInAEL = aelPrev;
+ }
+ e.nextInAEL = null;
+ e.prevInAEL = null;
+
+ LOGGER.exiting( ClipperBase.class.getName(), "deleteFromAEL" );
+ }
+}
\ No newline at end of file
diff --git a/open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/ClipperOffset.java b/open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/ClipperOffset.java
new file mode 100644
index 0000000..caf4a98
--- /dev/null
+++ b/open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/ClipperOffset.java
@@ -0,0 +1,481 @@
+package com.visual.open.anpr.core.clipper;
+
+import com.visual.open.anpr.core.clipper.Clipper.*;
+import com.visual.open.anpr.core.clipper.Point.DoublePoint;
+import com.visual.open.anpr.core.clipper.Point.LongPoint;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class ClipperOffset {
+ private static boolean nearZero( double val ) {
+ return val > -TOLERANCE && val < TOLERANCE;
+ }
+
+ private Paths destPolys;
+ private Path srcPoly;
+ private Path destPoly;
+
+ private final List normals;
+ private double delta, inA, sin, cos;
+
+ private double miterLim, stepsPerRad;
+ private LongPoint lowest;
+
+ private final PolyNode polyNodes;
+ private final double arcTolerance;
+
+ private final double miterLimit;
+ private final static double TWO_PI = Math.PI * 2;
+
+ private final static double DEFAULT_ARC_TOLERANCE = 0.25;
+
+ private final static double TOLERANCE = 1.0E-20;
+
+ public ClipperOffset() {
+ this( 2, DEFAULT_ARC_TOLERANCE );
+ }
+
+ public ClipperOffset( double miterLimit, double arcTolerance ) {
+ this.miterLimit = miterLimit;
+ this.arcTolerance = arcTolerance;
+ lowest = new LongPoint();
+ lowest.setX( -1L );
+ polyNodes = new PolyNode();
+ normals = new ArrayList<>();
+ }
+
+ public void addPath(Path path, JoinType joinType, EndType endType ) {
+ int highI = path.size() - 1;
+ if (highI < 0) {
+ return;
+ }
+ final PolyNode newNode = new PolyNode();
+ newNode.setJoinType( joinType );
+ newNode.setEndType( endType );
+
+ //strip duplicate points from path and also get index to the lowest point ...
+ if (endType == EndType.CLOSED_LINE || endType == EndType.CLOSED_POLYGON) {
+ while (highI > 0 && path.get( 0 ) == path.get( highI )) {
+ highI--;
+ }
+ }
+
+ newNode.getPolygon().add( path.get( 0 ) );
+ int j = 0, k = 0;
+ for (int i = 1; i <= highI; i++) {
+ if (newNode.getPolygon().get( j ) != path.get( i )) {
+ j++;
+ newNode.getPolygon().add( path.get( i ) );
+ if (path.get( i ).getY() > newNode.getPolygon().get( k ).getY() || path.get( i ).getY() == newNode.getPolygon().get( k ).getY()
+ && path.get( i ).getX() < newNode.getPolygon().get( k ).getX()) {
+ k = j;
+ }
+ }
+ }
+ if (endType == EndType.CLOSED_POLYGON && j < 2) {
+ return;
+ }
+
+ polyNodes.addChild( newNode );
+
+ //if this path's lowest pt is lower than all the others then update m_lowest
+ if (endType != EndType.CLOSED_POLYGON) {
+ return;
+ }
+ if (lowest.getX() < 0) {
+ lowest = new LongPoint( polyNodes.getChildCount() - 1, k );
+ }
+ else {
+ final LongPoint ip = polyNodes.getChilds().get( (int) lowest.getX() ).getPolygon().get( (int) lowest.getY() );
+ if (newNode.getPolygon().get( k ).getY() > ip.getY() || newNode.getPolygon().get( k ).getY() == ip.getY()
+ && newNode.getPolygon().get( k ).getX() < ip.getX()) {
+ lowest = new LongPoint( polyNodes.getChildCount() - 1, k );
+ }
+ }
+ }
+
+ public void addPaths(Paths paths, JoinType joinType, EndType endType ) {
+ for (final Path p : paths) {
+ addPath( p, joinType, endType );
+ }
+ }
+
+ public void clear() {
+ polyNodes.getChilds().clear();
+ lowest.setX( -1L );
+ }
+
+ private void doMiter( int j, int k, double r ) {
+ final double q = delta / r;
+ destPoly.add( new LongPoint( Math.round( srcPoly.get( j ).getX() + (normals.get( k ).getX() + normals.get( j ).getX()) * q ), Math
+ .round( srcPoly.get( j ).getY() + (normals.get( k ).getY() + normals.get( j ).getY()) * q ) ) );
+ }
+
+ private void doOffset( double delta ) {
+ destPolys = new Paths();
+ this.delta = delta;
+
+ //if Zero offset, just copy any CLOSED polygons to m_p and return ...
+ if (nearZero( delta )) {
+ for (int i = 0; i < polyNodes.getChildCount(); i++) {
+ final PolyNode node = polyNodes.getChilds().get( i );
+ if (node.getEndType() == EndType.CLOSED_POLYGON) {
+ destPolys.add( node.getPolygon() );
+ }
+ }
+ return;
+ }
+
+ //see offset_triginometry3.svg in the documentation folder ...
+ if (miterLimit > 2) {
+ miterLim = 2 / (miterLimit * miterLimit);
+ }
+ else {
+ miterLim = 0.5;
+ }
+
+ double y;
+ if (arcTolerance <= 0.0) {
+ y = DEFAULT_ARC_TOLERANCE;
+ }
+ else if (arcTolerance > Math.abs( delta ) * DEFAULT_ARC_TOLERANCE) {
+ y = Math.abs( delta ) * DEFAULT_ARC_TOLERANCE;
+ }
+ else {
+ y = arcTolerance;
+ }
+ //see offset_triginometry2.svg in the documentation folder ...
+ final double steps = Math.PI / Math.acos( 1 - y / Math.abs( delta ) );
+ sin = Math.sin( TWO_PI / steps );
+ cos = Math.cos( TWO_PI / steps );
+ stepsPerRad = steps / TWO_PI;
+ if (delta < 0.0) {
+ sin = -sin;
+ }
+
+ for (int i = 0; i < polyNodes.getChildCount(); i++) {
+ final PolyNode node = polyNodes.getChilds().get( i );
+ srcPoly = node.getPolygon();
+
+ final int len = srcPoly.size();
+
+ if (len == 0 || delta <= 0 && (len < 3 || node.getEndType() != EndType.CLOSED_POLYGON)) {
+ continue;
+ }
+
+ destPoly = new Path();
+
+ if (len == 1) {
+ if (node.getJoinType() == JoinType.ROUND) {
+ double X = 1.0, Y = 0.0;
+ for (int j = 1; j <= steps; j++) {
+ destPoly.add( new LongPoint( Math.round( srcPoly.get( 0 ).getX() + X * delta ), Math.round( srcPoly.get( 0 ).getY() + Y
+ * delta ) ) );
+ final double X2 = X;
+ X = X * cos - sin * Y;
+ Y = X2 * sin + Y * cos;
+ }
+ }
+ else {
+ double X = -1.0, Y = -1.0;
+ for (int j = 0; j < 4; ++j) {
+ destPoly.add( new LongPoint( Math.round( srcPoly.get( 0 ).getX() + X * delta ), Math.round( srcPoly.get( 0 ).getY() + Y
+ * delta ) ) );
+ if (X < 0) {
+ X = 1;
+ }
+ else if (Y < 0) {
+ Y = 1;
+ }
+ else {
+ X = -1;
+ }
+ }
+ }
+ destPolys.add( destPoly );
+ continue;
+ }
+
+ //build m_normals ...
+ normals.clear();
+ for (int j = 0; j < len - 1; j++) {
+ normals.add( Point.getUnitNormal( srcPoly.get( j ), srcPoly.get( j + 1 ) ) );
+ }
+ if (node.getEndType() == EndType.CLOSED_LINE || node.getEndType() == EndType.CLOSED_POLYGON) {
+ normals.add( Point.getUnitNormal( srcPoly.get( len - 1 ), srcPoly.get( 0 ) ) );
+ }
+ else {
+ normals.add( new DoublePoint( normals.get( len - 2 ) ) );
+ }
+
+ if (node.getEndType() == EndType.CLOSED_POLYGON) {
+ final int[] k = new int[] { len - 1 };
+ for (int j = 0; j < len; j++) {
+ offsetPoint( j, k, node.getJoinType() );
+ }
+ destPolys.add( destPoly );
+ }
+ else if (node.getEndType() == EndType.CLOSED_LINE) {
+ final int[] k = new int[] { len - 1 };
+ for (int j = 0; j < len; j++) {
+ offsetPoint( j, k, node.getJoinType() );
+ }
+ destPolys.add( destPoly );
+ destPoly = new Path();
+ //re-build m_normals ...
+ final DoublePoint n = normals.get( len - 1 );
+ for (int j = len - 1; j > 0; j--) {
+ normals.set( j, new DoublePoint( -normals.get( j - 1 ).getX(), -normals.get( j - 1 ).getY() ) );
+ }
+ normals.set( 0, new DoublePoint( -n.getX(), -n.getY(), 0 ) );
+ k[0] = 0;
+ for (int j = len - 1; j >= 0; j--) {
+ offsetPoint( j, k, node.getJoinType() );
+ }
+ destPolys.add( destPoly );
+ }
+ else {
+ final int[] k = new int[1];
+ for (int j = 1; j < len - 1; ++j) {
+ offsetPoint( j, k, node.getJoinType() );
+ }
+
+ LongPoint pt1;
+ if (node.getEndType() == EndType.OPEN_BUTT) {
+ final int j = len - 1;
+ pt1 = new LongPoint( Math.round( srcPoly.get( j ).getX() + normals.get( j ).getX() * delta ), Math.round( srcPoly.get( j )
+ .getY() + normals.get( j ).getY() * delta ), 0 );
+ destPoly.add( pt1 );
+ pt1 = new LongPoint( Math.round( srcPoly.get( j ).getX() - normals.get( j ).getX() * delta ), Math.round( srcPoly.get( j )
+ .getY() - normals.get( j ).getY() * delta ), 0 );
+ destPoly.add( pt1 );
+ }
+ else {
+ final int j = len - 1;
+ k[0] = len - 2;
+ inA = 0;
+ normals.set( j, new DoublePoint( -normals.get( j ).getX(), -normals.get( j ).getY() ) );
+ if (node.getEndType() == EndType.OPEN_SQUARE) {
+ doSquare( j, k[0] );
+ }
+ else {
+ doRound( j, k[0] );
+ }
+ }
+
+ //re-build m_normals ...
+ for (int j = len - 1; j > 0; j--) {
+ normals.set( j, new DoublePoint( -normals.get( j - 1 ).getX(), -normals.get( j - 1 ).getY() ) );
+ }
+
+ normals.set( 0, new DoublePoint( -normals.get( 1 ).getX(), -normals.get( 1 ).getY() ) );
+
+ k[0] = len - 1;
+ for (int j = k[0] - 1; j > 0; --j) {
+ offsetPoint( j, k, node.getJoinType() );
+ }
+
+ if (node.getEndType() == EndType.OPEN_BUTT) {
+ pt1 = new LongPoint( Math.round( srcPoly.get( 0 ).getX() - normals.get( 0 ).getX() * delta ), Math.round( srcPoly.get( 0 )
+ .getY() - normals.get( 0 ).getY() * delta ) );
+ destPoly.add( pt1 );
+ pt1 = new LongPoint( Math.round( srcPoly.get( 0 ).getX() + normals.get( 0 ).getX() * delta ), Math.round( srcPoly.get( 0 )
+ .getY() + normals.get( 0 ).getY() * delta ) );
+ destPoly.add( pt1 );
+ }
+ else {
+ k[0] = 1;
+ inA = 0;
+ if (node.getEndType() == EndType.OPEN_SQUARE) {
+ doSquare( 0, 1 );
+ }
+ else {
+ doRound( 0, 1 );
+ }
+ }
+ destPolys.add( destPoly );
+ }
+ }
+ }
+
+ private void doRound( int j, int k ) {
+ final double a = Math.atan2( inA, normals.get( k ).getX() * normals.get( j ).getX() + normals.get( k ).getY() * normals.get( j ).getY() );
+ final int steps = Math.max( (int) Math.round( stepsPerRad * Math.abs( a ) ), 1 );
+
+ double X = normals.get( k ).getX(), Y = normals.get( k ).getY(), X2;
+ for (int i = 0; i < steps; ++i) {
+ destPoly.add( new LongPoint( Math.round( srcPoly.get( j ).getX() + X * delta ), Math.round( srcPoly.get( j ).getY() + Y * delta ) ) );
+ X2 = X;
+ X = X * cos - sin * Y;
+ Y = X2 * sin + Y * cos;
+ }
+ destPoly.add( new LongPoint( Math.round( srcPoly.get( j ).getX() + normals.get( j ).getX() * delta ), Math.round( srcPoly.get( j ).getY()
+ + normals.get( j ).getY() * delta ) ) );
+ }
+
+ private void doSquare( int j, int k ) {
+ final double nkx = normals.get( k ).getX();
+ final double nky = normals.get( k ).getY();
+ final double njx = normals.get( j ).getX();
+ final double njy = normals.get( j ).getY();
+ final double sjx = srcPoly.get( j ).getX();
+ final double sjy = srcPoly.get( j ).getY();
+ final double dx = Math.tan( Math.atan2( inA, nkx * njx + nky * njy ) / 4 );
+ destPoly.add( new LongPoint( Math.round( sjx + delta * (nkx - nky * dx) ), Math.round( sjy + delta * (nky + nkx * dx) ), 0 ) );
+ destPoly.add( new LongPoint( Math.round( sjx + delta * (njx + njy * dx) ), Math.round( sjy + delta * (njy - njx * dx) ), 0 ) );
+ }
+
+ //------------------------------------------------------------------------------
+
+ public void execute(Paths solution, double delta ) {
+ solution.clear();
+ fixOrientations();
+ doOffset( delta );
+ //now clean up 'corners' ...
+ final DefaultClipper clpr = new DefaultClipper( Clipper.REVERSE_SOLUTION );
+ clpr.addPaths( destPolys, PolyType.SUBJECT, true );
+ if (delta > 0) {
+ clpr.execute( ClipType.UNION, solution, PolyFillType.POSITIVE, PolyFillType.POSITIVE );
+ }
+ else {
+ final LongRect r = destPolys.getBounds();
+ final Path outer = new Path( 4 );
+
+ outer.add( new LongPoint( r.left - 10, r.bottom + 10, 0 ) );
+ outer.add( new LongPoint( r.right + 10, r.bottom + 10, 0 ) );
+ outer.add( new LongPoint( r.right + 10, r.top - 10, 0 ) );
+ outer.add( new LongPoint( r.left - 10, r.top - 10, 0 ) );
+
+ clpr.addPath( outer, PolyType.SUBJECT, true );
+
+ clpr.execute( ClipType.UNION, solution, PolyFillType.NEGATIVE, PolyFillType.NEGATIVE );
+ if (solution.size() > 0) {
+ solution.remove( 0 );
+ }
+ }
+ }
+
+ //------------------------------------------------------------------------------
+
+ public void execute(PolyTree solution, double delta ) {
+ solution.Clear();
+ fixOrientations();
+ doOffset( delta );
+
+ //now clean up 'corners' ...
+ final DefaultClipper clpr = new DefaultClipper( Clipper.REVERSE_SOLUTION );
+ clpr.addPaths( destPolys, PolyType.SUBJECT, true );
+ if (delta > 0) {
+ clpr.execute( ClipType.UNION, solution, PolyFillType.POSITIVE, PolyFillType.POSITIVE );
+ }
+ else {
+ final LongRect r = destPolys.getBounds();
+ final Path outer = new Path( 4 );
+
+ outer.add( new LongPoint( r.left - 10, r.bottom + 10, 0 ) );
+ outer.add( new LongPoint( r.right + 10, r.bottom + 10, 0 ) );
+ outer.add( new LongPoint( r.right + 10, r.top - 10, 0 ) );
+ outer.add( new LongPoint( r.left - 10, r.top - 10, 0 ) );
+
+ clpr.addPath( outer, PolyType.SUBJECT, true );
+
+ clpr.execute( ClipType.UNION, solution, PolyFillType.NEGATIVE, PolyFillType.NEGATIVE );
+ //remove the outer PolyNode rectangle ...
+ if (solution.getChildCount() == 1 && solution.getChilds().get( 0 ).getChildCount() > 0) {
+ final PolyNode outerNode = solution.getChilds().get( 0 );
+ solution.getChilds().set( 0, outerNode.getChilds().get( 0 ) );
+ solution.getChilds().get( 0 ).setParent( solution );
+ for (int i = 1; i < outerNode.getChildCount(); i++) {
+ solution.addChild( outerNode.getChilds().get( i ) );
+ }
+ }
+ else {
+ solution.Clear();
+ }
+ }
+ }
+
+ //------------------------------------------------------------------------------
+
+ private void fixOrientations() {
+ //fixup orientations of all closed paths if the orientation of the
+ //closed path with the lowermost vertex is wrong ...
+ if (lowest.getX() >= 0 && !polyNodes.childs.get( (int) lowest.getX() ).getPolygon().orientation()) {
+ for (int i = 0; i < polyNodes.getChildCount(); i++) {
+ final PolyNode node = polyNodes.childs.get( i );
+ if (node.getEndType() == EndType.CLOSED_POLYGON || node.getEndType() == EndType.CLOSED_LINE && node.getPolygon().orientation()) {
+ Collections.reverse( node.getPolygon() );
+
+ }
+ }
+ }
+ else {
+ for (int i = 0; i < polyNodes.getChildCount(); i++) {
+ final PolyNode node = polyNodes.childs.get( i );
+ if (node.getEndType() == EndType.CLOSED_LINE && !node.getPolygon().orientation()) {
+ Collections.reverse( node.getPolygon() );
+ }
+ }
+ }
+ }
+
+ private void offsetPoint( int j, int[] kV, JoinType jointype ) {
+ //cross product ...
+ final int k = kV[0];
+ final double nkx = normals.get( k ).getX();
+ final double nky = normals.get( k ).getY();
+ final double njy = normals.get( j ).getY();
+ final double njx = normals.get( j ).getX();
+ final long sjx = srcPoly.get( j ).getX();
+ final long sjy = srcPoly.get( j ).getY();
+ inA = nkx * njy - njx * nky;
+
+ if (Math.abs( inA * delta ) < 1.0) {
+ //dot product ...
+
+ final double cosA = nkx * njx + njy * nky;
+ if (cosA > 0) // angle ==> 0 degrees
+ {
+ destPoly.add( new LongPoint( Math.round( sjx + nkx * delta ), Math.round( sjy + nky * delta ), 0 ) );
+ return;
+ }
+ //else angle ==> 180 degrees
+ }
+ else if (inA > 1.0) {
+ inA = 1.0;
+ }
+ else if (inA < -1.0) {
+ inA = -1.0;
+ }
+
+ if (inA * delta < 0) {
+ destPoly.add( new LongPoint( Math.round( sjx + nkx * delta ), Math.round( sjy + nky * delta ) ) );
+ destPoly.add( srcPoly.get( j ) );
+ destPoly.add( new LongPoint( Math.round( sjx + njx * delta ), Math.round( sjy + njy * delta ) ) );
+ }
+ else {
+ switch (jointype) {
+ case MITER: {
+ final double r = 1 + njx * nkx + njy * nky;
+ if (r >= miterLim) {
+ doMiter( j, k, r );
+ }
+ else {
+ doSquare( j, k );
+ }
+ break;
+ }
+ case SQUARE:
+ doSquare( j, k );
+ break;
+ case ROUND:
+ doRound( j, k );
+ break;
+ }
+ }
+ kV[0] = j;
+ }
+ //------------------------------------------------------------------------------
+}
\ No newline at end of file
diff --git a/open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/DefaultClipper.java b/open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/DefaultClipper.java
new file mode 100644
index 0000000..b9a519e
--- /dev/null
+++ b/open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/DefaultClipper.java
@@ -0,0 +1,2518 @@
+package com.visual.open.anpr.core.clipper;
+
+import com.visual.open.anpr.core.clipper.Path.Join;
+import com.visual.open.anpr.core.clipper.Path.OutRec;
+import com.visual.open.anpr.core.clipper.Point.LongPoint;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class DefaultClipper extends ClipperBase {
+ private class IntersectNode {
+ Edge edge1;
+ Edge Edge2;
+ private LongPoint pt;
+
+ LongPoint getPt() {
+ return pt;
+ }
+
+ void setPt( LongPoint pt ) {
+ this.pt = pt;
+ }
+
+ }
+
+ private static void getHorzDirection(Edge HorzEdge, Direction[] Dir, long[] Left, long[] Right ) {
+ if (HorzEdge.getBot().getX() < HorzEdge.getTop().getX()) {
+ Left[0] = HorzEdge.getBot().getX();
+ Right[0] = HorzEdge.getTop().getX();
+ Dir[0] = Direction.LEFT_TO_RIGHT;
+ }
+ else {
+ Left[0] = HorzEdge.getTop().getX();
+ Right[0] = HorzEdge.getBot().getX();
+ Dir[0] = Direction.RIGHT_TO_LEFT;
+ }
+ }
+
+ private static boolean getOverlap( long a1, long a2, long b1, long b2, long[] Left, long[] Right ) {
+ if (a1 < a2) {
+ if (b1 < b2) {
+ Left[0] = Math.max( a1, b1 );
+ Right[0] = Math.min( a2, b2 );
+ }
+ else {
+ Left[0] = Math.max( a1, b2 );
+ Right[0] = Math.min( a2, b1 );
+ }
+ }
+ else {
+ if (b1 < b2) {
+ Left[0] = Math.max( a2, b1 );
+ Right[0] = Math.min( a1, b2 );
+ }
+ else {
+ Left[0] = Math.max( a2, b2 );
+ Right[0] = Math.min( a1, b1 );
+ }
+ }
+ return Left[0] < Right[0];
+ }
+
+ private static boolean isOutRec1RightOfOutRec2(OutRec outRec1, OutRec outRec2 ) {
+ do {
+ outRec1 = outRec1.firstLeft;
+ if (outRec1 == outRec2) {
+ return true;
+ }
+ }
+ while (outRec1 != null);
+ return false;
+ }
+
+ //See "The Point in Polygon Problem for Arbitrary Polygons" by Hormann & Agathos
+ //http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf
+ private static int isPointInPolygon(LongPoint pt, Path.OutPt op ) {
+ //returns 0 if false, +1 if true, -1 if pt ON polygon boundary
+ int result = 0;
+ final Path.OutPt startOp = op;
+ final long ptx = pt.getX(), pty = pt.getY();
+ long poly0x = op.getPt().getX(), poly0y = op.getPt().getY();
+ do {
+ op = op.next;
+ final long poly1x = op.getPt().getX(), poly1y = op.getPt().getY();
+
+ if (poly1y == pty) {
+ if (poly1x == ptx || poly0y == pty && poly1x > ptx == poly0x < ptx) {
+ return -1;
+ }
+ }
+ if (poly0y < pty != poly1y < pty) {
+ if (poly0x >= ptx) {
+ if (poly1x > ptx) {
+ result = 1 - result;
+ }
+ else {
+ final double d = (double) (poly0x - ptx) * (poly1y - pty) - (double) (poly1x - ptx) * (poly0y - pty);
+ if (d == 0) {
+ return -1;
+ }
+ if (d > 0 == poly1y > poly0y) {
+ result = 1 - result;
+ }
+ }
+ }
+ else {
+ if (poly1x > ptx) {
+ final double d = (double) (poly0x - ptx) * (poly1y - pty) - (double) (poly1x - ptx) * (poly0y - pty);
+ if (d == 0) {
+ return -1;
+ }
+ if (d > 0 == poly1y > poly0y) {
+ result = 1 - result;
+ }
+ }
+ }
+ }
+ poly0x = poly1x;
+ poly0y = poly1y;
+ }
+ while (startOp != op);
+
+ return result;
+ }
+
+ //------------------------------------------------------------------------------
+ private static boolean joinHorz(Path.OutPt op1, Path.OutPt op1b, Path.OutPt op2, Path.OutPt op2b, LongPoint Pt, boolean DiscardLeft ) {
+ final Direction Dir1 = op1.getPt().getX() > op1b.getPt().getX() ? Direction.RIGHT_TO_LEFT : Direction.LEFT_TO_RIGHT;
+ final Direction Dir2 = op2.getPt().getX() > op2b.getPt().getX() ? Direction.RIGHT_TO_LEFT : Direction.LEFT_TO_RIGHT;
+ if (Dir1 == Dir2) {
+ return false;
+ }
+
+ //When DiscardLeft, we want Op1b to be on the Left of Op1, otherwise we
+ //want Op1b to be on the Right. (And likewise with Op2 and Op2b.)
+ //So, to facilitate this while inserting Op1b and Op2b ...
+ //when DiscardLeft, make sure we're AT or RIGHT of Pt before adding Op1b,
+ //otherwise make sure we're AT or LEFT of Pt. (Likewise with Op2b.)
+ if (Dir1 == Direction.LEFT_TO_RIGHT) {
+ while (op1.next.getPt().getX() <= Pt.getX() && op1.next.getPt().getX() >= op1.getPt().getX() && op1.next.getPt().getY() == Pt.getY()) {
+ op1 = op1.next;
+ }
+ if (DiscardLeft && op1.getPt().getX() != Pt.getX()) {
+ op1 = op1.next;
+ }
+ op1b = op1.duplicate( !DiscardLeft );
+ if (!op1b.getPt().equals( Pt )) {
+ op1 = op1b;
+ op1.setPt( new LongPoint( Pt ) );
+ op1b = op1.duplicate( !DiscardLeft );
+ }
+ }
+ else {
+ while (op1.next.getPt().getX() >= Pt.getX() && op1.next.getPt().getX() <= op1.getPt().getX() && op1.next.getPt().getY() == Pt.getY()) {
+ op1 = op1.next;
+ }
+ if (!DiscardLeft && op1.getPt().getX() != Pt.getX()) {
+ op1 = op1.next;
+ }
+ op1b = op1.duplicate( DiscardLeft );
+ if (!op1b.getPt().equals( Pt )) {
+ op1 = op1b;
+ op1.setPt( new LongPoint( Pt ) );
+ op1b = op1.duplicate( DiscardLeft );
+ }
+ }
+
+ if (Dir2 == Direction.LEFT_TO_RIGHT) {
+ while (op2.next.getPt().getX() <= Pt.getX() && op2.next.getPt().getX() >= op2.getPt().getX() && op2.next.getPt().getY() == Pt.getY()) {
+ op2 = op2.next;
+ }
+ if (DiscardLeft && op2.getPt().getX() != Pt.getX()) {
+ op2 = op2.next;
+ }
+ op2b = op2.duplicate( !DiscardLeft );
+ if (!op2b.getPt().equals( Pt )) {
+ op2 = op2b;
+ op2.setPt( new LongPoint( Pt ) );
+ op2b = op2.duplicate( !DiscardLeft );
+ }
+ }
+ else {
+ while (op2.next.getPt().getX() >= Pt.getX() && op2.next.getPt().getX() <= op2.getPt().getX() && op2.next.getPt().getY() == Pt.getY()) {
+ op2 = op2.next;
+ }
+ if (!DiscardLeft && op2.getPt().getX() != Pt.getX()) {
+ op2 = op2.next;
+ }
+ op2b = op2.duplicate( DiscardLeft );
+ if (!op2b.getPt().equals( Pt )) {
+ op2 = op2b;
+ op2.setPt( new LongPoint( Pt ) );
+ op2b = op2.duplicate( DiscardLeft );
+ }
+ }
+
+ if (Dir1 == Direction.LEFT_TO_RIGHT == DiscardLeft) {
+ op1.prev = op2;
+ op2.next = op1;
+ op1b.next = op2b;
+ op2b.prev = op1b;
+ }
+ else {
+ op1.next = op2;
+ op2.prev = op1;
+ op1b.prev = op2b;
+ op2b.next = op1b;
+ }
+ return true;
+ }
+
+ private static boolean joinPoints(Join j, OutRec outRec1, OutRec outRec2 ) {
+ Path.OutPt op1 = j.outPt1, op1b;
+ Path.OutPt op2 = j.outPt2, op2b;
+
+ //There are 3 kinds of joins for output polygons ...
+ //1. Horizontal joins where Join.OutPt1 & Join.OutPt2 are vertices anywhere
+ //along (horizontal) collinear edges (& Join.OffPt is on the same horizontal).
+ //2. Non-horizontal joins where Join.OutPt1 & Join.OutPt2 are at the same
+ //location at the Bottom of the overlapping segment (& Join.OffPt is above).
+ //3. StrictlySimple joins where edges touch but are not collinear and where
+ //Join.OutPt1, Join.OutPt2 & Join.OffPt all share the same point.
+ final boolean isHorizontal = j.outPt1.getPt().getY() == j.getOffPt().getY();
+
+ if (isHorizontal && j.getOffPt().equals( j.outPt1.getPt() ) && j.getOffPt().equals( j.outPt2.getPt() )) {
+ //Strictly Simple join ...
+ if (outRec1 != outRec2) {
+ return false;
+ }
+ op1b = j.outPt1.next;
+ while (op1b != op1 && op1b.getPt().equals( j.getOffPt() )) {
+ op1b = op1b.next;
+ }
+ final boolean reverse1 = op1b.getPt().getY() > j.getOffPt().getY();
+ op2b = j.outPt2.next;
+ while (op2b != op2 && op2b.getPt().equals( j.getOffPt() )) {
+ op2b = op2b.next;
+ }
+ final boolean reverse2 = op2b.getPt().getY() > j.getOffPt().getY();
+ if (reverse1 == reverse2) {
+ return false;
+ }
+ if (reverse1) {
+ op1b = op1.duplicate( false );
+ op2b = op2.duplicate( true );
+ op1.prev = op2;
+ op2.next = op1;
+ op1b.next = op2b;
+ op2b.prev = op1b;
+ j.outPt1 = op1;
+ j.outPt2 = op1b;
+ return true;
+ }
+ else {
+ op1b = op1.duplicate( true );
+ op2b = op2.duplicate( false );
+ op1.next = op2;
+ op2.prev = op1;
+ op1b.prev = op2b;
+ op2b.next = op1b;
+ j.outPt1 = op1;
+ j.outPt2 = op1b;
+ return true;
+ }
+ }
+ else if (isHorizontal) {
+ //treat horizontal joins differently to non-horizontal joins since with
+ //them we're not yet sure where the overlapping is. OutPt1.Pt & OutPt2.Pt
+ //may be anywhere along the horizontal edge.
+ op1b = op1;
+ while (op1.prev.getPt().getY() == op1.getPt().getY() && op1.prev != op1b && op1.prev != op2) {
+ op1 = op1.prev;
+ }
+ while (op1b.next.getPt().getY() == op1b.getPt().getY() && op1b.next != op1 && op1b.next != op2) {
+ op1b = op1b.next;
+ }
+ if (op1b.next == op1 || op1b.next == op2) {
+ return false;
+ } //a flat 'polygon'
+
+ op2b = op2;
+ while (op2.prev.getPt().getY() == op2.getPt().getY() && op2.prev != op2b && op2.prev != op1b) {
+ op2 = op2.prev;
+ }
+ while (op2b.next.getPt().getY() == op2b.getPt().getY() && op2b.next != op2 && op2b.next != op1) {
+ op2b = op2b.next;
+ }
+ if (op2b.next == op2 || op2b.next == op1) {
+ return false;
+ } //a flat 'polygon'
+
+ final long[] LeftV = new long[1], RightV = new long[1];
+ //Op1 -. Op1b & Op2 -. Op2b are the extremites of the horizontal edges
+ if (!getOverlap( op1.getPt().getX(), op1b.getPt().getX(), op2.getPt().getX(), op2b.getPt().getX(), LeftV, RightV )) {
+ return false;
+ }
+ final long Left = LeftV[0];
+ final long Right = RightV[0];
+
+ //DiscardLeftSide: when overlapping edges are joined, a spike will created
+ //which needs to be cleaned up. However, we don't want Op1 or Op2 caught up
+ //on the discard Side as either may still be needed for other joins ...
+ LongPoint Pt;
+ boolean DiscardLeftSide;
+ if (op1.getPt().getX() >= Left && op1.getPt().getX() <= Right) {
+ Pt = new LongPoint( op1.getPt() );
+ DiscardLeftSide = op1.getPt().getX() > op1b.getPt().getX();
+ }
+ else if (op2.getPt().getX() >= Left && op2.getPt().getX() <= Right) {
+ Pt = new LongPoint( op2.getPt() );
+ DiscardLeftSide = op2.getPt().getX() > op2b.getPt().getX();
+ }
+ else if (op1b.getPt().getX() >= Left && op1b.getPt().getX() <= Right) {
+ Pt = new LongPoint( op1b.getPt() );
+ DiscardLeftSide = op1b.getPt().getX() > op1.getPt().getX();
+ }
+ else {
+ Pt = new LongPoint( op2b.getPt() );
+ DiscardLeftSide = op2b.getPt().getX() > op2.getPt().getX();
+ }
+ j.outPt1 = op1;
+ j.outPt2 = op2;
+ return joinHorz( op1, op1b, op2, op2b, Pt, DiscardLeftSide );
+ }
+ else {
+ //nb: For non-horizontal joins ...
+ // 1. Jr.OutPt1.getPt().getY() == Jr.OutPt2.getPt().getY()
+ // 2. Jr.OutPt1.Pt > Jr.OffPt.getY()
+
+ //make sure the polygons are correctly oriented ...
+ op1b = op1.next;
+ while (op1b.getPt().equals( op1.getPt() ) && op1b != op1) {
+ op1b = op1b.next;
+ }
+ final boolean Reverse1 = op1b.getPt().getY() > op1.getPt().getY() || !Point.slopesEqual( op1.getPt(), op1b.getPt(), j.getOffPt() );
+ if (Reverse1) {
+ op1b = op1.prev;
+ while (op1b.getPt().equals( op1.getPt() ) && op1b != op1) {
+ op1b = op1b.prev;
+ }
+ if (op1b.getPt().getY() > op1.getPt().getY() || !Point.slopesEqual( op1.getPt(), op1b.getPt(), j.getOffPt() )) {
+ return false;
+ }
+ }
+ op2b = op2.next;
+ while (op2b.getPt().equals( op2.getPt() ) && op2b != op2) {
+ op2b = op2b.next;
+ }
+ final boolean Reverse2 = op2b.getPt().getY() > op2.getPt().getY() || !Point.slopesEqual( op2.getPt(), op2b.getPt(), j.getOffPt() );
+ if (Reverse2) {
+ op2b = op2.prev;
+ while (op2b.getPt().equals( op2.getPt() ) && op2b != op2) {
+ op2b = op2b.prev;
+ }
+ if (op2b.getPt().getY() > op2.getPt().getY() || !Point.slopesEqual( op2.getPt(), op2b.getPt(), j.getOffPt() )) {
+ return false;
+ }
+ }
+
+ if (op1b == op1 || op2b == op2 || op1b == op2b || outRec1 == outRec2 && Reverse1 == Reverse2) {
+ return false;
+ }
+
+ if (Reverse1) {
+ op1b = op1.duplicate( false );
+ op2b = op2.duplicate( true );
+ op1.prev = op2;
+ op2.next = op1;
+ op1b.next = op2b;
+ op2b.prev = op1b;
+ j.outPt1 = op1;
+ j.outPt2 = op1b;
+ return true;
+ }
+ else {
+ op1b = op1.duplicate( true );
+ op2b = op2.duplicate( false );
+ op1.next = op2;
+ op2.prev = op1;
+ op1b.prev = op2b;
+ op2b.next = op1b;
+ j.outPt1 = op1;
+ j.outPt2 = op1b;
+ return true;
+ }
+ }
+ }
+
+ private static Paths minkowski(Path pattern, Path path, boolean IsSum, boolean IsClosed ) {
+ final int delta = IsClosed ? 1 : 0;
+ final int polyCnt = pattern.size();
+ final int pathCnt = path.size();
+ final Paths result = new Paths( pathCnt );
+ if (IsSum) {
+ for (int i = 0; i < pathCnt; i++) {
+ final Path p = new Path( polyCnt );
+ for (final LongPoint ip : pattern) {
+ p.add( new LongPoint( path.get( i ).getX() + ip.getX(), path.get( i ).getY() + ip.getY(), 0 ) );
+ }
+ result.add( p );
+ }
+ }
+ else {
+ for (int i = 0; i < pathCnt; i++) {
+ final Path p = new Path( polyCnt );
+ for (final LongPoint ip : pattern) {
+ p.add( new LongPoint( path.get( i ).getX() - ip.getX(), path.get( i ).getY() - ip.getY(), 0 ) );
+ }
+ result.add( p );
+ }
+ }
+
+ final Paths quads = new Paths( (pathCnt + delta) * (polyCnt + 1) );
+ for (int i = 0; i < pathCnt - 1 + delta; i++) {
+ for (int j = 0; j < polyCnt; j++) {
+ final Path quad = new Path( 4 );
+ quad.add( result.get( i % pathCnt ).get( j % polyCnt ) );
+ quad.add( result.get( (i + 1) % pathCnt ).get( j % polyCnt ) );
+ quad.add( result.get( (i + 1) % pathCnt ).get( (j + 1) % polyCnt ) );
+ quad.add( result.get( i % pathCnt ).get( (j + 1) % polyCnt ) );
+ if (!quad.orientation()) {
+ Collections.reverse( quad );
+ }
+ quads.add( quad );
+ }
+ }
+ return quads;
+ }
+
+ public static Paths minkowskiDiff(Path poly1, Path poly2 ) {
+ final Paths paths = minkowski( poly1, poly2, false, true );
+ final DefaultClipper c = new DefaultClipper();
+ c.addPaths( paths, PolyType.SUBJECT, true );
+ c.execute( ClipType.UNION, paths, PolyFillType.NON_ZERO, PolyFillType.NON_ZERO );
+ return paths;
+ }
+
+ public static Paths minkowskiSum(Path pattern, Path path, boolean pathIsClosed ) {
+ final Paths paths = minkowski( pattern, path, true, pathIsClosed );
+ final DefaultClipper c = new DefaultClipper();
+ c.addPaths( paths, PolyType.SUBJECT, true );
+ c.execute( ClipType.UNION, paths, PolyFillType.NON_ZERO, PolyFillType.NON_ZERO );
+ return paths;
+ }
+
+ public static Paths minkowskiSum(Path pattern, Paths paths, boolean pathIsClosed ) {
+ final Paths solution = new Paths();
+ final DefaultClipper c = new DefaultClipper();
+ for (int i = 0; i < paths.size(); ++i) {
+ final Paths tmp = minkowski( pattern, paths.get( i ), true, pathIsClosed );
+ c.addPaths( tmp, PolyType.SUBJECT, true );
+ if (pathIsClosed) {
+ final Path path = paths.get( i ).TranslatePath( pattern.get( 0 ) );
+ c.addPath( path, PolyType.CLIP, true );
+ }
+ }
+ c.execute( ClipType.UNION, solution, PolyFillType.NON_ZERO, PolyFillType.NON_ZERO );
+ return solution;
+ }
+
+ private static boolean poly2ContainsPoly1(Path.OutPt outPt1, Path.OutPt outPt2 ) {
+ Path.OutPt op = outPt1;
+ do {
+ //nb: PointInPolygon returns 0 if false, +1 if true, -1 if pt on polygon
+ final int res = isPointInPolygon( op.getPt(), outPt2 );
+ if (res >= 0) {
+ return res > 0;
+ }
+ op = op.next;
+ }
+ while (op != outPt1);
+ return true;
+ }
+
+ //------------------------------------------------------------------------------
+ // SimplifyPolygon functions ...
+ // Convert self-intersecting polygons into simple polygons
+ //------------------------------------------------------------------------------
+ public static Paths simplifyPolygon(Path poly ) {
+ return simplifyPolygon( poly, PolyFillType.EVEN_ODD );
+ }
+
+ public static Paths simplifyPolygon(Path poly, PolyFillType fillType ) {
+ final Paths result = new Paths();
+ final DefaultClipper c = new DefaultClipper( STRICTLY_SIMPLE );
+
+ c.addPath( poly, PolyType.SUBJECT, true );
+ c.execute( ClipType.UNION, result, fillType, fillType );
+ return result;
+ }
+
+ public static Paths simplifyPolygons(Paths polys ) {
+ return simplifyPolygons( polys, PolyFillType.EVEN_ODD );
+ }
+
+ public static Paths simplifyPolygons(Paths polys, PolyFillType fillType ) {
+ final Paths result = new Paths();
+ final DefaultClipper c = new DefaultClipper( STRICTLY_SIMPLE );
+
+ c.addPaths( polys, PolyType.SUBJECT, true );
+ c.execute( ClipType.UNION, result, fillType, fillType );
+ return result;
+ }
+
+ private ClipType clipType;
+
+ private Maxima maxima;
+
+ private Edge sortedEdges;
+
+ private final List intersectList;
+
+ private final Comparator intersectNodeComparer;
+
+ private PolyFillType clipFillType;
+
+ //------------------------------------------------------------------------------
+
+ private PolyFillType subjFillType;
+
+ //------------------------------------------------------------------------------
+
+ private final List joins;
+
+ //------------------------------------------------------------------------------
+
+ private final List ghostJoins;
+
+ private boolean usingPolyTree;
+
+ private ZFillCallback zFillFunction;
+
+ //------------------------------------------------------------------------------
+
+ private final boolean reverseSolution;
+
+ //------------------------------------------------------------------------------
+
+ private final boolean strictlySimple;
+
+ private final static Logger LOGGER = Logger.getLogger( DefaultClipper.class.getName() );
+
+ public DefaultClipper() {
+ this( 0 );
+ }
+
+ public DefaultClipper( int InitOptions ) //constructor
+ {
+ super( (PRESERVE_COLINEAR & InitOptions) != 0 );
+ scanbeam = null;
+ maxima = null;
+ activeEdges = null;
+ sortedEdges = null;
+ intersectList = new ArrayList<>();
+ intersectNodeComparer = ( node1, node2 ) -> {
+ final long i = node2.getPt().getY() - node1.getPt().getY();
+ if (i > 0) {
+ return 1;
+ }
+ else if (i < 0) {
+ return -1;
+ }
+ else {
+ return 0;
+ }
+ };
+
+ usingPolyTree = false;
+ joins = new ArrayList<>();
+ ghostJoins = new ArrayList<>();
+ reverseSolution = (REVERSE_SOLUTION & InitOptions) != 0;
+ strictlySimple = (STRICTLY_SIMPLE & InitOptions) != 0;
+
+ zFillFunction = null;
+
+ }
+
+ private void addEdgeToSEL( Edge edge ) {
+ LOGGER.entering( DefaultClipper.class.getName(), "addEdgeToSEL" );
+
+ //SEL pointers in PEdge are use to build transient lists of horizontal edges.
+ //However, since we don't need to worry about processing order, all additions
+ //are made to the front of the list ...
+
+ if (sortedEdges == null) {
+ sortedEdges = edge;
+ edge.prevInSEL = null;
+ edge.nextInSEL = null;
+ }
+ else {
+ edge.nextInSEL = sortedEdges;
+ edge.prevInSEL = null;
+ sortedEdges.prevInSEL = edge;
+ sortedEdges = edge;
+ }
+ }
+
+ private void addGhostJoin(Path.OutPt Op, LongPoint OffPt ) {
+ final Join j = new Join();
+ j.outPt1 = Op;
+ j.setOffPt( new LongPoint( OffPt ) );
+ ghostJoins.add( j );
+ }
+
+ //------------------------------------------------------------------------------
+
+ private void addJoin(Path.OutPt Op1, Path.OutPt Op2, LongPoint OffPt ) {
+ LOGGER.entering( DefaultClipper.class.getName(), "addJoin" );
+ final Join j = new Join();
+ j.outPt1 = Op1;
+ j.outPt2 = Op2;
+ j.setOffPt( new LongPoint( OffPt ) );
+ joins.add( j );
+ }
+
+ //------------------------------------------------------------------------------
+
+ private void addLocalMaxPoly(Edge e1, Edge e2, LongPoint pt ) {
+ addOutPt( e1, pt );
+ if (e2.windDelta == 0) {
+ addOutPt( e2, pt );
+ }
+ if (e1.outIdx == e2.outIdx) {
+ e1.outIdx = Edge.UNASSIGNED;
+ e2.outIdx = Edge.UNASSIGNED;
+ }
+ else if (e1.outIdx < e2.outIdx) {
+ appendPolygon( e1, e2 );
+ }
+ else {
+ appendPolygon( e2, e1 );
+ }
+ }
+
+ //------------------------------------------------------------------------------
+
+ private Path.OutPt addLocalMinPoly(Edge e1, Edge e2, LongPoint pt ) {
+ LOGGER.entering( DefaultClipper.class.getName(), "addLocalMinPoly" );
+ Path.OutPt result;
+ Edge e, prevE;
+ if (e2.isHorizontal() || e1.deltaX > e2.deltaX) {
+ result = addOutPt( e1, pt );
+ e2.outIdx = e1.outIdx;
+ e1.side = Edge.Side.LEFT;
+ e2.side = Edge.Side.RIGHT;
+ e = e1;
+ if (e.prevInAEL == e2) {
+ prevE = e2.prevInAEL;
+ }
+ else {
+ prevE = e.prevInAEL;
+ }
+ }
+ else {
+ result = addOutPt( e2, pt );
+ e1.outIdx = e2.outIdx;
+ e1.side = Edge.Side.RIGHT;
+ e2.side = Edge.Side.LEFT;
+ e = e2;
+ if (e.prevInAEL == e1) {
+ prevE = e1.prevInAEL;
+ }
+ else {
+ prevE = e.prevInAEL;
+ }
+ }
+
+ if (prevE != null && prevE.outIdx >= 0 && prevE.getTop().getY() < pt.getY() && e.getTop().getY() < pt.getY()) {
+ long xPrev = Edge.topX( prevE, pt.getY() );
+ long xE = Edge.topX( e, pt.getY() );
+ if (xPrev == xE && e.windDelta != 0 && prevE.windDelta != 0 &&
+ Point.slopesEqual( new LongPoint( xPrev, pt.getY() ), prevE.getTop(), new LongPoint( xE, pt.getY() ), e.getTop() )) {
+ final Path.OutPt outPt = addOutPt( prevE, pt );
+ addJoin( result, outPt, e.getTop() );
+ }
+ }
+ return result;
+ }
+
+ private Path.OutPt addOutPt(Edge e, LongPoint pt ) {
+ LOGGER.entering( DefaultClipper.class.getName(), "addOutPt" );
+ if (e.outIdx < 0) {
+ final OutRec outRec = createOutRec();
+ outRec.isOpen = e.windDelta == 0;
+ final Path.OutPt newOp = new Path.OutPt();
+ outRec.setPoints( newOp );
+ newOp.idx = outRec.Idx;
+ newOp.setPt( new LongPoint( pt ) );
+ newOp.next = newOp;
+ newOp.prev = newOp;
+ if (!outRec.isOpen) {
+ setHoleState( e, outRec );
+ }
+ e.outIdx = outRec.Idx; //nb: do this after SetZ !
+ return newOp;
+ }
+ else {
+
+ final OutRec outRec = polyOuts.get( e.outIdx );
+ //OutRec.Pts is the 'Left-most' point & OutRec.Pts.Prev is the 'Right-most'
+ final Path.OutPt op = outRec.getPoints();
+ final boolean ToFront = e.side == Edge.Side.LEFT;
+ if (LOGGER.isLoggable( Level.FINEST )) {
+ LOGGER.finest( "op=" + Path.OutPt.getPointCount( op ) );
+ LOGGER.finest( ToFront + " " + pt + " " + op.getPt() );
+ }
+ if (ToFront && pt.equals( op.getPt() )) {
+ return op;
+ }
+ else if (!ToFront && pt.equals( op.prev.getPt() )) {
+ return op.prev;
+ }
+
+ final Path.OutPt newOp = new Path.OutPt();
+ newOp.idx = outRec.Idx;
+ newOp.setPt( new LongPoint( pt ) );
+ newOp.next = op;
+ newOp.prev = op.prev;
+ newOp.prev.next = newOp;
+ op.prev = newOp;
+ if (ToFront) {
+ outRec.setPoints( newOp );
+ }
+ return newOp;
+ }
+ }
+
+ private Path.OutPt getLastOutPt(Edge e) {
+ OutRec outRec = polyOuts.get( e.outIdx );
+ if (e.side == Edge.Side.LEFT)
+ return outRec.getPoints();
+ else
+ return outRec.getPoints().prev;
+ }
+
+ private void appendPolygon(Edge e1, Edge e2 ) {
+ LOGGER.entering( DefaultClipper.class.getName(), "appendPolygon" );
+
+ final OutRec outRec1 = polyOuts.get( e1.outIdx );
+ final OutRec outRec2 = polyOuts.get( e2.outIdx );
+ LOGGER.finest( "" + e1.outIdx );
+ LOGGER.finest( "" + e2.outIdx );
+
+ OutRec holeStateRec;
+ if (isOutRec1RightOfOutRec2( outRec1, outRec2 )) {
+ holeStateRec = outRec2;
+ }
+ else if (isOutRec1RightOfOutRec2( outRec2, outRec1 )) {
+ holeStateRec = outRec1;
+ }
+ else {
+ holeStateRec = Path.OutPt.getLowerMostRec( outRec1, outRec2 );
+ }
+
+ //get the start and ends of both output polygons and
+ //join E2 poly onto E1 poly and delete pointers to E2 ...
+ final Path.OutPt p1_lft = outRec1.getPoints();
+ final Path.OutPt p1_rt = p1_lft.prev;
+ final Path.OutPt p2_lft = outRec2.getPoints();
+ final Path.OutPt p2_rt = p2_lft.prev;
+
+ LOGGER.finest( "p1_lft.getPointCount() = " + Path.OutPt.getPointCount( p1_lft ) );
+ LOGGER.finest( "p1_rt.getPointCount() = " + Path.OutPt.getPointCount( p1_rt ) );
+ LOGGER.finest( "p2_lft.getPointCount() = " + Path.OutPt.getPointCount( p2_lft ) );
+ LOGGER.finest( "p2_rt.getPointCount() = " + Path.OutPt.getPointCount( p2_rt ) );
+
+ //join e2 poly onto e1 poly and delete pointers to e2 ...
+ if (e1.side == Edge.Side.LEFT) {
+ if (e2.side == Edge.Side.LEFT) {
+ //z y x a b c
+ p2_lft.reversePolyPtLinks();
+ p2_lft.next = p1_lft;
+ p1_lft.prev = p2_lft;
+ p1_rt.next = p2_rt;
+ p2_rt.prev = p1_rt;
+ outRec1.setPoints( 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.setPoints( p2_lft );
+ }
+ }
+ else {
+ if (e2.side == Edge.Side.RIGHT) {
+ //a b c z y x
+ p2_lft.reversePolyPtLinks();
+ 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;
+ }
+ }
+ outRec1.bottomPt = null;
+ if (holeStateRec.equals( outRec2 )) {
+ if (outRec2.firstLeft != outRec1) {
+ outRec1.firstLeft = outRec2.firstLeft;
+ }
+ outRec1.isHole = outRec2.isHole;
+ }
+ outRec2.setPoints( null );
+ outRec2.bottomPt = null;
+
+ outRec2.firstLeft = outRec1;
+
+ final int OKIdx = e1.outIdx;
+ final int ObsoleteIdx = e2.outIdx;
+
+ e1.outIdx = Edge.UNASSIGNED; //nb: safe because we only get here via AddLocalMaxPoly
+ e2.outIdx = Edge.UNASSIGNED;
+
+ Edge e = activeEdges;
+ while (e != null) {
+ if (e.outIdx == ObsoleteIdx) {
+ e.outIdx = OKIdx;
+ e.side = e1.side;
+ break;
+ }
+ e = e.nextInAEL;
+ }
+ outRec2.Idx = outRec1.Idx;
+ }
+
+ //------------------------------------------------------------------------------
+
+ private void buildIntersectList( long topY ) {
+ if (activeEdges == null) {
+ return;
+ }
+
+ //prepare for sorting ...
+ Edge e = activeEdges;
+ sortedEdges = e;
+ while (e != null) {
+ e.prevInSEL = e.prevInAEL;
+ e.nextInSEL = e.nextInAEL;
+ e.getCurrent().setX( Edge.topX( e, topY ) );
+ e = e.nextInAEL;
+ }
+
+ //bubblesort ...
+ boolean isModified = true;
+ while (isModified && sortedEdges != null) {
+ isModified = false;
+ e = sortedEdges;
+ while (e.nextInSEL != null) {
+ final Edge eNext = e.nextInSEL;
+ final LongPoint[] pt = new LongPoint[1];
+ if (e.getCurrent().getX() > eNext.getCurrent().getX()) {
+ intersectPoint( e, eNext, pt );
+ if (pt[0].getY() < topY) {
+ pt[0] = new LongPoint( Edge.topX( e, topY ), topY );
+ }
+ final IntersectNode newNode = new IntersectNode();
+ newNode.edge1 = e;
+ newNode.Edge2 = eNext;
+ newNode.setPt( new LongPoint( pt[0] ) ); // TODO is new instance necessary?
+ intersectList.add( newNode );
+
+ swapPositionsInSEL( e, eNext );
+ isModified = true;
+ }
+ else {
+ e = eNext;
+ }
+ }
+ if (e.prevInSEL != null) {
+ e.prevInSEL.nextInSEL = null;
+ }
+ else {
+ break;
+ }
+ }
+ sortedEdges = null;
+ }
+
+ //------------------------------------------------------------------------------
+
+ private void buildResult( Paths polyg ) {
+ polyg.clear();
+ for (int i = 0; i < polyOuts.size(); i++) {
+ final OutRec outRec = polyOuts.get( i );
+ if (outRec.getPoints() == null) {
+ continue;
+ }
+ Path.OutPt p = outRec.getPoints().prev;
+ final int cnt = Path.OutPt.getPointCount( p );
+ LOGGER.finest( "cnt = " + cnt );
+ if (cnt < 2) {
+ continue;
+ }
+ final Path pg = new Path( cnt );
+ for (int j = 0; j < cnt; j++) {
+ pg.add( new LongPoint( p.getPt() ) );
+ p = p.prev;
+ }
+ polyg.add( pg );
+ }
+ }
+
+ private void buildResult2( PolyTree polytree ) {
+ polytree.Clear();
+
+ //add each output polygon/contour to polytree ...
+ for (int i = 0; i < polyOuts.size(); i++) {
+ final OutRec outRec = polyOuts.get( i );
+ final int cnt = Path.OutPt.getPointCount( outRec.getPoints() );
+ if (outRec.isOpen && cnt < 2 || !outRec.isOpen && cnt < 3) {
+ continue;
+ }
+ outRec.fixHoleLinkage();
+ final PolyNode pn = new PolyNode();
+ polytree.getAllPolys().add( pn );
+ outRec.polyNode = pn;
+ Path.OutPt op = outRec.getPoints().prev;
+ for (int j = 0; j < cnt; j++) {
+ pn.getPolygon().add( op.getPt() );
+ op = op.prev;
+ }
+ }
+
+ //fixup PolyNode links etc ...
+ for (int i = 0; i < polyOuts.size(); i++) {
+ final OutRec outRec = polyOuts.get( i );
+ if (outRec.polyNode == null) {
+ continue;
+ }
+ else if (outRec.isOpen) {
+ outRec.polyNode.setOpen( true );
+ polytree.addChild( outRec.polyNode );
+ }
+ else if (outRec.firstLeft != null && outRec.firstLeft.polyNode != null) {
+ outRec.firstLeft.polyNode.addChild( outRec.polyNode );
+ }
+ else {
+ polytree.addChild( outRec.polyNode );
+ }
+ }
+ }
+
+ private void copyAELToSEL() {
+ Edge e = activeEdges;
+ sortedEdges = e;
+ while (e != null) {
+ e.prevInSEL = e.prevInAEL;
+ e.nextInSEL = e.nextInAEL;
+ e = e.nextInAEL;
+ }
+ }
+
+ private boolean deleteFromSEL( Edge[] e ) {
+ LOGGER.entering( DefaultClipper.class.getName(), "deleteFromSEL" );
+
+ //Pop edge from front of SEL (ie SEL is a FILO list)
+ e[0] = sortedEdges;
+ if (e[0] == null) {
+ return false;
+ }
+ final Edge oldE = e[0];
+ sortedEdges = e[0].nextInSEL;
+ if (sortedEdges != null) {
+ sortedEdges.prevInSEL = null;
+ }
+ oldE.nextInSEL = null;
+ oldE.prevInSEL = null;
+ return true;
+ }
+
+ private boolean doHorzSegmentsOverlap( long seg1a, long seg1b, long seg2a, long seg2b ) {
+ if (seg1a > seg1b) {
+ final long tmp = seg1a;
+ seg1a = seg1b;
+ seg1b = tmp;
+ }
+ if (seg2a > seg2b) {
+ final long tmp = seg2a;
+ seg2a = seg2b;
+ seg2b = tmp;
+ }
+ return seg1a < seg2b && seg2a < seg1b;
+ }
+
+ private void doMaxima( Edge e ) {
+ final Edge eMaxPair = e.getMaximaPairEx();
+ if (eMaxPair == null) {
+ if (e.outIdx >= 0) {
+ addOutPt( e, e.getTop() );
+ }
+ deleteFromAEL( e );
+ return;
+ }
+
+ Edge eNext = e.nextInAEL;
+ while (eNext != null && eNext != eMaxPair) {
+ final LongPoint tmp = new LongPoint( e.getTop() );
+ intersectEdges( e, eNext, tmp );
+ e.setTop( new LongPoint( tmp ) );
+ swapPositionsInAEL( e, eNext );
+ eNext = e.nextInAEL;
+ }
+
+ if (e.outIdx == Edge.UNASSIGNED && eMaxPair.outIdx == Edge.UNASSIGNED) {
+ deleteFromAEL( e );
+ deleteFromAEL( eMaxPair );
+ }
+ else if (e.outIdx >= 0 && eMaxPair.outIdx >= 0) {
+ if (e.outIdx >= 0) {
+ addLocalMaxPoly( e, eMaxPair, e.getTop() );
+ }
+ deleteFromAEL( e );
+ deleteFromAEL( eMaxPair );
+ }
+
+ else if (e.windDelta == 0) {
+ if (e.outIdx >= 0) {
+ addOutPt( e, e.getTop() );
+ e.outIdx = Edge.UNASSIGNED;
+ }
+ deleteFromAEL( e );
+
+ if (eMaxPair.outIdx >= 0) {
+ addOutPt( eMaxPair, e.getTop() );
+ eMaxPair.outIdx = Edge.UNASSIGNED;
+ }
+ deleteFromAEL( eMaxPair );
+ }
+ else {
+ throw new IllegalStateException( "DoMaxima error" );
+ }
+ }
+
+ //------------------------------------------------------------------------------
+
+ private void doSimplePolygons() {
+ int i = 0;
+ while (i < polyOuts.size()) {
+ final OutRec outrec = polyOuts.get( i++ );
+ Path.OutPt op = outrec.getPoints();
+ if (op == null || outrec.isOpen) {
+ continue;
+ }
+ do //for each Pt in Polygon until duplicate found do ...
+ {
+ Path.OutPt op2 = op.next;
+ while (op2 != outrec.getPoints()) {
+ if (op.getPt().equals( op2.getPt() ) && !op2.next.equals( op ) && !op2.prev.equals( op )) {
+ //split the polygon into two ...
+ final Path.OutPt op3 = op.prev;
+ final Path.OutPt op4 = op2.prev;
+ op.prev = op4;
+ op4.next = op;
+ op2.prev = op3;
+ op3.next = op2;
+
+ outrec.setPoints( op );
+ final OutRec outrec2 = createOutRec();
+ outrec2.setPoints( op2 );
+ updateOutPtIdxs( outrec2 );
+ if (poly2ContainsPoly1( outrec2.getPoints(), outrec.getPoints() )) {
+ //OutRec2 is contained by OutRec1 ...
+ outrec2.isHole = !outrec.isHole;
+ outrec2.firstLeft = outrec;
+ if (usingPolyTree) {
+ fixupFirstLefts2( outrec2, outrec );
+ }
+ }
+ else if (poly2ContainsPoly1( outrec.getPoints(), outrec2.getPoints() )) {
+ //OutRec1 is contained by OutRec2 ...
+ outrec2.isHole = outrec.isHole;
+ outrec.isHole = !outrec2.isHole;
+ outrec2.firstLeft = outrec.firstLeft;
+ outrec.firstLeft = outrec2;
+ if (usingPolyTree) {
+ fixupFirstLefts2( outrec, outrec2 );
+ }
+ }
+ else {
+ //the 2 polygons are separate ...
+ outrec2.isHole = outrec.isHole;
+ outrec2.firstLeft = outrec.firstLeft;
+ if (usingPolyTree) {
+ fixupFirstLefts1( outrec, outrec2 );
+ }
+ }
+ op2 = op; //ie get ready for the next iteration
+ }
+ op2 = op2.next;
+ }
+ op = op.next;
+ }
+ while (op != outrec.getPoints());
+ }
+ }
+
+ //------------------------------------------------------------------------------
+
+ private boolean EdgesAdjacent( IntersectNode inode ) {
+ return inode.edge1.nextInSEL == inode.Edge2 || inode.edge1.prevInSEL == inode.Edge2;
+ }
+
+ //------------------------------------------------------------------------------
+
+ @Override
+ public boolean execute(ClipType clipType, Paths solution ) {
+ return execute( clipType, solution, PolyFillType.EVEN_ODD, PolyFillType.EVEN_ODD );
+ }
+
+ @Override
+ public boolean execute(ClipType clipType, PolyTree polytree ) {
+ return execute( clipType, polytree, PolyFillType.EVEN_ODD, PolyFillType.EVEN_ODD );
+ }
+
+ @Override
+ public boolean execute(ClipType clipType, Paths solution, PolyFillType subjFillType, PolyFillType clipFillType ) {
+
+ synchronized (this) {
+
+ if (hasOpenPaths) {
+ throw new IllegalStateException( "Error: PolyTree struct is needed for open path clipping." );
+ }
+
+ solution.clear();
+ this.subjFillType = subjFillType;
+ this.clipFillType = clipFillType;
+ this.clipType = clipType;
+ usingPolyTree = false;
+ boolean succeeded;
+ try {
+ succeeded = executeInternal();
+ //build the return polygons ...
+ if (succeeded) {
+ buildResult( solution );
+ }
+ return succeeded;
+ }
+ finally {
+ polyOuts.clear();
+
+ }
+ }
+
+ }
+
+ @Override
+ public boolean execute(ClipType clipType, PolyTree polytree, PolyFillType subjFillType, PolyFillType clipFillType ) {
+ synchronized (this) {
+ this.subjFillType = subjFillType;
+ this.clipFillType = clipFillType;
+ this.clipType = clipType;
+ usingPolyTree = true;
+ boolean succeeded;
+ try {
+ succeeded = executeInternal();
+ //build the return polygons ...
+ if (succeeded) {
+ buildResult2( polytree );
+ }
+ }
+ finally {
+ polyOuts.clear();
+ }
+ return succeeded;
+ }
+ }
+
+ //------------------------------------------------------------------------------
+
+ private boolean executeInternal() {
+ try {
+ reset();
+ sortedEdges = null;
+ maxima = null;
+
+ long[] botY = new long[1], topY = new long[1];
+ if (!popScanbeam( botY )) return false;
+ insertLocalMinimaIntoAEL( botY[0] );
+ while ( popScanbeam( topY ) || localMinimaPending()) {
+ processHorizontals();
+ ghostJoins.clear();
+ if (!processIntersections( topY[0] )) {
+ return false;
+ }
+ processEdgesAtTopOfScanbeam( topY[0] );
+ botY[0] = topY[0];
+ insertLocalMinimaIntoAEL( botY[0] );
+ }
+
+ //fix orientations ...
+ for (OutRec outRec : polyOuts) {
+ if (outRec.getPoints() == null || outRec.isOpen) {
+ continue;
+ }
+ if ((outRec.isHole ^ reverseSolution) == outRec.area() > 0) {
+ outRec.getPoints().reversePolyPtLinks();
+ }
+ }
+
+ joinCommonEdges();
+
+ for (OutRec outRec : polyOuts) {
+ if (outRec.getPoints() == null) {
+ continue;
+ }
+ else if (outRec.isOpen) {
+ fixupOutPolygon( outRec );
+ }
+ else {
+ fixupOutPolygon( outRec );
+ }
+ }
+
+ if (strictlySimple) {
+ doSimplePolygons();
+ }
+ return true;
+ }
+ //catch { return false; }
+ finally {
+ joins.clear();
+ ghostJoins.clear();
+ }
+ }
+
+ //------------------------------------------------------------------------------
+
+ private void fixupFirstLefts1(OutRec OldOutRec, OutRec NewOutRec ) {
+ for (OutRec outRec : polyOuts) {
+ final OutRec firstLeft = OutRec.parseFirstLeft( outRec.firstLeft );
+ if (outRec.getPoints() != null && firstLeft == OldOutRec) {
+ if (poly2ContainsPoly1( outRec.getPoints(), NewOutRec.getPoints() )) {
+ outRec.firstLeft = NewOutRec;
+ }
+ }
+ }
+ }
+ //------------------------------------------------------------------------------
+
+ private void fixupFirstLefts2(OutRec innerOutRec, OutRec outerOutRec ) {
+ //A polygon has split into two such that one is now the inner of the other.
+ //It's possible that these polygons now wrap around other polygons, so check
+ //every polygon that's also contained by OuterOutRec's FirstLeft container
+ //(including nil) to see if they've become inner to the new inner polygon ...
+ final OutRec orfl = outerOutRec.firstLeft;
+ for (OutRec outRec : polyOuts) {
+ if (outRec.getPoints() == null || outRec == outerOutRec || outRec == innerOutRec) {
+ continue;
+ }
+ final OutRec firstLeft = OutRec.parseFirstLeft( outRec.firstLeft );
+ if (firstLeft != orfl && firstLeft != innerOutRec && firstLeft != outerOutRec) {
+ continue;
+ }
+ if (poly2ContainsPoly1( outRec.getPoints(), innerOutRec.getPoints() )) {
+ outRec.firstLeft = innerOutRec;
+ }
+ else if (poly2ContainsPoly1( outRec.getPoints(), outerOutRec.getPoints() )) {
+ outRec.firstLeft = outerOutRec;
+ }
+ else if (outRec.firstLeft == innerOutRec || outRec.firstLeft == outerOutRec) {
+ outRec.firstLeft = orfl;
+ }
+ }
+ }
+ //----------------------------------------------------------------------
+
+ private void fixupFirstLefts3(OutRec oldOutRec, OutRec newOutRec ) {
+ //same as FixupFirstLefts1 but doesn't call Poly2ContainsPoly1()
+ for (OutRec outRec : polyOuts) {
+ final OutRec firstLeft = OutRec.parseFirstLeft( outRec.firstLeft );
+ if (outRec.getPoints() != null && firstLeft == oldOutRec) {
+ outRec.firstLeft = newOutRec;
+ }
+ }
+ }
+
+ private boolean fixupIntersectionOrder() {
+ //pre-condition: intersections are sorted bottom-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 ...
+ Collections.sort( intersectList, intersectNodeComparer );
+
+ copyAELToSEL();
+ final int cnt = intersectList.size();
+ for (int i = 0; i < cnt; i++) {
+ if (!EdgesAdjacent( intersectList.get( i ) )) {
+ int j = i + 1;
+ while (j < cnt && !EdgesAdjacent( intersectList.get( j ) )) {
+ j++;
+ }
+ if (j == cnt) {
+ return false;
+ }
+
+ final IntersectNode tmp = intersectList.get( i );
+ intersectList.set( i, intersectList.get( j ) );
+ intersectList.set( j, tmp );
+
+ }
+ swapPositionsInSEL( intersectList.get( i ).edge1, intersectList.get( i ).Edge2 );
+ }
+ return true;
+ }
+
+ //----------------------------------------------------------------------
+
+ private void fixupOutPolyline( OutRec outrec ) {
+ Path.OutPt pp = outrec.getPoints();
+ Path.OutPt lastPP = pp.prev;
+ while (pp != lastPP) {
+ pp = pp.next;
+ if (pp.getPt() == pp.prev.getPt()) {
+ if (pp == lastPP) {
+ lastPP = pp.prev;
+ }
+ Path.OutPt tmpPP = pp.prev;
+ tmpPP.next = pp.next;
+ pp.next.prev = tmpPP;
+ pp = tmpPP;
+ }
+ }
+ if (pp == pp.prev) {
+ outrec.setPoints( null );
+ }
+ }
+
+ //------------------------------------------------------------------------------
+
+ private void fixupOutPolygon( OutRec outRec ) {
+ //FixupOutPolygon() - removes duplicate points and simplifies consecutive
+ //parallel edges by removing the middle vertex.
+ Path.OutPt lastOK = null;
+ outRec.bottomPt = null;
+ Path.OutPt pp = outRec.getPoints();
+ final boolean preserveCol = preserveCollinear || strictlySimple;
+ for (;;) {
+ if (pp.prev == pp || pp.prev == pp.next) {
+ outRec.setPoints( null );
+ return;
+ }
+ //test for duplicate points and collinear edges ...
+ if (pp.getPt().equals( pp.next.getPt() ) || pp.getPt().equals( pp.prev.getPt() )
+ || Point.slopesEqual( pp.prev.getPt(), pp.getPt(), pp.next.getPt() )
+ && (!preserveCol || !Point.isPt2BetweenPt1AndPt3( pp.prev.getPt(), pp.getPt(), pp.next.getPt() ))) {
+ lastOK = null;
+ pp.prev.next = pp.next;
+ pp.next.prev = pp.prev;
+ pp = pp.prev;
+ }
+ else if (pp == lastOK) {
+ break;
+ }
+ else {
+ if (lastOK == null) {
+ lastOK = pp;
+ }
+ pp = pp.next;
+ }
+ }
+ outRec.setPoints( pp );
+ }
+
+ private OutRec getOutRec(int idx ) {
+ OutRec outrec = polyOuts.get( idx );
+ while (outrec != polyOuts.get( outrec.Idx )) {
+ outrec = polyOuts.get( outrec.Idx );
+ }
+ return outrec;
+ }
+
+ private void insertEdgeIntoAEL(Edge edge, Edge startEdge ) {
+ LOGGER.entering( DefaultClipper.class.getName(), "insertEdgeIntoAEL" );
+
+ if (activeEdges == null) {
+ edge.prevInAEL = null;
+ edge.nextInAEL = null;
+ LOGGER.finest( "Edge " + edge.outIdx + " -> " + null );
+ activeEdges = edge;
+ }
+ else if (startEdge == null && Edge.doesE2InsertBeforeE1( activeEdges, edge )) {
+ edge.prevInAEL = null;
+ edge.nextInAEL = activeEdges;
+ LOGGER.finest( "Edge " + edge.outIdx + " -> " + edge.nextInAEL.outIdx );
+ activeEdges.prevInAEL = edge;
+ activeEdges = edge;
+ }
+ else {
+ LOGGER.finest( "activeEdges unchanged" );
+ if (startEdge == null) {
+ startEdge = activeEdges;
+ }
+ while (startEdge.nextInAEL != null && !Edge.doesE2InsertBeforeE1( startEdge.nextInAEL, edge )) {
+ startEdge = startEdge.nextInAEL;
+ }
+ edge.nextInAEL = startEdge.nextInAEL;
+ if (startEdge.nextInAEL != null) {
+ startEdge.nextInAEL.prevInAEL = edge;
+ }
+ edge.prevInAEL = startEdge;
+ startEdge.nextInAEL = edge;
+ }
+ }
+
+ //------------------------------------------------------------------------------
+
+ private void insertLocalMinimaIntoAEL( long botY ) {
+ LOGGER.entering( DefaultClipper.class.getName(), "insertLocalMinimaIntoAEL" );
+
+ LocalMinima[] lm = new LocalMinima[1];
+ while ( popLocalMinima( botY, lm )) {
+ final Edge lb = lm[0].leftBound;
+ final Edge rb = lm[0].rightBound;
+
+ Path.OutPt Op1 = null;
+ if (lb == null) {
+ insertEdgeIntoAEL( rb, null );
+ updateWindingCount( rb );
+ if (rb.isContributing( clipFillType, subjFillType, clipType )) {
+ Op1 = addOutPt( rb, rb.getBot() );
+ }
+ }
+ else if (rb == null) {
+ insertEdgeIntoAEL( lb, null );
+ updateWindingCount( lb );
+ if (lb.isContributing( clipFillType, subjFillType, clipType )) {
+ Op1 = addOutPt( lb, lb.getBot() );
+ }
+ insertScanbeam( lb.getTop().getY() );
+ }
+ else {
+ insertEdgeIntoAEL( lb, null );
+ insertEdgeIntoAEL( rb, lb );
+ updateWindingCount( lb );
+ rb.windCnt = lb.windCnt;
+ rb.windCnt2 = lb.windCnt2;
+ if (lb.isContributing( clipFillType, subjFillType, clipType )) {
+ Op1 = addLocalMinPoly( lb, rb, lb.getBot() );
+ }
+ insertScanbeam( lb.getTop().getY() );
+ }
+
+ if (rb != null) {
+ if (rb.isHorizontal()) {
+ if (rb.nextInLML != null) {
+ insertScanbeam( rb.nextInLML.getTop().getY() );
+ }
+ addEdgeToSEL( rb );
+ }
+ else {
+ insertScanbeam( rb.getTop().getY() );
+ }
+ }
+
+ if (lb == null || rb == null) {
+ continue;
+ }
+
+ //if output polygons share an Edge with a horizontal rb, they'll need joining later ...
+ if (Op1 != null && rb.isHorizontal() && ghostJoins.size() > 0 && rb.windDelta != 0) {
+ for (int i = 0; i < ghostJoins.size(); i++) {
+ //if the horizontal Rb and a 'ghost' horizontal overlap, then convert
+ //the 'ghost' join to a real join ready for later ...
+ final Join j = ghostJoins.get( i );
+ if (doHorzSegmentsOverlap( j.outPt1.getPt().getX(), j.getOffPt().getX(), rb.getBot().getX(), rb.getTop().getX() )) {
+ addJoin( j.outPt1, Op1, j.getOffPt() );
+ }
+ }
+ }
+
+ if (lb.outIdx >= 0 && lb.prevInAEL != null && lb.prevInAEL.getCurrent().getX() == lb.getBot().getX() && lb.prevInAEL.outIdx >= 0
+ && Point.slopesEqual( lb.prevInAEL.getCurrent(), lb.prevInAEL.getTop(), lb.getCurrent(), lb.getTop() ) && lb.windDelta != 0 && lb.prevInAEL.windDelta != 0) {
+ final Path.OutPt Op2 = addOutPt( lb.prevInAEL, lb.getBot() );
+ addJoin( Op1, Op2, lb.getTop() );
+ }
+
+ if (lb.nextInAEL != rb) {
+
+ if (rb.outIdx >= 0 && rb.prevInAEL.outIdx >= 0 && Point.slopesEqual( rb.prevInAEL.getCurrent(), rb.prevInAEL.getTop(), rb.getCurrent(), rb.getTop() ) && rb.windDelta != 0 && rb.prevInAEL.windDelta != 0) {
+ final Path.OutPt Op2 = addOutPt( rb.prevInAEL, rb.getBot() );
+ addJoin( Op1, Op2, rb.getTop() );
+ }
+
+ Edge e = lb.nextInAEL;
+ if (e != null) {
+ while (e != rb) {
+ //nb: For calculating winding counts etc, IntersectEdges() assumes
+ //that param1 will be to the right of param2 ABOVE the intersection ...
+ //nb: For calculating winding counts etc, IntersectEdges() assumes
+ //that param1 will be to the right of param2 ABOVE the intersection ...
+ intersectEdges( rb, e, lb.getCurrent() ); //order important here
+ e = e.nextInAEL;
+
+ }
+ }
+ }
+ }
+ }
+
+ //------------------------------------------------------------------------------
+
+ private void intersectEdges(Edge e1, Edge e2, LongPoint pt ) {
+ LOGGER.entering( DefaultClipper.class.getName(), "insersectEdges" );
+
+ //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 ...
+
+ final boolean e1Contributing = e1.outIdx >= 0;
+ final boolean e2Contributing = e2.outIdx >= 0;
+
+ setZ( pt, e1, e2 );
+
+ //if either edge is on an OPEN path ...
+ if (e1.windDelta == 0 || e2.windDelta == 0) {
+ //ignore subject-subject open path intersections UNLESS they
+ //are both open paths, AND they are both 'contributing maximas' ...
+ if (e1.windDelta == 0 && e2.windDelta == 0) {
+ return;
+ }
+ else if (e1.polyTyp == e2.polyTyp && e1.windDelta != e2.windDelta && clipType == ClipType.UNION) {
+ if (e1.windDelta == 0) {
+ if (e2Contributing) {
+ addOutPt( e1, pt );
+ if (e1Contributing) {
+ e1.outIdx = Edge.UNASSIGNED;
+ }
+ }
+ }
+ else {
+ if (e1Contributing) {
+ addOutPt( e2, pt );
+ if (e2Contributing) {
+ e2.outIdx = Edge.UNASSIGNED;
+ }
+ }
+ }
+ }
+ else if (e1.polyTyp != e2.polyTyp) {
+ if (e1.windDelta == 0 && Math.abs( e2.windCnt ) == 1 && (clipType != ClipType.UNION || e2.windCnt2 == 0)) {
+ addOutPt( e1, pt );
+ if (e1Contributing) {
+ e1.outIdx = Edge.UNASSIGNED;
+ }
+ }
+ else if (e2.windDelta == 0 && Math.abs( e1.windCnt ) == 1 && (clipType != ClipType.UNION || e1.windCnt2 == 0)) {
+ addOutPt( e2, pt );
+ if (e2Contributing) {
+ e2.outIdx = Edge.UNASSIGNED;
+ }
+ }
+ }
+ return;
+ }
+
+ //update winding counts...
+ //assumes that e1 will be to the Right of e2 ABOVE the intersection
+ if (e1.polyTyp == e2.polyTyp) {
+ if (e1.isEvenOddFillType( clipFillType, subjFillType )) {
+ final 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 (!e2.isEvenOddFillType( clipFillType, subjFillType )) {
+ e1.windCnt2 += e2.windDelta;
+ }
+ else {
+ e1.windCnt2 = e1.windCnt2 == 0 ? 1 : 0;
+ }
+ if (!e1.isEvenOddFillType( clipFillType, subjFillType )) {
+ e2.windCnt2 -= e1.windDelta;
+ }
+ else {
+ e2.windCnt2 = e2.windCnt2 == 0 ? 1 : 0;
+ }
+ }
+
+ PolyFillType e1FillType, e2FillType, e1FillType2, e2FillType2;
+ if (e1.polyTyp == PolyType.SUBJECT) {
+ e1FillType = subjFillType;
+ e1FillType2 = clipFillType;
+ }
+ else {
+ e1FillType = clipFillType;
+ e1FillType2 = subjFillType;
+ }
+ if (e2.polyTyp == PolyType.SUBJECT) {
+ e2FillType = subjFillType;
+ e2FillType2 = clipFillType;
+ }
+ else {
+ e2FillType = clipFillType;
+ e2FillType2 = subjFillType;
+ }
+
+ int e1Wc, e2Wc;
+ switch (e1FillType) {
+ case POSITIVE:
+ e1Wc = e1.windCnt;
+ break;
+ case NEGATIVE:
+ e1Wc = -e1.windCnt;
+ break;
+ default:
+ e1Wc = Math.abs( e1.windCnt );
+ break;
+ }
+ switch (e2FillType) {
+ case POSITIVE:
+ e2Wc = e2.windCnt;
+ break;
+ case NEGATIVE:
+ e2Wc = -e2.windCnt;
+ break;
+ default:
+ e2Wc = Math.abs( e2.windCnt );
+ break;
+ }
+
+ if (e1Contributing && e2Contributing) {
+ if (e1Wc != 0 && e1Wc != 1 || e2Wc != 0 && e2Wc != 1 || e1.polyTyp != e2.polyTyp && clipType != ClipType.XOR) {
+ addLocalMaxPoly( e1, e2, pt );
+ }
+ else {
+ addOutPt( e1, pt );
+ addOutPt( e2, pt );
+ Edge.swapSides( e1, e2 );
+ Edge.swapPolyIndexes( e1, e2 );
+ }
+ }
+ else if (e1Contributing) {
+ if (e2Wc == 0 || e2Wc == 1) {
+ addOutPt( e1, pt );
+ Edge.swapSides( e1, e2 );
+ Edge.swapPolyIndexes( e1, e2 );
+ }
+
+ }
+ else if (e2Contributing) {
+ if (e1Wc == 0 || e1Wc == 1) {
+ addOutPt( e2, pt );
+ Edge.swapSides( e1, e2 );
+ Edge.swapPolyIndexes( e1, e2 );
+ }
+ }
+ else if ((e1Wc == 0 || e1Wc == 1) && (e2Wc == 0 || e2Wc == 1)) {
+ //neither edge is currently contributing ...
+ int e1Wc2, e2Wc2;
+ switch (e1FillType2) {
+ case POSITIVE:
+ e1Wc2 = e1.windCnt2;
+ break;
+ case NEGATIVE:
+ e1Wc2 = -e1.windCnt2;
+ break;
+ default:
+ e1Wc2 = Math.abs( e1.windCnt2 );
+ break;
+ }
+ switch (e2FillType2) {
+ case POSITIVE:
+ e2Wc2 = e2.windCnt2;
+ break;
+ case NEGATIVE:
+ e2Wc2 = -e2.windCnt2;
+ break;
+ default:
+ e2Wc2 = Math.abs( e2.windCnt2 );
+ break;
+ }
+
+ if (e1.polyTyp != e2.polyTyp) {
+ addLocalMinPoly( e1, e2, pt );
+ }
+ else if (e1Wc == 1 && e2Wc == 1) {
+ switch (clipType) {
+ case INTERSECTION:
+ if (e1Wc2 > 0 && e2Wc2 > 0) {
+ addLocalMinPoly( e1, e2, pt );
+ }
+ break;
+ case UNION:
+ if (e1Wc2 <= 0 && e2Wc2 <= 0) {
+ addLocalMinPoly( e1, e2, pt );
+ }
+ break;
+ case DIFFERENCE:
+ if (e1.polyTyp == PolyType.CLIP && e1Wc2 > 0 && e2Wc2 > 0 || e1.polyTyp == PolyType.SUBJECT && e1Wc2 <= 0 && e2Wc2 <= 0) {
+ addLocalMinPoly( e1, e2, pt );
+ }
+ break;
+ case XOR:
+ addLocalMinPoly( e1, e2, pt );
+ break;
+ }
+ }
+ else {
+ Edge.swapSides( e1, e2 );
+ }
+ }
+ }
+
+ private void intersectPoint(Edge edge1, Edge edge2, LongPoint[] ipV ) {
+ final LongPoint ip = ipV[0] = new LongPoint();
+
+ double b1, b2;
+ //nb: with very large coordinate values, it's possible for SlopesEqual() to
+ //return false but for the edge.Dx value be equal due to double precision rounding.
+ if (edge1.deltaX == edge2.deltaX) {
+ ip.setY( edge1.getCurrent().getY() );
+ ip.setX( Edge.topX( edge1, ip.getY() ) );
+ return;
+ }
+
+ if (edge1.getDelta().getX() == 0) {
+ ip.setX( edge1.getBot().getX() );
+ if (edge2.isHorizontal()) {
+ ip.setY( edge2.getBot().getY() );
+ }
+ else {
+ b2 = edge2.getBot().getY() - edge2.getBot().getX() / edge2.deltaX;
+ ip.setY( Math.round( ip.getX() / edge2.deltaX + b2 ) );
+ }
+ }
+ else if (edge2.getDelta().getX() == 0) {
+ ip.setX( edge2.getBot().getX() );
+ if (edge1.isHorizontal()) {
+ ip.setY( edge1.getBot().getY() );
+ }
+ else {
+ b1 = edge1.getBot().getY() - edge1.getBot().getX() / edge1.deltaX;
+ ip.setY( Math.round( ip.getX() / edge1.deltaX + b1 ) );
+ }
+ }
+ else {
+ b1 = edge1.getBot().getX() - edge1.getBot().getY() * edge1.deltaX;
+ b2 = edge2.getBot().getX() - edge2.getBot().getY() * edge2.deltaX;
+ final double q = (b2 - b1) / (edge1.deltaX - edge2.deltaX);
+ ip.setY( Math.round( q ) );
+ if (Math.abs( edge1.deltaX ) < Math.abs( edge2.deltaX )) {
+ ip.setX( Math.round( edge1.deltaX * q + b1 ) );
+ }
+ else {
+ ip.setX( Math.round( edge2.deltaX * q + b2 ) );
+ }
+ }
+
+ if (ip.getY() < edge1.getTop().getY() || ip.getY() < edge2.getTop().getY()) {
+ if (edge1.getTop().getY() > edge2.getTop().getY()) {
+ ip.setY( edge1.getTop().getY() );
+ }
+ else {
+ ip.setY( edge2.getTop().getY() );
+ }
+ if (Math.abs( edge1.deltaX ) < Math.abs( edge2.deltaX )) {
+ ip.setX( Edge.topX( edge1, ip.getY() ) );
+ }
+ else {
+ ip.setX( Edge.topX( edge2, ip.getY() ) );
+ }
+ }
+ //finally, don't allow 'ip' to be BELOW curr.getY() (ie bottom of scanbeam) ...
+ if (ip.getY() > edge1.getCurrent().getY()) {
+ ip.setY( edge1.getCurrent().getY() );
+ //better to use the more vertical edge to derive X ...
+ if (Math.abs( edge1.deltaX ) > Math.abs( edge2.deltaX )) {
+ ip.setX( Edge.topX( edge2, ip.getY() ) );
+ }
+ else {
+ ip.setX( Edge.topX( edge1, ip.getY() ) );
+ }
+ }
+ }
+
+ private void joinCommonEdges() {
+ for (int i = 0; i < joins.size(); i++) {
+ final Join join = joins.get( i );
+
+ final OutRec outRec1 = getOutRec( join.outPt1.idx );
+ OutRec outRec2 = getOutRec( join.outPt2.idx );
+
+ if (outRec1.getPoints() == null || outRec2.getPoints() == null) {
+ continue;
+ }
+ if (outRec1.isOpen || outRec2.isOpen) {
+ continue;
+ }
+
+ //get the polygon fragment with the correct hole state (FirstLeft)
+ //before calling JoinPoints() ...
+ OutRec holeStateRec;
+ if (outRec1 == outRec2) {
+ holeStateRec = outRec1;
+ }
+ else if (isOutRec1RightOfOutRec2( outRec1, outRec2 )) {
+ holeStateRec = outRec2;
+ }
+ else if (isOutRec1RightOfOutRec2( outRec2, outRec1 )) {
+ holeStateRec = outRec1;
+ }
+ else {
+ holeStateRec = Path.OutPt.getLowerMostRec( outRec1, outRec2 );
+ }
+
+ if (!joinPoints( join, outRec1, outRec2 )) {
+ continue;
+ }
+
+ if (outRec1 == outRec2) {
+ //instead of joining two polygons, we've just created a new one by
+ //splitting one polygon into two.
+ outRec1.setPoints( join.outPt1 );
+ outRec1.bottomPt = null;
+ outRec2 = createOutRec();
+ outRec2.setPoints( join.outPt2 );
+
+ //update all OutRec2.Pts Idx's ...
+ updateOutPtIdxs( outRec2 );
+
+ if (poly2ContainsPoly1( outRec2.getPoints(), outRec1.getPoints() )) {
+ //outRec1 contains outRec2 ...
+ outRec2.isHole = !outRec1.isHole;
+ outRec2.firstLeft = outRec1;
+
+ if (usingPolyTree) {
+ fixupFirstLefts2( outRec2, outRec1 );
+ }
+
+ if ((outRec2.isHole ^ reverseSolution) == outRec2.area() > 0) {
+ outRec2.getPoints().reversePolyPtLinks();
+ }
+
+ }
+ else if (poly2ContainsPoly1( outRec1.getPoints(), outRec2.getPoints() )) {
+ //outRec2 contains outRec1 ...
+ outRec2.isHole = outRec1.isHole;
+ outRec1.isHole = !outRec2.isHole;
+ outRec2.firstLeft = outRec1.firstLeft;
+ outRec1.firstLeft = outRec2;
+
+ if (usingPolyTree) {
+ fixupFirstLefts2( outRec1, outRec2 );
+ }
+
+ if ((outRec1.isHole ^ reverseSolution) == outRec1.area() > 0) {
+ outRec1.getPoints().reversePolyPtLinks();
+ }
+ }
+ else {
+ //the 2 polygons are completely separate ...
+ outRec2.isHole = outRec1.isHole;
+ outRec2.firstLeft = outRec1.firstLeft;
+
+ //fixup FirstLeft pointers that may need reassigning to OutRec2
+ if (usingPolyTree) {
+ fixupFirstLefts1( outRec1, outRec2 );
+ }
+ }
+
+ }
+ else {
+ //joined 2 polygons together ...
+
+ outRec2.setPoints( null );
+ outRec2.bottomPt = null;
+ 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 (usingPolyTree) {
+ fixupFirstLefts3( outRec2, outRec1 );
+ }
+ }
+ }
+ }
+
+ private void processEdgesAtTopOfScanbeam( long topY ) {
+ LOGGER.entering( DefaultClipper.class.getName(), "processEdgesAtTopOfScanbeam" );
+
+ Edge e = activeEdges;
+ while (e != null) {
+ //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.
+ boolean IsMaximaEdge = e.isMaxima( topY );
+
+ if (IsMaximaEdge) {
+ final Edge eMaxPair = e.getMaximaPairEx();
+ IsMaximaEdge = eMaxPair == null || !eMaxPair.isHorizontal();
+ }
+
+ if (IsMaximaEdge) {
+ if (strictlySimple) insertMaxima( e.getTop().getX() );
+ final Edge ePrev = e.prevInAEL;
+ doMaxima( e );
+ if (ePrev == null) {
+ e = activeEdges;
+ }
+ else {
+ e = ePrev.nextInAEL;
+ }
+ }
+ else {
+ //2. promote horizontal edges, otherwise update Curr.getX() and Curr.getY() ...
+ if (e.isIntermediate( topY ) && e.nextInLML.isHorizontal()) {
+ final Edge[] t = new Edge[] { e };
+ updateEdgeIntoAEL( t );
+ e = t[0];
+ if (e.outIdx >= 0) {
+ addOutPt( e, e.getBot() );
+ }
+ addEdgeToSEL( e );
+ }
+ else {
+ e.getCurrent().setX( Edge.topX( e, topY ) );
+ e.getCurrent().setY( topY );
+ if (e.getTop().getY() == topY) {
+ e.getCurrent().setZ( e.getTop().getZ() );
+ }
+ else if (e.getBot().getY() == topY) {
+ e.getCurrent().setZ( e.getBot().getZ() );
+ }
+ else {
+ e.getCurrent().setZ( 0L );
+ }
+ }
+
+ //When StrictlySimple and 'e' is being touched by another edge, then
+ //make sure both edges have a vertex here ...
+ if (strictlySimple) {
+ final Edge ePrev = e.prevInAEL;
+ if (e.outIdx >= 0 && e.windDelta != 0 && ePrev != null && ePrev.outIdx >= 0 && ePrev.getCurrent().getX() == e.getCurrent().getX()
+ && ePrev.windDelta != 0) {
+ final LongPoint ip = new LongPoint( e.getCurrent() );
+
+ setZ( ip, ePrev, e );
+
+ final Path.OutPt op = addOutPt( ePrev, ip );
+ final Path.OutPt op2 = addOutPt( e, ip );
+ addJoin( op, op2, ip ); //StrictlySimple (type-3) join
+ }
+ }
+
+ e = e.nextInAEL;
+ }
+ }
+
+ //3. Process horizontals at the Top of the scanbeam ...
+ processHorizontals();
+ maxima = null;
+
+ //4. Promote intermediate vertices ...
+ e = activeEdges;
+ while (e != null) {
+ if (e.isIntermediate( topY )) {
+ Path.OutPt op = null;
+ if (e.outIdx >= 0) {
+ op = addOutPt( e, e.getTop() );
+ }
+ final Edge[] t = new Edge[] { e };
+ updateEdgeIntoAEL( t );
+ e = t[0];
+
+ //if output polygons share an edge, they'll need joining later ...
+ final Edge ePrev = e.prevInAEL;
+ final Edge eNext = e.nextInAEL;
+ if (ePrev != null && ePrev.getCurrent().getX() == e.getBot().getX() && ePrev.getCurrent().getY() == e.getBot().getY() && op != null
+ && ePrev.outIdx >= 0 && ePrev.getCurrent().getY() > ePrev.getTop().getY() && Point.slopesEqual( e.getCurrent(), e.getTop(), ePrev.getCurrent(), ePrev.getTop() ) && e.windDelta != 0
+ && ePrev.windDelta != 0) {
+ final Path.OutPt op2 = addOutPt( ePrev, e.getBot() );
+ addJoin( op, op2, e.getTop() );
+ }
+ else if (eNext != null && eNext.getCurrent().getX() == e.getBot().getX() && eNext.getCurrent().getY() == e.getBot().getY() && op != null
+ && eNext.outIdx >= 0 && eNext.getCurrent().getY() > eNext.getTop().getY() && Point.slopesEqual( e.getCurrent(), e.getTop(), eNext.getCurrent(), eNext.getTop() ) && e.windDelta != 0
+ && eNext.windDelta != 0) {
+ final Path.OutPt op2 = addOutPt( eNext, e.getBot() );
+ addJoin( op, op2, e.getTop() );
+ }
+ }
+ e = e.nextInAEL;
+ }
+ LOGGER.exiting( DefaultClipper.class.getName(), "processEdgesAtTopOfScanbeam" );
+ }
+
+ private void processHorizontal( Edge horzEdge ) {
+ LOGGER.entering( DefaultClipper.class.getName(), "isHorizontal" );
+ final Direction[] dir = new Direction[1];
+ final long[] horzLeft = new long[1], horzRight = new long[1];
+ boolean isOpen = horzEdge.windDelta == 0;
+
+ getHorzDirection( horzEdge, dir, horzLeft, horzRight );
+
+ Edge eLastHorz = horzEdge, eMaxPair = null;
+ while (eLastHorz.nextInLML != null && eLastHorz.nextInLML.isHorizontal()) {
+ eLastHorz = eLastHorz.nextInLML;
+ }
+ if (eLastHorz.nextInLML == null) {
+ eMaxPair = eLastHorz.getMaximaPair();
+ }
+
+ Maxima currMax = maxima;
+ if (currMax != null) {
+ //get the first maxima in range (X) ...
+ if (dir[0] == Direction.LEFT_TO_RIGHT) {
+ while (currMax != null && currMax.x <= horzEdge.getBot().getX()) {
+ currMax = currMax.next;
+ }
+ if (currMax != null && currMax.x >= eLastHorz.getTop().getX()) {
+ currMax = null;
+ }
+ }
+ else {
+ while (currMax.next != null && currMax.next.x < horzEdge.getBot().getX()) {
+ currMax = currMax.next;
+ }
+ if (currMax.x <= eLastHorz.getTop().getX()) {
+ currMax = null;
+ }
+ }
+ }
+
+ Path.OutPt op1;
+ for (;;) { //loop through consec. horizontal edges
+ final boolean IsLastHorz = horzEdge == eLastHorz;
+ Edge e = horzEdge.getNextInAEL( dir[0] );
+ while (e != null) {
+ //this code block inserts extra coords into horizontal edges (in output
+ //polygons) whereever maxima touch these horizontal edges. This helps
+ //'simplifying' polygons (ie if the Simplify property is set).
+ if (currMax != null) {
+ if (dir[0] == Direction.LEFT_TO_RIGHT) {
+ while (currMax != null && currMax.x < e.getCurrent().getX()) {
+ if (horzEdge.outIdx >= 0 && !isOpen) {
+ addOutPt( horzEdge, new LongPoint( currMax.x, horzEdge.getBot().getY() ) );
+ }
+ currMax = currMax.next;
+ }
+ }
+ else {
+ while (currMax != null && currMax.x > e.getCurrent().getX()) {
+ if (horzEdge.outIdx >= 0 && !isOpen) {
+ addOutPt( horzEdge, new LongPoint( currMax.x, horzEdge.getBot().getY() ) );
+ }
+ currMax = currMax.prev;
+ }
+ }
+ }
+
+ if ((dir[0] == Direction.LEFT_TO_RIGHT && e.getCurrent().getX() > horzRight[0]) ||
+ (dir[0] == Direction.RIGHT_TO_LEFT && e.getCurrent().getX() < horzLeft[0])) break;
+
+ //Also break if we've got to the end of an intermediate horizontal edge ...
+ //nb: Smaller Dx's are to the right of larger Dx's ABOVE the horizontal.
+ if (e.getCurrent().getX() == horzEdge.getTop().getX() && horzEdge.nextInLML != null && e.deltaX < horzEdge.nextInLML.deltaX) {
+ break;
+ }
+
+ if (horzEdge.outIdx >= 0 && !isOpen) { //note: may be done multiple times
+ if (dir[0] == Direction.LEFT_TO_RIGHT) setZ( e.getCurrent(), horzEdge, e );
+ else setZ( e.getCurrent(), e, horzEdge );
+
+ op1 = addOutPt( horzEdge, e.getCurrent() );
+ Edge eNextHorz = sortedEdges;
+ while (eNextHorz != null) {
+ if (eNextHorz.outIdx >= 0 &&
+ doHorzSegmentsOverlap( horzEdge.getBot().getX(),
+ horzEdge.getTop().getX(), eNextHorz.getBot().getX(), eNextHorz.getTop().getX() ))
+ {
+ Path.OutPt op2 = getLastOutPt( eNextHorz );
+ addJoin( op2, op1, eNextHorz.getTop() );
+ }
+ eNextHorz = eNextHorz.nextInSEL;
+ }
+ addGhostJoin( op1, horzEdge.getBot() );
+ }
+
+ //OK, so far we're still in range of the horizontal Edge but make sure
+ //we're at the last of consec. horizontals when matching with eMaxPair
+ if (e == eMaxPair && IsLastHorz) {
+ if (horzEdge.outIdx >= 0) {
+ addLocalMaxPoly( horzEdge, eMaxPair, horzEdge.getTop() );
+ }
+ deleteFromAEL( horzEdge );
+ deleteFromAEL( eMaxPair );
+ return;
+ }
+
+ if (dir[0] == Direction.LEFT_TO_RIGHT) {
+ final LongPoint Pt = new LongPoint( e.getCurrent().getX(), horzEdge.getCurrent().getY() );
+ intersectEdges( horzEdge, e, Pt );
+ }
+ else {
+ final LongPoint Pt = new LongPoint( e.getCurrent().getX(), horzEdge.getCurrent().getY() );
+ intersectEdges( e, horzEdge, Pt );
+ }
+ final Edge eNext = e.getNextInAEL( dir[0] );
+ swapPositionsInAEL( horzEdge, e );
+ e = eNext;
+ } //end while
+
+ //Break out of loop if HorzEdge.NextInLML is not also horizontal ...
+ if (horzEdge.nextInLML == null || !horzEdge.nextInLML.isHorizontal()) break;
+
+ final Edge[] t = new Edge[] { horzEdge };
+ updateEdgeIntoAEL( t );
+ horzEdge = t[0];
+ if (horzEdge.outIdx >= 0) {
+ addOutPt( horzEdge, horzEdge.getBot() );
+ }
+ getHorzDirection( horzEdge, dir, horzLeft, horzRight );
+
+ } //end for (;;)
+
+ if (horzEdge.nextInLML != null) {
+ if (horzEdge.outIdx >= 0) {
+ op1 = addOutPt( horzEdge, horzEdge.getTop() );
+ final Edge[] t = new Edge[] { horzEdge };
+ updateEdgeIntoAEL( t );
+ horzEdge = t[0];
+
+ if (horzEdge.windDelta == 0) {
+ return;
+ }
+ //nb: HorzEdge is no longer horizontal here
+ final Edge ePrev = horzEdge.prevInAEL;
+ final Edge eNext = horzEdge.nextInAEL;
+ if (ePrev != null && ePrev.getCurrent().getX() == horzEdge.getBot().getX() && ePrev.getCurrent().getY() == horzEdge.getBot().getY()
+ && ePrev.windDelta != 0 && ePrev.outIdx >= 0 && ePrev.getCurrent().getY() > ePrev.getTop().getY()
+ && Edge.slopesEqual( horzEdge, ePrev )) {
+ final Path.OutPt op2 = addOutPt( ePrev, horzEdge.getBot() );
+ addJoin( op1, op2, horzEdge.getTop() );
+ }
+ else if (eNext != null && eNext.getCurrent().getX() == horzEdge.getBot().getX() && eNext.getCurrent().getY() == horzEdge.getBot().getY()
+ && eNext.windDelta != 0 && eNext.outIdx >= 0 && eNext.getCurrent().getY() > eNext.getTop().getY()
+ && Edge.slopesEqual( horzEdge, eNext )) {
+ final Path.OutPt op2 = addOutPt( eNext, horzEdge.getBot() );
+ addJoin( op1, op2, horzEdge.getTop() );
+ }
+ }
+ else {
+ final Edge[] t = new Edge[] { horzEdge };
+ updateEdgeIntoAEL( t );
+ horzEdge = t[0];
+ }
+ }
+ else {
+ if (horzEdge.outIdx >= 0) {
+ addOutPt( horzEdge, horzEdge.getTop() );
+ }
+ deleteFromAEL( horzEdge );
+ }
+ }
+
+ //------------------------------------------------------------------------------
+
+ private void processHorizontals() {
+ Edge[] horzEdge = new Edge[1]; //m_SortedEdges;
+ while ( deleteFromSEL( horzEdge) ) {
+ processHorizontal( horzEdge[0] );
+ }
+ }
+
+ //------------------------------------------------------------------------------
+
+ private boolean processIntersections( long topY ) {
+ LOGGER.entering( DefaultClipper.class.getName(), "processIntersections" );
+
+ if (activeEdges == null) {
+ return true;
+ }
+ try {
+ buildIntersectList( topY );
+ if (intersectList.size() == 0) {
+ return true;
+ }
+ if (intersectList.size() == 1 || fixupIntersectionOrder()) {
+ processIntersectList();
+ }
+ else {
+ return false;
+ }
+ }
+ catch (final Exception e) {
+ sortedEdges = null;
+ intersectList.clear();
+ throw new IllegalStateException( "ProcessIntersections error", e );
+ }
+ sortedEdges = null;
+ return true;
+ }
+
+ private void processIntersectList() {
+ for (int i = 0; i < intersectList.size(); i++) {
+ final IntersectNode iNode = intersectList.get( i );
+ {
+ intersectEdges( iNode.edge1, iNode.Edge2, iNode.getPt() );
+ swapPositionsInAEL( iNode.edge1, iNode.Edge2 );
+ }
+ }
+ intersectList.clear();
+ }
+
+ //------------------------------------------------------------------------------
+
+ private void insertMaxima( long x ) {
+ //double-linked list: sorted ascending, ignoring dups.
+ final Maxima newMax = new Maxima();
+ newMax.x = x;
+ if (maxima == null) {
+ maxima = newMax;
+ maxima.next = null;
+ maxima.prev = null;
+ }
+ else if (x < maxima.x) {
+ newMax.next = maxima;
+ newMax.prev = null;
+ maxima = newMax;
+ }
+ else {
+ Maxima m = maxima;
+ while (m.next != null && (x >= m.next.x)) {
+ m = m.next;
+ }
+ if (x == m.x) {
+ return; //ie ignores duplicates (& CG to clean up newMax)
+ }
+ //insert newMax between m and m.Next ...
+ newMax.next = m.next;
+ newMax.prev = m;
+ if (m.next != null) m.next.prev = newMax;
+ m.next = newMax;
+ }
+ }
+
+ private void setHoleState(Edge e, OutRec outRec ) {
+ Edge e2 = e.prevInAEL;
+ Edge eTmp = null;
+ while (e2 != null) {
+ if (e2.outIdx >= 0 && e2.windDelta != 0) {
+ if (eTmp == null) {
+ eTmp = e2;
+ }
+ else if (eTmp.outIdx == e2.outIdx) {
+ eTmp = null; //paired
+ }
+ }
+ e2 = e2.prevInAEL;
+ }
+ if (eTmp == null) {
+ outRec.firstLeft = null;
+ outRec.isHole = false;
+ }
+ else {
+ outRec.firstLeft = polyOuts.get( eTmp.outIdx );
+ outRec.isHole = !outRec.firstLeft.isHole;
+ }
+ }
+
+ private void setZ(LongPoint pt, Edge e1, Edge e2 ) {
+ if (pt.getZ() != 0 || zFillFunction == null) {
+ return;
+ }
+ else if (pt.equals( e1.getBot() )) {
+ pt.setZ( e1.getBot().getZ() );
+ }
+ else if (pt.equals( e1.getTop() )) {
+ pt.setZ( e1.getTop().getZ() );
+ }
+ else if (pt.equals( e2.getBot() )) {
+ pt.setZ( e2.getBot().getZ() );
+ }
+ else if (pt.equals( e2.getTop() )) {
+ pt.setZ( e2.getTop().getZ() );
+ }
+ else {
+ zFillFunction.zFill( e1.getBot(), e1.getTop(), e2.getBot(), e2.getTop(), pt );
+ }
+ }
+
+ //------------------------------------------------------------------------------;
+
+ private void swapPositionsInSEL(Edge edge1, Edge edge2 ) {
+ if (edge1.nextInSEL == null && edge1.prevInSEL == null) {
+ return;
+ }
+ if (edge2.nextInSEL == null && edge2.prevInSEL == null) {
+ return;
+ }
+
+ if (edge1.nextInSEL == edge2) {
+ final Edge next = edge2.nextInSEL;
+ if (next != null) {
+ next.prevInSEL = edge1;
+ }
+ final Edge prev = edge1.prevInSEL;
+ if (prev != null) {
+ prev.nextInSEL = edge2;
+ }
+ edge2.prevInSEL = prev;
+ edge2.nextInSEL = edge1;
+ edge1.prevInSEL = edge2;
+ edge1.nextInSEL = next;
+ }
+ else if (edge2.nextInSEL == edge1) {
+ final Edge next = edge1.nextInSEL;
+ if (next != null) {
+ next.prevInSEL = edge2;
+ }
+ final Edge prev = edge2.prevInSEL;
+ if (prev != null) {
+ prev.nextInSEL = edge1;
+ }
+ edge1.prevInSEL = prev;
+ edge1.nextInSEL = edge2;
+ edge2.prevInSEL = edge1;
+ edge2.nextInSEL = next;
+ }
+ else {
+ final Edge next = edge1.nextInSEL;
+ final Edge prev = edge1.prevInSEL;
+ edge1.nextInSEL = edge2.nextInSEL;
+ if (edge1.nextInSEL != null) {
+ edge1.nextInSEL.prevInSEL = edge1;
+ }
+ edge1.prevInSEL = edge2.prevInSEL;
+ if (edge1.prevInSEL != null) {
+ edge1.prevInSEL.nextInSEL = edge1;
+ }
+ edge2.nextInSEL = next;
+ if (edge2.nextInSEL != null) {
+ edge2.nextInSEL.prevInSEL = edge2;
+ }
+ edge2.prevInSEL = prev;
+ if (edge2.prevInSEL != null) {
+ edge2.prevInSEL.nextInSEL = edge2;
+ }
+ }
+
+ if (edge1.prevInSEL == null) {
+ sortedEdges = edge1;
+ }
+ else if (edge2.prevInSEL == null) {
+ sortedEdges = edge2;
+ }
+ }
+
+ private void updateEdgeIntoAEL( Edge[] eV ) {
+ Edge e = eV[0];
+ if (e.nextInLML == null) {
+ throw new IllegalStateException( "UpdateEdgeIntoAEL: invalid call" );
+ }
+ final Edge AelPrev = e.prevInAEL;
+ final Edge AelNext = e.nextInAEL;
+ e.nextInLML.outIdx = e.outIdx;
+ if (AelPrev != null) {
+ AelPrev.nextInAEL = e.nextInLML;
+ }
+ else {
+ activeEdges = e.nextInLML;
+ }
+ if (AelNext != null) {
+ AelNext.prevInAEL = e.nextInLML;
+ }
+ e.nextInLML.side = e.side;
+ e.nextInLML.windDelta = e.windDelta;
+ e.nextInLML.windCnt = e.windCnt;
+ e.nextInLML.windCnt2 = e.windCnt2;
+ eV[0] = e = e.nextInLML;
+ e.setCurrent( new LongPoint( e.getBot() ) );
+ e.prevInAEL = AelPrev;
+ e.nextInAEL = AelNext;
+ if (!e.isHorizontal()) {
+ insertScanbeam( e.getTop().getY() );
+ }
+ }
+
+ private void updateOutPtIdxs( OutRec outrec ) {
+ Path.OutPt op = outrec.getPoints();
+ do {
+ op.idx = outrec.Idx;
+ op = op.prev;
+ }
+ while (op != outrec.getPoints());
+ }
+
+ private void updateWindingCount( Edge edge ) {
+ LOGGER.entering( DefaultClipper.class.getName(), "updateWindingCount" );
+
+ Edge e = edge.prevInAEL;
+ //find the edge of the same polytype that immediately preceeds 'edge' in AEL
+ while (e != null && (e.polyTyp != edge.polyTyp || e.windDelta == 0)) {
+ e = e.prevInAEL;
+ }
+ if (e == null) {
+ PolyFillType pft;
+ pft = (edge.polyTyp == PolyType.SUBJECT ? subjFillType : clipFillType);
+ if (edge.windDelta == 0) {
+ edge.windCnt = (pft == PolyFillType.NEGATIVE ? -1 : 1);
+ }
+ else {
+ edge.windCnt = edge.windDelta;
+ }
+ edge.windCnt2 = 0;
+ e = activeEdges; //ie get ready to calc WindCnt2
+ }
+ else if (edge.windDelta == 0 && clipType != ClipType.UNION) {
+ edge.windCnt = 1;
+ edge.windCnt2 = e.windCnt2;
+ e = e.nextInAEL; //ie get ready to calc WindCnt2
+ }
+ else if (edge.isEvenOddFillType( clipFillType, subjFillType )) {
+ //EvenOdd filling ...
+ if (edge.windDelta == 0) {
+ //are we inside a subj polygon ...
+ boolean Inside = true;
+ Edge e2 = e.prevInAEL;
+ while (e2 != null) {
+ if (e2.polyTyp == e.polyTyp && e2.windDelta != 0) {
+ Inside = !Inside;
+ }
+ e2 = e2.prevInAEL;
+ }
+ edge.windCnt = Inside ? 0 : 1;
+ }
+ else {
+ edge.windCnt = edge.windDelta;
+ }
+ 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) {
+ //prev edge is 'decreasing' WindCount (WC) toward zero
+ //so we're outside the previous polygon ...
+ if (Math.abs( e.windCnt ) > 1) {
+ //outside prev poly but still inside another.
+ //when reversing direction of prev poly use the same WC
+ if (e.windDelta * edge.windDelta < 0) {
+ edge.windCnt = e.windCnt;
+ }
+ else {
+ edge.windCnt = e.windCnt + edge.windDelta;
+ }
+ }
+ else {
+ //now outside all polys of same polytype so set own WC ...
+ edge.windCnt = edge.windDelta == 0 ? 1 : edge.windDelta;
+ }
+ }
+ else {
+ //prev edge is 'increasing' WindCount (WC) away from zero
+ //so we're inside the previous polygon ...
+ if (edge.windDelta == 0) {
+ edge.windCnt = e.windCnt < 0 ? e.windCnt - 1 : e.windCnt + 1;
+ }
+ else if (e.windDelta * 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 (edge.isEvenOddAltFillType( clipFillType, subjFillType )) {
+ //EvenOdd filling ...
+ while (e != edge) {
+ if (e.windDelta != 0) {
+ 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;
+ }
+ }
+ }
+
+} //end Clipper
+
diff --git a/open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/Edge.java b/open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/Edge.java
new file mode 100644
index 0000000..7518a28
--- /dev/null
+++ b/open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/Edge.java
@@ -0,0 +1,339 @@
+package com.visual.open.anpr.core.clipper;
+
+import com.visual.open.anpr.core.clipper.Clipper.ClipType;
+import com.visual.open.anpr.core.clipper.Clipper.Direction;
+import com.visual.open.anpr.core.clipper.Clipper.PolyFillType;
+import com.visual.open.anpr.core.clipper.Clipper.PolyType;
+import com.visual.open.anpr.core.clipper.Point.LongPoint;
+
+import java.util.logging.Logger;
+
+class Edge {
+ enum Side {
+ LEFT, RIGHT
+ }
+
+ static boolean doesE2InsertBeforeE1(Edge e1, Edge e2 ) {
+ if (e2.current.getX() == e1.current.getX()) {
+ if (e2.top.getY() > e1.top.getY()) {
+ return e2.top.getX() < topX( e1, e2.top.getY() );
+ }
+ else {
+ return e1.top.getX() > topX( e2, e1.top.getY() );
+ }
+ }
+ else {
+ return e2.current.getX() < e1.current.getX();
+ }
+ }
+
+ static boolean slopesEqual(Edge e1, Edge e2 ) {
+ return e1.getDelta().getY() * e2.getDelta().getX() == e1.getDelta().getX() * e2.getDelta().getY();
+
+ }
+
+ static void swapPolyIndexes(Edge edge1, Edge edge2 ) {
+ final int outIdx = edge1.outIdx;
+ edge1.outIdx = edge2.outIdx;
+ edge2.outIdx = outIdx;
+ }
+
+ static void swapSides(Edge edge1, Edge edge2 ) {
+ final Side side = edge1.side;
+ edge1.side = edge2.side;
+ edge2.side = side;
+ }
+
+ static long topX(Edge edge, long currentY ) {
+ if (currentY == edge.getTop().getY()) {
+ return edge.getTop().getX();
+ }
+ return edge.getBot().getX() + Math.round( edge.deltaX * (currentY - edge.getBot().getY()) );
+ }
+
+ private final LongPoint bot;
+
+ private final LongPoint current; //current (updated for every new scanbeam)
+
+ private final LongPoint top;
+
+ private final LongPoint delta;
+ double deltaX;
+
+ PolyType polyTyp;
+
+ Side side; //side only refers to current side of solution poly
+
+ int windDelta; //1 or -1 depending on winding direction
+
+ int windCnt;
+ int windCnt2; //winding count of the opposite polytype
+ int outIdx;
+ Edge next;
+ Edge prev;
+ Edge nextInLML;
+ Edge nextInAEL;
+ Edge prevInAEL;
+ Edge nextInSEL;
+ Edge prevInSEL;
+
+ protected final static int SKIP = -2;
+
+ protected final static int UNASSIGNED = -1;
+
+ protected final static double HORIZONTAL = -3.4E+38;
+
+ private final static Logger LOGGER = Logger.getLogger( Edge.class.getName() );
+
+ public Edge() {
+ delta = new LongPoint();
+ top = new LongPoint();
+ bot = new LongPoint();
+ current = new LongPoint();
+ }
+
+ public Edge findNextLocMin() {
+ Edge e = this;
+ Edge e2;
+ for (;;) {
+ while (!e.bot.equals( e.prev.bot ) || e.current.equals( e.top )) {
+ e = e.next;
+ }
+ if (e.deltaX != Edge.HORIZONTAL && e.prev.deltaX != Edge.HORIZONTAL) {
+ break;
+ }
+ while (e.prev.deltaX == Edge.HORIZONTAL) {
+ e = e.prev;
+ }
+ e2 = e;
+ while (e.deltaX == Edge.HORIZONTAL) {
+ e = e.next;
+ }
+ if (e.top.getY() == e.prev.bot.getY()) {
+ continue; //ie just an intermediate horz.
+ }
+ if (e2.prev.bot.getX() < e.bot.getX()) {
+ e = e2;
+ }
+ break;
+ }
+ return e;
+ }
+
+ public LongPoint getBot() {
+ return bot;
+ }
+
+ public LongPoint getCurrent() {
+ return current;
+ }
+
+ public LongPoint getDelta() {
+ return delta;
+ }
+
+ public Edge getMaximaPair() {
+ if (next.top.equals( top ) && next.nextInLML == null) {
+ return next;
+ }
+ else if (prev.top.equals( top ) && prev.nextInLML == null) {
+ return prev;
+ }
+ return null;
+ }
+
+ Edge getMaximaPairEx() {
+ //as above but returns null if MaxPair isn't in AEL (unless it's horizontal)
+ Edge result = getMaximaPair();
+ if (result == null || result.outIdx == Edge.SKIP ||
+ ((result.nextInAEL == result.prevInAEL) && !result.isHorizontal())) {
+ return null;
+ }
+ return result;
+ }
+
+ public Edge getNextInAEL(Direction direction ) {
+ return direction == Direction.LEFT_TO_RIGHT ? nextInAEL : prevInAEL;
+ }
+
+ public LongPoint getTop() {
+ return top;
+ }
+
+ public boolean isContributing(PolyFillType clipFillType, PolyFillType subjFillType, ClipType clipType ) {
+ LOGGER.entering( Edge.class.getName(), "isContributing" );
+
+ PolyFillType pft, pft2;
+ if (polyTyp == PolyType.SUBJECT) {
+ pft = subjFillType;
+ pft2 = clipFillType;
+ }
+ else {
+ pft = clipFillType;
+ pft2 = subjFillType;
+ }
+
+ switch (pft) {
+ case EVEN_ODD:
+ //return false if a subj line has been flagged as inside a subj polygon
+ if (windDelta == 0 && windCnt != 1) {
+ return false;
+ }
+ break;
+ case NON_ZERO:
+ if (Math.abs( windCnt ) != 1) {
+ return false;
+ }
+ break;
+ case POSITIVE:
+ if (windCnt != 1) {
+ return false;
+ }
+ break;
+ default: //PolyFillType.pftNegative
+ if (windCnt != -1) {
+ return false;
+ }
+ break;
+ }
+
+ switch (clipType) {
+ case INTERSECTION:
+ switch (pft2) {
+ case EVEN_ODD:
+ case NON_ZERO:
+ return windCnt2 != 0;
+ case POSITIVE:
+ return windCnt2 > 0;
+ default:
+ return windCnt2 < 0;
+ }
+ case UNION:
+ switch (pft2) {
+ case EVEN_ODD:
+ case NON_ZERO:
+ return windCnt2 == 0;
+ case POSITIVE:
+ return windCnt2 <= 0;
+ default:
+ return windCnt2 >= 0;
+ }
+ case DIFFERENCE:
+ if (polyTyp == PolyType.SUBJECT) {
+ switch (pft2) {
+ case EVEN_ODD:
+ case NON_ZERO:
+ return windCnt2 == 0;
+ case POSITIVE:
+ return windCnt2 <= 0;
+ default:
+ return windCnt2 >= 0;
+ }
+ }
+ else {
+ switch (pft2) {
+ case EVEN_ODD:
+ case NON_ZERO:
+ return windCnt2 != 0;
+ case POSITIVE:
+ return windCnt2 > 0;
+ default:
+ return windCnt2 < 0;
+ }
+ }
+ case XOR:
+ if (windDelta == 0) {
+ switch (pft2) {
+ case EVEN_ODD:
+ case NON_ZERO:
+ return windCnt2 == 0;
+ case POSITIVE:
+ return windCnt2 <= 0;
+ default:
+ return windCnt2 >= 0;
+ }
+ }
+ else {
+ return true;
+ }
+ }
+ return true;
+ }
+
+ public boolean isEvenOddAltFillType(PolyFillType clipFillType, PolyFillType subjFillType ) {
+ if (polyTyp == PolyType.SUBJECT) {
+ return clipFillType == PolyFillType.EVEN_ODD;
+ }
+ else {
+ return subjFillType == PolyFillType.EVEN_ODD;
+ }
+ }
+
+ public boolean isEvenOddFillType(PolyFillType clipFillType, PolyFillType subjFillType ) {
+ if (polyTyp == PolyType.SUBJECT) {
+ return subjFillType == PolyFillType.EVEN_ODD;
+ }
+ else {
+ return clipFillType == PolyFillType.EVEN_ODD;
+ }
+ }
+
+ public boolean isHorizontal() {
+ return delta.getY() == 0;
+ }
+
+ public boolean isIntermediate( double y ) {
+ return top.getY() == y && nextInLML != null;
+ }
+
+ public boolean isMaxima( double Y ) {
+ return top.getY() == Y && nextInLML == null;
+ }
+
+ public void reverseHorizontal() {
+ //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.]
+ long temp = top.getX();
+ top.setX( bot.getX() );
+ bot.setX( temp );
+
+ temp = top.getZ();
+ top.setZ( bot.getZ() );
+ bot.setZ( temp );
+
+ }
+
+ public void setBot( LongPoint bot ) {
+ this.bot.set( bot );
+ }
+
+ public void setCurrent( LongPoint current ) {
+ this.current.set( current );
+ }
+
+ public void setTop( LongPoint top ) {
+ this.top.set( top );
+ }
+
+ @Override
+ public String toString() {
+ return "TEdge [Bot=" + bot + ", Curr=" + current + ", Top=" + top + ", Delta=" + delta + ", Dx=" + deltaX + ", PolyTyp=" + polyTyp + ", Side=" + side
+ + ", WindDelta=" + windDelta + ", WindCnt=" + windCnt + ", WindCnt2=" + windCnt2 + ", OutIdx=" + outIdx + ", Next=" + next + ", Prev="
+ + prev + ", NextInLML=" + nextInLML + ", NextInAEL=" + nextInAEL + ", PrevInAEL=" + prevInAEL + ", NextInSEL=" + nextInSEL
+ + ", PrevInSEL=" + prevInSEL + "]";
+ }
+
+ public void updateDeltaX() {
+
+ delta.setX( top.getX() - bot.getX() );
+ delta.setY( top.getY() - bot.getY() );
+ if (delta.getY() == 0) {
+ deltaX = Edge.HORIZONTAL;
+ }
+ else {
+ deltaX = (double) delta.getX() / delta.getY();
+ }
+ }
+
+}
diff --git a/open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/LongRect.java b/open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/LongRect.java
new file mode 100644
index 0000000..089331f
--- /dev/null
+++ b/open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/LongRect.java
@@ -0,0 +1,26 @@
+package com.visual.open.anpr.core.clipper;
+
+public class LongRect {
+ public long left;
+ public long top;
+ public long right;
+ public long bottom;
+
+ public LongRect() {
+
+ }
+
+ public LongRect( long l, long t, long r, long b ) {
+ left = l;
+ top = t;
+ right = r;
+ bottom = b;
+ }
+
+ public LongRect( LongRect ir ) {
+ left = ir.left;
+ top = ir.top;
+ right = ir.right;
+ bottom = ir.bottom;
+ }
+}
diff --git a/open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/Path.java b/open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/Path.java
new file mode 100644
index 0000000..16cfa57
--- /dev/null
+++ b/open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/Path.java
@@ -0,0 +1,414 @@
+package com.visual.open.anpr.core.clipper;
+
+import com.visual.open.anpr.core.clipper.Point.LongPoint;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+/**
+ * A pure convenience class to avoid writing List everywhere.
+ *
+ * @author Tobias Mahlmann
+ *
+ */
+public class Path extends ArrayList {
+ static class Join {
+ OutPt outPt1;
+ OutPt outPt2;
+ private LongPoint offPt;
+
+ public LongPoint getOffPt() {
+ return offPt;
+ }
+
+ public void setOffPt( LongPoint offPt ) {
+ this.offPt = offPt;
+ }
+
+ }
+
+ static class OutPt {
+ public static OutRec getLowerMostRec( OutRec outRec1, OutRec outRec2 ) {
+ //work out which polygon fragment has the correct hole state ...
+ if (outRec1.bottomPt == null) {
+ outRec1.bottomPt = outRec1.pts.getBottomPt();
+ }
+ if (outRec2.bottomPt == null) {
+ outRec2.bottomPt = outRec2.pts.getBottomPt();
+ }
+ final OutPt bPt1 = outRec1.bottomPt;
+ final OutPt bPt2 = outRec2.bottomPt;
+ if (bPt1.getPt().getY() > bPt2.getPt().getY()) {
+ return outRec1;
+ }
+ else if (bPt1.getPt().getY() < bPt2.getPt().getY()) {
+ return outRec2;
+ }
+ else if (bPt1.getPt().getX() < bPt2.getPt().getX()) {
+ return outRec1;
+ }
+ else if (bPt1.getPt().getX() > bPt2.getPt().getX()) {
+ return outRec2;
+ }
+ else if (bPt1.next == bPt1) {
+ return outRec2;
+ }
+ else if (bPt2.next == bPt2) {
+ return outRec1;
+ }
+ else if (isFirstBottomPt( bPt1, bPt2 )) {
+ return outRec1;
+ }
+ else {
+ return outRec2;
+ }
+ }
+
+ private static boolean isFirstBottomPt(OutPt btmPt1, OutPt btmPt2 ) {
+ OutPt p = btmPt1.prev;
+ while (p.getPt().equals( btmPt1.getPt() ) && !p.equals( btmPt1 )) {
+ p = p.prev;
+ }
+ final double dx1p = Math.abs( LongPoint.getDeltaX( btmPt1.getPt(), p.getPt() ) );
+ p = btmPt1.next;
+ while (p.getPt().equals( btmPt1.getPt() ) && !p.equals( btmPt1 )) {
+ p = p.next;
+ }
+ final double dx1n = Math.abs( LongPoint.getDeltaX( btmPt1.getPt(), p.getPt() ) );
+
+ p = btmPt2.prev;
+ while (p.getPt().equals( btmPt2.getPt() ) && !p.equals( btmPt2 )) {
+ p = p.prev;
+ }
+ final double dx2p = Math.abs( LongPoint.getDeltaX( btmPt2.getPt(), p.getPt() ) );
+ p = btmPt2.next;
+ while (p.getPt().equals( btmPt2.getPt() ) && p.equals( btmPt2 )) {
+ p = p.next;
+ }
+ final double dx2n = Math.abs( LongPoint.getDeltaX( btmPt2.getPt(), p.getPt() ) );
+
+ if (Math.max( dx1p, dx1n ) == Math.max( dx2p, dx2n ) && Math.min( dx1p, dx1n ) == Math.min( dx2p, dx2n )) {
+ return btmPt1.area() > 0; //if otherwise identical use orientation
+ }
+ else {
+ return dx1p >= dx2p && dx1p >= dx2n || dx1n >= dx2p && dx1n >= dx2n;
+ }
+ }
+
+ int idx;
+ private LongPoint pt;
+ OutPt next;
+
+ OutPt prev;
+
+ public OutPt duplicate(boolean InsertAfter ) {
+ final OutPt result = new OutPt();
+ result.setPt( new LongPoint( getPt() ) );
+ result.idx = idx;
+ if (InsertAfter) {
+ result.next = next;
+ result.prev = this;
+ next.prev = result;
+ next = result;
+ }
+ else {
+ result.prev = prev;
+ result.next = this;
+ prev.next = result;
+ prev = result;
+ }
+ return result;
+ }
+
+ OutPt getBottomPt() {
+ OutPt dups = null;
+ OutPt p = next;
+ OutPt pp = this;
+ while (p != pp) {
+ if (p.getPt().getY() > pp.getPt().getY()) {
+ pp = p;
+ dups = null;
+ }
+ else if (p.getPt().getY() == pp.getPt().getY() && p.getPt().getX() <= pp.getPt().getX()) {
+ if (p.getPt().getX() < pp.getPt().getX()) {
+ dups = null;
+ pp = p;
+ }
+ else {
+ if (p.next != pp && p.prev != pp) {
+ dups = p;
+ }
+ }
+ }
+ p = p.next;
+ }
+ if (dups != null) {
+ //there appears to be at least 2 vertices at bottomPt so ...
+ while (dups != p) {
+ if (!isFirstBottomPt( p, dups )) {
+ pp = dups;
+ }
+ dups = dups.next;
+ while (!dups.getPt().equals( pp.getPt() )) {
+ dups = dups.next;
+ }
+ }
+ }
+ return pp;
+ }
+
+ public static int getPointCount( OutPt pts ) {
+ if (pts == null) return 0;
+ int result = 0;
+ OutPt p = pts;
+ do {
+ result++;
+ p = p.next;
+ }
+ while (p != pts);
+ return result;
+ }
+
+ public LongPoint getPt() {
+ return pt;
+ }
+
+ public void reversePolyPtLinks() {
+
+ OutPt pp1;
+ OutPt pp2;
+ pp1 = this;
+ do {
+ pp2 = pp1.next;
+ pp1.next = pp1.prev;
+ pp1.prev = pp2;
+ pp1 = pp2;
+ }
+ while (pp1 != this);
+ }
+
+ public void setPt( LongPoint pt ) {
+ this.pt = pt;
+ }
+
+ private double area() {
+ OutPt op = this;
+ double a = 0;
+ do {
+ a = a + (double) (op.prev.getPt().getX() + op.getPt().getX()) * (double) (op.prev.getPt().getY() - op.getPt().getY());
+ op = op.next;
+ } while (op != this);
+ return a * 0.5;
+ }
+ }
+
+ /** OutRec: contains a path in the clipping solution. Edges in the AEL will
+ carry a pointer to an OutRec when they are part of the clipping solution.*/
+ static class OutRec {
+ int Idx;
+
+ boolean isHole;
+
+ boolean isOpen;
+ OutRec firstLeft; //see comments in clipper.pas
+ private OutPt pts;
+ OutPt bottomPt;
+ PolyNode polyNode;
+
+ public double area() {
+ return pts.area();
+ }
+
+ public void fixHoleLinkage() {
+ //skip if an outermost polygon or
+ //already already points to the correct FirstLeft ...
+ if (firstLeft == null || isHole != firstLeft.isHole && firstLeft.pts != null) {
+ return;
+ }
+
+ OutRec orfl = firstLeft;
+ while (orfl != null && (orfl.isHole == isHole || orfl.pts == null)) {
+ orfl = orfl.firstLeft;
+ }
+ firstLeft = orfl;
+ }
+
+ public OutPt getPoints() {
+ return pts;
+ }
+
+ public static OutRec parseFirstLeft( OutRec firstLeft ) {
+ while (firstLeft != null && firstLeft.getPoints() == null) {
+ firstLeft = firstLeft.firstLeft;
+ }
+ return firstLeft;
+ }
+
+ public void setPoints( OutPt pts ) {
+ this.pts = pts;
+ }
+ }
+
+ private static OutPt excludeOp(OutPt op ) {
+ final OutPt result = op.prev;
+ result.next = op.next;
+ op.next.prev = result;
+ result.idx = 0;
+ return result;
+ }
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = -7120161578077546673L;
+
+ public Path() {
+ super();
+
+ }
+
+ public Path( int cnt ) {
+ super( cnt );
+ }
+
+ public double area() {
+ final int cnt = size();
+ if (cnt < 3) {
+ return 0;
+ }
+ double a = 0;
+ for (int i = 0, j = cnt - 1; i < cnt; ++i) {
+ a += ((double) get( j ).getX() + get( i ).getX()) * ((double) get( j ).getY() - get( i ).getY());
+ j = i;
+ }
+ return -a * 0.5;
+ }
+
+ public Path cleanPolygon() {
+ return cleanPolygon( 1.415 );
+ }
+
+ public Path cleanPolygon(double distance ) {
+ //distance = proximity in units/pixels below which vertices will be stripped.
+ //Default ~= sqrt(2) so when adjacent vertices or semi-adjacent vertices have
+ //both x & y coords within 1 unit, then the second vertex will be stripped.
+
+ int cnt = size();
+
+ if (cnt == 0) {
+ return new Path();
+ }
+
+ OutPt[] outPts = new OutPt[cnt];
+ for (int i = 0; i < cnt; ++i) {
+ outPts[i] = new OutPt();
+ }
+
+ for (int i = 0; i < cnt; ++i) {
+ outPts[i].pt = get( i );
+ outPts[i].next = outPts[(i + 1) % cnt];
+ outPts[i].next.prev = outPts[i];
+ outPts[i].idx = 0;
+ }
+
+ final double distSqrd = distance * distance;
+ OutPt op = outPts[0];
+ while (op.idx == 0 && op.next != op.prev) {
+ if (Point.arePointsClose( op.pt, op.prev.pt, distSqrd )) {
+ op = excludeOp( op );
+ cnt--;
+ }
+ else if (Point.arePointsClose( op.prev.pt, op.next.pt, distSqrd )) {
+ excludeOp( op.next );
+ op = excludeOp( op );
+ cnt -= 2;
+ }
+ else if (Point.slopesNearCollinear( op.prev.pt, op.pt, op.next.pt, distSqrd )) {
+ op = excludeOp( op );
+ cnt--;
+ }
+ else {
+ op.idx = 1;
+ op = op.next;
+ }
+ }
+
+ if (cnt < 3) {
+ cnt = 0;
+ }
+ final Path result = new Path( cnt );
+ for (int i = 0; i < cnt; ++i) {
+ result.add( op.pt );
+ op = op.next;
+ }
+ outPts = null;
+ return result;
+ }
+
+ public int isPointInPolygon( LongPoint pt ) {
+ //returns 0 if false, +1 if true, -1 if pt ON polygon boundary
+ //See "The Point in Polygon Problem for Arbitrary Polygons" by Hormann & Agathos
+ //http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf
+ int result = 0;
+ final int cnt = size();
+ if (cnt < 3) {
+ return 0;
+ }
+ LongPoint ip = get( 0 );
+ for (int i = 1; i <= cnt; ++i) {
+ final LongPoint ipNext = i == cnt ? get( 0 ) : get( i );
+ if (ipNext.getY() == pt.getY()) {
+ if (ipNext.getX() == pt.getX() || ip.getY() == pt.getY() && ipNext.getX() > pt.getX() == ip.getX() < pt.getX()) {
+ return -1;
+ }
+ }
+ if (ip.getY() < pt.getY() != ipNext.getY() < pt.getY()) {
+ if (ip.getX() >= pt.getX()) {
+ if (ipNext.getX() > pt.getX()) {
+ result = 1 - result;
+ }
+ else {
+ final double d = (double) (ip.getX() - pt.getX()) * (ipNext.getY() - pt.getY()) - (double) (ipNext.getX() - pt.getX())
+ * (ip.getY() - pt.getY());
+ if (d == 0) {
+ return -1;
+ }
+ else if (d > 0 == ipNext.getY() > ip.getY()) {
+ result = 1 - result;
+ }
+ }
+ }
+ else {
+ if (ipNext.getX() > pt.getX()) {
+ final double d = (double) (ip.getX() - pt.getX()) * (ipNext.getY() - pt.getY()) - (double) (ipNext.getX() - pt.getX())
+ * (ip.getY() - pt.getY());
+ if (d == 0) {
+ return -1;
+ }
+ else if (d > 0 == ipNext.getY() > ip.getY()) {
+ result = 1 - result;
+ }
+ }
+ }
+ }
+ ip = ipNext;
+ }
+ return result;
+ }
+
+ public boolean orientation() {
+ return area() >= 0;
+ }
+
+ public void reverse() {
+ Collections.reverse( this );
+ }
+
+ public Path TranslatePath(LongPoint delta ) {
+ final Path outPath = new Path( size() );
+ for (int i = 0; i < size(); i++) {
+ outPath.add( new LongPoint( get( i ).getX() + delta.getX(), get( i ).getY() + delta.getY() ) );
+ }
+ return outPath;
+ }
+}
diff --git a/open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/Paths.java b/open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/Paths.java
new file mode 100644
index 0000000..306567d
--- /dev/null
+++ b/open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/Paths.java
@@ -0,0 +1,125 @@
+package com.visual.open.anpr.core.clipper;
+
+import java.util.ArrayList;
+
+/**
+ * A pure convenience class to avoid writing List everywhere.
+ *
+ * @author Tobias Mahlmann
+ *
+ */
+public class Paths extends ArrayList {
+
+ public static Paths closedPathsFromPolyTree(PolyTree polytree ) {
+ final Paths result = new Paths();
+ // result.Capacity = polytree.Total;
+ result.addPolyNode( polytree, PolyNode.NodeType.CLOSED );
+ return result;
+ }
+
+ public static Paths makePolyTreeToPaths(PolyTree polytree ) {
+
+ final Paths result = new Paths();
+ // result.Capacity = polytree.Total;
+ result.addPolyNode( polytree, PolyNode.NodeType.ANY );
+ return result;
+ }
+
+ public static Paths openPathsFromPolyTree(PolyTree polytree ) {
+ final Paths result = new Paths();
+ // result.Capacity = polytree.ChildCount;
+ for (final PolyNode c : polytree.getChilds()) {
+ if (c.isOpen()) {
+ result.add( c.getPolygon() );
+ }
+ }
+ return result;
+ }
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1910552127810480852L;
+
+ public Paths() {
+ super();
+ }
+
+ public Paths( int initialCapacity ) {
+ super( initialCapacity );
+ }
+
+ public void addPolyNode(PolyNode polynode, PolyNode.NodeType nt ) {
+ boolean match = true;
+ switch (nt) {
+ case OPEN:
+ return;
+ case CLOSED:
+ match = !polynode.isOpen();
+ break;
+ default:
+ break;
+ }
+
+ if (polynode.getPolygon().size() > 0 && match) {
+ add( polynode.getPolygon() );
+ }
+ for (final PolyNode pn : polynode.getChilds()) {
+ addPolyNode( pn, nt );
+ }
+ }
+
+ public Paths cleanPolygons() {
+ return cleanPolygons( 1.415 );
+ }
+
+ public Paths cleanPolygons(double distance ) {
+ final Paths result = new Paths( size() );
+ for (int i = 0; i < size(); i++) {
+ result.add( get( i ).cleanPolygon( distance ) );
+ }
+ return result;
+ }
+
+ public LongRect getBounds() {
+
+ int i = 0;
+ final int cnt = size();
+ final LongRect result = new LongRect();
+ while (i < cnt && get( i ).isEmpty()) {
+ i++;
+ }
+ if (i == cnt) {
+ return result;
+ }
+
+ result.left = get( i ).get( 0 ).getX();
+ result.right = result.left;
+ result.top = get( i ).get( 0 ).getY();
+ result.bottom = result.top;
+ for (; i < cnt; i++) {
+ for (int j = 0; j < get( i ).size(); j++) {
+ if (get( i ).get( j ).getX() < result.left) {
+ result.left = get( i ).get( j ).getX();
+ }
+ else if (get( i ).get( j ).getX() > result.right) {
+ result.right = get( i ).get( j ).getX();
+ }
+ if (get( i ).get( j ).getY() < result.top) {
+ result.top = get( i ).get( j ).getY();
+ }
+ else if (get( i ).get( j ).getY() > result.bottom) {
+ result.bottom = get( i ).get( j ).getY();
+ }
+ }
+ }
+ return result;
+ }
+
+ public void reversePaths() {
+ for (final Path poly : this) {
+ poly.reverse();
+ }
+ }
+
+}
diff --git a/open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/Point.java b/open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/Point.java
new file mode 100644
index 0000000..96591e3
--- /dev/null
+++ b/open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/Point.java
@@ -0,0 +1,220 @@
+package com.visual.open.anpr.core.clipper;
+
+import java.util.Comparator;
+
+public abstract class Point> {
+ public static class DoublePoint extends Point {
+ public DoublePoint() {
+ this( 0, 0 );
+ }
+
+ public DoublePoint( double x, double y ) {
+ this( x, y, 0 );
+ }
+
+ public DoublePoint( double x, double y, double z ) {
+ super( x, y, z );
+ }
+
+ public DoublePoint( DoublePoint other ) {
+ super( other );
+ }
+
+ public double getX() {
+ return x;
+ }
+
+ public double getY() {
+ return y;
+ }
+
+ public double getZ() {
+ return z;
+ }
+ }
+
+ public static class LongPoint extends Point {
+ public static double getDeltaX(LongPoint pt1, LongPoint pt2 ) {
+ if (pt1.getY() == pt2.getY()) {
+ return Edge.HORIZONTAL;
+ }
+ else {
+ return (double) (pt2.getX() - pt1.getX()) / (pt2.getY() - pt1.getY());
+ }
+ }
+
+ public LongPoint() {
+ this( 0, 0 );
+ }
+
+ public LongPoint( long x, long y ) {
+ this( x, y, 0 );
+ }
+
+ public LongPoint( long x, long y, long z ) {
+ super( x, y, z );
+ }
+
+ public LongPoint( LongPoint other ) {
+ super( other );
+ }
+
+ public long getX() {
+ return x;
+ }
+
+ public long getY() {
+ return y;
+ }
+
+ public long getZ() {
+ return z;
+ }
+ }
+
+ private static class NumberComparator> implements Comparator {
+
+ @Override
+ public int compare( T a, T b ) throws ClassCastException {
+ return a.compareTo( b );
+ }
+ }
+
+ static boolean arePointsClose(Point extends Number> pt1, Point extends Number> pt2, double distSqrd ) {
+ final double dx = pt1.x.doubleValue() - pt2.x.doubleValue();
+ final double dy = pt1.y.doubleValue() - pt2.y.doubleValue();
+ return dx * dx + dy * dy <= distSqrd;
+ }
+
+ static double distanceFromLineSqrd(Point extends Number> pt, Point extends Number> ln1, Point extends Number> ln2 ) {
+ //The equation of a line in general form (Ax + By + C = 0)
+ //given 2 points (x¹,y¹) & (x²,y²) is ...
+ //(y¹ - y²)x + (x² - x¹)y + (y² - y¹)x¹ - (x² - x¹)y¹ = 0
+ //A = (y¹ - y²); B = (x² - x¹); C = (y² - y¹)x¹ - (x² - x¹)y¹
+ //perpendicular distance of point (x³,y³) = (Ax³ + By³ + C)/Sqrt(A² + B²)
+ //see http://en.wikipedia.org/wiki/Perpendicular_distance
+ final double A = ln1.y.doubleValue() - ln2.y.doubleValue();
+ final double B = ln2.x.doubleValue() - ln1.x.doubleValue();
+ double C = A * ln1.x.doubleValue() + B * ln1.y.doubleValue();
+ C = A * pt.x.doubleValue() + B * pt.y.doubleValue() - C;
+ return C * C / (A * A + B * B);
+ }
+
+ static DoublePoint getUnitNormal( LongPoint pt1, LongPoint pt2 ) {
+ double dx = pt2.x - pt1.x;
+ double dy = pt2.y - pt1.y;
+ if (dx == 0 && dy == 0) {
+ return new DoublePoint();
+ }
+
+ final double f = 1 * 1.0 / Math.sqrt( dx * dx + dy * dy );
+ dx *= f;
+ dy *= f;
+
+ return new DoublePoint( dy, -dx );
+ }
+
+ protected static boolean isPt2BetweenPt1AndPt3( LongPoint pt1, LongPoint pt2, LongPoint pt3 ) {
+ if (pt1.equals( pt3 ) || pt1.equals( pt2 ) || pt3.equals( pt2 )) {
+ return false;
+ }
+ else if (pt1.x != pt3.x) {
+ return pt2.x > pt1.x == pt2.x < pt3.x;
+ }
+ else {
+ return pt2.y > pt1.y == pt2.y < pt3.y;
+ }
+ }
+
+ protected static boolean slopesEqual( LongPoint pt1, LongPoint pt2, LongPoint pt3 ) {
+ return (pt1.y - pt2.y) * (pt2.x - pt3.x) - (pt1.x - pt2.x) * (pt2.y - pt3.y) == 0;
+ }
+
+ protected static boolean slopesEqual( LongPoint pt1, LongPoint pt2, LongPoint pt3, LongPoint pt4 ) {
+ return (pt1.y - pt2.y) * (pt3.x - pt4.x) - (pt1.x - pt2.x) * (pt3.y - pt4.y) == 0;
+ }
+
+ static boolean slopesNearCollinear( LongPoint pt1, LongPoint pt2, LongPoint pt3, double distSqrd ) {
+ //this function is more accurate when the point that's GEOMETRICALLY
+ //between the other 2 points is the one that's tested for distance.
+ //nb: with 'spikes', either pt1 or pt3 is geometrically between the other pts
+ if (Math.abs( pt1.x - pt2.x ) > Math.abs( pt1.y - pt2.y )) {
+ if (pt1.x > pt2.x == pt1.x < pt3.x) {
+ return distanceFromLineSqrd( pt1, pt2, pt3 ) < distSqrd;
+ }
+ else if (pt2.x > pt1.x == pt2.x < pt3.x) {
+ return distanceFromLineSqrd( pt2, pt1, pt3 ) < distSqrd;
+ }
+ else {
+ return distanceFromLineSqrd( pt3, pt1, pt2 ) < distSqrd;
+ }
+ }
+ else {
+ if (pt1.y > pt2.y == pt1.y < pt3.y) {
+ return distanceFromLineSqrd( pt1, pt2, pt3 ) < distSqrd;
+ }
+ else if (pt2.y > pt1.y == pt2.y < pt3.y) {
+ return distanceFromLineSqrd( pt2, pt1, pt3 ) < distSqrd;
+ }
+ else {
+ return distanceFromLineSqrd( pt3, pt1, pt2 ) < distSqrd;
+ }
+ }
+ }
+
+ private final static NumberComparator NUMBER_COMPARATOR = new NumberComparator();
+
+ protected T x;
+
+ protected T y;
+
+ protected T z;
+
+ protected Point( Point pt ) {
+ this( pt.x, pt.y, pt.z );
+ }
+
+ protected Point( T x, T y, T z ) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == null) {
+ return false;
+ }
+ if (obj instanceof Point>) {
+ final Point> a = (Point>) obj;
+ return NUMBER_COMPARATOR.compare( x, a.x ) == 0 && NUMBER_COMPARATOR.compare( y, a.y ) == 0;
+ }
+ else {
+ return false;
+ }
+ }
+
+ public void set( Point other ) {
+ x = other.x;
+ y = other.y;
+ z = other.z;
+ }
+
+ public void setX( T x ) {
+ this.x = x;
+ }
+
+ public void setY( T y ) {
+ this.y = y;
+ }
+
+ public void setZ( T z ) {
+ this.z = z;
+ }
+
+ @Override
+ public String toString() {
+ return "Point [x=" + x + ", y=" + y + ", z=" + z + "]";
+ }
+
+}// end struct IntPoint
\ No newline at end of file
diff --git a/open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/PolyNode.java b/open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/PolyNode.java
new file mode 100644
index 0000000..b9d3529
--- /dev/null
+++ b/open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/PolyNode.java
@@ -0,0 +1,116 @@
+package com.visual.open.anpr.core.clipper;
+
+import com.visual.open.anpr.core.clipper.Clipper.EndType;
+import com.visual.open.anpr.core.clipper.Clipper.JoinType;
+import com.visual.open.anpr.core.clipper.Point.LongPoint;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class PolyNode {
+
+ enum NodeType {
+ ANY, OPEN, CLOSED
+ }
+
+ private PolyNode parent;
+ private final Path polygon = new Path();
+ private int index;
+ private JoinType joinType;
+ private EndType endType;
+ final List childs = new ArrayList<>();
+ private boolean isOpen;
+
+ public void addChild( PolyNode child ) {
+ final int cnt = childs.size();
+ childs.add( child );
+ child.parent = this;
+ child.index = cnt;
+ }
+
+ public int getChildCount() {
+ return childs.size();
+ }
+
+ public List getChilds() {
+ return Collections.unmodifiableList( childs );
+ }
+
+ public List getContour() {
+ return polygon;
+ }
+
+ public EndType getEndType() {
+ return endType;
+ }
+
+ public JoinType getJoinType() {
+ return joinType;
+ }
+
+ public PolyNode getNext() {
+ if (!childs.isEmpty()) {
+ return childs.get( 0 );
+ }
+ else {
+ return getNextSiblingUp();
+ }
+ }
+
+ private PolyNode getNextSiblingUp() {
+ if (parent == null) {
+ return null;
+ }
+ else if (index == parent.childs.size() - 1) {
+ return parent.getNextSiblingUp();
+ }
+ else {
+ return parent.childs.get( index + 1 );
+ }
+ }
+
+ public PolyNode getParent() {
+ return parent;
+ }
+
+ public Path getPolygon() {
+ return polygon;
+ }
+
+ public boolean isHole() {
+ return isHoleNode();
+ }
+
+ private boolean isHoleNode() {
+ boolean result = true;
+ PolyNode node = parent;
+ while (node != null) {
+ result = !result;
+ node = node.parent;
+ }
+ return result;
+ }
+
+ public boolean isOpen() {
+ return isOpen;
+ }
+
+ public void setEndType( EndType value ) {
+ endType = value;
+ }
+
+ public void setJoinType( JoinType value ) {
+ joinType = value;
+ }
+
+ public void setOpen( boolean isOpen ) {
+ this.isOpen = isOpen;
+ }
+
+ public void setParent( PolyNode n ) {
+ parent = n;
+
+ }
+
+}
diff --git a/open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/PolyTree.java b/open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/PolyTree.java
new file mode 100644
index 0000000..f0d3ed4
--- /dev/null
+++ b/open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/PolyTree.java
@@ -0,0 +1,37 @@
+package com.visual.open.anpr.core.clipper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PolyTree extends PolyNode {
+ private final List allPolys = new ArrayList();
+
+ public void Clear() {
+ allPolys.clear();
+ childs.clear();
+ }
+
+ public List getAllPolys() {
+ return allPolys;
+ }
+
+ public PolyNode getFirst() {
+ if (!childs.isEmpty()) {
+ return childs.get( 0 );
+ }
+ else {
+ return null;
+ }
+ }
+
+ public int getTotalSize() {
+ int result = allPolys.size();
+ //with negative offsets, ignore the hidden outer polygon ...
+ if (result > 0 && childs.get( 0 ) != allPolys.get( 0 )) {
+ result--;
+ }
+ return result;
+
+ }
+
+}
diff --git a/open-anpr-core/src/main/java/com/visual/open/anpr/core/domain/ImageMat.java b/open-anpr-core/src/main/java/com/visual/open/anpr/core/domain/ImageMat.java
new file mode 100755
index 0000000..ee1ebd3
--- /dev/null
+++ b/open-anpr-core/src/main/java/com/visual/open/anpr/core/domain/ImageMat.java
@@ -0,0 +1,692 @@
+package com.visual.open.anpr.core.domain;
+
+import ai.onnxruntime.OnnxTensor;
+import ai.onnxruntime.OrtEnvironment;
+import com.visual.open.anpr.core.utils.MatUtil;
+import org.opencv.core.Point;
+import org.opencv.core.*;
+import org.opencv.dnn.Dnn;
+import org.opencv.highgui.HighGui;
+import org.opencv.imgcodecs.Imgcodecs;
+import org.opencv.imgproc.Imgproc;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBufferByte;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Base64;
+
+/**
+ * 图片加载工具
+ */
+public class ImageMat implements Serializable {
+
+ //静态加载动态链接库
+ static{ nu.pattern.OpenCV.loadShared(); }
+ private OrtEnvironment env = OrtEnvironment.getEnvironment();
+
+ //对象成员
+ private Mat mat;
+ private ImageMat(Mat mat){
+ this.mat = mat;
+ }
+
+ /**
+ * 读取图片,并转换为Mat
+ * @param imagePath 图片地址
+ * @return
+ */
+ public static ImageMat fromImage(String imagePath){
+ return new ImageMat(Imgcodecs.imread(imagePath));
+ }
+
+ /**
+ * 直接读取Mat
+ * @param mat 图片mat值
+ * @return
+ */
+ public static ImageMat fromCVMat(Mat mat){
+ try {
+ return new ImageMat(mat);
+ }catch (Exception e){
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * 读取图片,并转换为Mat
+ * @param base64Str 图片Base64编码值
+ * @return
+ */
+ public static ImageMat fromBase64(String base64Str){
+ InputStream inputStream = null;
+ try {
+ Base64.Decoder decoder = Base64.getMimeDecoder();
+ byte[] data = decoder.decode(base64Str);
+ inputStream = new ByteArrayInputStream(data);
+ return fromInputStream(inputStream);
+ }catch (Exception e){
+ throw new RuntimeException(e);
+ }finally {
+ if(null != inputStream){
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ /**
+ * 读取图片,并转换为Mat
+ * @param inputStream 图片数据
+ * @return
+ */
+ public static ImageMat fromInputStream(InputStream inputStream){
+ try {
+ BufferedImage image = ImageIO.read(inputStream);
+ return fromBufferedImage(image);
+ }catch (Exception e){
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * 读取图片,并转换为Mat
+ * @param image 图片数据
+ * @return
+ */
+ public static ImageMat fromBufferedImage(BufferedImage image){
+ try {
+ if(image.getType() != BufferedImage.TYPE_3BYTE_BGR){
+ BufferedImage temp = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_3BYTE_BGR);
+ Graphics2D g = temp.createGraphics();
+ try {
+ g.setComposite(AlphaComposite.Src);
+ g.drawImage(image, 0, 0, null);
+ } finally {
+ g.dispose();
+ }
+ image = temp;
+ }
+ byte[] pixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
+ Mat mat = Mat.eye(image.getHeight(), image.getWidth(), CvType.CV_8UC3);
+ mat.put(0, 0, pixels);
+ return new ImageMat(mat);
+ }catch (Exception e){
+ throw new RuntimeException(e);
+ }
+ }
+
+
+ /**
+ * 显示图片,用于数据调试
+ */
+ public void imShow() {
+ HighGui.imshow("image", mat);
+ HighGui.waitKey();
+ }
+
+
+
+ /**
+ *获取数据的宽度
+ * @return
+ */
+ public int getWidth(){
+ return (int) mat.size().width;
+ }
+
+ /**
+ * 获取数据的高度
+ * @return
+ */
+ public int getHeight(){
+ return (int) mat.size().height;
+ }
+
+ /**
+ * 克隆ImageMat
+ * @return
+ */
+ public ImageMat clone(){
+ return ImageMat.fromCVMat(this.mat.clone());
+ }
+
+ /**
+ * 获取图像的中心点
+ * @return
+ */
+ public Point center(){
+ return new Point(mat.size(1)/2, mat.size(0)/2);
+ }
+
+ /**
+ * 获取当前的CV Mat
+ * @return
+ */
+ public Mat toCvMat() {
+ return mat;
+ }
+
+ /**
+ * 数据格式转换,不释放原始图片数据
+ * @param code Imgproc.COLOR_*
+ * @param release 是否释放参数mat
+ */
+ public ImageMat cvtColorAndNoReleaseMat(int code, boolean release) {
+ return this.cvtColor(code, false);
+ }
+
+ /**
+ * 数据格式转换,并释放原始图片数据
+ * @param code Imgproc.COLOR_*
+ * @param release 是否释放参数mat
+ */
+ public ImageMat cvtColorAndDoReleaseMat(int code, boolean release) {
+ return this.cvtColor(code, true);
+ }
+
+ /**
+ * 数据格式转换
+ * @param code Imgproc.COLOR_*
+ * @param release 是否释放参数mat
+ */
+ private ImageMat cvtColor(int code, boolean release) {
+ try {
+ Mat dst = new Mat();
+ Imgproc.cvtColor(mat, dst, code);
+ return new ImageMat(dst);
+ }finally {
+ if(release){
+ this.release();
+ }
+ }
+ }
+
+ /**
+ * 重新设置图片尺寸,不释放原始图片数据
+ * @param width 图片宽度
+ * @param height 图片高度
+ * @return
+ */
+ public ImageMat resizeAndNoReleaseMat(int width, int height){
+ return this.resize(width, height, false);
+ }
+
+ /**
+ * 重新设置图片尺寸,并释放原始图片数据
+ * @param width 图片宽度
+ * @param height 图片高度
+ * @return
+ */
+ public ImageMat resizeAndDoReleaseMat(int width, int height){
+ return this.resize(width, height, true);
+ }
+
+ /**
+ * 重新设置图片尺寸
+ * @param width 图片宽度
+ * @param height 图片高度
+ * @param release 是否释放参数mat
+ * @return
+ */
+ private ImageMat resize(int width, int height, boolean release){
+ try {
+ Mat dst = new Mat();
+ Imgproc.resize(mat, dst, new Size(width,height), 0, 0, Imgproc.INTER_AREA);
+ return new ImageMat(dst);
+ }finally {
+ if(release){
+ this.release();
+ }
+ }
+ }
+
+ /**
+ * 对图像进行预处理,不释放原始图片数据
+ * @param scale 图像各通道数值的缩放比例
+ * @param mean 用于各通道减去的值,以降低光照的影响
+ * @param swapRB 交换RB通道,默认为False.
+ * @return
+ */
+ public ImageMat blobFromImageAndNoReleaseMat(double scale, Scalar mean, boolean swapRB){
+ return this.blobFromImage(scale, mean, swapRB, false);
+ }
+
+ /**
+ * 对图像进行预处理,并释放原始图片数据
+ * @param scale 图像各通道数值的缩放比例
+ * @param mean 用于各通道减去的值,以降低光照的影响
+ * @param swapRB 交换RB通道,默认为False.
+ * @return
+ */
+ public ImageMat blobFromImageAndDoReleaseMat(double scale, Scalar mean, boolean swapRB){
+ return this.blobFromImage(scale, mean, swapRB, true);
+ }
+
+ /**
+ * 对图像进行预处理
+ * @param scale 图像各通道数值的缩放比例
+ * @param mean 用于各通道减去的值,以降低光照的影响
+ * @param swapRB 交换RB通道,默认为False.
+ * @param release 是否释放参数mat
+ * @return
+ */
+ private ImageMat blobFromImage(double scale, Scalar mean, boolean swapRB, boolean release){
+ try {
+ Mat dst = Dnn.blobFromImage(mat, scale, new Size( mat.cols(), mat.rows()), mean, swapRB);
+ java.util.List mats = new ArrayList<>();
+ Dnn.imagesFromBlob(dst, mats);
+ dst.release();
+ return new ImageMat(mats.get(0));
+ }finally {
+ if(release){
+ this.release();
+ }
+ }
+ }
+
+
+ /**
+ * 转换为base64,不释放原始图片数据
+ * @return
+ */
+ public String toBase64AndNoReleaseMat(){
+ return toBase64(false);
+ }
+
+ /**
+ * 转换为base64,并释放原始图片数据
+ * @return
+ */
+ public String toBase64AndDoReleaseMat(){
+ return toBase64(true);
+ }
+
+ /**
+ * 转换为base64
+ * @param release 是否释放参数mat
+ * @return
+ */
+ private String toBase64(boolean release){
+ if(null != mat){
+ try {
+ return MatUtil.matToBase64(mat);
+ }finally {
+ if(release){
+ this.release();
+ }
+ }
+ }else{
+ return null;
+ }
+ }
+
+ /**
+ * 转换为整形数组,不释放原始图片数据
+ * @param firstChannel
+ * @return
+ */
+ public int[][][][] to4dIntArrayAndNoReleaseMat(boolean firstChannel){
+ return this.to4dIntArray(firstChannel, false);
+ }
+
+ /**
+ * 转换为整形数组,并释放原始图片数据
+ * @param firstChannel
+ * @return
+ */
+ public int[][][][] to4dIntArrayAndDoReleaseMat(boolean firstChannel){
+ return this.to4dIntArray(firstChannel, true);
+ }
+
+
+ /**
+ * 转换为整形数组
+ * @param firstChannel
+ * @param release 是否释放参数mat
+ * @return
+ */
+ private int[][][][] to4dIntArray(boolean firstChannel, boolean release){
+ try {
+ int width = this.mat.cols();
+ int height = this.mat.rows();
+ int channel = this.mat.channels();
+ int[][][][] array;
+ if(firstChannel){
+ array = new int[1][channel][height][width];
+ for(int i=0; i= (1.0 * stdWidth/stdHeight)){
+ h = (int) Math.floor(1.0 * imgHeight);
+ w = (int) Math.floor(1.0 * stdWidth * imgHeight / stdHeight);
+
+ }else{
+ w = (int) Math.floor(1.0 * imgWidth);
+ h = (int) Math.floor(1.0 * stdHeight * imgWidth / stdWidth);
+ }
+ //需要裁剪图片
+ rectMat = new Mat(warp, new Rect(0, 0, w, h));
+ Mat crop = new Mat();
+ Imgproc.resize(rectMat, crop, new Size(stdWidth, stdHeight), 0, 0, Imgproc.INTER_NEAREST);
+ return crop;
+ }finally {
+ if(null != rectMat){
+ rectMat.release();
+ }
+ if(null != warp){
+ warp.release();
+ }
+ }
+ }
+
+
+ /**
+ * 图像仿射变换
+ * @param image 图像数据
+ * @param imgPoint 图像中的关键点
+ * @param stdPoint 定义的标准关键点
+ * @return 图像的仿射结果图
+ */
+ public static Mat warpAffine(Mat image, double[][] imgPoint, double[][] stdPoint){
+ Mat matM = null;
+ Mat matMTemp = null;
+ try {
+ //转换为矩阵
+ RealMatrix imgPointMatrix = MathUtil.createMatrix(imgPoint);
+ RealMatrix stdPointMatrix = MathUtil.createMatrix(stdPoint);
+ //判断数据的行列是否一致
+ int row = imgPointMatrix.getRowDimension();
+ int col = imgPointMatrix.getColumnDimension();
+ if(row <= 0 || col <=0 || row != stdPointMatrix.getRowDimension() || col != stdPointMatrix.getColumnDimension()){
+ throw new RuntimeException("row or col is not equal");
+ }
+ //求列的均值
+ RealVector imgPointMeanVector = MathUtil.mean(imgPointMatrix, 0);
+ RealVector stdPointMeanVector = MathUtil.mean(stdPointMatrix, 0);
+ //对关键点进行减去均值
+ RealMatrix imgPointMatrix1 = imgPointMatrix.subtract(MathUtil.createMatrix(row, imgPointMeanVector.toArray()));
+ RealMatrix stdPointMatrix1 = stdPointMatrix.subtract(MathUtil.createMatrix(row, stdPointMeanVector.toArray()));
+ //计算关键点的标准差
+ double imgPointStd = MathUtil.std(imgPointMatrix1);
+ double stdPointStd = MathUtil.std(stdPointMatrix1);
+ //对关键点除以标准差
+ RealMatrix imgPointMatrix2 = MathUtil.scalarDivision(imgPointMatrix1, imgPointStd);
+ RealMatrix stdPointMatrix2 = MathUtil.scalarDivision(stdPointMatrix1, stdPointStd);
+ //获取矩阵的分量
+ RealMatrix pointsT = imgPointMatrix2.transpose().multiply(stdPointMatrix2);
+ SingularValueDecomposition svdH = new SingularValueDecomposition(pointsT);
+ RealMatrix U = svdH.getU(); RealMatrix S = svdH.getS(); RealMatrix Vt = svdH.getVT();
+ //计算仿射矩阵
+ RealMatrix R = U.multiply(Vt).transpose();
+ RealMatrix R1 = R.scalarMultiply(stdPointStd/imgPointStd);
+ RealMatrix v21 = MathUtil.createMatrix(1, stdPointMeanVector.toArray()).transpose();
+ RealMatrix v22 = R.multiply(MathUtil.createMatrix(1, imgPointMeanVector.toArray()).transpose());
+ RealMatrix v23 = v22.scalarMultiply(stdPointStd/imgPointStd);
+ RealMatrix R2 = v21.subtract(v23);
+ RealMatrix M = MathUtil.hstack(R1, R2);
+ //变化仿射矩阵为Mat
+ matMTemp = new MatOfDouble(MathUtil.flatMatrix(M, 1).toArray());
+ matM = new Mat(2, 3, CvType.CV_32FC3);
+ matMTemp.reshape(1,2).copyTo(matM);
+ //使用open cv做仿射变换
+ Mat dst = new Mat();
+ Imgproc.warpAffine(image, dst, matM, image.size());
+ return dst;
+ }finally {
+ if(null != matM){
+ matM.release();
+ }
+ if(null != matMTemp){
+ matMTemp.release();
+ }
+ }
+ }
+
+}
diff --git a/open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/ArrayUtil.java b/open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/ArrayUtil.java
new file mode 100644
index 0000000..800fd9d
--- /dev/null
+++ b/open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/ArrayUtil.java
@@ -0,0 +1,37 @@
+package com.visual.open.anpr.core.utils;
+
+public class ArrayUtil {
+
+ public static double [] floatToDouble(float[] input){
+ if (input == null){
+ return null;
+ }
+ double[] output = new double[input.length];
+ for (int i = 0; i < input.length; i++){
+ output[i] = input[i];
+ }
+ return output;
+ }
+
+ public static float [] doubleToFloat(double[] input){
+ if (input == null){
+ return null;
+ }
+ float[] output = new float[input.length];
+ for (int i = 0; i < input.length; i++){
+ output[i] = Double.valueOf(input[i]).floatValue();
+ }
+ return output;
+ }
+
+ public static double matrixNorm(double[][] matrix){
+ double sum=0.0;
+ for(double[] temp1:matrix){
+ for(double temp2:temp1){
+ sum+=Math.pow(temp2,2);
+ }
+ }
+ return Math.sqrt(sum);
+ }
+
+}
diff --git a/open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/CropUtil.java b/open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/CropUtil.java
new file mode 100755
index 0000000..44a975c
--- /dev/null
+++ b/open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/CropUtil.java
@@ -0,0 +1,59 @@
+package com.visual.open.anpr.core.utils;
+
+import com.visual.face.search.core.domain.FaceInfo;
+import org.opencv.core.CvType;
+import org.opencv.core.Mat;
+import org.opencv.core.Point;
+import org.opencv.core.Size;
+import org.opencv.imgproc.Imgproc;
+import org.opencv.utils.Converters;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 图像裁剪工具
+ */
+public class CropUtil {
+
+ /**
+ * 根据4个点裁剪图像
+ * @param image
+ * @param faceBox
+ * @return
+ */
+ public static Mat crop(Mat image, FaceInfo.FaceBox faceBox){
+ Mat endM = null;
+ Mat startM = null;
+ Mat perspectiveTransform = null;
+ try {
+ List dest = new ArrayList<>();
+ dest.add(new Point(faceBox.leftTop.x, faceBox.leftTop.y));
+ dest.add(new Point(faceBox.rightTop.x, faceBox.rightTop.y));
+ dest.add(new Point(faceBox.rightBottom.x, faceBox.rightBottom.y));
+ dest.add(new Point(faceBox.leftBottom.x, faceBox.leftBottom.y));
+ startM = Converters.vector_Point2f_to_Mat(dest);
+ List ends = new ArrayList<>();
+ ends.add(new Point(0, 0));
+ ends.add(new Point(faceBox.width(), 0));
+ ends.add(new Point(faceBox.width(), faceBox.height()));
+ ends.add(new Point(0, faceBox.height()));
+ endM = Converters.vector_Point2f_to_Mat(ends);
+ perspectiveTransform = Imgproc.getPerspectiveTransform(startM, endM);
+ Mat outputMat = new Mat((int)faceBox.height() , (int)faceBox.width(), CvType.CV_8UC4);
+ Imgproc.warpPerspective(image, outputMat, perspectiveTransform, new Size((int)faceBox.width(), (int)faceBox.height()), Imgproc.INTER_CUBIC);
+ return outputMat;
+ }finally {
+ if(null != endM){
+ endM.release();
+ }
+ if(null != startM){
+ startM.release();
+ }
+ if(null != perspectiveTransform){
+ perspectiveTransform.release();
+ }
+ }
+ }
+
+}
diff --git a/open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/JsonUtil.java b/open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/JsonUtil.java
new file mode 100755
index 0000000..cefd623
--- /dev/null
+++ b/open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/JsonUtil.java
@@ -0,0 +1,128 @@
+package com.visual.open.anpr.core.utils;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.TypeReference;
+import com.alibaba.fastjson.serializer.SerializerFeature;
+
+import java.util.List;
+import java.util.Map;
+
+public class JsonUtil {
+
+ /**
+ * 将Bean转化为json字符串
+ *
+ * @param obj bean对象
+ * @return json
+ */
+ public static String toString(Object obj) {
+ return toString(obj, false, false);
+ }
+
+ public static String toSimpleString(Object obj) {
+ return toString(obj, false, true);
+ }
+
+ /**
+ * 将Bean转化为json字符串
+ *
+ * @param obj bean对象
+ * @param prettyFormat 是否格式化
+ * @return json
+ */
+ public static String toString(Object obj, boolean prettyFormat, boolean noNull) {
+ if (prettyFormat) {
+ if (noNull) {
+ return JSON.toJSONString(obj, SerializerFeature.DisableCircularReferenceDetect, SerializerFeature.PrettyFormat);
+ } else {
+ return JSON.toJSONString(obj, SerializerFeature.WriteMapNullValue, SerializerFeature.WriteNullListAsEmpty, SerializerFeature.DisableCircularReferenceDetect, SerializerFeature.PrettyFormat);
+ }
+ } else {
+ if (noNull) {
+ return JSON.toJSONString(obj, SerializerFeature.DisableCircularReferenceDetect);
+ } else {
+ return JSON.toJSONString(obj, SerializerFeature.WriteMapNullValue, SerializerFeature.WriteNullListAsEmpty, SerializerFeature.DisableCircularReferenceDetect);
+ }
+ }
+ }
+
+
+ /**
+ * 将字符串转换为Entity
+ *
+ * @param json 数据字符串
+ * @param clazz Entity class
+ * @return
+ */
+ public static T toEntity(String json, Class clazz) {
+ return JSON.parseObject(json, clazz);
+ }
+
+ /**
+ * 将字符串转换为Entity
+ *
+ * @param json 数据字符串
+ * @param typeReference Entity class
+ * @return
+ */
+ public static T toEntity(String json, TypeReference typeReference) {
+ return JSON.parseObject(json, typeReference);
+ }
+
+ /**
+ * 将字符串转换为Map
+ *
+ * @param json 数据字符串
+ * @return Map
+ */
+ public static Map toMap(String json) {
+ return JSON.parseObject(json, new TypeReference