Hi,
This patch fixes the transformations we use when calculating
device-space bounds to support rotations and shears in the transform
(before, we had assumed that the shape would only be translated/scaled -
which is true for most, but not all, cases).
Included is also a small optimization to updateClip() that tries to
avoid converting Rectangles to GeneralPath's when possible.
Hope everyone has a great holiday season!
Francis
2006-12-22 Francis Kung <[EMAIL PROTECTED]>
* gnu/java/awt/peer/gtk/BufferedImageGraphics.java
(locked): Removed field.
(BufferedImageGraphics): Remove locked flag.
(drawComposite): Transform bounds properly; set cairo composite
directly.
(drawImage): Transform bounds properly.
(updateBufferedImage): Remove locked flag; transform bounds properly.
* gnu/java/awt/peer/gtk/CairoGraphics2D.java
(copyArea): Add comment.
(drawImage): Transform bounds properly; update clipping region.
(getClipInDevSpace): Transform bounds properly.
(getTransformedBounds): New method.
(setAntialias): Updated javadoc.
(setCustomPaint): Transform bounds properly.
(updateClip): Avoid use of GeneralPath when possible.
Index: gnu/java/awt/peer/gtk/BufferedImageGraphics.java
===================================================================
RCS file: /cvsroot/classpath/classpath/gnu/java/awt/peer/gtk/BufferedImageGraphics.java,v
retrieving revision 1.22
diff -u -r1.22 BufferedImageGraphics.java
--- gnu/java/awt/peer/gtk/BufferedImageGraphics.java 18 Dec 2006 15:35:04 -0000 1.22
+++ gnu/java/awt/peer/gtk/BufferedImageGraphics.java 22 Dec 2006 19:41:37 -0000
@@ -40,6 +40,7 @@
import java.awt.AlphaComposite;
import java.awt.Color;
+import java.awt.Composite;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
@@ -49,7 +50,6 @@
import java.awt.Toolkit;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
-import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
@@ -75,12 +75,6 @@
private BufferedImage image, buffer;
/**
- * Allows us to lock the image from updates (if we want to perform a few
- * intermediary operations on the cairo surface, then update it all at once)
- */
- private boolean locked;
-
- /**
* Image size.
*/
private int imageWidth, imageHeight;
@@ -109,7 +103,6 @@
this.image = bi;
imageWidth = bi.getWidth();
imageHeight = bi.getHeight();
- locked = false;
if (!(image.getSampleModel() instanceof SinglePixelPackedSampleModel))
hasFastCM = false;
@@ -189,7 +182,6 @@
cairo_t = surface.newCairoContext();
imageWidth = copyFrom.imageWidth;
imageHeight = copyFrom.imageHeight;
- locked = false;
hasFastCM = copyFrom.hasFastCM;
hasAlpha = copyFrom.hasAlpha;
@@ -202,15 +194,12 @@
*/
private void updateBufferedImage(int x, int y, int width, int height)
{
- if (locked)
- return;
-
- double[] points = new double[]{x, y, width+x, height+y};
- transform.transform(points, 0, points, 0, 2);
- x = (int)points[0];
- y = (int)points[1];
- width = (int)Math.ceil(points[2] - points[0]);
- height = (int)Math.ceil(points[3] - points[1]);
+ Rectangle bounds = new Rectangle(x, y, width, height);
+ bounds = getTransformedBounds(bounds, transform).getBounds();
+ x = bounds.x;
+ y = bounds.y;
+ width = bounds.width;
+ height = bounds.height;
int[] pixels = surface.getPixels(imageWidth * imageHeight);
@@ -403,14 +392,10 @@
BufferedImage bImg = (BufferedImage) img;
// Find translated bounds
- Point2D origin = new Point2D.Double(bImg.getMinX(), bImg.getMinY());
- Point2D pt = new Point2D.Double(bImg.getWidth() + bImg.getMinX(),
- bImg.getHeight() + bImg.getMinY());
+ Rectangle2D bounds = new Rectangle(bImg.getMinX(), bImg.getMinY(),
+ bImg.getWidth(), bImg.getHeight());
if (xform != null)
- {
- origin = xform.transform(origin, origin);
- pt = xform.transform(pt, pt);
- }
+ bounds = getTransformedBounds(bounds, xform);
// Create buffer and draw image
createBuffer();
@@ -420,10 +405,7 @@
g2d.drawImage(img, xform, obs);
// Perform compositing
- return drawComposite(new Rectangle2D.Double(origin.getX(),
- origin.getY(),
- pt.getX(), pt.getY()),
- obs);
+ return drawComposite(bounds, obs);
}
}
@@ -473,12 +455,7 @@
private boolean drawComposite(Rectangle2D bounds, ImageObserver observer)
{
// Find bounds in device space
- double[] points = new double[] {bounds.getX(), bounds.getY(),
- bounds.getMaxX(), bounds.getMaxY()};
- transform.transform(points, 0, points, 0, 2);
- bounds = new Rectangle2D.Double(points[0], points[1],
- (points[2] - points[0]),
- (points[3] - points[1]));
+ bounds = getTransformedBounds(bounds, transform);
// Clip bounds by the stored clip, and by the internal buffer
Rectangle2D devClip = this.getClipInDevSpace();
@@ -487,17 +464,15 @@
buffer.getWidth(), buffer.getHeight());
Rectangle2D.intersect(bounds, devClip, bounds);
- // Round bounds as needed, but be conservative in our rounding
+ // Round bounds as needed, but be careful in our rounding
// (otherwise it may leave unpainted stripes)
double x = bounds.getX();
double y = bounds.getY();
- double w = bounds.getWidth();
- double h = bounds.getHeight();
- if (Math.floor(x) != x)
- w--;
- if (Math.floor(y) != y)
- h--;
- bounds.setRect(Math.ceil(x), Math.ceil(y), Math.floor(w), Math.floor(h));
+ double maxX = x + bounds.getWidth();
+ double maxY = y + bounds.getHeight();
+ x = Math.round(x);
+ y = Math.round(y);
+ bounds.setRect(x, y, Math.round(maxX - x), Math.round(maxY - y));
// Find subimage of internal buffer for updating
BufferedImage buffer2 = buffer;
@@ -516,9 +491,10 @@
compCtx.compose(buffer2.getRaster(), current.getRaster(),
current.getRaster());
- // Prevent the clearRect in CairoGraphics2D.drawImage from clearing
- // our composited image
- locked = true;
+ // Set cairo's composite to direct SRC, since we've already done our own
+ // compositing
+ Composite oldcomp = comp;
+ setComposite(AlphaComposite.Src);
// This MUST call directly into the "action" method in CairoGraphics2D,
// not one of the wrappers, to ensure that the composite isn't processed
@@ -526,8 +502,9 @@
boolean rv = super.drawImage(current,
AffineTransform.getTranslateInstance(bounds.getX(),
bounds.getY()),
- new Color(0,0,0,0), null);
- locked = false;
+ null, null);
+ setComposite(oldcomp);
+ updateColor();
return rv;
}
Index: gnu/java/awt/peer/gtk/CairoGraphics2D.java
===================================================================
RCS file: /cvsroot/classpath/classpath/gnu/java/awt/peer/gtk/CairoGraphics2D.java,v
retrieving revision 1.59
diff -u -r1.59 CairoGraphics2D.java
--- gnu/java/awt/peer/gtk/CairoGraphics2D.java 15 Dec 2006 21:48:39 -0000 1.59
+++ gnu/java/awt/peer/gtk/CairoGraphics2D.java 22 Dec 2006 19:41:37 -0000
@@ -770,15 +770,11 @@
int userHeight = bounds.height;
// Find bounds in device space
- Point2D origin = transform.transform(new Point2D.Double(userX, userY),
- null);
- Point2D extreme = transform.transform(new Point2D.Double(userWidth + userX,
- userHeight + userY),
- null);
- int deviceX = (int)origin.getX();
- int deviceY = (int)origin.getY();
- int deviceWidth = (int)Math.ceil(extreme.getX() - origin.getX());
- int deviceHeight = (int)Math.ceil(extreme.getY() - origin.getY());
+ Rectangle2D bounds2D = getTransformedBounds(bounds, transform);
+ int deviceX = (int)bounds2D.getX();
+ int deviceY = (int)bounds2D.getY();
+ int deviceWidth = (int)Math.ceil(bounds2D.getWidth());
+ int deviceHeight = (int)Math.ceil(bounds2D.getHeight());
// Get raster of the paint background
PaintContext pc = paint.createContext(CairoSurface.cairoColorModel,
@@ -983,18 +979,7 @@
if (transform == null)
return uclip;
else
- {
- Point2D pos = transform.transform(new Point2D.Double(uclip.getX(),
- uclip.getY()),
- (Point2D) null);
- Point2D extent = transform.deltaTransform(new Point2D.Double(uclip
- .getWidth(),
- uclip
- .getHeight()),
- (Point2D) null);
- return new Rectangle2D.Double(pos.getX(), pos.getY(), extent.getX(),
- extent.getY());
- }
+ return getTransformedBounds(clip.getBounds2D(), transform);
}
public void setClip(int x, int y, int width, int height)
@@ -1334,6 +1319,8 @@
public void copyArea(int ox, int oy, int owidth, int oheight,
int odx, int ody)
{
+ // FIXME: does this handle a rotation transform properly?
+ // (the width/height might not be correct)
Point2D pos = transform.transform(new Point2D.Double(ox, oy),
(Point2D) null);
Point2D dim = transform.transform(new Point2D.Double(ox + owidth,
@@ -1449,7 +1436,7 @@
* Set antialias if needed. If the ignoreAA flag is set, this method will
* return without doing anything.
*
- * @param value RenderingHints.VALUE_ANTIALIAS_ON or RenderingHints.VALUE_ANTIALIAS_OFF
+ * @param needAA RenderingHints.VALUE_ANTIALIAS_ON or RenderingHints.VALUE_ANTIALIAS_OFF
*/
private void setAntialias(boolean needAA)
{
@@ -1539,24 +1526,31 @@
Color oldColor = bg;
setBackground(bgcolor);
- double[] origin = new double[] {0,0};
- double[] dimensions = new double[] {width, height};
- xform.transform(origin, 0, origin, 0, 1);
- xform.deltaTransform(dimensions, 0, dimensions, 0, 1);
- clearRect((int)origin[0], (int)origin[1],
- (int)dimensions[0], (int)dimensions[1]);
+ Rectangle2D bounds = new Rectangle2D.Double(0, 0, width, height);
+ bounds = getTransformedBounds(bounds, xform);
+
+ clearRect((int)bounds.getX(), (int)bounds.getY(),
+ (int)bounds.getWidth(), (int)bounds.getHeight());
setBackground(oldColor);
}
int[] pixels = b.getRGB(0, 0, width, height, null, 0, width);
-
// FIXME: The above method returns data in the standard ARGB colorspace,
// meaning data should NOT be alpha pre-multiplied; however Cairo expects
// data to be premultiplied.
+
+ cairoSave(nativePointer);
+ cairoResetClip(nativePointer);
+ Rectangle2D bounds = new Rectangle2D.Double(0, 0, width, height);
+ bounds = getTransformedBounds(bounds, xform);
+ cairoRectangle(nativePointer, bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight());
+ cairoClip(nativePointer);
drawPixels(nativePointer, pixels, width, height, width, i2u, alpha,
getInterpolation());
+
+ cairoRestore(nativePointer);
// Cairo seems to lose the current color which must be restored.
updateColor();
@@ -2076,11 +2070,27 @@
if (clip == null)
return;
- if (! (clip instanceof GeneralPath))
- clip = new GeneralPath(clip);
-
- GeneralPath p = (GeneralPath) clip;
- p.transform(t);
+ // If the clip is a rectangle, and the transformation preserves the shape
+ // (translate/stretch only), then keep the clip as a rectangle
+ double[] matrix = new double[4];
+ t.getMatrix(matrix);
+ if (clip instanceof Rectangle2D && matrix[1] == 0 && matrix[2] == 0)
+ {
+ Rectangle2D rect = (Rectangle2D)clip;
+ double[] origin = new double[] {rect.getX(), rect.getY()};
+ double[] dimensions = new double[] {rect.getWidth(), rect.getHeight()};
+ t.transform(origin, 0, origin, 0, 1);
+ t.deltaTransform(dimensions, 0, dimensions, 0, 1);
+ rect.setRect(origin[0], origin[1], dimensions[0], dimensions[1]);
+ }
+ else
+ {
+ if (! (clip instanceof GeneralPath))
+ clip = new GeneralPath(clip);
+
+ GeneralPath p = (GeneralPath) clip;
+ p.transform(t);
+ }
}
private static Rectangle computeIntersection(int x, int y, int w, int h,
@@ -2103,4 +2113,39 @@
return rect;
}
+
+ static Rectangle2D getTransformedBounds(Rectangle2D bounds, AffineTransform tx)
+ {
+ double x1 = bounds.getX();
+ double x2 = bounds.getX() + bounds.getWidth();
+ double x3 = x1;
+ double x4 = x2;
+ double y1 = bounds.getY();
+ double y2 = y1;
+ double y3 = bounds.getY() + bounds.getHeight();
+ double y4 = y3;
+
+ double[] points = new double[] {x1, y1, x2, y2, x3, y3, x4, y4};
+ tx.transform(points, 0, points, 0, 4);
+
+ double minX = points[0];
+ double maxX = minX;
+ double minY = points[1];
+ double maxY = minY;
+ for (int i = 0; i < 8; i++)
+ {
+ if (points[i] < minX)
+ minX = points[i];
+ if (points[i] > maxX)
+ maxX = points[i];
+ i++;
+
+ if (points[i] < minY)
+ minY = points[i];
+ if (points[i] > maxY)
+ maxY = points[i];
+ }
+
+ return new Rectangle2D.Double(minX, minY, (maxX - minX), (maxY - minY));
+ }
}
\ No newline at end of file