
14 changed files with 3 additions and 5063 deletions
@ -1,54 +0,0 @@ |
|||
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); |
|||
} |
@ -1,691 +0,0 @@ |
|||
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<OutRec> 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<Edge> 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" ); |
|||
} |
|||
} |
@ -1,481 +0,0 @@ |
|||
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<DoublePoint> 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; |
|||
} |
|||
//------------------------------------------------------------------------------
|
|||
} |
File diff suppressed because it is too large
@ -1,339 +0,0 @@ |
|||
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(); |
|||
} |
|||
} |
|||
|
|||
} |
@ -1,26 +0,0 @@ |
|||
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; |
|||
} |
|||
} |
@ -1,414 +0,0 @@ |
|||
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<IntPoint> everywhere. |
|||
* |
|||
* @author Tobias Mahlmann |
|||
* |
|||
*/ |
|||
public class Path extends ArrayList<LongPoint> { |
|||
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; |
|||
} |
|||
} |
@ -1,125 +0,0 @@ |
|||
package com.visual.open.anpr.core.clipper; |
|||
|
|||
import java.util.ArrayList; |
|||
|
|||
/** |
|||
* A pure convenience class to avoid writing List<Path> everywhere. |
|||
* |
|||
* @author Tobias Mahlmann |
|||
* |
|||
*/ |
|||
public class Paths extends ArrayList<Path> { |
|||
|
|||
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(); |
|||
} |
|||
} |
|||
|
|||
} |
@ -1,220 +0,0 @@ |
|||
package com.visual.open.anpr.core.clipper; |
|||
|
|||
import java.util.Comparator; |
|||
|
|||
public abstract class Point<T extends Number & Comparable<T>> { |
|||
public static class DoublePoint extends Point<Double> { |
|||
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<Long> { |
|||
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<T extends Number & Comparable<T>> implements Comparator<T> { |
|||
|
|||
@Override |
|||
public int compare( T a, T b ) throws ClassCastException { |
|||
return a.compareTo( b ); |
|||
} |
|||
} |
|||
|
|||
static boolean arePointsClose(Point<? extends Number> pt1, Point<? extends Number> pt2, double distSqrd ) { |
|||
final double dx = pt1.x.doubleValue() - pt2.x.doubleValue(); |
|||
final double dy = pt1.y.doubleValue() - pt2.y.doubleValue(); |
|||
return dx * dx + dy * dy <= distSqrd; |
|||
} |
|||
|
|||
static double distanceFromLineSqrd(Point<? extends Number> pt, Point<? extends Number> ln1, Point<? extends Number> ln2 ) { |
|||
//The equation of a line in general form (Ax + By + C = 0)
|
|||
//given 2 points (x¹,y¹) & (x²,y²) is ...
|
|||
//(y¹ - y²)x + (x² - x¹)y + (y² - y¹)x¹ - (x² - x¹)y¹ = 0
|
|||
//A = (y¹ - y²); B = (x² - x¹); C = (y² - y¹)x¹ - (x² - x¹)y¹
|
|||
//perpendicular distance of point (x³,y³) = (Ax³ + By³ + C)/Sqrt(A² + B²)
|
|||
//see http://en.wikipedia.org/wiki/Perpendicular_distance
|
|||
final double A = ln1.y.doubleValue() - ln2.y.doubleValue(); |
|||
final double B = ln2.x.doubleValue() - ln1.x.doubleValue(); |
|||
double C = A * ln1.x.doubleValue() + B * ln1.y.doubleValue(); |
|||
C = A * pt.x.doubleValue() + B * pt.y.doubleValue() - C; |
|||
return C * C / (A * A + B * B); |
|||
} |
|||
|
|||
static DoublePoint getUnitNormal( LongPoint pt1, LongPoint pt2 ) { |
|||
double dx = pt2.x - pt1.x; |
|||
double dy = pt2.y - pt1.y; |
|||
if (dx == 0 && dy == 0) { |
|||
return new DoublePoint(); |
|||
} |
|||
|
|||
final double f = 1 * 1.0 / Math.sqrt( dx * dx + dy * dy ); |
|||
dx *= f; |
|||
dy *= f; |
|||
|
|||
return new DoublePoint( dy, -dx ); |
|||
} |
|||
|
|||
protected static boolean isPt2BetweenPt1AndPt3( LongPoint pt1, LongPoint pt2, LongPoint pt3 ) { |
|||
if (pt1.equals( pt3 ) || pt1.equals( pt2 ) || pt3.equals( pt2 )) { |
|||
return false; |
|||
} |
|||
else if (pt1.x != pt3.x) { |
|||
return pt2.x > pt1.x == pt2.x < pt3.x; |
|||
} |
|||
else { |
|||
return pt2.y > pt1.y == pt2.y < pt3.y; |
|||
} |
|||
} |
|||
|
|||
protected static boolean slopesEqual( LongPoint pt1, LongPoint pt2, LongPoint pt3 ) { |
|||
return (pt1.y - pt2.y) * (pt2.x - pt3.x) - (pt1.x - pt2.x) * (pt2.y - pt3.y) == 0; |
|||
} |
|||
|
|||
protected static boolean slopesEqual( LongPoint pt1, LongPoint pt2, LongPoint pt3, LongPoint pt4 ) { |
|||
return (pt1.y - pt2.y) * (pt3.x - pt4.x) - (pt1.x - pt2.x) * (pt3.y - pt4.y) == 0; |
|||
} |
|||
|
|||
static boolean slopesNearCollinear( LongPoint pt1, LongPoint pt2, LongPoint pt3, double distSqrd ) { |
|||
//this function is more accurate when the point that's GEOMETRICALLY
|
|||
//between the other 2 points is the one that's tested for distance.
|
|||
//nb: with 'spikes', either pt1 or pt3 is geometrically between the other pts
|
|||
if (Math.abs( pt1.x - pt2.x ) > Math.abs( pt1.y - pt2.y )) { |
|||
if (pt1.x > pt2.x == pt1.x < pt3.x) { |
|||
return distanceFromLineSqrd( pt1, pt2, pt3 ) < distSqrd; |
|||
} |
|||
else if (pt2.x > pt1.x == pt2.x < pt3.x) { |
|||
return distanceFromLineSqrd( pt2, pt1, pt3 ) < distSqrd; |
|||
} |
|||
else { |
|||
return distanceFromLineSqrd( pt3, pt1, pt2 ) < distSqrd; |
|||
} |
|||
} |
|||
else { |
|||
if (pt1.y > pt2.y == pt1.y < pt3.y) { |
|||
return distanceFromLineSqrd( pt1, pt2, pt3 ) < distSqrd; |
|||
} |
|||
else if (pt2.y > pt1.y == pt2.y < pt3.y) { |
|||
return distanceFromLineSqrd( pt2, pt1, pt3 ) < distSqrd; |
|||
} |
|||
else { |
|||
return distanceFromLineSqrd( pt3, pt1, pt2 ) < distSqrd; |
|||
} |
|||
} |
|||
} |
|||
|
|||
private final static NumberComparator NUMBER_COMPARATOR = new NumberComparator(); |
|||
|
|||
protected T x; |
|||
|
|||
protected T y; |
|||
|
|||
protected T z; |
|||
|
|||
protected Point( Point<T> 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<T> 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
|
@ -1,116 +0,0 @@ |
|||
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<PolyNode> 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<PolyNode> getChilds() { |
|||
return Collections.unmodifiableList( childs ); |
|||
} |
|||
|
|||
public List<LongPoint> 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; |
|||
|
|||
} |
|||
|
|||
} |
@ -1,37 +0,0 @@ |
|||
package com.visual.open.anpr.core.clipper; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
|
|||
public class PolyTree extends PolyNode { |
|||
private final List<PolyNode> allPolys = new ArrayList<PolyNode>(); |
|||
|
|||
public void Clear() { |
|||
allPolys.clear(); |
|||
childs.clear(); |
|||
} |
|||
|
|||
public List<PolyNode> 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; |
|||
|
|||
} |
|||
|
|||
} |
@ -1,39 +0,0 @@ |
|||
package com.visual.open.anpr.core.domain; |
|||
|
|||
import org.opencv.core.Point; |
|||
|
|||
import java.util.List; |
|||
|
|||
public class Polygon { |
|||
private List<Point> points; |
|||
|
|||
public Polygon(List<Point> points){ |
|||
this.points = points; |
|||
} |
|||
|
|||
public double getArea(){ |
|||
int i, j; |
|||
double area = 0; |
|||
for (i = 0; i < this.points.size(); i++) |
|||
{ |
|||
j = (i + 1) % this.points.size(); |
|||
area += this.points.get(i).x * this.points.get(j).y; |
|||
area -= this.points.get(i).y * this.points.get(j).x; |
|||
} |
|||
area /= 2; |
|||
return Math.abs(area); |
|||
} |
|||
|
|||
public double getLength(){ |
|||
double result = 0; |
|||
for (int i = 0; i < this.points.size(); i++){ |
|||
Point u = points.get(i); |
|||
Point v = (i+1==this.points.size()) ? points.get(0): points.get(i+1); |
|||
double m = u.x - v.x; |
|||
double n = u.y - v.y; |
|||
result += Math.sqrt(m * m + n * n); |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
} |
Loading…
Reference in new issue