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 pt1, Point 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 pt, Point ln1, Point 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>() { + }); + } + + /** + * 将字符串转换为List + * + * @param json 数据字符串 + * @param collectionClass 泛型 + * @return list + */ + public static List toList(String json, Class collectionClass) { + return JSON.parseArray(json, collectionClass); + } + + /** + * 将字符串转换为List> + * + * @param json 数据字符串 + * @return list + */ + public static List> toListMap(String json) { + return JSON.parseObject(json, new TypeReference>>() { + }); + } + + /** + * 将字符串转换为Object + * + * @param json 数据字符串 + * @return list + */ + public static JSONObject toJsonObject(String json) { + return JSON.parseObject(json); + } + + /** + * 将字符串转换为Array + * + * @param json 数据字符串 + * @return list + */ + public static JSONArray toJsonArray(String json) { + return JSON.parseArray(json); + } + +} + diff --git a/open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/MaskUtil.java b/open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/MaskUtil.java new file mode 100755 index 0000000..37f84e8 --- /dev/null +++ b/open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/MaskUtil.java @@ -0,0 +1,111 @@ +package com.visual.open.anpr.core.utils; + +import com.visual.face.search.core.domain.FaceInfo; +import com.visual.face.search.core.domain.ImageMat; +import org.opencv.core.*; +import org.opencv.imgproc.Imgproc; + +import java.util.ArrayList; +import java.util.List; + +public class MaskUtil { + + /**添加遮罩层所需要的索引号:InsightCoordFaceKeyPoint**/ + private static int [] MASK_106_IST_ROUND_INDEX = new int[]{ + 1,9,10,11,12,13,14,15,16,2,3,4,5,6,7,8,0, + 24,23,22,21,20,19,18,32,31,30,29,28,27,26,25,17, + 101,105,104,103,102,50,51,49,48,43 + }; + + /** + * 添加遮罩层 + * @param image 原始图像 + * @param pts 指定不不需要填充的区域 + * @param release 是否释放参数image + * @return + */ + public static Mat mask(Mat image, List pts, boolean release){ + Mat pattern = null; + try { + pattern = MatOfPoint.zeros(image.size(), CvType.CV_8U); + Imgproc.fillPoly(pattern, pts, new Scalar(1,1,1)); + Mat dst = new Mat(); + image.copyTo(dst, pattern); + return dst; + }finally { + if(null != pattern){ + pattern.release(); + } + if(release && null != pts){ + for(MatOfPoint pt : pts){ + pt.release(); + } + } + if(release && null != image){ + image.release(); + } + } + } + + /** + * 添加遮罩层 + * @param image 原始图像 + * @param fillPoints 指定不不需要填充的区域的点 + * @param release 是否释放参数image + * @return + */ + public static Mat mask(Mat image, Point[] fillPoints, boolean release){ + List pts = null; + try { + pts = new ArrayList<>(); + pts.add(new MatOfPoint(fillPoints)); + return mask(image, pts, false); + }finally { + if(null != pts){ + for(MatOfPoint pt : pts){ + pt.release(); + } + } + if(release && null != image){ + image.release(); + } + } + } + + /** + * 添加遮罩层:InsightCoordFaceKeyPoint + * @param image 原始图像 + * @param points 人脸标记点 + * @param release 是否释放参数image + * @return + */ + public static Mat maskFor106InsightCoordModel(Mat image, FaceInfo.Points points, boolean release){ + try { + Point[] fillPoints = PointUtil.convert(points.select(MASK_106_IST_ROUND_INDEX)); + return mask(image, fillPoints, false); + }finally { + if(release && null != image){ + image.release(); + } + } + } + + /** + * 添加遮罩层:InsightCoordFaceKeyPoint + * @param image 原始图像 + * @param points 人脸标记点 + * @param release 是否释放参数image + * @return + */ + public static ImageMat maskFor106InsightCoordModel(ImageMat image, FaceInfo.Points points, boolean release){ + try { + Mat mat = maskFor106InsightCoordModel(image.toCvMat(), points, false); + return ImageMat.fromCVMat(mat); + }finally { + if(release && null != image){ + image.release(); + } + } + } + +} diff --git a/open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/MatUtil.java b/open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/MatUtil.java new file mode 100755 index 0000000..3e7fbd2 --- /dev/null +++ b/open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/MatUtil.java @@ -0,0 +1,97 @@ +package com.visual.open.anpr.core.utils; + +import org.opencv.core.Mat; +import org.opencv.core.Range; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.util.Base64; +import java.util.Objects; + +public class MatUtil { + + /** + * 将Mat转换为BufferedImage + * @param mat + * @return BufferedImage + */ + public static BufferedImage matToBufferedImage(Mat mat) { + int dataSize = mat.cols() * mat.rows() * (int) mat.elemSize(); + byte[] data = new byte[dataSize]; + mat.get(0, 0, data); + int type = mat.channels() == 1 ? BufferedImage.TYPE_BYTE_GRAY : BufferedImage.TYPE_3BYTE_BGR; + if (type == BufferedImage.TYPE_3BYTE_BGR) { + for (int i = 0; i < dataSize; i += 3) { + byte blue = data[i + 0]; + data[i + 0] = data[i + 2]; + data[i + 2] = blue; + } + } + BufferedImage image = new BufferedImage(mat.cols(), mat.rows(), type); + image.getRaster().setDataElements(0, 0, mat.cols(), mat.rows(), data); + return image; + } + + /** + * 将Mat转换为 Base64 + * @param mat + * @return Base64 + */ + public static String matToBase64(Mat mat) { + ByteArrayOutputStream byteArrayOutputStream = null; + try { + byteArrayOutputStream = new ByteArrayOutputStream(); + ImageIO.write(matToBufferedImage(mat), "jpg", byteArrayOutputStream); + byte[] bytes = byteArrayOutputStream.toByteArray(); + Base64.Encoder encoder = Base64.getMimeEncoder(); + return encoder.encodeToString(Objects.requireNonNull(bytes)); + }catch (Exception e){ + throw new RuntimeException(e); + }finally { + if(null != byteArrayOutputStream){ + try { + byteArrayOutputStream.close(); + } catch (Exception e) {} + } + } + } + + /** + * 横向拼接两个图像的数据(Mat),该两个图像的类型必须是相同的类型,如:均为CvType.CV_8UC3类型 + * @author bailichun + * @since 2020.02.20 15:00 + * @param m1 要合并的图像1(左图) + * @param m2 要合并的图像2(右图) + * @return 拼接好的Mat图像数据集。其高度等于两个图像中高度较大者的高度;其宽度等于两个图像的宽度之和。类型与两个输入图像相同。 + * @throws Exception 当两个图像数据的类型不同时,抛出异常 + */ + public static Mat concat(Mat m1, Mat m2){ + if(m1.type() != m2.type()){ + throw new RuntimeException("concat:两个图像数据的类型不同!"); + } + long time = System.currentTimeMillis(); + //宽度为两图的宽度之和 + double w = m1.size().width + m2.size().width; + //高度取两个矩阵中的较大者的高度 + double h = m1.size().height > m2.size().height ? m1.size().height : m2.size().height; + //创建一个大矩阵对象 + Mat des = Mat.zeros((int)h, (int)w, m1.type()); + //在最终的大图上标记一块区域,用于存放复制图1(左图)的数据,大小为从第0列到m1.cols()列 + Mat rectForM1 = des.colRange(new Range(0, m1.cols())); + //标记出位于rectForM1的垂直方向上中间位置的区域,高度为图1的高度,此时该区域的大小已经和图1的大小相同。(用于存放复制图1(左图)的数据) + int rowOffset1 = (int)(rectForM1.size().height-m1.rows())/2; + rectForM1 = rectForM1.rowRange(rowOffset1, rowOffset1 + m1.rows()); + //在最终的大图上标记一块区域,用于存放复制图2(右图)的数据 + Mat rectForM2 = des.colRange(new Range(m1.cols(), des.cols())); + //标记出位于rectForM2的垂直方向上中间位置的区域,高度为图2的高度,此时该区域的大小已经和图2的大小相同。(用于存放复制图2(右图)的数据) + int rowOffset2 = (int)(rectForM2.size().height-m2.rows())/2; + rectForM2 = rectForM2.rowRange(rowOffset2, rowOffset2 + m2.rows()); + //将图1拷贝到des的指定区域 rectForM1 + m1.copyTo(rectForM1); + //将图2拷贝到des的指定区域 rectForM2 + m2.copyTo(rectForM2); + return des; + } + +} diff --git a/open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/MathUtil.java b/open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/MathUtil.java new file mode 100755 index 0000000..5efbba5 --- /dev/null +++ b/open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/MathUtil.java @@ -0,0 +1,289 @@ +package com.visual.open.anpr.core.utils; + +import org.apache.commons.math3.linear.Array2DRowRealMatrix; +import org.apache.commons.math3.linear.ArrayRealVector; +import org.apache.commons.math3.linear.RealMatrix; +import org.apache.commons.math3.linear.RealVector; +import org.apache.commons.math3.stat.descriptive.moment.Mean; +import org.apache.commons.math3.stat.descriptive.moment.StandardDeviation; + +public class MathUtil { + + /** + * 创建向量 + * @param array 数组 + * @return 向量 + */ + public static RealVector createVector(double[] array){ + return new ArrayRealVector(array); + } + /** + * 创建向量 + * @param array 数组 + * @return 向量 + */ + public static RealVector createVector(Double[] array){ + return new ArrayRealVector(array); + } + + + /** + * 创建矩阵 + * @param array 矩阵数组 + * @return 矩阵 + */ + public static RealMatrix createMatrix(double[][] array){ + return new Array2DRowRealMatrix(array); + } + + /** + * 创建矩阵 + * @param array 矩阵数组 + * @return 矩阵 + */ + public static RealMatrix createMatrix(Double[][] array){ + double[][] data = new double[array.length][]; + for(int i=0; i< array.length; i++){ + double [] item = new double[array[i].length]; + for(int j=0; j1/(1+ Math.exp(-1 * p)); + + /** + * sigmoid + * @param tensor + * @return + */ + public static double[] sigmoid(double[] tensor){ + double[] result = new double[tensor.length]; + for (int i = 0; i < result.length; i++){ + result[i] = sigmoid.applyAsDouble(tensor[i]); + } + return result; + } + + /** + * sigmoid + * @param tensor + * @return + */ + public static double[][] sigmoid(double[][] tensor){ + double[][] result = new double[tensor.length][]; + for (int i = 0; i < result.length; i++){ + result[i] = sigmoid(tensor[i]); + } + return result; + } + +} diff --git a/open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/Similarity.java b/open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/Similarity.java new file mode 100644 index 0000000..fb2644c --- /dev/null +++ b/open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/Similarity.java @@ -0,0 +1,96 @@ +package com.visual.open.anpr.core.utils; + +import org.apache.commons.math3.linear.RealMatrix; + +import static com.visual.face.search.core.utils.ArrayUtil.matrixNorm; + +public class Similarity { + + /** + * 向量余弦相似度 + * @param leftVector + * @param rightVector + * @return + */ + public static float cosineSimilarity(float[] leftVector, float[] rightVector) { + double dotProduct = 0; + for (int i=0; i< leftVector.length; i++) { + dotProduct += leftVector[i] * rightVector[i]; + } + double d1 = 0.0d; + for (float value : leftVector) { + d1 += Math.pow(value, 2); + } + double d2 = 0.0d; + for (float value : rightVector) { + d2 += Math.pow(value, 2); + } + double cosineSimilarity; + if (d1 <= 0.0 || d2 <= 0.0) { + cosineSimilarity = 0.0; + } else { + cosineSimilarity = (dotProduct / (Math.sqrt(d1) * Math.sqrt(d2))); + } + return (float) cosineSimilarity; + } + + + /** + * 两个向量可以为任意维度,但必须保持维度相同,表示n维度中的两点 + * 欧式距离 + * @param vector1 + * @param vector2 + * @return 两点间距离 + */ + public static float euclideanDistance(float[] vector1, float[] vector2) { + double distance = 0; + if (vector1.length == vector2.length) { + for (int i = 0; i < vector1.length; i++) { + double temp = Math.pow((vector1[i] - vector2[i]), 2); + distance += temp; + } + distance = Math.sqrt(distance); + }else { + throw new RuntimeException("vector length not equal"); + } + return (float) distance; + } + + /** + * 向量余弦相似度,加入了norm变换 + * @param leftVector + * @param rightVector + * @return + */ + public static float cosineSimilarityNorm(float[] leftVector, float[] rightVector) { + RealMatrix rm1 = MathUtil.createMatrix(1, ArrayUtil.floatToDouble(leftVector)); + RealMatrix rm2 = MathUtil.createMatrix(1, ArrayUtil.floatToDouble(rightVector)); + RealMatrix num = rm1.multiply(rm2.transpose()); + double deco = matrixNorm(rm1.getData()) * matrixNorm(rm2.getData()); + double cos = num.getEntry(0, 0) / deco; + double sim = cos; + if(cos >= 0.5){ + sim = cos + 2 * (cos - 0.5) * (1 - cos); + }else if(cos >= 0){ + sim = cos - 2 * (cos - 0.5) * (0 - cos); + } + return Double.valueOf(sim).floatValue(); + } + + /** + * 对cos的原始值进行进行增强 + * @param cos + * @return + */ + public static float cosEnhance(float cos){ + double sim = cos; + if(cos >= 0.5){ + sim = cos + 2 * (cos - 0.5) * (1 - cos); + }else if(cos >= 0){ + sim = cos - 2 * (cos - 0.5) * (0 - cos); + } + return Double.valueOf(sim).floatValue(); + } + + +} diff --git a/open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/SoftMaxUtil.java b/open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/SoftMaxUtil.java new file mode 100755 index 0000000..c61a81c --- /dev/null +++ b/open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/SoftMaxUtil.java @@ -0,0 +1,40 @@ +package com.visual.open.anpr.core.utils; + +import java.util.Arrays; +import java.util.NoSuchElementException; + +/** + * SoftMaxUtil + */ +public class SoftMaxUtil { + + /** + * softMax + * @param tensor + * @return + */ + public static double[] softMax(double[] tensor){ + if(Arrays.stream(tensor).max().isPresent()){ + double maxValue = Arrays.stream(tensor).max().getAsDouble(); + double[] value = Arrays.stream(tensor).map(y-> Math.exp(y - maxValue)).toArray(); + double total = Arrays.stream(value).sum(); + return Arrays.stream(value).map(p -> p/total).toArray(); + }else{ + throw new NoSuchElementException("No value present"); + } + } + + /** + * softMax + * @param tensor + * @return + */ + public double[][] softMax(double[][] tensor){ + double[][] result = new double[tensor.length][]; + for (int i = 0; i < result.length; i++){ + result[i] = softMax(tensor[i]); + } + return result; + } + +} diff --git a/open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/ThreadUtil.java b/open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/ThreadUtil.java new file mode 100755 index 0000000..e2af18d --- /dev/null +++ b/open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/ThreadUtil.java @@ -0,0 +1,9 @@ +package com.visual.open.anpr.core.utils; + +public class ThreadUtil { + + public static void run(Runnable runnable){ + new Thread(runnable).start(); + } + +} diff --git a/open-anpr-core/src/main/resources/models/det.onnx b/open-anpr-core/src/main/resources/models/det.onnx new file mode 100644 index 0000000..7dd1227 Binary files /dev/null and b/open-anpr-core/src/main/resources/models/det.onnx differ