This improves the rasterizer alot. It increases the accuracy of the calculation of the pixel coverage by a factor of 16, without any performance impact (because we already know the intersection points, but haven't used this knowledge before).
2007-05-24 Roman Kennke <[EMAIL PROTECTED]> * gnu/java/awt/java2d/AbstractGraphics2D.java (fillShape): Pass rendering hints to scanline converter. * gnu/java/awt/java2d/ScanlineConverter.java (ONE): New constant for the number 1 as fixed point number. (Y_RESOLUTION): New constant for the Y resolution. (doScanline): Handle the Y resolution. (renderShape): Accept rendering hints. (setResolution): Adjust maximum resolution with Y resolution. * gnu/java/awt/java2d/ScanlineCoverage.java (Iterator.handledPixelCoverage): New field. (Iterator.next): Handle single pixel coverage. (Iterator.hasNext): Handle single pixel coverage. (Iterator.reset): Reset single pixel coverage. (Range.toString): New method. (Coverage.pixelCoverage): New field. (add): Include Y (pixel) coverage. (findOrInsert): Reset Y coverage in reused entries. /Roman -- Dipl.-Inf. Roman Kennke, Software Engineer, http://kennke.org aicas Allerton Interworks Computer Automated Systems GmbH Haid-und-Neu-Straße 18 * D-76131 Karlsruhe * Germany http://www.aicas.com * Tel: +49-721-663 968-0 USt-Id: DE216375633, Handelsregister HRB 109481, AG Karlsruhe Geschäftsführer: Dr. James J. Hunt
Index: gnu/java/awt/java2d/AbstractGraphics2D.java =================================================================== RCS file: /cvsroot/classpath/classpath/gnu/java/awt/java2d/AbstractGraphics2D.java,v retrieving revision 1.17 diff -u -1 -5 -r1.17 AbstractGraphics2D.java --- gnu/java/awt/java2d/AbstractGraphics2D.java 24 May 2007 16:26:57 -0000 1.17 +++ gnu/java/awt/java2d/AbstractGraphics2D.java 24 May 2007 20:27:48 -0000 @@ -1556,31 +1556,31 @@ antialias = (v == RenderingHints.VALUE_TEXT_ANTIALIAS_ON || v == RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT); } else { Object v = renderingHints.get(RenderingHints.KEY_ANTIALIASING); antialias = (v == RenderingHints.VALUE_ANTIALIAS_ON); } ScanlineConverter sc = getScanlineConverter(); int resolution = 0; if (antialias) { // Adjust resolution according to rendering hints. resolution = 2; } - sc.renderShape(this, s, clip, transform, resolution); + sc.renderShape(this, s, clip, transform, resolution, renderingHints); } /** * Returns the color model of this Graphics object. * * @return the color model of this Graphics object */ protected abstract ColorModel getColorModel(); /** * Returns the bounds of the target. * * @return the bounds of the target */ protected Rectangle getDeviceBounds() Index: gnu/java/awt/java2d/ScanlineConverter.java =================================================================== RCS file: /cvsroot/classpath/classpath/gnu/java/awt/java2d/ScanlineConverter.java,v retrieving revision 1.4 diff -u -1 -5 -r1.4 ScanlineConverter.java --- gnu/java/awt/java2d/ScanlineConverter.java 24 May 2007 16:26:57 -0000 1.4 +++ gnu/java/awt/java2d/ScanlineConverter.java 24 May 2007 20:27:48 -0000 @@ -28,46 +28,57 @@ 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 [EMAIL PROTECTED] 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 number of significant bits for the Y resolution. + */ + private static int Y_RESOLUTION = 4; + + /** * 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 * [EMAIL PROTECTED] #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. * @@ -121,33 +132,36 @@ 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 */ - void renderShape(Pixelizer p, Shape shape, Shape clip, - AffineTransform trans, int res) + public void renderShape(Pixelizer p, Shape shape, Shape clip, + AffineTransform trans, int res, RenderingHints hints) { + // TODO: Do something useful with the rendering hints. Like, adjusting + // the resolution. + // Prepare resolution and upper bounds. clear(); setResolution(res); 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); } @@ -230,82 +244,88 @@ /** * 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); - //System.err.println("render scanline AA: " + Fixed.floatValue(FIXED_DIGITS, y) + ", " + pix0 + ", " + pix1 + "(" + Fixed.floatValue(FIXED_DIGITS, x0) + ", " + Fixed.floatValue(FIXED_DIGITS, x1) +")") - scanlineCoverage.add(pix0, 1); - scanlineCoverage.add(pix1, -1); + 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 - Y_RESOLUTION); + frac1 = frac1 >> (FIXED_DIGITS - Y_RESOLUTION); + scanlineCoverage.add(pix0, 1 * (1 << Y_RESOLUTION), frac0); + scanlineCoverage.add(pix1, -1 * (1 << Y_RESOLUTION), -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 scanlinesPerPixel = 1 << res; int one = Fixed.fixedValue(FIXED_DIGITS, 1); resolution = one / (scanlinesPerPixel); halfStep = resolution / 2; - scanlineCoverage.setMaxCoverage(scanlinesPerPixel); + + scanlineCoverage.setMaxCoverage(scanlinesPerPixel << Y_RESOLUTION); } /** * 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. * Index: gnu/java/awt/java2d/ScanlineCoverage.java =================================================================== RCS file: /cvsroot/classpath/classpath/gnu/java/awt/java2d/ScanlineCoverage.java,v retrieving revision 1.2 diff -u -1 -5 -r1.2 ScanlineCoverage.java --- gnu/java/awt/java2d/ScanlineCoverage.java 24 May 2007 16:26:57 -0000 1.2 +++ gnu/java/awt/java2d/ScanlineCoverage.java 24 May 2007 20:27:48 -0000 @@ -57,79 +57,111 @@ * This instance is reused in the iteration. */ private Range range; /** * The pointer to the current item in the iteration. */ private Coverage currentItem; /** * The current coverage value. */ private int currentCoverage; /** + * True when the current pixel coverage has already been handled, false + * otherwise. + */ + private boolean handledPixelCoverage; + + /** * Creates a new CoverageIterator. */ Iterator() { range = new Range(); } /** * Returns the next coverage range on the scanline. The returned object * will always be the same object, but with different values. Keep that * in mind when dealing with this object. * * @return the next coverage range on the scanline */ public Range next() { - currentCoverage += currentItem.covDelta; - range.setCoverage(currentCoverage); - range.setXPos(currentItem.xPos); - currentItem = currentItem.next; - range.setLength(currentItem.xPos - range.xPos); + // TODO: Lump together the single-pixel coverage and the + // between-pixel coverage when the pixel coverage delta is 0. + if (handledPixelCoverage == false) + { + // Handle single pixel coverage. + range.setXPos(currentItem.xPos); + range.setLength(1); + range.setCoverage(currentCoverage + currentItem.pixelCoverage); + handledPixelCoverage = true; + } + else + { + // Handle pixel span coverage. + currentCoverage += currentItem.covDelta; + range.setCoverage(currentCoverage); + range.setXPos(currentItem.xPos + 1); + currentItem = currentItem.next; + range.setLength(currentItem.xPos - range.xPos); + handledPixelCoverage = false; + } return range; } /** * Returns {@ true} when there are more coverage ranges to iterate, * {@ false} otherwise. * * @return {@ true} when there are more coverage ranges to iterate, * {@ false} otherwise */ public boolean hasNext() { boolean hasNext; - if (currentItem == null || currentItem.next == null + if (currentItem != null && handledPixelCoverage == false) + { + // We have at least one more coverage item when there's a pixel + // coverage piece left. + hasNext = true; + } + else if (currentItem == null || currentItem.next == null || currentItem.next == last) - hasNext = false; + { + hasNext = false; + } else - hasNext = true; + { + hasNext = true; + } return hasNext; } /** * Resets this iterator to the start of the list. */ void reset() { currentItem = head; currentCoverage = 0; + handledPixelCoverage = false; } } /** * A data object that carries information about pixel coverage on a scanline. * The data consists of a starting X position on the scanline, the * length of the range in pixels and the actual coverage value. ´ */ public static final class Range { /** * The X position on the scanline, in pixels. */ private int xPos; @@ -214,48 +246,63 @@ void setCoverage(int cov) { coverage = cov; } /** * Returns the coverage of the pixel range. The relation of this value * depends on [EMAIL PROTECTED] ScanlineCoverage#getMaxCoverage()}. * * @return the coverage of the pixel range */ public int getCoverage() { return coverage; } + + /** + * Returns a string representation. + */ + public String toString() + { + return "Coverage range: xPos=" + xPos + ", length=" + length + + ", coverage: " + coverage; + } } /** * One bucket in the list. */ private static final class Coverage { /** * The X coordinate on the scanline to which this bucket belongs. */ int xPos; /** - * The X coverage delta. + * The coverage delta from the pixel at xPos to xPos + 1. */ int covDelta; /** + * The delta for the pixel at xPos. This is added to the pixel at xPos, + * but not to the following pixel. + */ + int pixelCoverage; + + /** * Implements a linked list. This points to the next element of the list. */ Coverage next; /** * Returns the X coordinate for this entry. * * @return the X coordinate for this entry */ public int getXPos() { return xPos; } /** @@ -373,34 +420,35 @@ lastPrev = null; current = head; currentPrev = null; minX = Integer.MAX_VALUE; maxX = Integer.MIN_VALUE; } /** * This adds the specified coverage to the pixel at the specified * X position. * * @param x the X position * @param xc the x coverage * @param yc the y coverage */ - public void add(int x, int xc) + public void add(int x, int xc, int yc) { Coverage bucket = findOrInsert(x); bucket.covDelta += xc; + bucket.pixelCoverage += yc; minX = Math.min(minX, x); maxX = Math.max(maxX, x); } /** * Returns the maximum coverage value for the scanline. * * @return the maximum coverage value for the scanline */ public int getMaxCoverage() { return maxCoverage; } /** @@ -479,30 +527,31 @@ match.xPos = x; if (prev != null) prev.next = match; current = match; currentPrev = prev; return match; } else if (match == last) { // End of the list. Reuse this item. Expand list. // Testpoint 3. last = match.next; lastPrev = match; match.xPos = x; match.covDelta = 0; + match.pixelCoverage = 0; // Keep link to last element or null, indicating the end of the list. current = match; currentPrev = prev; return match; } if (x == match.xPos) { // Special case: We have another coverage entry at the same location // as an already existing entry. Return this. // Testpoint 4. current = match; currentPrev = prev; return match; } @@ -516,30 +565,31 @@ if (last != null) { // Testpoint 5. cov = last; last = cov.next; lastPrev.next = last; } else { // Testpoint 6. cov = new Coverage(); } cov.xPos = x; cov.covDelta = 0; + cov.pixelCoverage = 0; // Insert this item in the list. if (prev != null) { // Testpoint 5 & 6. prev.next = cov; cov.next = match; current = cov; currentPrev = prev; } else { // Testpoint 7. assert (match == head); // Insert at head.