package iiuf.jai; import java.awt.image.DataBuffer; import java.awt.image.Raster; import java.awt.image.RenderedImage; import java.awt.image.WritableRaster; import java.awt.Point; import java.awt.Rectangle; import java.util.ArrayList; import java.util.List; import javax.media.jai.RasterAccessor; import javax.media.jai.RasterFormatTag; import javax.media.jai.ROIShape; import javax.media.jai.StatisticsOpImage; /** (c) 1999, IIUF<p> This is the implementation of the Connected Components extraction algorithm for the Java Advanced Imaging API. This operator does only work with binary images satisfying certain criteria. @author $Author: hassan $ @version $Revision: 1.1 $ */ public class CCOpImage extends StatisticsOpImage { /** Inner class representing a sort of "hierarchical rectangles" where every rectangle has a parent. */ private class HRectangle extends Rectangle { public int parent; public Point member; public HRectangle(int x, int y, int p) { super(x, y, 1, 1); member = new Point(x, y); parent = p; } } /** Constructs a CCOpImage object */ public CCOpImage(RenderedImage source, Rectangle sample) { super(source, new ROIShape(new Rectangle(source.getMinX(), source.getMinY(), source.getWidth(), source.getHeight())), source.getMinX(), source.getMinY(), 1, 1); sampleRectangle = sample; image = source; } /** A sample rectangle which is cloned for returning the results */ protected Rectangle sampleRectangle; /** The candidate image */ protected RenderedImage image; /** An object for accessing the image */ protected DirectRasterAccessor raster; /** Name of the two available operations provided by this operator */ private static final String[] statisticsNames = { "CC4", "CC8", "CC4points", "CC8points" }; /** Returns the names of the two operations */ public String[] getStatisticsNames() { return statisticsNames; } /** Apply one of the two operations */ public Object createStatistics(String name) { raster = new DirectRasterAccessor(getData(), getColorModel()); name = name.toLowerCase(); if (name.equals("cc4")) { return makeRectangles(CC4()); } else if (name.equals("cc4points")) { return makePoints(CC8()); } else if (name.equals("cc8")) { return makeRectangles(CC8()); } else if (name.equals("cc8points")) { return makePoints(CC8()); } else { throw new RuntimeException(getClass().getName() + " statistics " + name + " not supported"); } } /** Not supported */ public void accumulateStatistics(String name, Raster source, Object stats) { } /** 4-neighbors connected components (rectangles) */ List makeRectangles(List zones) { List ccs = new ArrayList(); /* If the user did not provide a prototype rectangle to clone, create new rectangles, which is faster than cloning a existing rectangle. */ if (sampleRectangle == null) { for (int i = 1; i < zones.size(); i++) { HRectangle hr = (HRectangle) zones.get(i); if (hr.parent == i) { ccs.add(new Rectangle(hr.x, hr.y, hr.width, hr.height)); } } } else { for (int i = 1; i < zones.size(); i++) { HRectangle hr = (HRectangle) zones.get(i); if (hr.parent == i) { Rectangle r = (Rectangle) sampleRectangle.clone(); r.setBounds(hr.x, hr.y, hr.width, hr.height); ccs.add(r); } } } return ccs; } List makePoints(List zones) { List points = new ArrayList(); for (int i = 1; i < zones.size(); i++) { HRectangle hr = (HRectangle) zones.get(i); if (hr.parent == i) { points.add(hr.member); } } return points; } private int[] scanline; private int[] lastScanline; private List zones; /** Creates a new zone containing the pixel at (x, y) */ private void newZone(int x, int y) { scanline[x] = zones.size(); zones.add(new HRectangle(x, y, zones.size())); } /** Gets the number of the zone which contains the pixel at (x,currentLine) */ private int getZone(int x) { int idx = scanline[x]; while (idx != ((HRectangle) zones.get(idx)).parent) { idx = ((HRectangle) zones.get(idx)).parent; } return idx; } private int getLastZone(int x) { int idx = lastScanline[x]; while (idx != ((HRectangle) zones.get(idx)).parent) { idx = ((HRectangle) zones.get(idx)).parent; } return idx; } /** Fusion the two zones i and j */ private void fusionZones(int i, int j) { if (i == j) { return; } HRectangle zi = (HRectangle) zones.get(i); HRectangle zj = (HRectangle) zones.get(j); if (zi.parent > zj.parent) { int tmp = j; j = i; i = tmp; HRectangle tmpz = zj; zj = zi; zi = tmpz; } zj.parent = zi.parent; int newx1 = Math.min(zi.x, zj.x); int newy1 = Math.min(zi.y, zj.y); int newx2 = Math.max(zi.x+zi.width, zj.x+zj.width); int newy2 = Math.max(zi.y+zi.height, zj.y+zj.height); zi.x = newx1; zi.y = newy1; zi.width = newx2-newx1; zi.height = newy2-newy1; } /** 8-neighbors connected components */ List cc8cache; List CC8() { if (cc8cache != null) { return cc8cache; } int width = getWidth(); int height = getHeight(); zones = new ArrayList(); scanline = new int[width]; lastScanline = new int[width]; newZone(0, 0); int x, y; int black = raster.black; // 1st column on 1st line if (raster.getPixel(0, 0) == black) { newZone(0, 0); } else { scanline[0] = 0; } // other columns on 1st line for (x = 1; x < width; x++) { if (raster.getPixel(x, 0) == black) { if (getZone(x-1) == 0) { newZone(x, 0); } else { scanline[x] = getZone(x-1); ((HRectangle) zones.get(getZone(x))).width++; } } else { scanline[x] = 0; } } for (y = 1; y < height; y++) { // save the last scanline System.arraycopy(scanline, 0, lastScanline, 0, scanline.length); // 1st column if (raster.getPixel(0, y) == black) { if (getZone(0) == 0) { newZone(0, y); } else { scanline[0] = getZone(0); ((HRectangle) zones.get(getZone(0))).height++; } } else { scanline[0] = 0; } // other columns for (x = 1; x < width; x++) { if (raster.getPixel(x, y) == black) { int zx1 = getZone(x-1); int zx2 = getZone(x); if (zx1 == 0 && zx2 == 0) { if (lastScanline[x-1] != 0) { scanline[x] = getLastZone(x-1); HRectangle hr = (HRectangle) zones.get(getZone(x)); if (x >= hr.x + hr.width) { hr.width++; } if (y >= hr.y + hr.height) { hr.height++; } } else { newZone(x, y); } } else if (zx1 != 0 && zx2 == 0) { scanline[x] = zx1; HRectangle hr = (HRectangle) zones.get(zx1); if (x >= hr.x + hr.width) { hr.width++; } } else if (zx2 != 0) { scanline[x] = zx2; HRectangle hr = (HRectangle) zones.get(zx2); if (y >= hr.y + hr.height) { hr.height++; } if (zx1 != 0) { fusionZones(zx1, zx2); } } } else { scanline[x] = 0; if (scanline[x-1] != 0 && lastScanline[x] != 0) { fusionZones(getZone(x-1), getLastZone(x)); } } } } cc8cache = zones; return zones; } List cc4cache; List CC4() { if (cc4cache != null) { return cc4cache; } int width = getWidth(); int height = getHeight(); zones = new ArrayList(); scanline = new int[width]; newZone(0, 0); int x, y; int black = raster.black; // 1st column on 1st line if (raster.getPixel(0, 0) == black) { newZone(0, 0); } else { scanline[0] = 0; } // other columns on 1st line for (x = 1; x < width; x++) { if (raster.getPixel(x, 0) == black) { if (getZone(x-1) == 0) { newZone(x, 0); } else { scanline[x] = getZone(x-1); ((HRectangle) zones.get(getZone(x))).width++; } } else { scanline[x] = 0; } } for (y = 1; y < height; y++) { // 1st column if (raster.getPixel(0, y) == black) { if (getZone(0) == 0) { newZone(0, y); } else { scanline[0] = getZone(0); ((HRectangle) zones.get(getZone(0))).height++; } } else { scanline[0] = 0; } // other columns for (x = 1; x < width; x++) { if (raster.getPixel(x, y) == black) { int zx1 = getZone(x-1); int zx2 = getZone(x); if (zx1 == 0 && zx2 == 0) { newZone(x, y); } else if (zx1 != 0 && zx2 == 0) { scanline[x] = zx1; HRectangle hr = (HRectangle) zones.get(zx1); if (x >= hr.x + hr.width) { hr.width++; } } else if (zx2 != 0) { scanline[x] = zx2; HRectangle hr = (HRectangle) zones.get(zx2); if (y >= hr.y + hr.height) { hr.height++; } if (zx1 != 0) { fusionZones(zx1, zx2); } } } else { scanline[x] = 0; } } } cc4cache = zones; return zones; } }