xmlgraphics-batik icon indicating copy to clipboard operation
xmlgraphics-batik copied to clipboard

BATIK-1361: Animated `rotate()` tranform ignores y-origin at exactly 270 degrees

Open stanio opened this issue 2 years ago • 2 comments

See BATIK-1361 for more background but I've traced the problem down to this condition:

https://github.com/apache/xmlgraphics-batik/blob/3c83de345f21cae4f4a64dfe6244483f87885808/batik-svg-dom/src/main/java/org/apache/batik/dom/svg/AbstractSVGTransformList.java#L286-L288

rotate(angle) uncoditionally sets a rotation with cx=0, cy=0 origin/anchor/center:

https://github.com/apache/xmlgraphics-batik/blob/3c83de345f21cae4f4a64dfe6244483f87885808/batik-svg-dom/src/main/java/org/apache/batik/dom/svg/AbstractSVGTransformList.java#L328-L330

The matrix.getE() == 0.0f condition appears to apply when cx and cy are both 0, but also when the transform is at 270 deg, cx == cy and they are non-zero. Then cx and cy get ignored. I don't know why it happens just cy gets ignored in the rendered result.

I don't have knowledge of the Batik code base, and I hope someone could provide more definitive statement on it, but seems this test is done just to initialize angleOnly flag used for serialization (getStringValue()), and not as a performance optimization. Then it appears wrong. As far as I see, the e element of a rotate matrix could be zero in more cases that are not-angle-only:

x=0, y=50, a=0.0
|   1.0 |  -0.0 |   0.0 |
|   1.0 |   0.0 |   0.0 |

x=0, y=50, a=180.0  <--
|  -1.0 |  -0.0 |   0.0 |
|  -1.0 |   0.0 | 100.0 |

x=0, y=50, a=360.0
|   1.0 |  -0.0 |   0.0 |
|   1.0 |   0.0 |   0.0 |

x=50, y=0, a=0.0
|   1.0 |  -0.0 |   0.0 |
|   1.0 |   0.0 |   0.0 |

x=50, y=0, a=360.0
|   1.0 |  -0.0 |   0.0 |
|   1.0 |   0.0 |   0.0 |

x=50, y=50, a=0.0
|   1.0 |  -0.0 |   0.0 |
|   1.0 |   0.0 |   0.0 |

x=50, y=50, a=270.0  <--
|   0.0 |   1.0 |   0.0 |
|   0.0 |  -1.0 | 100.0 |

x=50, y=50, a=360.0
|   1.0 |  -0.0 |   0.0 |
|   1.0 |   0.0 |   0.0 |

While debugging I can see the problem codinition kicking in for 0 and 360 deg as well, but the rendered result doesn't exhibit a problem (another unknown to me).

Then I've looked at this condition (in the other branch):

https://github.com/apache/xmlgraphics-batik/blob/3c83de345f21cae4f4a64dfe6244483f87885808/batik-svg-dom/src/main/java/org/apache/batik/dom/svg/AbstractSVGTransformList.java#L290-L293

As far as I could verify when matrix.getA() == 1.0f then matrix.getE() and matrix.getF() are always 0. I guess this is where the rotate(angle) (angleOnly) should apply?


Including my test for verifying the matrices:

TransformTest.java
import java.util.Locale;

import java.awt.geom.AffineTransform;

class TransformTest {

    public static void main(String[] args) {
        for (int x = 0; x <= 50; x += 50) {
            for (int y = 0; y <= 50; y += 50) {
                debugRotateMatrices(x, y);
            }
        }
    }

    static boolean debugCondition(AffineTransform transform) {
        return (transform.getTranslateX() == 0.0);
        //return (transform.getScaleX() == 1.0
        //        && (transform.getTranslateX() != 0.0
        //                || transform.getTranslateY() != 0.0));
    }

    static void debugRotateMatrices(int x, int y) {
        double a = 0.0;
        final double ten = 10.0;
        for (int step = 1; a <= 360.0; a = step++ / ten) {
            AffineTransform transform = AffineTransform
                    .getRotateInstance(Math.toRadians(a), x, y);
            //if (debugCondition(transform)) {
            if (debugCondition(transform) && !(x == 0 && y == 0)) {
                System.out.printf(Locale.ROOT,
                        "x=%d, y=%d, a=%.1f%n", x, y, a);
                printMatrix(transform);
                System.out.println();
            }
        }
    }

    static void printMatrix(AffineTransform transform) {
        System.out.printf(Locale.ROOT,
                          "| %5.1f | %5.1f | %5.1f |%n",
                          transform.getScaleX(),
                          transform.getShearX(),
                          transform.getTranslateX());
        System.out.printf(Locale.ROOT,
                          "| %5.1f | %5.1f | %5.1f |%n",
                          transform.getScaleY(),
                          transform.getShearY(),
                          transform.getTranslateY());
    }

}

stanio avatar Oct 08 '23 05:10 stanio

While debugging I can see the problem codinition kicking in for 0 and 360 deg as well, but the rendered result doesn't exhibit a problem (another unknown to me).

Should be obvious – whatever the rotation center the result is the same at 0 and 360 deg. Not a "mystery".

stanio avatar Oct 11 '23 11:10 stanio

See also css4j/echosvg@615824511ac59e7b49b11aeab27f4a53065e5e45 that suggests a more minimal fix:

--- a/batik-svg-dom/src/main/java/org/apache/batik/dom/svg/AbstractSVGTransformList.java
+++ b/batik-svg-dom/src/main/java/org/apache/batik/dom/svg/AbstractSVGTransformList.java
@@ -284,7 +284,7 @@ public void assign(SVGTransform transform) {
                     setScale(matrix.getA(), matrix.getD());
                     break;
                 case SVGTransform.SVG_TRANSFORM_ROTATE:
-                    if (matrix.getE() == 0.0f) {
+                    if (matrix.getE() == 0.0f && matrix.getF() == 0.0f) {
                         rotate(transform.getAngle());
                     } else {
                         angleOnly = false;

stanio avatar Oct 12 '23 05:10 stanio