Hi there, (I CC classpath@gnu.org because this contains a summary of the state of the Java-based Java2D rendering pipeline, which might also be interesting to people not reading cp-patches).
I added support for clipping against arbitrary shapes to my AbstractGraphics2D renderer. This makes the rendering pipeline basically complete. It does now perform the following: 0. stroking (for the draw* methods) 1. transforming the shape to device space 2. clipping against arbitrary shapes 3. rasterizing into device space 4. painting (color, gradient and textures*) 5. compositing (blending colors with the current surface) (* While the pipeline can perform painting, the implementation in java.awt.TexturePaint and GradientPaint are still missing the PaintContext implementations, so these won't actually work unfortunately). The obligatory screenshot (a polygon, rectangle and string rendered using a circle-shape clip): http://kennke.org/~roman/java2d-clipping.png I already discovered a usecase where my implementation is better than Sun's: you can't drawPolygon with a custom composite setting with Sun's JRE. I suppose they optimize the normal case and forward it to the backend's polygon function, but forgot to implement the same non-standard case :-) The state of the other 2 pipelines (fonts and images) is as follows: - Fonts basically work on top of the above renderer with all features and Sascha's font work. We have problems though with small fonts due to missing hinting/kerning/etc. TextLayout also needs to be implemented. But besides that it is already pretty cool to see rotating glyph vectors blended using an alpha composite against arbitrary backgrounds and clipped against whatnot ;-) - I haven't looked into image rendering at all. Should not be too hard to implement, making it fast will be the tricky part though. 2006-05-08 Roman Kennke <[EMAIL PROTECTED]> * gnu/java/awt/java2d/AbstractGraphics2D.java (fill): Removed commented out code. (fillShape): Also determine the outline of the clip and feed it into the rendering method. Use new helper method for converting the shapes into lists of segments. (getUserBounds): Removed obsolete method. (rawFillShape): Respect the clip when rendering shapes. (fillShapeAntialias): Adjusted signature for new clipped rendering. However, the implementation can't clip still. (getSegments): New helper method for converting a shape into a list of segments. (clipShape): Removed obsolete method. * gnu/java/awt/java2d/PolyEdge.java (isClip): New field. (PolyEdge): Added isField argument to constructor. /Roman -- “Improvement makes straight roads, but the crooked roads, without Improvement, are roads of Genius.” - William Blake
Index: gnu/java/awt/java2d/AbstractGraphics2D.java =================================================================== RCS file: /cvsroot/classpath/classpath/gnu/java/awt/java2d/AbstractGraphics2D.java,v retrieving revision 1.5 diff -u -1 -0 -r1.5 AbstractGraphics2D.java --- gnu/java/awt/java2d/AbstractGraphics2D.java 7 May 2006 01:30:35 -0000 1.5 +++ gnu/java/awt/java2d/AbstractGraphics2D.java 8 May 2006 14:38:33 -0000 @@ -306,23 +306,20 @@ drawGlyphVector(gv, x, y); } /** * Fills the specified shape with the current foreground. * * @param shape the shape to fill */ public void fill(Shape shape) { -// Shape clipped = clipShape(shape); -// if (clipped != null) -// fillShape(clipped, false); fillShape(shape, false); } public boolean hit(Rectangle rect, Shape text, boolean onStroke) { // FIXME: Implement this. throw new UnsupportedOperationException("Not yet implemented"); } /** @@ -1157,80 +1154,33 @@ 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); } Rectangle2D userBounds = s.getBounds2D(); - - // Flatten the path. TODO: Determine the best flattening factor - // wrt to speed and quality. - PathIterator path = s.getPathIterator(getTransform(), 1.0); - - // Build up polygons and let the native backend render this using - // rawFillShape() which would provide a default implementation for - // drawPixel using a PolyScan algorithm. - double[] seg = new double[6]; - - // TODO: Use ArrayList<PolyEdge> here when availble. - ArrayList segs = new ArrayList(); - double segX = 0.; // The start point of the current edge. - double segY = 0.; - double polyX = 0.; // The start point of the current polygon. - double polyY = 0.; - - double minX = Integer.MAX_VALUE; - double maxX = Integer.MIN_VALUE; - double minY = Integer.MAX_VALUE; - double maxY = Integer.MIN_VALUE; - - //System.err.println("fill polygon"); - while (! path.isDone()) - { - int segType = path.currentSegment(seg); - minX = Math.min(minX, seg[0]); - maxX = Math.max(maxX, seg[0]); - minY = Math.min(minY, seg[1]); - maxY = Math.max(maxY, seg[1]); - - //System.err.println("segment: " + segType + ", " + seg[0] + ", " + seg[1]); - if (segType == PathIterator.SEG_MOVETO) - { - segX = seg[0]; - segY = seg[1]; - polyX = seg[0]; - polyY = seg[1]; - } - else if (segType == PathIterator.SEG_CLOSE) - { - // Close the polyline. - PolyEdge edge = new PolyEdge(segX, segY, polyX, polyY); - segs.add(edge); - } - else if (segType == PathIterator.SEG_LINETO) - { - PolyEdge edge = new PolyEdge(segX, segY, seg[0], seg[1]); - segs.add(edge); - segX = seg[0]; - segY = seg[1]; - } - path.next(); - } + Rectangle2D deviceBounds = new Rectangle2D.Double(); + ArrayList segs = getSegments(s, transform, deviceBounds, false); + Rectangle2D clipBounds = new Rectangle2D.Double(); + ArrayList clipSegs = getSegments(clip, transform, clipBounds, true); + segs.addAll(clipSegs); + Rectangle2D inclClipBounds = new Rectangle2D.Double(); + Rectangle2D.union(clipBounds, deviceBounds, inclClipBounds); if (segs.size() > 0) { if (antialias) - fillShapeAntialias(segs, minX, minY, maxX, maxY, userBounds); + fillShapeAntialias(segs, deviceBounds, userBounds); else - rawFillShape(segs, minX, minY, maxX, maxY, userBounds); + rawFillShape(segs, deviceBounds, userBounds, inclClipBounds); } } /** * Draws one pixel in the target coordinate space. This method draws the * specified pixel by getting the painting pixel for that coordinate * from the paintContext and compositing the pixel with the compositeContext. * The resulting pixel is then set by calling [EMAIL PROTECTED] #rawSetPixel}. * * @param x the x coordinate @@ -1285,33 +1235,20 @@ * Returns the bounds of the target. * * @return the bounds of the target */ protected Rectangle getDeviceBounds() { return destinationRaster.getBounds(); } /** - * Returns the bounds of the drawing area in user space. - * - * @return the bounds of the drawing area in user space - */ - protected Rectangle2D getUserBounds() - { - PathIterator pathIter = getDeviceBounds().getPathIterator(getTransform()); - GeneralPath path = new GeneralPath(); - path.append(pathIter, true); - return path.getBounds(); - - } - /** * Draws a line in optimization mode. The implementation should respect the * clip but can assume that it is a rectangle. * * @param x0 the starting point, X coordinate * @param y0 the starting point, Y coordinate * @param x1 the end point, X coordinate * @param y1 the end point, Y coordinate */ protected void rawDrawLine(int x0, int y0, int x1, int y1) { @@ -1396,63 +1333,70 @@ } } /** * Fills the specified polygon. This should be overridden by backends * that support accelerated (native) polygon filling, which is the * case for most toolkit window and offscreen image implementations. * * The polygon is already clipped when this method is called. */ - protected void rawFillShape(ArrayList segs, double minX, double minY, - double maxX, double maxY, Rectangle2D userBounds) + protected void rawFillShape(ArrayList segs, Rectangle2D deviceBounds2D, + Rectangle2D userBounds, + Rectangle2D inclClipBounds) { // This is an implementation of a polygon scanline conversion algorithm // described here: // http://www.cs.berkeley.edu/~ug/slide/pipeline/assignments/scan/ // Create table of all edges. // The edge buckets, sorted and indexed by their Y values. + double minX = deviceBounds2D.getMinX(); + double minY = deviceBounds2D.getMinY(); + double maxX = deviceBounds2D.getMaxX(); + double maxY = deviceBounds2D.getMaxY(); + double icMinY = inclClipBounds.getMinY(); + double icMaxY = inclClipBounds.getMaxY(); Rectangle deviceBounds = new Rectangle((int) minX, (int) minY, (int) Math.ceil(maxX) - (int) minX, (int) Math.ceil(maxY) - (int) minY); PaintContext pCtx = paint.createContext(getColorModel(), deviceBounds, userBounds, transform, renderingHints); - ArrayList[] edgeTable = new ArrayList[(int) Math.ceil(maxY) - - (int) Math.ceil(minY) + 1]; + ArrayList[] edgeTable = new ArrayList[(int) Math.ceil(icMaxY) + - (int) Math.ceil(icMinY) + 1]; for (Iterator i = segs.iterator(); i.hasNext();) { PolyEdge edge = (PolyEdge) i.next(); - int yindex = (int) ((int) Math.ceil(edge.y0) - (int) Math.ceil(minY)); + int yindex = (int) ((int) Math.ceil(edge.y0) - (int) Math.ceil(icMinY)); if (edgeTable[yindex] == null) // Create bucket when needed. edgeTable[yindex] = new ArrayList(); edgeTable[yindex].add(edge); // Add edge to the bucket of its line. } // TODO: The following could be useful for a future optimization. // // Sort all the edges in the edge table within their buckets. // for (int y = 0; y < edgeTable.length; y++) // { // if (edgeTable[y] != null) // Collections.sort(edgeTable[y]); // } // The activeEdges list contains all the edges of the current scanline // ordered by their intersection points with this scanline. ArrayList activeEdges = new ArrayList(); PolyEdgeComparator comparator = new PolyEdgeComparator(); // Scan all relevant lines. - int minYInt = (int) Math.ceil(minY); + int minYInt = (int) Math.ceil(icMinY); for (int y = minYInt; y <= maxY; y++) { ArrayList bucket = edgeTable[y - minYInt]; // Update all the x intersections in the current activeEdges table // and remove entries that are no longer in the scanline. for (Iterator i = activeEdges.iterator(); i.hasNext();) { PolyEdge edge = (PolyEdge) i.next(); if (y > edge.y1) i.remove(); @@ -1489,48 +1433,46 @@ if (j >= 1) e1 = (PolyEdge) activeEdges.get(j - 1); } while (j >= 1 && comparator.compare(e1, e2) > 0); } } } // Now draw all pixels inside the polygon. // This is the last edge that intersected the scanline. PolyEdge previous = null; // Gets initialized below. - boolean active = false; + boolean insideShape = false; + boolean insideClip = false; //System.err.println("scanline: " + y); for (Iterator i = activeEdges.iterator(); i.hasNext();) { PolyEdge edge = (PolyEdge) i.next(); - // Only fill scanline, if the current edge actually intersects - // the scanline. There may be edges that lie completely - // within the current scanline. - //System.err.println("previous: " + previous); - //System.err.println("edge: " + edge); - if (active) + if (edge.y1 <= y) + continue; + + // Draw scanline when we are inside the shape AND inside the + // clip. + if (insideClip && insideShape) { - if (edge.y1 > y) - { - int x0 = (int) previous.xIntersection; - int x1 = (int) edge.xIntersection; - fillScanline(pCtx, x0, x1, y); - previous = edge; - active = false; - } + int x0 = (int) previous.xIntersection; + int x1 = (int) edge.xIntersection; + if (x0 < x1) + fillScanline(pCtx, x0, x1, y); } - else + // Update state. + if (edge.y1 > y) { - if (edge.y1 > y) - { - previous = edge; - active = true; - } + previous = edge; + if (edge.isClip) + insideClip = ! insideClip; + else + insideShape = ! insideShape; } } } pCtx.dispose(); } /** * Paints a scanline between x0 and x1. * * @param x0 the left offset @@ -1546,35 +1488,35 @@ renderingHints); cCtx.compose(paintRaster, destinationRaster, destinationRaster); updateRaster(destinationRaster, x0, y, x1 - x0, 1); cCtx.dispose(); } /** * Fills arbitrary shapes in an anti-aliased fashion. * * @param segs the line segments which define the shape which is to be filled - * @param minX the bounding box, left X - * @param minY the bounding box, upper Y - * @param maxX the bounding box, right X - * @param maxY the bounding box, lower Y */ - private void fillShapeAntialias(ArrayList segs, double minX, double minY, - double maxX, double maxY, + private void fillShapeAntialias(ArrayList segs, Rectangle2D deviceBounds2D, Rectangle2D userBounds) { // This is an implementation of a polygon scanline conversion algorithm // described here: // http://www.cs.berkeley.edu/~ug/slide/pipeline/assignments/scan/ // The antialiasing is implemented using a sampling technique, we do // not scan whole lines but fractions of the line. + double minX = deviceBounds2D.getMinX(); + double minY = deviceBounds2D.getMinY(); + double maxX = deviceBounds2D.getMaxX(); + double maxY = deviceBounds2D.getMaxY(); + Rectangle deviceBounds = new Rectangle((int) minX, (int) minY, (int) Math.ceil(maxX) - (int) minX, (int) Math.ceil(maxY) - (int) minY); PaintContext pCtx = paint.createContext(getColorModel(), deviceBounds, userBounds, transform, renderingHints); // This array will contain the oversampled transparency values for // each pixel in the scanline. int numScanlines = (int) Math.ceil(maxY) - (int) minY; @@ -1905,47 +1847,81 @@ private void updateClip(AffineTransform t) { if (! (clip instanceof GeneralPath)) clip = new GeneralPath(clip); GeneralPath p = (GeneralPath) clip; p.transform(t); } /** - * Clips the specified shape using the current clip. If the resulting shape - * is empty, this will return <code>null</code>. + * Converts the specified shape into a list of segments. * - * @param s the shape to clip + * @param s the shape to convert + * @param t the transformation to apply before converting + * @param deviceBounds an output parameter; holds the bounding rectangle of + * s in device space after return + * @param isClip true when the shape is a clip, false for normal shapes; + * this influences the settings in the created PolyEdge instances. * - * @return the clipped shape or <code>null</code> if the result is empty + * @return a list of PolyEdge that form the shape in device space */ - private Shape clipShape(Shape s) + private ArrayList getSegments(Shape s, AffineTransform t, + Rectangle2D deviceBounds, boolean isClip) { - Shape clipped = null; + // Flatten the path. TODO: Determine the best flattening factor + // wrt to speed and quality. + PathIterator path = s.getPathIterator(getTransform(), 1.0); - // Clip the shape if necessary. - if (clip != null) - { - Area a; - if (! (s instanceof Area)) - a = new Area(s); - else - a = (Area) s; + // Build up polygons and let the native backend render this using + // rawFillShape() which would provide a default implementation for + // drawPixel using a PolyScan algorithm. + double[] seg = new double[6]; - Area clipArea; - if (! (clip instanceof Area)) - clipArea = new Area(clip); - else - clipArea = (Area) clip; + // TODO: Use ArrayList<PolyEdge> here when availble. + ArrayList segs = new ArrayList(); + double segX = 0.; // The start point of the current edge. + double segY = 0.; + double polyX = 0.; // The start point of the current polygon. + double polyY = 0.; - a.intersect(clipArea); - if (! a.isEmpty()) - clipped = a; - } - else + double minX = Integer.MAX_VALUE; + double maxX = Integer.MIN_VALUE; + double minY = Integer.MAX_VALUE; + double maxY = Integer.MIN_VALUE; + + //System.err.println("fill polygon"); + while (! path.isDone()) { - clipped = s; + int segType = path.currentSegment(seg); + minX = Math.min(minX, seg[0]); + maxX = Math.max(maxX, seg[0]); + minY = Math.min(minY, seg[1]); + maxY = Math.max(maxY, seg[1]); + + //System.err.println("segment: " + segType + ", " + seg[0] + ", " + seg[1]); + if (segType == PathIterator.SEG_MOVETO) + { + segX = seg[0]; + segY = seg[1]; + polyX = seg[0]; + polyY = seg[1]; + } + else if (segType == PathIterator.SEG_CLOSE) + { + // Close the polyline. + PolyEdge edge = new PolyEdge(segX, segY, polyX, polyY, isClip); + segs.add(edge); + } + else if (segType == PathIterator.SEG_LINETO) + { + PolyEdge edge = new PolyEdge(segX, segY, seg[0], seg[1], isClip); + segs.add(edge); + segX = seg[0]; + segY = seg[1]; + } + path.next(); } - return clipped; + deviceBounds.setRect(minX, minY, maxX - minX, maxY - minY); + return segs; } } Index: gnu/java/awt/java2d/PolyEdge.java =================================================================== RCS file: /cvsroot/classpath/classpath/gnu/java/awt/java2d/PolyEdge.java,v retrieving revision 1.1 diff -u -1 -0 -r1.1 PolyEdge.java --- gnu/java/awt/java2d/PolyEdge.java 25 Apr 2006 13:18:04 -0000 1.1 +++ gnu/java/awt/java2d/PolyEdge.java 8 May 2006 14:38:33 -0000 @@ -58,29 +58,35 @@ * The slope of the edge. This is dx / dy. */ double slope; /** * The intersection of this edge with the current scanline. */ double xIntersection; /** + * Indicates whether this edge is from the clip or from the target shape. + */ + boolean isClip; + + /** * Creates a new PolyEdge with the specified coordinates. * * @param x0 the starting point, x coordinate * @param y0 the starting point, y coordinate * @param x1 the end point, x coordinate * @param y1 the end point, y coordinate */ - PolyEdge(double x0, double y0, double x1, double y1) + PolyEdge(double x0, double y0, double x1, double y1, boolean clip) { + isClip = clip; if (y0 < y1) { this.x0 = x0; this.y0 = y0; this.x1 = x1; this.y1 = y1; } else { this.x0 = x1;
signature.asc
Description: Dies ist ein digital signierter Nachrichtenteil