/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package jjil.algorithm; import java.util.Enumeration; import java.util.Random; import java.util.Vector; import jjil.core.Error; import jjil.core.Image; import jjil.core.PipelineStage; import jjil.core.Rect; import jjil.core.RgbImage; import jjil.core.RgbRegion; import jjil.core.RgbRegion.MeanVar; import jjil.core.RgbVal; /** * Uses region splitting to break the input image down into rectangular areas * of similar color. The region granularity can be controlled by setting a * maximum standard deviation in each color channel. * @author webb */ public class RgbSplit extends PipelineStage { int nRVar, nGVar, nBVar; Random random = new Random(); RgbImage rgbInput; Vector vecROk = null; // vector of RgbRegions /** * Construct the class, setting the maximum standard deviation in each * color channel. * @param rStdDev maximum red standard deviation * @param gStdDev maximum green standard deviation * @param bStdDev maximum blue standard deviation. */ public RgbSplit(int rStdDev, int gStdDev, int bStdDev) { this.nRVar = rStdDev; this.nGVar = gStdDev; this.nBVar = bStdDev; this.nRVar *= this.nRVar; this.nGVar *= this.nGVar; this.nBVar *= this.nBVar; } /** * Compute variance of a rectangle in the input image. * @param r the Rect outlining the region to compute * @return a MeanVar object containing the mean and variance of the region. */ private MeanVar computeVariance(Rect r) { int[] nData = rgbInput.getData(); int nSumR = 0, nSumG = 0, nSumB = 0; int nSumRSq = 0, nSumGSq = 0, nSumBSq = 0; for (int i=r.getTop(); i<r.getBottom(); i++) { for (int j=r.getLeft(); j<r.getRight(); j++) { int nRgbVal = nData[i*this.rgbInput.getWidth()+j]; int nR = RgbVal.getR(nRgbVal); int nG = RgbVal.getG(nRgbVal); int nB = RgbVal.getB(nRgbVal); nSumR += nR; nSumG += nG; nSumB += nB; nSumRSq += nR * nR; nSumGSq += nG * nG; nSumBSq += nB * nB; } } // compute average int nAvgR = nSumR / r.getArea(); int nAvgG = nSumG / r.getArea(); int nAvgB = nSumB / r.getArea(); int nVarR = nSumRSq / r.getArea() - nAvgR * nAvgR; int nVarG = nSumGSq / r.getArea() - nAvgG * nAvgG; int nVarB = nSumBSq / r.getArea() - nAvgB * nAvgB; return new MeanVar( RgbVal.toRgb((byte)nAvgR, (byte)nAvgG, (byte)nAvgB), nVarR, nVarG, nVarB); } /** * Split an input RgbImage into rectangular regions of standard deviation * less than or equal to the thresholds specified in the constructor. The * minimum region size is 2x2. Regions are split horizontally and vertically * in each pass so as to converge to roughly square blocks. * @param rgbImage the input RgbImage */ public void split(RgbImage rgbImage) { this.rgbInput = rgbImage; Vector vecRNotOk = new Vector(); this.vecROk = new Vector(); vecRNotOk.addElement(new Rect( 0, 0, this.rgbInput.getWidth(), this.rgbInput.getHeight())); while (!vecRNotOk.isEmpty()) { Rect r = (Rect) vecRNotOk.elementAt(0); vecRNotOk.removeElementAt(0); if (r.getHeight() >= 2 && r.getWidth() >= 2) { MeanVar nVar = computeVariance(r); if (nVar.getRVar()>this.nRVar || nVar.getGVar()>this.nGVar || nVar.getB()>this.nBVar) { // split horizontally or vertically, whichever // is longer if (r.getWidth() >= r.getHeight()) { // split horizontally int nHalfWidth = r.getWidth()/2; Rect rNew = new Rect(r.getLeft(), r.getTop(), nHalfWidth, r.getHeight()); vecRNotOk.addElement(rNew); rNew = new Rect(r.getLeft()+nHalfWidth, r.getTop(), r.getWidth() - nHalfWidth, r.getHeight()); vecRNotOk.addElement(rNew); } else { // split vertically int nHalfHeight = r.getHeight()/2; Rect rNew = new Rect(r.getLeft(), r.getTop(), r.getWidth(), nHalfHeight); vecRNotOk.addElement(rNew); rNew = new Rect(r.getLeft(), r.getTop()+nHalfHeight, r.getWidth(), r.getHeight() - nHalfHeight); vecRNotOk.addElement(rNew); } } else { RgbRegion reg = new RgbRegion(r, nVar); this.vecROk.addElement(reg); } } else { // region too small, stop splitting MeanVar nVar = computeVariance(r); RgbRegion reg = new RgbRegion(r, nVar); this.vecROk.addElement(reg); } } } /** * Returns a color image with colors randomly assigned to regions. This * is used during debugging to see how the image has been split so that * the threshold can be adjusted. * @return RgbImage with colors randomly assigned to regions. * @throws jjil.core.Error if push() hasn't been called yet */ public RgbImage getRandomizedRgbImage() throws jjil.core.Error { if (this.vecROk == null) { throw new jjil.core.Error( jjil.core.Error.PACKAGE.CORE, jjil.core.ErrorCodes.NO_RESULT_AVAILABLE, null, null, null); } RgbImage rgbImage = new RgbImage( this.rgbInput.getWidth(), this.rgbInput.getHeight()); for (Enumeration e = this.vecROk.elements(); e.hasMoreElements();) { RgbRegion r = (RgbRegion) e.nextElement(); int nRgb = RgbVal.toRgb( (byte)((this.random.nextInt()&0xff)+Byte.MIN_VALUE), (byte)((this.random.nextInt()&0xff)+Byte.MIN_VALUE), (byte)((this.random.nextInt()&0xff)+Byte.MIN_VALUE)); Rect rect = r.getRect(); rgbImage = rgbImage.fill(rect, nRgb); } return rgbImage; } /** * Return the region list. * @return an Enumeration on RgbRegion objects. * @throws jjil.core.Error if push() hasn't been called yet */ public Enumeration getRegions() throws jjil.core.Error { if (this.vecROk == null) { throw new jjil.core.Error( jjil.core.Error.PACKAGE.CORE, jjil.core.ErrorCodes.NO_RESULT_AVAILABLE, null, null, null); } return this.vecROk.elements(); } /** * Return an RgbImage with the mean color of each region assigned. This * should be an approximation of the original input image, except more * blocky, depending on the thresholds set. * @return RgbImage with the mean color of each region assigned to the * region. * @throws jjil.core.Error if push() hasn't been called yet */ public RgbImage getRgbImage() throws jjil.core.Error { if (this.vecROk == null) { throw new jjil.core.Error( jjil.core.Error.PACKAGE.CORE, jjil.core.ErrorCodes.NO_RESULT_AVAILABLE, null, null, null); } RgbImage rgbImage = new RgbImage( this.rgbInput.getWidth(), this.rgbInput.getHeight()); for (Enumeration e = this.vecROk.elements(); e.hasMoreElements();) { RgbRegion r = (RgbRegion) e.nextElement(); int nRgb = r.getColor(); Rect rect = r.getRect(); rgbImage = rgbImage.fill(rect, nRgb); } return rgbImage; } /** * Implement toString * @return a string giving the name of this class, the parameters, and * the vector result if push has been called. */ public String toString() { String szResult = super.toString() + "(" + new Integer(this.nRVar).toString() + "," + new Integer(this.nGVar).toString() + "," + new Integer(this.nBVar).toString(); if (this.vecROk != null) { szResult += "," + this.vecROk.toString(); } return szResult + ")"; } 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); } this.split((RgbImage)imageInput); super.setOutput(this.getRgbImage()); } }