diff options
author | Roman Kennke <roman@kennke.org> | 2006-05-02 14:03:08 +0000 |
---|---|---|
committer | Roman Kennke <roman@kennke.org> | 2006-05-02 14:03:08 +0000 |
commit | dfbdcd419856d2ffdc06eff022fce6072b049f33 (patch) | |
tree | 9500a792a7d543bc25865f949e6b64a0897d2a83 | |
parent | 39fb4a816ac3be8493f86dadb040a43e8eca26f9 (diff) | |
download | classpath-dfbdcd419856d2ffdc06eff022fce6072b049f33.tar.gz |
2006-05-02 Roman Kennke <kennke@aicas.com>
* gnu/java/awt/java2d/AbstractGraphics2D.java
(AA_SAMPLING): New constant.
(alpha): New field. Used in the antialiasing renderer.
(edgeTable): New field. Used in the antialiasing renderer.
(AbstractGraphics2D): Initialize rendering hints wrt
anti-aliasing.
(draw): Clip after stroking. Commented out clipping for now,
it seems to be buggy.
(fill): Commented out clipping for now, it seems to be buggy.
(setComposite): Don't create composite context.
(setPaint): Only change paint when parameter is not null.
(translate): Call setClip() so subclasses can update their clip
too.
(clip): Call setClip() so subclasses can update their clip
too.
(drawGlyphVector): Added clipping, but left it commented out
because it's buggy.
(getClipBounds): Returns null when clip is null.
(drawLine): Call rawDrawLine with translation applied.
(filLRect): Call rawFillRect with translation applied.
(fillShape): Added support for anti-aliasing.
(rawSetForeground(int,int,int)): New method.
(rawFillShape): A couple of painting fixes.
(fillScanline): Implemented to call rawDrawLine.
(fillShapeAntialias): New method. Implements an anti-aliasing
shape filler.
(fillScanlineAA): New method. Used for the anti-aliasing
shape filler.
(fillScanlineAlpha): New method. Used for the anti-aliasing
shape filler.
(init): Initialize clip with the device bounds.
(updateOptimization): Fixed the optimization condition.
-rw-r--r-- | ChangeLog | 35 | ||||
-rw-r--r-- | gnu/java/awt/java2d/AbstractGraphics2D.java | 469 |
2 files changed, 435 insertions, 69 deletions
@@ -1,3 +1,38 @@ +2006-05-02 Roman Kennke <kennke@aicas.com> + + * gnu/java/awt/java2d/AbstractGraphics2D.java + (AA_SAMPLING): New constant. + (alpha): New field. Used in the antialiasing renderer. + (edgeTable): New field. Used in the antialiasing renderer. + (AbstractGraphics2D): Initialize rendering hints wrt + anti-aliasing. + (draw): Clip after stroking. Commented out clipping for now, + it seems to be buggy. + (fill): Commented out clipping for now, it seems to be buggy. + (setComposite): Don't create composite context. + (setPaint): Only change paint when parameter is not null. + (translate): Call setClip() so subclasses can update their clip + too. + (clip): Call setClip() so subclasses can update their clip + too. + (drawGlyphVector): Added clipping, but left it commented out + because it's buggy. + (getClipBounds): Returns null when clip is null. + (drawLine): Call rawDrawLine with translation applied. + (filLRect): Call rawFillRect with translation applied. + (fillShape): Added support for anti-aliasing. + (rawSetForeground(int,int,int)): New method. + (rawFillShape): A couple of painting fixes. + (fillScanline): Implemented to call rawDrawLine. + (fillShapeAntialias): New method. Implements an anti-aliasing + shape filler. + (fillScanlineAA): New method. Used for the anti-aliasing + shape filler. + (fillScanlineAlpha): New method. Used for the anti-aliasing + shape filler. + (init): Initialize clip with the device bounds. + (updateOptimization): Fixed the optimization condition. + 2006-05-02 Robert Schuster <robertschuster@fsfe.org> * javax/swing/text/GapContent.java: diff --git a/gnu/java/awt/java2d/AbstractGraphics2D.java b/gnu/java/awt/java2d/AbstractGraphics2D.java index a69c934b7..d89158c5e 100644 --- a/gnu/java/awt/java2d/AbstractGraphics2D.java +++ b/gnu/java/awt/java2d/AbstractGraphics2D.java @@ -42,14 +42,12 @@ import java.awt.AlphaComposite; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Composite; -import java.awt.CompositeContext; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Paint; -import java.awt.PaintContext; import java.awt.Polygon; import java.awt.Rectangle; import java.awt.RenderingHints; @@ -78,6 +76,7 @@ import java.awt.image.RenderedImage; import java.awt.image.renderable.RenderableImage; import java.text.AttributedCharacterIterator; import java.util.ArrayList; +import java.util.HashMap; import java.util.Iterator; import java.util.Map; @@ -92,6 +91,13 @@ public abstract class AbstractGraphics2D { /** + * Accuracy of the sampling in the anti-aliasing shape filler. + * Lower values give more speed, while higher values give more quality. + * It is advisable to choose powers of two. + */ + private static final int AA_SAMPLING = 8; + + /** * The transformation for this Graphics2D instance */ private AffineTransform transform; @@ -132,24 +138,25 @@ public abstract class AbstractGraphics2D private RenderingHints renderingHints; /** - * The paint context to use for draw and fill operations. + * The paint raster. */ - private PaintContext paintContext; + private Raster paintRaster; /** - * The paint raster. + * A cached pixel array. */ - private Raster paintRaster; + private int[] pixel; /** - * The composite context to use for draw and fill operations. + * Stores the alpha values for a scanline in the anti-aliasing shape + * renderer. */ - private CompositeContext compositeContext; + private transient int[] alpha; /** - * A cached pixel array. + * The edge table for the scanline conversion algorithms. */ - private int[] pixel; + private transient ArrayList[] edgeTable; /** * Indicates if cerain graphics primitives can be rendered in an optimized @@ -176,8 +183,12 @@ public abstract class AbstractGraphics2D background = Color.WHITE; composite = AlphaComposite.SrcOver; stroke = new BasicStroke(); - renderingHints = new RenderingHints(RenderingHints.KEY_RENDERING, - RenderingHints.VALUE_RENDER_DEFAULT); + HashMap hints = new HashMap(); + hints.put(RenderingHints.KEY_TEXT_ANTIALIASING, + RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT); + hints.put(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_DEFAULT); + renderingHints = new RenderingHints(hints); pixel = new int[4]; } @@ -190,15 +201,18 @@ public abstract class AbstractGraphics2D */ public void draw(Shape shape) { - // Clip the shape. - Shape clipped = clipShape(shape); - if (clipped != null) - { - // Stroke the shape. - Shape strokedShape = stroke.createStrokedShape(clipped); - // Fill the shape. - fillShape(strokedShape); - } + // Stroke the shape. + Shape strokedShape = stroke.createStrokedShape(shape); + + // Clip the stroked shape. +// Shape clipped = clipShape(strokedShape); +// if (clipped != null) +// { +// // Fill the shape. +// fillShape(clipped, false); +// } + // FIXME: Clipping doesn't seem to work. + fillShape(strokedShape, false); } public boolean drawImage(Image image, AffineTransform xform, ImageObserver obs) @@ -290,9 +304,10 @@ public abstract class AbstractGraphics2D */ public void fill(Shape shape) { - Shape clipped = clipShape(shape); - if (clipped != null) - fillShape(shape); +// Shape clipped = clipShape(shape); +// if (clipped != null) +// fillShape(clipped, false); + fillShape(shape, false); } public boolean hit(Rectangle rect, Shape text, boolean onStroke) @@ -309,9 +324,6 @@ public abstract class AbstractGraphics2D public void setComposite(Composite comp) { composite = comp; - compositeContext = composite.createContext(getColorModel(), - getDestinationColorModel(), - renderingHints); if (! (comp.equals(AlphaComposite.SrcOver))) isOptimized = false; else @@ -325,14 +337,17 @@ public abstract class AbstractGraphics2D */ public void setPaint(Paint p) { - paint = p; - - if (paint != null && ! (paint instanceof Color)) - isOptimized = false; - else + if (p != null) { - updateOptimization(); - rawSetForeground((Color) paint); + paint = p; + + if (! (paint instanceof Color)) + isOptimized = false; + else + { + updateOptimization(); + rawSetForeground((Color) paint); + } } } @@ -423,6 +438,7 @@ public abstract class AbstractGraphics2D Rectangle r = (Rectangle) clip; r.x -= x; r.y -= y; + setClip(r); } else { @@ -660,6 +676,8 @@ public abstract class AbstractGraphics2D Rectangle clipRect = (Rectangle) clip; Rectangle r = (Rectangle) s; computeIntersection(r.x, r.y, r.width, r.height, clipRect); + // Call setClip so that subclasses get notified. + setClip(clipRect); } else { @@ -678,6 +696,8 @@ public abstract class AbstractGraphics2D current.intersect(intersect); clip = current; isOptimized = false; + // Call setClip so that subclasses get notified. + setClip(clip); } } @@ -700,14 +720,18 @@ public abstract class AbstractGraphics2D AffineTransform t = new AffineTransform(); t.translate(x, y); - // TODO: We could use fill(gv.getOutline()), but that doesn't seem - // to work yet with the font infrastructure I use. +// // TODO: We could use fill(gv.getOutline()), but that seems to be + // slightly more inefficient. for (int i = 0; i < numGlyphs; i++) { //fill(gv.getGlyphVisualBounds(i)); GeneralPath p = new GeneralPath(gv.getGlyphOutline(i)); p.transform(t); - fill(p); + //Shape clipped = clipShape(p); + //if (clipped != null) + // fillShape(clipped, true); + // FIXME: Clipping doesn't seem to work correctly. + fillShape(p, true); } } @@ -827,7 +851,10 @@ public abstract class AbstractGraphics2D */ public Rectangle getClipBounds() { - return clip.getBounds(); + Rectangle b = null; + if (clip != null) + b = clip.getBounds(); + return b; } /** @@ -895,7 +922,11 @@ public abstract class AbstractGraphics2D public void drawLine(int x1, int y1, int x2, int y2) { if (isOptimized) - rawDrawLine(x1, y1, x2, y2); + { + int tx = (int) transform.getTranslateX(); + int ty = (int) transform.getTranslateY(); + rawDrawLine(x1 + tx, y1 + ty, x2 + tx, y2 + ty); + } else { Line2D line = new Line2D.Double(x1, y1, x2, y2); @@ -914,7 +945,11 @@ public abstract class AbstractGraphics2D public void fillRect(int x, int y, int width, int height) { if (isOptimized) - rawFillRect(x, y, width, height); + { + int tx = (int) transform.getTranslateX(); + int ty = (int) transform.getTranslateY(); + rawFillRect(x + tx, y + ty, width, height); + } else { fill(new Rectangle(x, y, width, height)); @@ -1099,12 +1134,32 @@ public abstract class AbstractGraphics2D * current clip. * * @param s the shape to fill + * @param isFont <code>true</code> if the shape is a font outline */ - protected void fillShape(Shape s) + protected void fillShape(Shape s, boolean isFont) { + // Determine if we need to antialias stuff. + boolean antialias = false; + if (isFont) + { + Object v = renderingHints.get(RenderingHints.KEY_TEXT_ANTIALIASING); + // We default to antialiasing on for text as long as we have no + // good hinting implemented. + 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); + } + + // 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 - // rawFillPolygon() which would provide a default implementation for + // rawFillShape() which would provide a default implementation for // drawPixel using a PolyScan algorithm. double[] seg = new double[6]; @@ -1153,7 +1208,12 @@ public abstract class AbstractGraphics2D path.next(); } if (segs.size() > 0) - rawFillShape(segs, minX, minY, maxX, maxY); + { + if (antialias) + fillShapeAntialias(segs, minX, minY, maxX, maxY); + else + rawFillShape(segs, minX, minY, maxX, maxY); + } } /** @@ -1192,6 +1252,11 @@ public abstract class AbstractGraphics2D */ protected abstract void rawSetForeground(Color c); + protected void rawSetForeground(int r, int g, int b) + { + rawSetForeground(new Color(r, g, b)); + } + /** * Returns the color model of this Graphics object. * @@ -1340,12 +1405,6 @@ public abstract class AbstractGraphics2D for (Iterator i = segs.iterator(); i.hasNext();) { PolyEdge edge = (PolyEdge) i.next(); - - // Horizontal edges are not needed and make things only more - // complicated. Skip them. - if (Math.ceil(edge.y0) == Math.ceil(edge.y1)) - continue; - int yindex = (int) ((int) Math.ceil(edge.y0) - (int) Math.ceil(minY)); if (edgeTable[yindex] == null) // Create bucket when needed. edgeTable[yindex] = new ArrayList(); @@ -1375,7 +1434,7 @@ public abstract class AbstractGraphics2D for (Iterator i = activeEdges.iterator(); i.hasNext();) { PolyEdge edge = (PolyEdge) i.next(); - if (y >= edge.y0 && y >= edge.y1) + if (y > edge.y1) i.remove(); else { @@ -1415,43 +1474,309 @@ public abstract class AbstractGraphics2D } // Now draw all pixels inside the polygon. - int x0 = 0; // Gets initialized in the first else branch below. + // This is the last edge that intersected the scanline. + PolyEdge previous = null; // Gets initialized below. boolean active = 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); - int x = (int) edge.xIntersection; if (active) { - fillScanline(x0, x, y); - active = false; + if (edge.y1 > y) + { + int x0 = (int) previous.xIntersection; + int x1 = (int) edge.xIntersection; + fillScanline(x0, x1, y); + previous = edge; + active = false; + } } else { - x0 = x; - active = true; + if (edge.y1 > y) + { + previous = edge; + active = true; + } + } + } + } + } + + /** + * Paints a scanline between x0 and x1. + * + * @param x0 the left offset + * @param x1 the right offset + * @param y the scanline + */ + protected void fillScanline(int x0, int x1, int y) + { + if (paint instanceof Color && composite == AlphaComposite.SrcOver) + { + rawDrawLine(x0, y, x1, y); + } + else + { + throw new UnsupportedOperationException("Not yet implemented."); + } + } + + /** + * 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) + { + // 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. + + // This array will contain the oversampled transparency values for + // each pixel in the scanline. + int numScanlines = (int) Math.ceil(maxY) - (int) minY; + int numScanlinePixels = (int) Math.ceil(maxX) - (int) minX + 1; + if (alpha == null || alpha.length < (numScanlinePixels + 1)) + alpha = new int[numScanlinePixels + 1]; + + int firstLine = (int) minY; + //System.err.println("minY: " + minY); + int firstSubline = (int) (Math.ceil((minY - Math.floor(minY)) * AA_SAMPLING)); + double firstLineDouble = firstLine + firstSubline / (double) AA_SAMPLING; + //System.err.println("firstSubline: " + firstSubline); + + // Create table of all edges. + // The edge buckets, sorted and indexed by their Y values. + //System.err.println("numScanlines: " + numScanlines); + if (edgeTable == null + || edgeTable.length < numScanlines * AA_SAMPLING + AA_SAMPLING) + edgeTable = new ArrayList[numScanlines * AA_SAMPLING + AA_SAMPLING]; + + //System.err.println("firstLineDouble: " + firstLineDouble); + + for (Iterator i = segs.iterator(); i.hasNext();) + { + PolyEdge edge = (PolyEdge) i.next(); + int yindex = (int) (Math.ceil((edge.y0 - firstLineDouble) * AA_SAMPLING)); + //System.err.println("yindex: " + yindex + " for y0: " + edge.y0); + // Initialize edge's slope and initial xIntersection. + edge.slope = ((edge.x1 - edge.x0) / (edge.y1 - edge.y0)) / AA_SAMPLING; + if (edge.y0 == edge.y1) // Horizontal edge. + edge.xIntersection = Math.min(edge.x0, edge.x1); + else + { + double alignedFirst = Math.ceil(edge.y0 * AA_SAMPLING) / AA_SAMPLING; + edge.xIntersection = edge.x0 + (edge.slope * AA_SAMPLING) * (alignedFirst - edge.y0); + } + //System.err.println(edge); + // FIXME: Sanity check should not be needed when clipping works. + if (yindex >= 0 && yindex < edgeTable.length) + { + if (edgeTable[yindex] == null) // Create bucket when needed. + edgeTable[yindex] = new ArrayList(); + edgeTable[yindex].add(edge); // Add edge to the bucket of its line. + } + } + + // 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 lines. + int yindex = 0; + //System.err.println("firstLine: " + firstLine + ", maxY: " + maxY + ", firstSubline: " + firstSubline); + for (int y = firstLine; y <= maxY; y++) + { + for (int subY = firstSubline; subY < AA_SAMPLING; subY++) + { + //System.err.println("scanline: " + y + ", subScanline: " + subY); + ArrayList bucket = edgeTable[yindex]; + // 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(); + // TODO: Do the following using integer arithmetics. + if ((y + ((double) subY / (double) AA_SAMPLING)) > edge.y1) + i.remove(); + else + { + edge.xIntersection += edge.slope; + //System.err.println("edge: " + edge); + //edge.xIntersection = edge.x0 + edge.slope * (y - edge.y0); + //System.err.println("edge.xIntersection: " + edge.xIntersection); + } + } + + if (bucket != null) + { + activeEdges.addAll(bucket); + edgeTable[yindex].clear(); + } + + // Sort current edges. We are using a bubble sort, because the order + // of the intersections will not change in most situations. They + // will only change, when edges intersect each other. + int size = activeEdges.size(); + if (size > 1) + { + for (int i = 1; i < size; i++) + { + PolyEdge e1 = (PolyEdge) activeEdges.get(i - 1); + PolyEdge e2 = (PolyEdge) activeEdges.get(i); + if (comparator.compare(e1, e2) > 0) + { + // Swap e2 with its left neighbor until it 'fits'. + int j = i; + do + { + activeEdges.set(j, e1); + activeEdges.set(j - 1, e2); + j--; + 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; + //System.err.println("scanline: " + y + ", subscanline: " + subY); + 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) + { + // TODO: Use integer arithmetics here. + if (edge.y1 > (y + (subY / (double) AA_SAMPLING))) + { + //System.err.println(edge); + // TODO: Eliminate the aligments. + int x0 = (int) Math.min(Math.max(previous.xIntersection, minX), maxX); + int x1 = (int) Math.min(Math.max(edge.xIntersection, minX), maxX); + //System.err.println("minX: " + minX + ", x0: " + x0 + ", x1: " + x1 + ", maxX: " + maxX); + // TODO: Pull out cast. + alpha[x0 - (int) minX]++; + alpha[x1 - (int) minX + 1]--; + previous = edge; + active = false; + } + } + else + { + // TODO: Use integer arithmetics here. + if (edge.y1 > (y + (subY / (double) AA_SAMPLING))) + { + //System.err.println(edge); + previous = edge; + active = true; + } + } } + yindex++; } + firstSubline = 0; + // Render full scanline. + //System.err.println("scanline: " + y); + fillScanlineAA(alpha, (int) minX, (int) y, numScanlinePixels); } + if (paint instanceof Color && composite == AlphaComposite.SrcOver) + rawSetForeground((Color) paint); } /** - * Fills a horizontal line between x0 and x1 with the current paint and - * composize. Backends should override this if they want to accelerate - * general shape drawing. They should respect the current paint and - * composite though (or call super for these tasks). It is not necessary - * to clip the line. + * Fills a horizontal line between x0 and x1 for anti aliased rendering. + * the alpha array contains the deltas of the alpha values from one pixel + * to the next. * + * @param alpha the alpha values in the scanline * @param x0 the beginning of the scanline - * @param x1 the end of the scanline * @param y the y coordinate of the line */ - protected void fillScanline(int x0, int x1, int y) + private void fillScanlineAA(int[] alpha, int x0, int y, int numScanlinePixels) { - for (int x = x0; x < x1; x++) - drawPixel(x, y); + int lastX = x0; + int lastAlpha = 0; + for (int i = 0; i < numScanlinePixels; i++) + { + if (alpha[i] == 0) + continue; + if (lastAlpha > 0) + { + //System.err.println("rawDrawScanline: " + lastX + ", " + (i+x0) + ", " + lastAlpha); + // TODO: Avoid double arithmetic. + fillScanlineAlpha(lastX, i + x0, y, lastAlpha); + } + lastAlpha += alpha[i]; + alpha[i] = 0; + lastX = i + x0; + } + if (lastAlpha > 0) + // TODO: Avoid double arithmetic. + fillScanlineAlpha(lastX, x0 + numScanlinePixels, y, lastAlpha); + } + + /** + * Renders one scanline with alpha sampling for anti-aliased shape rendering. + * + * @param x0 the start offset + * @param x1 the end offset + * @param y the scanline + * @param sample the sample value, relative to AA_SAMPLING + */ + private void fillScanlineAlpha(int x0, int x1, int y, int sample) + { + if (paint instanceof Color && composite == AlphaComposite.SrcOver) + { + // FIXME: We should composite over current pixels, not over the + // background color. + Color fg = (Color) paint; + if (sample < AA_SAMPLING) + { + Color bg = background; + int bgShare = (AA_SAMPLING - sample); + int red = (fg.getRed() * sample + bg.getRed() * bgShare) + / AA_SAMPLING; + int green = (fg.getGreen() * sample + bg.getGreen() * bgShare) + / AA_SAMPLING; + int blue = (fg.getBlue() * sample + bg.getBlue() * bgShare) + / AA_SAMPLING; + rawSetForeground(red, green, blue); + fillScanline(x0, x1 - 1, y); + } + else + { + rawSetForeground(fg); + fillScanline(x0, x1 - 1, y); + } + } + else + { + throw new UnsupportedOperationException("Not yet implemented"); + } } /** @@ -1463,6 +1788,10 @@ public abstract class AbstractGraphics2D setPaint(Color.BLACK); setFont(new Font("SansSerif", Font.PLAIN, 12)); isOptimized = true; + + // FIXME: Should not be necessary. A clip of null should mean + // 'clip against device bounds. + clip = getDeviceBounds(); } //protected abstract Raster getDestinationRaster(int x, int y, int w, int h); @@ -1475,11 +1804,13 @@ public abstract class AbstractGraphics2D private void updateOptimization() { int transformType = transform.getType(); - boolean optimizedTransform = - (transformType & - (AffineTransform.TYPE_TRANSLATION | AffineTransform.TYPE_IDENTITY)) != 0; + boolean optimizedTransform = false; + if (transformType == AffineTransform.TYPE_IDENTITY + || transformType == AffineTransform.TYPE_TRANSLATION) + optimizedTransform = true; - isOptimized = clip instanceof Rectangle + boolean optimizedClip = (clip == null || clip instanceof Rectangle); + isOptimized = optimizedClip && optimizedTransform && paint instanceof Color && composite == AlphaComposite.SrcOver && stroke.equals(new BasicStroke()); |