/* * Copyright 2008 by Jon pA. Webb * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR pA PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the Lesser GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package jjil.algorithm; import jjil.core.Error; import jjil.core.Image; import jjil.core.PipelineStage; import jjil.core.Point; import jjil.core.Quad; import jjil.core.Rect; import jjil.core.RgbImage; import jjil.core.RgbMaskedImage; /** * Masks a polygon in an RgbImage, setting the area inside * or outside the polygon to masked, depending on a parameter. * * @author webb */ public class RgbMaskPoly extends PipelineStage { private boolean bInside; private Point points[]; private BinaryHeap hx[]; private int rnX[][]; /** * Initialize class, setting polygon to mask and * inside/outside choice. * @param points list of points to mask, in clockwise order (interior * on right). * @param bInside if true then area inside the polygon * is masked; if false, the area outside. */ public RgbMaskPoly(Point points[], boolean bInside) { this.points = points; this.bInside = bInside; } /** * Mask a quadrilateral. * @param quad quadrilateral to mask * @param bInside if true interior of quadrilateral is masked, if false * exterior * @throws jjil.core.Error if Error thrown from getCorner. */ public RgbMaskPoly(Quad quad, boolean bInside) throws Error { this.points = new Point[4]; this.points[0] = quad.getCorner(0); this.points[1] = quad.getCorner(1); this.points[2] = quad.getCorner(2); this.points[3] = quad.getCorner(3); this.bInside = bInside; } /** * Mask a rectangle. * @param rect rectangle to mask * @param bInside if true, interior is masked; if false, exterior. */ public RgbMaskPoly(Rect rect, boolean bInside) { this.points = new Point[4]; this.points[0] = new Point(rect.getLeft(), rect.getTop()); this.points[1] = new Point(rect.getRight(), rect.getTop()); this.points[2] = new Point(rect.getRight(), rect.getBottom()); this.points[3] = new Point(rect.getLeft(), rect.getBottom()); this.bInside = bInside; } /** * Build a vector of intersection points of the polygon for every * row in the output image. * @param nWidth output image width * @param nHeight output image height * @throws jjil.core.Error if the BinaryHeap code does. */ private void buildVector(int nWidth, int nHeight) throws Error { this.hx = new BinaryHeap[nHeight]; for (int i=0; i<nHeight; i++) { this.hx[i] = new BinaryHeap(); } for (int i=0; i<this.points.length-1; i++) { drawLine(this.points[i],this.points[i+1]); } drawLine(this.points[this.points.length-1],this.points[0]); this.rnX = new int[nHeight][]; for (int i=0; i<nHeight; i++) { int j = 0; if (!this.bInside) { this.rnX[i] = new int[this.hx[i].size()+2]; this.rnX[i][0] = 0; j = 1; } if (this.hx[i].size() > 0) { if (this.bInside) { this.rnX[i] = new int[this.hx[i].size()]; } while (!this.hx[i].isEmpty()) { this.rnX[i][j++] = ((BinaryHeap.ComparableInt) this.hx[i].deleteMin()).intValue(); } } if (!this.bInside) { this.rnX[i][j] = nWidth; } } } /** * Starting at p1 and ending at p2, add all the intersection points for * a scanline to the array hx. * @param p1 starting point * @param p2 ending point * @throws jjil.core.Error if the BinaryHeap code does */ private void drawLine(Point p1, Point p2) throws Error { if (p1.getY()>p2.getY()) { Point pSwap = p1; p1 = p2; p2 = pSwap; } int yDiff = p2.getY()-p1.getY(); for (int y = 0; y<yDiff; y++) { int x = p2.getX() + (p1.getX()-p2.getX())*(yDiff-y)/yDiff; this.hx[y+p1.getY()].insert(new BinaryHeap.ComparableInt(x)); } } /** * Mask the polygon in the input image. The algorithm used first * finds all intersections of the polygon with each row in the * output image and builds an array containing the x locations of those * intersections in sorted order. Then it scans the output image, setting * the mask value, for each row, starting and ending at each even-numbered * pair of intersection points.</br> * This code was strongly influenced by Darel Rex Finley's code described * at <a href="http://alienryderflex.com/polygon_fill/"> * http://alienryderflex.com/polygon_fill/.</a> * @param imageInput input RgbImage * @throws jjil.core.Error if input is not RgbImage or the BinaryHeap * code used to do the sorting throws. */ public void push(Image imageInput) throws Error { if (!(imageInput instanceof RgbImage)) { throw new Error( Error.PACKAGE.ALGORITHM, ErrorCodes.IMAGE_NOT_RGBIMAGE, imageInput.toString(), null, null); } RgbImage rgbInput = (RgbImage) imageInput; RgbMaskedImage rgbOutput = new RgbMaskedImage(rgbInput); buildVector(rgbInput.getWidth(), rgbInput.getHeight()); for (int i=0; i<rgbInput.getHeight(); i++) { if (this.rnX[i] != null) { for (int j=0; j<this.rnX[i].length; j+= 2) { for (int k = this.rnX[i][j]; k<this.rnX[i][j+1]; k++) { rgbOutput.setMask(i, k); } } } } super.setOutput(rgbOutput); } }