001/******************************************************************************* 002 * This software is provided as a supplement to the authors' textbooks on digital 003 * image processing published by Springer-Verlag in various languages and editions. 004 * Permission to use and distribute this software is granted under the BSD 2-Clause 005 * "Simplified" License (see http://opensource.org/licenses/BSD-2-Clause). 006 * Copyright (c) 2006-2023 Wilhelm Burger, Mark J. Burge. All rights reserved. 007 * Visit https://imagingbook.com for additional details. 008 ******************************************************************************/ 009 010package imagingbook.common.regions; 011 012import imagingbook.common.geometry.basic.NeighborhoodType2D; 013import imagingbook.common.geometry.basic.Pnt2d; 014 015import java.awt.geom.Path2D; 016import java.util.ArrayList; 017import java.util.Iterator; 018import java.util.List; 019 020/** 021 * <p> 022 * This class represents a closed contour as a sequence of pixel coordinates. It implements the {@link Comparable} 023 * interface for sorting contours by length. It supports iteration over the points along the contour, e.g., by 024 * </p> 025 * <pre> 026 * Contour C = ...; 027 * for (Point p : C) { 028 * // process p ... 029 * } 030 * </pre> 031 * 032 * @author WB 033 * @version 2020/12/21 034 */ 035public class Contour implements Comparable<Contour>, Iterable<Pnt2d> { 036 037 static private int INITIAL_SIZE = 50; 038 039 private final int label; 040 private final List<Pnt2d> points; 041 042 /** 043 * Creates a new (empty) contour with the given region label. 044 * @param label the region label for this contour. 045 */ 046 public Contour (int label) { 047 this.label = label; 048 points = new ArrayList<Pnt2d>(INITIAL_SIZE); 049 } 050 051 public void addPoint (Pnt2d p) { 052 points.add(p); 053 } 054 055 //--------------------- retrieve contour points ------- 056 057 /** 058 * Get the list of contour points. 059 * @return a reference to the internal list of contour points. 060 */ 061 public List<Pnt2d> getPointList() { 062 return points; 063 } 064 065 /** 066 * Get the contour points as an array. 067 * @return a new array of contour points. 068 */ 069 public Pnt2d[] getPointArray() { 070 return points.toArray(new Pnt2d[0]); 071 } 072 073 //--------------------- contour statistics ------------ 074 075 /** 076 * Get the length of the contour. 077 * @return the number of points on the contour. 078 */ 079 public int getLength() { 080 return points.size(); 081 } 082 083 /** 084 * Get the region label associated with this contour. 085 * @return the region label of the contour. 086 */ 087 public int getLabel() { 088 return label; 089 } 090 091 //--------------------- debug methods ------------------ 092 093 @Override 094 public String toString(){ 095 return 096 "Contour " + label + ": " + this.getLength() + " points"; 097 } 098 099 /** 100 * Get the polygon for this contour (for subsequent drawing). 101 * @return the polygon. 102 */ 103 public Path2D getPolygonPath() { 104 return getPolygonPath(0.0, 0.0); 105 } 106 107 /** 108 * Get the polygon for this contour (for subsequent drawing). An offset can be specified for shifting the contour 109 * positions to pixel centers (by passing 0.5, 0.5). 110 * 111 * @param xOffset the horizontal offset. 112 * @param yOffset the vertical offset. 113 * @return a polygon. 114 */ 115 public Path2D getPolygonPath(double xOffset, double yOffset) { 116 Path2D path = new Path2D.Float(); 117 Pnt2d[] pnts = this.getPointArray(); 118 if (pnts.length > 1) { 119 path.moveTo(pnts[0].getX() + xOffset, pnts[0].getY() + yOffset); 120 for (int i = 1; i < pnts.length; i++) { 121 path.lineTo(pnts[i].getX() + xOffset, pnts[i].getY() + yOffset); 122 } 123 path.closePath(); 124 } 125 else { // special case: mark a single pixel region "X" 126 double x = pnts[0].getX(); 127 double y = pnts[0].getY(); 128 path.moveTo(x + xOffset - 0.5, y + yOffset - 0.5); 129 path.lineTo(x + xOffset + 0.5, y + yOffset + 0.5); 130 path.moveTo(x + xOffset - 0.5, y + yOffset + 0.5); 131 path.lineTo(x + xOffset + 0.5, y + yOffset - 0.5); 132 } 133 return path; 134 } 135 136 /** 137 * Returns the number of successive duplicates in this contour. The result should be zero. 138 * 139 * @return as described. 140 */ 141 public int countDuplicatePoints() { 142 Pnt2d[] pnts = this.getPointArray(); 143 if (pnts.length <= 1) { 144 return 0; 145 } 146 int cnt = 0; 147 for (int i = 0; i < pnts.length; i++) { 148 int j = (i + 1) % pnts.length; 149 if (pnts[i].getX() == pnts[j].getX() && pnts[i].getY() == pnts[j].getY()) { 150 cnt++; 151 } 152 } 153 return cnt; 154 } 155 156 /** 157 * Checks if this contour is closed w.r.t. the specified {@link NeighborhoodType2D}, i.e., if the last and the first 158 * contour point are "connected". 159 * 160 * @param nht the (@link NeighborhoodType}. 161 * @return true if the contour is closed. 162 */ 163 public boolean isClosed(NeighborhoodType2D nht) { 164 Pnt2d[] pnts = this.getPointArray(); 165 if (pnts.length < 2) 166 return true; 167 Pnt2d p1 = pnts[pnts.length - 1]; 168 Pnt2d p2 = pnts[0]; 169 double d2 = p1.distanceSq(p2); // N4: max 1, N8: max 2 170 //System.out.println(nht + " dist=" + d2); 171 172 if (nht == NeighborhoodType2D.N4 && d2 <= 1) 173 return true; 174 if (nht == NeighborhoodType2D.N8 && d2 <= 2) 175 return true; 176 return false; 177 } 178 179 180 // Compare method for sorting contours by length (longer contours at front) 181 @Override 182 public int compareTo(Contour other) { 183 //return other.points.size() - this.points.size(); 184 return Integer.compare(other.points.size(), this.points.size()); 185 } 186 187 @Override 188 public Iterator<Pnt2d> iterator() { 189 return points.iterator(); 190 } 191 192 // ----------------------------------------------------------------------------------- 193 194 public static class Outer extends Contour { 195 public Outer(int label) { 196 super(label); 197 } 198 199 @Override 200 public String toString(){ 201 return 202 "Contour.Outer " + this.getLabel() + ": " + this.getLength() + " points"; 203 } 204 } 205 206 public static class Inner extends Contour { 207 public Inner(int label) { 208 super(label); 209 } 210 211 @Override 212 public String toString(){ 213 return 214 "Contour.Inner " + this.getLabel() + ": " + this.getLength() + " points"; 215 } 216 } 217 218 219}