//----------------------------------------------------------------------------// // // // B a s i c S e c t i o n // // // //----------------------------------------------------------------------------// // <editor-fold defaultstate="collapsed" desc="hdr"> // // Copyright © Hervé Bitteur and others 2000-2013. All rights reserved. // // This software is released under the GNU General Public License. // // Goto http://kenai.com/projects/audiveris to report bugs or suggestions. // //----------------------------------------------------------------------------// // </editor-fold> package omr.lag; import omr.glyph.Nest; import omr.glyph.Shape; import omr.glyph.facets.Glyph; import omr.graph.BasicVertex; import omr.math.Barycenter; import omr.math.BasicLine; import omr.math.Line; import omr.math.PointsCollector; import omr.run.Orientation; import omr.run.Run; import omr.sheet.SystemInfo; import omr.stick.SectionRole; import omr.stick.StickRelation; import omr.ui.Colors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Polygon; import java.awt.Rectangle; import java.awt.Stroke; import java.awt.geom.PathIterator; import java.awt.image.BufferedImage; import java.awt.image.WritableRaster; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Set; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.adapters.XmlAdapter; import omr.ui.util.UIUtil; /** * Class {@code BasicSection} is a basic implementation of {@link Section}. * * <p>TODO: Get rid of StickRelation part ASAP? * * @author Hervé Bitteur */ @XmlAccessorType(XmlAccessType.NONE) @XmlRootElement(name = "section") public class BasicSection extends BasicVertex<Lag, Section> implements Section { //~ Static fields/initializers --------------------------------------------- /** Usual logger utility */ private static final Logger logger = LoggerFactory.getLogger(BasicSection.class); //~ Instance fields -------------------------------------------------------- /** Position of first run */ @XmlAttribute(name = "first-pos") private int firstPos; /** Section orientation */ @XmlAttribute(name = "orientation") private Orientation orientation; /** The collection of runs that make up the section */ @XmlElement(name = "run") private final List<Run> runs = new ArrayList<>(); /** Oriented bounding rectangle */ protected Rectangle orientedBounds; /** Absolute mass center */ private Point centroid; /** Contribution to the foreground */ private int foreWeight; /** Length of longest run */ private int maxRunLength; /** Number of pixels, whatever the gray level */ private int weight; /** Absolute contour points */ private Polygon polygon; /** Absolute contour box */ private Rectangle bounds; /** Adjacent sections from the other orientation */ private Set<Section> oppositeSections; /** * Glyph this section belongs to. * This reference is kept in sync with the containing GlyphLag activeMap. * Don't directly assign a value to 'glyph', use the setGlyph() method * instead. */ private Glyph glyph; /** To flag sections too thick for staff line (null = don't know) */ private Boolean fat = null; /** Flag to remember processing has been done */ private boolean processed = false; /** (Debug) flag this section as VIP */ private boolean vip; /** Relation between section and stick */ protected StickRelation relation; /** Approximating oriented line for this section */ protected Line orientedLine; /** The containing system, if any */ private SystemInfo system; /** * Default color. This is the permanent default which is used when * the color is reset by {@link #resetColor} */ protected Color defaultColor; /** * Color currently used. * By default, the color is the defaultColor chosen out of the palette. * But, temporarily, a section can be assigned a different color, * for example to highlight the section. */ protected Color color; //~ Constructors ----------------------------------------------------------- //--------------// // BasicSection // //--------------// /** * Creates a new BasicSection. */ public BasicSection () { } //~ Methods ---------------------------------------------------------------- //---------------// // allocateTable // //---------------// /** * For basic print out, allocate a drawing table, to be later filled * with section pixels * * @param box the limits of the drawing table * @return the table ready to be filled */ public static char[][] allocateTable (Rectangle box) { char[][] table = new char[box.height + 1][box.width + 1]; for (int i = 0; i < table.length; i++) { Arrays.fill(table[i], ' '); } return table; } //----------------// // drawingOfTable // //----------------// /** * Printout the filled drawing table * * @param table the filled table * @param box the table limits in the image */ public static String drawingOfTable (char[][] table, Rectangle box) { StringBuilder sb = new StringBuilder(); sb.append(String.format("%n")); sb.append(String.format( "xMin=%d, xMax=%d%n", box.x, box.x + box.width - 1)); sb.append(String.format( "yMin=%d, yMax=%d%n", box.y, box.y + box.height - 1)); for (int iy = 0; iy < table.length; iy++) { sb.append(String.format("%d:", iy + box.y)); char[] line = table[iy]; for (int ix = 0; ix < line.length; ix++) { sb.append(line[ix]); } sb.append(String.format("%n")); } return sb.toString(); } //--------------------// // addOppositeSection // //--------------------// @Override public void addOppositeSection (Section otherSection) { if (oppositeSections == null) { oppositeSections = new HashSet<>(); } oppositeSections.add(otherSection); } //--------// // append // //--------// @Override public void append (Run run) { runs.add(run); addRun(run); logger.debug("Appended {} to {}", run, this); } //-----------// // compareTo // //-----------// /** * Needed to implement Comparable, sorting sections first by absolute * abscissa, then by absolute ordinate. * * @param other the other section to compare to * @return the result of ordering */ @Override public int compareTo (Section other) { if (this == other) { return 0; } final Point ref = this.getBounds().getLocation(); final Point otherRef = other.getBounds().getLocation(); // Are x values different? final int dx = ref.x - otherRef.x; if (dx != 0) { return dx; } // Vertically aligned, so use ordinates final int dy = ref.y - otherRef.y; if (dy != 0) { return dy; } // Finally, use id. Note this should return zero since different // sections cannot overlap return this.getId() - other.getId(); } //-------------------// // computeParameters // //-------------------// @Override public void computeParameters () { // weight & foreWeight & maxRunLength weight = 0; foreWeight = 0; maxRunLength = 0; // maxRunLength for (Run run : runs) { computeRunContribution(run); } // Invalidate cached data invalidateCache(); logger.debug("Parameters of {} maxRunLength={} meanRunLength={}" + " weight={} foreWeight={}", this, getMaxRunLength(), getMeanRunLength(), weight, foreWeight); } //----------// // contains // //----------// @Override public boolean contains (int x, int y) { return getPolygon().contains(x, y); } //----------// // cumulate // //----------// @Override public void cumulate (Barycenter barycenter, Rectangle absRoi) { if (barycenter == null) { throw new IllegalArgumentException("Barycenter is null"); } if (absRoi == null) { // Take all run pixels int pos = firstPos - 1; for (Run run : runs) { double coord = run.getStart() + (run.getLength() / 2d); pos++; if (orientation == Orientation.HORIZONTAL) { barycenter.include(run.getLength(), coord, pos); } else { barycenter.include(run.getLength(), pos, coord); } } } else { Rectangle oRoi = orientation.oriented(absRoi); // Take only the pixels contained by the oriented roi int pos = firstPos - 1; int posMax = Math.min(firstPos + runs.size(), oRoi.y + oRoi.height) - 1; int coordMax = (oRoi.x + oRoi.width) - 1; for (Run run : runs) { pos++; if (pos < oRoi.y) { continue; } if (pos > posMax) { break; } final int roiStart = Math.max(run.getStart(), oRoi.x); final int roiStop = Math.min(run.getStop(), coordMax); for (int coord = roiStart; coord <= roiStop; coord++) { if (orientation == Orientation.HORIZONTAL) { barycenter.include(coord, pos); } else { barycenter.include(pos, coord); } } } } } //----------// // cumulate // //----------// @Override public void cumulate (PointsCollector collector) { final Rectangle roi = collector.getRoi(); if (roi == null) { int p = firstPos; for (Run run : runs) { final int start = run.getStart(); for (int ic = run.getLength() - 1; ic >= 0; ic--) { if (orientation == Orientation.HORIZONTAL) { collector.include(start + ic, p); } else { collector.include(p, start + ic); } } p++; } } else { // Take only the pixels contained by the absolute roi Rectangle oRoi = orientation.oriented(roi); final int pMin = oRoi.y; final int pMax = -1 + Math.min( firstPos + runs.size(), oRoi.y + oRoi.height); final int cMin = oRoi.x; final int cMax = (oRoi.x + oRoi.width) - 1; int p = firstPos - 1; for (Run run : runs) { p++; if (p < pMin) { continue; } if (p > pMax) { break; } final int roiStart = Math.max(run.getStart(), cMin); final int roiStop = Math.min(run.getStop(), cMax); final int length = roiStop - roiStart + 1; if (length > 0) { for (int c = roiStart; c <= roiStop; c++) { if (orientation == Orientation.HORIZONTAL) { collector.include(c, p); } else { collector.include(p, c); } } } } } } //-----------// // drawAscii // //-----------// @Override public void drawAscii () { System.out.println("Section#" + getId()); // Determine the absolute bounds Rectangle box = getBounds(); char[][] table = allocateTable(box); fillTable(table, box); drawingOfTable(table, box); } //-----------// // fillImage // //-----------// @Override public void fillImage (BufferedImage im, Rectangle box) { final WritableRaster raster = im.getRaster(); if (isVertical()) { int x = getFirstPos() - box.x; for (Run run : runs) { for (int y = run.getStart(); y <= run.getStop(); y++) { raster.setSample(x, y - box.y, 0, 255); } x += 1; } } else { int y = getFirstPos() - box.y; for (Run run : runs) { for (int x = run.getStart(); x <= run.getStop(); x++) { raster.setSample(x - box.x, y, 0, 255); } y += 1; } } } //-----------// // fillTable // //-----------// @Override public void fillTable (char[][] table, Rectangle box) { // Determine the bounds getPolygon(); // Make sure the polygon is available int xPrev = 0; int yPrev = 0; int x; int y; for (int i = 0; i <= polygon.npoints; i++) { if (i == polygon.npoints) { // Last point x = polygon.xpoints[0] - box.x; y = polygon.ypoints[0] - box.y; } else { x = polygon.xpoints[i] - box.x; y = polygon.ypoints[i] - box.y; } if (i > 0) { if (x != xPrev) { // Horizontal int x1 = Math.min(x, xPrev); int x2 = Math.max(x, xPrev); for (int ix = x1 + 1; ix < x2; ix++) { table[y][ix] = '-'; } } else { // Vertical int y1 = Math.min(y, yPrev); int y2 = Math.max(y, yPrev); for (int iy = y1 + 1; iy < y2; iy++) { table[iy][x] = '|'; } } } table[y][x] = '+'; xPrev = x; yPrev = y; } } //-----------------// // getAbsoluteLine // //-----------------// @Override public Line getAbsoluteLine () { getOrientedLine(); return orientation.switchRef(orientedLine); } //---------------// // getAreaCenter // //---------------// @Override public Point getAreaCenter () { Rectangle box = getBounds(); return new Point( box.x + (box.width / 2), box.y + (box.height / 2)); } //-----------// // getAspect // //-----------// @Override public double getAspect (Orientation orientation) { return (double) getLength(orientation) / (double) getThickness( orientation); } //-----------// // getBounds // //-----------// @Override public Rectangle getBounds () { if (bounds == null) { bounds = new Rectangle(getPolygon().getBounds()); } return new Rectangle(bounds); // Copy! } //-------------// // getCentroid // //-------------// @Override public Point getCentroid () { if (centroid == null) { Point orientedPoint = new Point(0, 0); int y = firstPos; for (Run run : runs) { final int length = run.getLength(); orientedPoint.y += (length * (2 * y)); orientedPoint.x += (length * ((2 * run.getStart()) + length)); y++; } orientedPoint.x /= (2 * getWeight()); orientedPoint.y /= (2 * getWeight()); centroid = orientation.absolute(orientedPoint); logger.debug("Centroid of {} is {}", this, centroid); } return centroid; } //-----------------// // getDefaultColor // //-----------------// @Override public Color getDefaultColor () { return defaultColor; } //-------------------// // getFirstAdjacency // //-------------------// @Override public double getFirstAdjacency () { Run run = getFirstRun(); int runStart = run.getStart(); int runStop = run.getStop(); int adjacency = 0; for (Section source : getSources()) { Run lastRun = source.getLastRun(); int start = Math.max(runStart, lastRun.getStart()); int stop = Math.min(runStop, lastRun.getStop()); if (stop >= start) { adjacency += (stop - start + 1); } } return (double) adjacency / (double) run.getLength(); } //-------------// // getFirstPos // //-------------// @Override public int getFirstPos () { return firstPos; } //-------------// // getFirstRun // //-------------// @Override public Run getFirstRun () { return runs.get(0); } //---------------// // getForeWeight // //---------------// @Override public int getForeWeight () { return foreWeight; } //----------// // getGlyph // //----------// @Override public Glyph getGlyph () { return glyph; } //----------// // getGraph // //----------// /** * Report the containing graph (lag) of this vertex (section) * * @return the containing graph */ @Override public Lag getGraph () { return graph; } //------------------// // getLastAdjacency // //------------------// @Override public double getLastAdjacency () { Run run = getLastRun(); int runStart = run.getStart(); int runStop = run.getStop(); int adjacency = 0; for (Section target : getTargets()) { Run firstRun = target.getFirstRun(); int start = Math.max(runStart, firstRun.getStart()); int stop = Math.min(runStop, firstRun.getStop()); if (stop >= start) { adjacency += (stop - start + 1); } } return (double) adjacency / (double) run.getLength(); } //------------// // getLastPos // //------------// @Override public int getLastPos () { return (firstPos + getRunCount()) - 1; } //------------// // getLastRun // //------------// @Override public Run getLastRun () { return runs.get(runs.size() - 1); } //-----------// // getLength // //-----------// @Override public int getLength (Orientation orientation) { if (orientation == Orientation.HORIZONTAL) { return getBounds().width; } else { return getBounds().height; } } //----------// // getLevel // //----------// @Override public int getLevel () { return (int) Math.rint((double) foreWeight / (double) weight); } //-----------------// // getMaxRunLength // //-----------------// @Override public int getMaxRunLength () { return maxRunLength; } //---------------// // getMeanAspect // //---------------// @Override public double getMeanAspect (Orientation orientation) { return getLength(orientation) / getMeanThickness(orientation); } //------------------// // getMeanRunLength // //------------------// @Override public int getMeanRunLength () { return weight / getRunCount(); } //------------------// // getMeanThickness // //------------------// @Override public double getMeanThickness (Orientation orientation) { return (double) getWeight() / getLength(orientation); } //---------------------// // getOppositeSections // //---------------------// @Override public Set<Section> getOppositeSections () { if (oppositeSections != null) { return Collections.unmodifiableSet(oppositeSections); } else { return Collections.emptySet(); } } //----------------// // getOrientation // //----------------// @Override public Orientation getOrientation () { return orientation; } //-------------------// // getOrientedBounds // //-------------------// @Override public Rectangle getOrientedBounds () { if (orientedBounds == null) { orientedBounds = new Rectangle(orientation.oriented(getBounds())); } return orientedBounds; } //-----------------// // getOrientedLine // //-----------------// @Override public Line getOrientedLine () { if (orientedLine == null) { // Compute the section line orientedLine = new BasicLine(); int y = getFirstPos(); for (Run run : getRuns()) { int stop = run.getStop(); for (int x = run.getStart(); x <= stop; x++) { orientedLine.includePoint((double) x, (double) y); } y++; } } return orientedLine; } //-----------------// // getPathIterator // //-----------------// @Override public PathIterator getPathIterator () { return getPolygon().getPathIterator(null); } //------------// // getPolygon // //------------// @Override public Polygon getPolygon () { if (polygon == null) { polygon = computePolygon(); } return polygon; } //----------------------// // getRectangleCentroid // //----------------------// @Override public Point getRectangleCentroid (Rectangle absRoi) { if (absRoi == null) { throw new IllegalArgumentException("Rectangle of Interest is null"); } Barycenter barycenter = new Barycenter(); cumulate(barycenter, absRoi); if (barycenter.getWeight() != 0) { return new Point( (int) Math.rint(barycenter.getX()), (int) Math.rint(barycenter.getY())); } else { return null; } } //-------------// // getRelation // //-------------// @Override public StickRelation getRelation () { return relation; } //-------------// // getRunCount // //-------------// @Override public int getRunCount () { return runs.size(); } //---------// // getRuns // //---------// @Override public List<Run> getRuns () { return runs; } //---------------// // getStartCoord // //---------------// @Override public int getStartCoord () { return getOrientedBounds().x; } //--------------// // getStopCoord // //--------------// @Override public int getStopCoord () { Rectangle bounds = getOrientedBounds(); return bounds.x + (bounds.width - 1); } //-----------// // getSystem // //-----------// @Override public SystemInfo getSystem () { return system; } //--------------// // getThickness // //--------------// @Override public int getThickness (Orientation orientation) { if (orientation == Orientation.HORIZONTAL) { return getBounds().height; } else { return getBounds().width; } } //-----------// // getWeight // //-----------// @Override public int getWeight () { if (weight == 0) { computeParameters(); } return weight; } //---------------// // inNextSibling // //---------------// @Override public Section inNextSibling () { // Check we have sources if (getInDegree() == 0) { return null; } // Proper source section Section source = getSources().get(getInDegree() - 1); // Browse till we get to this as target for (Iterator<Section> li = source.getTargets().iterator(); li.hasNext();) { Section section = li.next(); if (section == this) { if (li.hasNext()) { return li.next(); } else { return null; } } } logger.error("inNextSibling inconsistent graph"); return null; } //-------------------// // inPreviousSibling // //-------------------// @Override public Section inPreviousSibling () { if (getInDegree() == 0) { return null; } // Proper source section Section source = getSources().get(0); // Browse till we get to this as target for (ListIterator<Section> li = source.getTargets().listIterator( source.getOutDegree()); li.hasPrevious();) { Section section = li.previous(); if (section == this) { if (li.hasPrevious()) { return li.previous(); } else { return null; } } } logger.error("inPreviousSibling inconsistent graph"); return null; } //------------// // intersects // //------------// @Override public boolean intersects (Rectangle rect) { return getPolygon().intersects(rect); } //--------------// // isAggregable // //--------------// @Override public boolean isAggregable () { if ((relation == null) || !relation.isCandidate()) { return false; } return !isKnown(); } //-------------// // isColorized // //-------------// @Override public boolean isColorized () { return defaultColor != null; } //-------// // isFat // //-------// @Override public Boolean isFat () { return fat; } //---------------// // isGlyphMember // //---------------// @Override public boolean isGlyphMember () { return glyph != null; } //---------// // isKnown // //---------// @Override public boolean isKnown () { return (glyph != null) && (glyph.isSuccessful() || glyph.isWellKnown()); } //-------------// // isProcessed // //-------------// @Override public boolean isProcessed () { return processed; } //------------// // isVertical // //------------// @Override public boolean isVertical () { return orientation == Orientation.VERTICAL; } //-------// // isVip // //-------// @Override public boolean isVip () { return vip; } //-------// // merge // //-------// @Override public void merge (Section other) { logger.debug("Merging {} with {}", this, other); runs.addAll(other.getRuns()); computeParameters(); logger.debug("Merged {}", this); } //----------------// // outNextSibling // //----------------// @Override public Section outNextSibling () { if (getOutDegree() == 0) { return null; } // Proper target section Section target = getTargets().get(getOutDegree() - 1); // Browse till we get to this as source for (Iterator<Section> li = target.getSources().iterator(); li.hasNext();) { Section section = li.next(); if (section == this) { if (li.hasNext()) { return li.next(); } else { return null; } } } logger.error("outNextSibling inconsistent graph"); return null; } //--------------------// // outPreviousSibling // //--------------------// @Override public Section outPreviousSibling () { if (getOutDegree() == 0) { return null; } // Proper target section Section target = getTargets().get(getOutDegree() - 1); // Browse till we get to this as source for (ListIterator<Section> li = target.getSources().listIterator( target.getInDegree()); li.hasPrevious();) { Section section = li.previous(); if (section == this) { if (li.hasPrevious()) { return li.previous(); } else { return null; } } } logger.error("outPreviousSibling inconsistent graph"); return null; } //---------// // prepend // //---------// @Override public void prepend (Run run) { logger.debug("Prepending {} to {}", run, this); firstPos--; runs.add(0, run); addRun(run); logger.debug("Prepended {}", this); } //--------// // render // //--------// @Override public boolean render (Graphics g, boolean drawBorders) { Rectangle clip = g.getClipBounds(); Rectangle rect = getBounds(); Color oldColor = g.getColor(); if (clip.intersects(rect)) { // Default section color Color color = isVertical() ? Colors.GRID_VERTICAL : Colors.GRID_HORIZONTAL; // Use color defined for section glyph shape, if any Glyph glyph = getGlyph(); if (glyph != null) { Shape shape = glyph.getShape(); if (shape != null) { color = shape.getColor(); } } g.setColor(color); // Fill polygon with proper color Polygon polygon = getPolygon(); g.fillPolygon(polygon.xpoints, polygon.ypoints, polygon.npoints); // Draw polygon borders if so desired if (drawBorders) { g.setColor(Color.black); g.drawPolygon( polygon.xpoints, polygon.ypoints, polygon.npoints); } g.setColor(oldColor); return true; } else { return false; } } //----------------// // renderSelected // //----------------// @Override public boolean renderSelected (Graphics g) { Rectangle clip = g.getClipBounds(); Rectangle rect = getBounds(); if (clip.intersects(rect)) { Graphics2D g2 = (Graphics2D) g; final Stroke oldStroke = UIUtil.setAbsoluteStroke(g2, 1f); Polygon polygon = getPolygon(); g.setColor(Color.white); g.fillPolygon(polygon.xpoints, polygon.ypoints, polygon.npoints); g.setColor(Color.black); g.drawPolygon(polygon.xpoints, polygon.ypoints, polygon.npoints); g2.setStroke(oldStroke); return true; } else { return false; } } //------------// // resetColor // //------------// @Override public void resetColor () { setColor(defaultColor); } //----------// // resetFat // //----------// @Override public void resetFat () { this.fat = null; } //----------// // setColor // //----------// @Override public void setColor (Color color) { this.color = color; } //-----------------// // setDefaultColor // //-----------------// @Override public void setDefaultColor (Color color) { defaultColor = color; } //--------// // setFat // //--------// @Override public void setFat (boolean fat) { this.fat = fat; } //-------------// // setFirstPos // //-------------// @Override public void setFirstPos (int firstPos) { this.firstPos = firstPos; } //----------// // setGlyph // //----------// @Override public void setGlyph (Glyph glyph) { // Keep the activeMap of the containing Nest in sync! Nest nest = null; if ((glyph != null) && (glyph.getNest() != null)) { nest = glyph.getNest(); } else if ((this.glyph != null) && (this.glyph.getNest() != null)) { nest = this.glyph.getNest(); } this.glyph = glyph; if (nest != null) { nest.mapSection(this, glyph); } if (isVip()) { logger.info("{} linkedTo {}", this, glyph); if (glyph != null) { glyph.setVip(); } } } //----------// // setGraph // //----------// /** * (package access from graph) */ @Override public void setGraph (Lag lag) { super.setGraph(lag); if (lag != null) { orientation = lag.getOrientation(); } } //-----------// // setParams // //-----------// /** * Assign major parameters (kind, layer and direction), since the enclosing * stick may be assigned later. * * @param role the role of this section in stick elaboration * @param layer the layer from stick core * @param direction the direction when departing from the stick core */ public void setParams (SectionRole role, int layer, int direction) { if (relation == null) { relation = new StickRelation(); } relation.setParams(role, layer, direction); } //--------------// // setProcessed // //--------------// @Override public void setProcessed (boolean processed) { this.processed = processed; } //-----------// // setSystem // //-----------// @Override public void setSystem (SystemInfo system) { this.system = system; } //--------// // setVip // //--------// @Override public void setVip () { vip = true; } //----------// // toString // //----------// @Override public String toString () { StringBuilder sb = new StringBuilder(); sb.append("{Section"); if (orientation != null) { sb.append(isVertical() ? "V" : "H"); } else { sb.append("?"); } sb.append("#").append(getId()); sb.append(internalsString()); sb.append("}"); return sb.toString(); } //-----------// // translate // //-----------// @Override public void translate (Point vector) { // Get the coord/pos equivalent of dx/dy vector Point cp = orientation.oriented(vector); int dc = cp.x; int dp = cp.y; // Apply the needed modifications firstPos += dp; for (Run run : runs) { run.translate(dc); } // Force update invalidateCache(); } //----------------// // computePolygon // //----------------// /** * Compute the arrays of points needed to draw the section runs. * This is an absolute definition. */ protected Polygon computePolygon () { final int maxNb = 1 + (4 * getRunCount()); // Upper value final int[] xx = new int[maxNb]; final int[] yy = new int[maxNb]; int idx = 0; // Current filling index in xx & yy arrays if (isVertical()) { idx = populatePolygon(yy, xx, idx, 1); idx = populatePolygon(yy, xx, idx, -1); } else { idx = populatePolygon(xx, yy, idx, 1); idx = populatePolygon(xx, yy, idx, -1); } Polygon poly = new Polygon(xx, yy, idx); return poly; } //-----------------// // internalsString // //-----------------// @Override protected String internalsString () { StringBuilder sb = new StringBuilder(super.internalsString()); if (oppositeSections != null) { sb.append("/").append(oppositeSections.size()); } // sb.append(" fPos=") // .append(firstPos) // .append(" "); // sb.append(getFirstRun()); // // if (getRunCount() > 1) { // sb.append("-") // .append(getRunCount()) // .append("-") // .append(getLastRun()); // } // // sb.append(" Wt=") // .append(weight); // sb.append(" lv=") // .append(getLevel()); // sb.append(" fW=") // .append(foreWeight); if ((isFat() != null) && isFat()) { sb.append(" fat"); } if (relation != null) { sb.append(" ").append(relation); } if (glyph != null) { sb.append(" ").append(glyph.idString()); if (glyph.getShape() != null) { sb.append(":").append(glyph.getShape()); } } // if (system != null) { // sb.append(" syst:") // .append(system.getId()); // } return sb.toString(); } //-----------------// // invalidateCache // //-----------------// protected void invalidateCache () { orientedBounds = null; centroid = null; polygon = null; bounds = null; orientedLine = null; } //--------// // addRun // //--------// /** * Compute incrementally the cached parameters */ private void addRun (Run run) { // Invalidate cached data invalidateCache(); // Link back from run to section run.setSection(this); // Compute contribution of this run computeRunContribution(run); } //------------------------// // computeRunContribution // //------------------------// private void computeRunContribution (Run run) { final int length = run.getLength(); weight += length; foreWeight += (length * run.getLevel()); maxRunLength = Math.max(maxRunLength, length); } //-----------------// // populatePolygon // //-----------------// /** * Compute the arrays of points needed to draw the section runs * * @param xpoints to receive abscissae * @param ypoints to receive coordinates * @param dir direction for browsing runs * @param index first index available in arrays * @return last index value */ private int populatePolygon (int[] xpoints, int[] ypoints, int index, int dir) { // Precise delimitating points int runNb = getRunCount(); int iStart = (dir > 0) ? 0 : (runNb - 1); int iBreak = (dir > 0) ? runNb : (-1); int y = (dir > 0) ? getFirstPos() : (getFirstPos() + runNb); int xPrev = -1; for (int i = iStart; i != iBreak; i += dir) { Run run = runs.get(i); // +----------------------------+ // +--+-------------------------+ // +----------------------+--+ // +----------------------+ // // Order of the 4 angle points for a run is // Vertical lag: Horizontal lag: // 1 2 1 4 // 4 3 2 3 int x = (dir > 0) ? run.getStart() : (run.getStop() + 1); if (x != xPrev) { if (xPrev != -1) { // Insert last vertex xpoints[index] = xPrev; ypoints[index] = y; index++; } // Insert new vertex xpoints[index] = x; ypoints[index] = y; index++; xPrev = x; } y += dir; } // Complete the sequence, with a new vertex xpoints[index] = xPrev; ypoints[index] = y; index++; if (dir < 0) { // Finish with starting point xpoints[index] = runs.get(0).getStart(); ypoints[index] = getFirstPos(); index++; } return index; } //~ Inner Classes ---------------------------------------------------------- //---------// // Adapter // //---------// /** * Meant for JAXB handling of Section interface */ public static class Adapter extends XmlAdapter<BasicSection, Section> { //~ Methods ------------------------------------------------------------ @Override public BasicSection marshal (Section s) { return (BasicSection) s; } @Override public Section unmarshal (BasicSection s) { return s; } } }