Search code examples
javaswingscalatransformgraphics2d

Graphics2D transformation result does not match manual transformation


I am using Java's Graphics2D to draw on a component using AffineTransform's to manipulate my drawing. Graphics2D offers an method transform for this, which takes an AffineTransform.

Sometimes I need to manipulate a point manually without using the builtin-transformation. But when I try to transform a point using the same transformation I gave to Graphics2D.transform sometimes the resulting point is not the same.

The following code reproduces the problem (It's Scala code, but I think you can imagine the Java code.):

   var transformationMatrix = new AffineTransform()
   /*
    * transformationMatrix is modified throughout the program
    * ...
    */
   override def paintComponent(g: Graphics2D) = {
      super.paintComponent(g)
      /* 1. transform using graphics transform */
      g.transform(transformationMatrix)
      g.setColor(Color.RED)
      g.fill(new Rectangle(0, 0, 1, 1))
      /* 2. transform point manually */
      g.setTransform(new AffineTransform) // reset transformation to standard
      val p0 = new Point(0, 0)
      val pDest = new Point()
      transformationMatrix.transform(p0, pDest)
      g.setColor(Color.BLUE)
      g.fill(new Rectangle(pDest.x, pDest.y, 1, 1)
   }

Expected behaviour

The blue rectangle (manually calculated) overdraws the red one (calculated by transform).

Experienced behaviour

The blue rectangle has an offset of 1

I admit that my transformationMatrix is not really integer, but that should'nt be the problem, should it?

   affineTransform = 1.1, 0.0, 520.55
                     0.0, 1.1, 182.54999999999995
                     0.0, 0.0,    1.0

Is this a bug or am I missing some deep insight?

Edit: You can reproduce the bug, if you set transformationMatrix to

transformationMatrix = new AffineTransform(1.1, 0.0, 0.0, 1.1, 521.55, 183.54999999999995)

at the beginning of paintComponent. Please note, that g is of type Graphics2D.


Solution

  • Well, you are doing two different things.

    In (1) you are subjecting a shape (and it is irrelevant that it is Rectangle and not Rectangle2D.Double) to a transform that yields fractional coordinates. It only is painted aliased, because you haven't set specific rendering hints (RenderingHints.KEY_ANTIALIASING -> RenderingHints.VALUE_ANTIALIAS_ON, and RenderingHints.KEY_STROKE_CONTROL -> RenderingHints.VALUE_STROKE_PURE).

    In (2) you are subjecting a point to the transform, and coerce it into aliased coordinates (Point instead of Point2D.Double). Then successively construct a rectangle from that point.

    Clearly there may be very different things happening under the hood, and I wouldn't expect at all that transforming into an integer point versus painting floating point shapes in an aliasing graphics context yield the same results.

    (Without testing) I would guess that a valid equivalent statement for (1) would be

    g.fill(transformationMatrix.createTransformedShape(new Rectangle(0, 0, 1, 1)))