//----------------------------------------------------------------------------// // // // T a r g e t B u i l d e r // // // //----------------------------------------------------------------------------// // <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.grid; import omr.Main; import omr.constant.Constant; import omr.constant.ConstantSet; import omr.score.ScoresManager; import omr.sheet.Scale; import omr.sheet.Sheet; import omr.sheet.Skew; import omr.sheet.SystemInfo; import omr.sheet.picture.jai.JaiDewarper; import omr.ui.Colors; import omr.ui.view.RubberPanel; import omr.ui.view.ScrollView; import omr.util.HorizontalSide; import static omr.util.HorizontalSide.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.awt.BasicStroke; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Stroke; import java.awt.geom.AffineTransform; import java.awt.geom.Path2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.RenderedImage; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import javax.imageio.ImageIO; /** * Class {@code TargetBuilder} is in charge of building a "perfect" * definition of target systems, staves and lines as well as the * dewarp grid that allows to transform the original image in to the * perfect image. * * @author Hervé Bitteur */ public class TargetBuilder { //~ Static fields/initializers --------------------------------------------- /** Specific application parameters */ private static final Constants constants = new Constants(); /** Usual logger utility */ private static final Logger logger = LoggerFactory.getLogger(TargetBuilder.class); //~ Instance fields -------------------------------------------------------- /** Related sheet */ private final Sheet sheet; /** Target width */ private double targetWidth; /** Target height */ private double targetHeight; /** Transform from initial point to deskewed point */ private AffineTransform at; /** The target page */ private TargetPage targetPage; /** All target lines */ private List<TargetLine> allTargetLines = new ArrayList<>(); /** Source points */ private List<Point2D> srcPoints = new ArrayList<>(); /** Destination points */ private List<Point2D> dstPoints = new ArrayList<>(); //~ Constructors ----------------------------------------------------------- //---------------// // TargetBuilder // //---------------// /** * Creates a new TargetBuilder object. * * @param sheet the related sheet */ public TargetBuilder (Sheet sheet) { this.sheet = sheet; } //~ Methods ---------------------------------------------------------------- //-----------// // buildInfo // //-----------// public void buildInfo () { buildTarget(); JaiDewarper dewarper = new JaiDewarper(sheet); buildWarpGrid(dewarper); // Dewarp the initial image RenderedImage dewarpedImage = dewarper.dewarpImage(); // Add a view on dewarped image? if (Main.getGui() != null) { sheet.getAssembly() .addViewTab( "Dewarped", new ScrollView(new DewarpedView(dewarpedImage)), null); } // Store dewarped image on disk if (constants.storeDewarp.getValue()) { storeImage(dewarpedImage); } } //---------------// // renderSystems // //---------------// /** * TODO: This should be done from a more central class * * @param g graphical context */ public void renderSystems (Graphics2D g) { Scale scale = sheet.getScale(); Skew skew = sheet.getSkew(); // Make sure we are not painting changing data... if (scale == null || skew == null) { return; } double absDx = scale.toPixelsDouble(constants.systemMarkWidth); double absDy = skew.getSlope() * absDx; Stroke systemStroke = new BasicStroke( (float) scale.toPixelsDouble(constants.systemMarkStroke), BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND); g.setStroke(systemStroke); g.setColor(Colors.SYSTEM_BRACKET); for (SystemInfo system : sheet.getSystems()) { for (HorizontalSide side : HorizontalSide.values()) { Point2D top = system.getFirstStaff() .getFirstLine() .getEndPoint(side); Point2D bot = system.getLastStaff() .getLastLine() .getEndPoint(side); // Draw something like a vertical bracket double dx = (side == LEFT) ? (-absDx) : absDx; double dy = (side == LEFT) ? (-absDy) : absDy; Path2D p = new Path2D.Double(); p.moveTo(top.getX(), top.getY()); p.lineTo(top.getX() + dx, top.getY() + dy); p.lineTo(bot.getX() + dx, bot.getY() + dy); p.lineTo(bot.getX(), bot.getY()); g.draw(p); } } } //----------------// // renderWarpGrid // //----------------// /** * Render the grid used to dewarp the sheet image * * @param g the graphic context * @param useSource true to renderAttachments the source grid, false to * renderAttachments the * destination grid */ public void renderWarpGrid (Graphics g, boolean useSource) { if (!constants.displayGrid.getValue()) { return; } Graphics2D g2 = (Graphics2D) g; List<Point2D> points = useSource ? srcPoints : dstPoints; double radius = sheet.getScale() .toPixelsDouble(constants.gridPointSize); g2.setColor(Colors.WARP_POINT); Rectangle2D rect = new Rectangle2D.Double(); for (Point2D pt : points) { rect.setRect( pt.getX() - radius, pt.getY() - radius, 2 * radius, 2 * radius); g2.fill(rect); } } //-------------// // buildTarget // //-------------// /** * Build a perfect definition of target page, systems, staves and * lines. * * We apply a rotation on every top-left corner */ private void buildTarget () { final Skew skew = sheet.getSkew(); // Target page parameters targetPage = new TargetPage(targetWidth, targetHeight); TargetLine prevLine = null; // Latest staff line // Target system parameters for (SystemInfo system : sheet.getSystems()) { StaffInfo firstStaff = system.getFirstStaff(); LineInfo firstLine = firstStaff.getFirstLine(); Point2D dskLeft = skew.deskewed(firstLine.getEndPoint(LEFT)); Point2D dskRight = skew.deskewed(firstLine.getEndPoint(RIGHT)); if (prevLine != null) { // Preserve position relative to bottom left of previous system Point2D prevDskLeft = skew.deskewed( prevLine.info.getEndPoint(LEFT)); TargetSystem prevSystem = prevLine.staff.system; double dx = prevSystem.left - prevDskLeft.getX(); double dy = prevLine.y - prevDskLeft.getY(); dskLeft.setLocation(dskLeft.getX() + dx, dskLeft.getY() + dy); dskRight.setLocation( dskRight.getX() + dx, dskRight.getY() + dy); } TargetSystem targetSystem = new TargetSystem( system, dskLeft.getY(), dskLeft.getX(), dskRight.getX()); targetPage.systems.add(targetSystem); // Target staff parameters for (StaffInfo staff : system.getStaves()) { dskLeft = skew.deskewed(staff.getFirstLine().getEndPoint(LEFT)); if (prevLine != null) { // Preserve inter-staff vertical gap Point2D prevDskLeft = skew.deskewed( prevLine.info.getEndPoint(LEFT)); dskLeft.setLocation( dskLeft.getX(), dskLeft.getY() + (prevLine.y - prevDskLeft.getY())); } TargetStaff targetStaff = new TargetStaff( staff, dskLeft.getY(), targetSystem); targetSystem.staves.add(targetStaff); // Target line parameters int lineIdx = -1; for (LineInfo line : staff.getLines()) { lineIdx++; // Enforce perfect staff interline TargetLine targetLine = new TargetLine( line, targetStaff.top + (staff.getSpecificScale().getInterline() * lineIdx), targetStaff); allTargetLines.add(targetLine); targetStaff.lines.add(targetLine); prevLine = targetLine; } } } } //---------------// // buildWarpGrid // //---------------// private void buildWarpGrid (JaiDewarper dewarper) { int xStep = sheet.getInterline(); int xNumCells = (int) Math.ceil(sheet.getWidth() / (double) xStep); int yStep = sheet.getInterline(); int yNumCells = (int) Math.ceil(sheet.getHeight() / (double) yStep); for (int ir = 0; ir <= yNumCells; ir++) { for (int ic = 0; ic <= xNumCells; ic++) { Point2D dst = new Point2D.Double(ic * xStep, ir * yStep); dstPoints.add(dst); Point2D src = sourceOf(dst); srcPoints.add(src); } } float[] warpPositions = new float[srcPoints.size() * 2]; int i = 0; for (Point2D p : srcPoints) { warpPositions[i++] = (float) p.getX(); warpPositions[i++] = (float) p.getY(); } dewarper.createWarpGrid( 0, xStep, xNumCells, 0, yStep, yNumCells, warpPositions); } //----------// // sourceOf // //----------// /** * This key method provides the source point (in original sheet image) * that corresponds to a given destination point (in target dewarped image). * * The strategy is to stay consistent with the staff lines nearby which * are used as grid references. * * @param dst the given destination point * @return the corresponding source point */ private Point2D sourceOf (Point2D dst) { double dstX = dst.getX(); double dstY = dst.getY(); // Retrieve north & south lines, if any TargetLine northLine = null; TargetLine southLine = null; for (TargetLine line : allTargetLines) { if (line.y <= dstY) { northLine = line; } else { southLine = line; break; } } // Case of image top: no northLine if (northLine == null) { return southLine.sourceOf(dst); } // Case of image bottom: no southLine if (southLine == null) { return northLine.sourceOf(dst); } // Normal case: use y barycenter between projections sources Point2D srcNorth = northLine.sourceOf(dstX); Point2D srcSouth = southLine.sourceOf(dstX); double yRatio = (dstY - northLine.y) / (southLine.y - northLine.y); return new Point2D.Double( ((1 - yRatio) * srcNorth.getX()) + (yRatio * srcSouth.getX()), ((1 - yRatio) * srcNorth.getY()) + (yRatio * srcSouth.getY())); } //------------// // storeImage // //------------// private void storeImage (RenderedImage dewarpedImage) { String pageId = sheet.getPage().getId(); File file = new File( ScoresManager.getInstance().getDefaultDewarpDirectory(), pageId + ".dewarped.png"); try { String path = file.getCanonicalPath(); ImageIO.write(dewarpedImage, "png", file); logger.info("Wrote {}", path); } catch (IOException ex) { logger.warn("Could not write {}", file); } } //~ Inner Classes ---------------------------------------------------------- //-----------// // Constants // //-----------// private static final class Constants extends ConstantSet { //~ Instance fields ---------------------------------------------------- Constant.Boolean displayGrid = new Constant.Boolean( false, "Should we display the dewarp grid?"); Scale.LineFraction gridPointSize = new Scale.LineFraction( 0.2, "Size of displayed grid points"); Scale.Fraction systemMarkWidth = new Scale.Fraction( 2.0, "Width of system marks"); Scale.LineFraction systemMarkStroke = new Scale.LineFraction( 2.0, "Thickness of system marks"); Constant.Boolean storeDewarp = new Constant.Boolean( false, "Should we store the dewarped image on disk?"); } //--------------// // DewarpedView // //--------------// private class DewarpedView extends RubberPanel { //~ Instance fields ---------------------------------------------------- private final AffineTransform identity = new AffineTransform(); private final RenderedImage image; //~ Constructors ------------------------------------------------------- public DewarpedView (RenderedImage image) { this.image = image; setModelSize(new Dimension(image.getWidth(), image.getHeight())); // Location service setLocationService(sheet.getLocationService()); setName("DewarpedView"); } //~ Methods ------------------------------------------------------------ @Override public void render (Graphics2D g) { // Display the dewarped image g.drawRenderedImage(image, identity); // Display also the Destination Points renderWarpGrid(g, false); } } }