| /* ScanlineConverter.java -- Rasterizes Shapes | |
| Copyright (C) 2006, 2007 Free Software Foundation, Inc. | |
| This file is part of GNU Classpath. | |
| GNU Classpath is free software; you can redistribute it and/or modify | |
| it under the terms of the GNU General Public License as published by | |
| the Free Software Foundation; either version 2, or (at your option) | |
| any later version. | |
| GNU Classpath is distributed in the hope that it will be useful, but | |
| WITHOUT ANY WARRANTY; without even the implied warranty of | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| General Public License for more details. | |
| You should have received a copy of the GNU General Public License | |
| along with GNU Classpath; see the file COPYING. If not, write to the | |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | |
| 02110-1301 USA. | |
| Linking this library statically or dynamically with other modules is | |
| making a combined work based on this library. Thus, the terms and | |
| conditions of the GNU General Public License cover the whole | |
| combination. | |
| As a special exception, the copyright holders of this library give you | |
| permission to link this library with independent modules to produce an | |
| executable, regardless of the license terms of these independent | |
| modules, and to copy and distribute the resulting executable under | |
| terms of your choice, provided that you also meet, for each linked | |
| independent module, the terms and conditions of the license of that | |
| module. An independent module is a module which is not derived from | |
| or based on this library. If you modify this library, you may extend | |
| this exception to your version of the library, but you are not | |
| obligated to do so. If you do not wish to do so, delete this | |
| exception statement from your version. */ | |
| package gnu.java.awt.java2d; | |
| import gnu.java.math.Fixed; | |
| import java.awt.RenderingHints; | |
| import java.awt.Shape; | |
| import java.awt.geom.AffineTransform; | |
| import java.awt.geom.PathIterator; | |
| /** | |
| * Rasterizes {@link Shape} objects on an AbstractGraphics2D. | |
| */ | |
| public final class ScanlineConverter | |
| { | |
| /** | |
| * The number of digits to use for fixed point arithmetics. | |
| */ | |
| private static int FIXED_DIGITS = 6; | |
| /** | |
| * The fixed point constant for the number one. | |
| */ | |
| private static int ONE = Fixed.fixedValue(FIXED_DIGITS, 1); | |
| /** | |
| * The actual number of scanlines. | |
| */ | |
| private int numScanlines; | |
| /** | |
| * The number of scanlines. This can contain more elements than we have | |
| * scanlines. The real number of scanlines is stored in | |
| * {@link #numScanlines}. This can also contain null values for empty | |
| * scanlines. | |
| */ | |
| private Scanline[] scanlines; | |
| /** | |
| * The upper bounds which correspond to the index 0 in the scanline array. | |
| * | |
| * This is a fixed point value. | |
| */ | |
| private int upperBounds; | |
| /** | |
| * The resolution of the scanline converter. | |
| * | |
| * This is a fixed point value. | |
| */ | |
| private int resolution; | |
| /** | |
| * The number of significant bits for the 'Y' resolution. | |
| */ | |
| private int yResolution; | |
| /** | |
| * One half step according to the resolution. This is stored to avoid | |
| * unnecessary operations during rendering. | |
| */ | |
| private int halfStep; | |
| /** | |
| * This is used in {@link #addShape(PathIterator, boolean)} to | |
| * receive the coordinates of the path. | |
| */ | |
| private float[] coords; | |
| /** | |
| * The active edges. | |
| */ | |
| private ActiveEdges activeEdges; | |
| private PolyEdge edgePool; | |
| private PolyEdge edgePoolLast; | |
| private int minY; | |
| private int maxY; | |
| private int minX; | |
| private int maxX; | |
| /** | |
| * Holds and manages information about the pixel coverage. | |
| */ | |
| private ScanlineCoverage scanlineCoverage; | |
| /** | |
| * Create a new ScanlineConverter. | |
| */ | |
| ScanlineConverter() | |
| { | |
| scanlines = new Scanline[10]; | |
| coords = new float[6]; | |
| activeEdges = new ActiveEdges(); | |
| edgePool = new PolyEdge(); | |
| edgePoolLast = edgePool; | |
| scanlineCoverage = new ScanlineCoverage(); | |
| } | |
| /** | |
| * Renders the specified shape using the specified clip and transform. | |
| * | |
| * @param p the pixelizer that receives the coverage information | |
| * @param shape the shape to render | |
| * @param clip the clip | |
| * @param trans the transform | |
| */ | |
| public void renderShape(Pixelizer p, Shape shape, Shape clip, | |
| AffineTransform trans, int res, int yRes, | |
| RenderingHints hints) | |
| { | |
| // TODO: Do something useful with the rendering hints. Like, adjusting | |
| // the resolution. | |
| // Prepare resolution and upper bounds. | |
| clear(); | |
| setResolution(res, yRes); | |
| boolean haveClip = clip != null; | |
| // Add shapes. | |
| float flatness = Fixed.floatValue(FIXED_DIGITS, resolution / 2); | |
| PathIterator path = shape.getPathIterator(trans, flatness); | |
| addShape(path, false); | |
| if (haveClip) | |
| { | |
| path= clip.getPathIterator(trans, flatness); | |
| addShape(path, true); | |
| } | |
| setUpperBounds(minY); | |
| PolyEdge edge = edgePool; | |
| while (edge != edgePoolLast) | |
| { | |
| addEdge(edge); | |
| edge = edge.poolNext; | |
| } | |
| int y = upperBounds; | |
| int index; | |
| activeEdges.clear(); | |
| // The render loop... | |
| Scanline scanline = null; | |
| int lastRealY = Fixed.intValue(FIXED_DIGITS, y); | |
| while (y <= maxY) | |
| { | |
| // First we put together our list of active edges. | |
| index = scanlineIndex(y); | |
| // If we go outside the scanline array we still need to render the | |
| // remaining edges until they end. | |
| scanline = index < scanlines.length ? scanlines[index] : null; | |
| if (scanline != null) | |
| { | |
| edge = scanline.getEdges(); | |
| while (edge != null) | |
| { | |
| activeEdges.add(edge); | |
| edge = edge.scanlineNext; | |
| } | |
| } | |
| // Then we intersect all active edges with the current scanline | |
| // and sort them according to their intersection points. | |
| activeEdges.intersectSortAndPack(FIXED_DIGITS, y + halfStep); | |
| // Ok, now we can perform the actual scanlining. | |
| int realY = Fixed.intValue(FIXED_DIGITS, y + resolution); | |
| boolean push = lastRealY != realY; | |
| doScanline(p, y, push, haveClip); | |
| // Remove obsolete active edges. | |
| //activeEdges.remove(y + halfStep); | |
| // Go on with the next line... | |
| y += resolution; | |
| lastRealY = realY; | |
| } | |
| } | |
| /** | |
| * Clears all scanlines. | |
| */ | |
| private void clear() | |
| { | |
| // Reset edge pool. | |
| edgePoolLast = edgePool; | |
| // Reset scanlines. | |
| for (int i = scanlines.length - 1; i >= 0 ; i--) | |
| { | |
| Scanline sl = scanlines[i]; | |
| if (sl != null) | |
| sl.clear(); | |
| } | |
| // Reset scanline coverage. | |
| scanlineCoverage.clear(); | |
| // Reset bounds. | |
| minY = Integer.MAX_VALUE; | |
| maxY = Integer.MIN_VALUE; | |
| minX = Integer.MAX_VALUE; | |
| maxX = Integer.MIN_VALUE; | |
| } | |
| /** | |
| * Performs the scanlining on the current set of active edges. | |
| * | |
| * @param p the pixelizer to receive the pixel coverage data | |
| * @param y the Y coordinate | |
| * @param push true when the scanline is ready to be pushed to the | |
| * pixelizer | |
| * @param haveClip true when there's a clip, false otherwise | |
| */ | |
| private void doScanline(Pixelizer p, int y, boolean push, | |
| boolean haveClip) | |
| { | |
| // First, rewind the scanline coverage. | |
| scanlineCoverage.rewind(); | |
| // We begin outside the clip and outside the shape. We only draw when | |
| // we are inside the clip AND inside the shape. | |
| boolean inClip = ! haveClip; | |
| boolean inShape = false; | |
| PolyEdge lastEdge = null; | |
| int numEdges = activeEdges.getNumActiveEdges(); | |
| for (int i = 0; i < numEdges; i++) | |
| { | |
| PolyEdge edge = activeEdges.getActiveEdge(i); | |
| if (inClip && inShape) | |
| { | |
| assert lastEdge != null; | |
| int x0 = lastEdge.xIntersection; | |
| int x1 = edge.xIntersection; | |
| assert x0 <= x1; | |
| int pix0 = Fixed.intValue(FIXED_DIGITS, x0); | |
| int pix1 = Fixed.intValue(FIXED_DIGITS, x1); | |
| int frac0 = ONE - Fixed.trunc(FIXED_DIGITS, x0); | |
| int frac1 = ONE - Fixed.trunc(FIXED_DIGITS, x1); | |
| // Only keep the first 4 digits after the point. | |
| frac0 = frac0 >> (FIXED_DIGITS - yResolution); | |
| frac1 = frac1 >> (FIXED_DIGITS - yResolution); | |
| scanlineCoverage.add(pix0, 1 * (1 << yResolution), frac0); | |
| scanlineCoverage.add(pix1, -1 * (1 << yResolution), -frac1); | |
| } | |
| if (edge.isClip) | |
| inClip = ! inClip; | |
| else | |
| inShape = ! inShape; | |
| lastEdge = edge; | |
| } | |
| // Push out the whole scanline to the pixelizer. | |
| if (push && ! scanlineCoverage.isEmpty()) | |
| { | |
| p.renderScanline(Fixed.intValue(FIXED_DIGITS, y), scanlineCoverage); | |
| scanlineCoverage.clear(); | |
| } | |
| } | |
| /** | |
| * Sets the resolution. A value of 0 rasterizes the shape normally without | |
| * anti-aliasing. Greater values renders with a resolution of 2 ^ res. | |
| * | |
| * @param res the resolution | |
| */ | |
| private void setResolution(int res, int yRes) | |
| { | |
| int scanlinesPerPixel = 1 << res; | |
| int one = Fixed.fixedValue(FIXED_DIGITS, 1); | |
| resolution = one / (scanlinesPerPixel); | |
| halfStep = resolution / 2; | |
| scanlineCoverage.setMaxCoverage(scanlinesPerPixel << yResolution); | |
| yResolution = yRes; | |
| } | |
| /** | |
| * Sets the vertical bounds of that shape that is beeing rendered. | |
| * | |
| * @param y0 the upper bounds | |
| */ | |
| private void setUpperBounds(int y0) | |
| { | |
| upperBounds = fit(y0); | |
| } | |
| /** | |
| * Add a shape to the scanline converter. | |
| * | |
| * @param path | |
| * @param clip | |
| */ | |
| private void addShape(PathIterator path, boolean clip) | |
| { | |
| int startX = 0; | |
| int startY = 0; | |
| int lastX = 0; | |
| int lastY = 0; | |
| while (! path.isDone()) | |
| { | |
| int type = path.currentSegment(coords); | |
| switch (type) | |
| { | |
| case PathIterator.SEG_MOVETO: | |
| startX = lastX = Fixed.fixedValue(FIXED_DIGITS, coords[0]); | |
| startY = lastY = Fixed.fixedValue(FIXED_DIGITS, coords[1]); | |
| minY = Math.min(startY, minY); | |
| maxY = Math.max(startY, maxY); | |
| minX = Math.min(startX, minX); | |
| maxX = Math.max(startX, maxX); | |
| break; | |
| case PathIterator.SEG_LINETO: | |
| int x = Fixed.fixedValue(FIXED_DIGITS, coords[0]); | |
| int y = Fixed.fixedValue(FIXED_DIGITS, coords[1]); | |
| edgePoolAdd(lastX, lastY, x, y, clip); | |
| lastX = x; | |
| lastY = y; | |
| minY = Math.min(lastY, minY); | |
| maxY = Math.max(lastY, maxY); | |
| minX = Math.min(lastX, minX); | |
| maxX = Math.max(lastX, maxX); | |
| break; | |
| case PathIterator.SEG_CLOSE: | |
| edgePoolAdd(lastX, lastY, startX, startY, clip); | |
| lastX = startX; | |
| lastY = startY; | |
| break; | |
| case PathIterator.SEG_CUBICTO: | |
| case PathIterator.SEG_QUADTO: | |
| default: | |
| assert false; | |
| } | |
| path.next(); | |
| } | |
| } | |
| /** | |
| * Adds an edge into the scanline array. | |
| */ | |
| private void addEdge(PolyEdge edge) | |
| { | |
| // Determine index. | |
| int upper = Math.min(edge.y0, edge.y1); | |
| // Fit to raster. | |
| int index = scanlineIndex(upper); | |
| // Grow array when necessary. | |
| if (index >= scanlines.length) | |
| { | |
| int oldSize = scanlines.length; | |
| int newSize = Math.max(oldSize + oldSize / 2 + 1, index + 10); | |
| Scanline[] newScanlines = new Scanline[newSize]; | |
| System.arraycopy(scanlines, 0, newScanlines, 0, oldSize); | |
| scanlines = newScanlines; | |
| } | |
| // Add edge. | |
| if (scanlines[index] == null) | |
| { | |
| scanlines[index] = new Scanline(); | |
| } | |
| scanlines[index].addEdge(edge); | |
| } | |
| /** | |
| * Fits an Y coordinate to the grid. | |
| * | |
| * @param y the Y coordinate to fit | |
| * | |
| * @return the fitted Y coordinate | |
| */ | |
| private int fit(int y) | |
| { | |
| int val1 = Fixed.div(FIXED_DIGITS, y, resolution); | |
| int rounded = Fixed.round(FIXED_DIGITS, val1); | |
| return Fixed.mul(FIXED_DIGITS, rounded, resolution); | |
| } | |
| /** | |
| * Calculates the scanline index for the specified y coordinate. | |
| * | |
| * @param y the y coordinate as fixed point value | |
| * | |
| * @return the scanline index | |
| */ | |
| private int scanlineIndex(int y) | |
| { | |
| int fitted = fit(y); | |
| // Cleverly skip the fixed point conversions here. | |
| return (fitted - upperBounds)/ resolution; | |
| } | |
| private void edgePoolAdd(int x0, int y0, int x1, int y1, boolean clip) | |
| { | |
| // Don't need no horizontal edges. | |
| if (y0 != y1) | |
| { | |
| edgePoolLast.init(FIXED_DIGITS, x0, y0, x1, y1, clip); | |
| if (edgePoolLast.poolNext == null) | |
| { | |
| edgePoolLast.poolNext = new PolyEdge(); | |
| } | |
| edgePoolLast = edgePoolLast.poolNext; | |
| } | |
| } | |
| } |