| /* 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; |
| } |
| } |
| } |