xmlgraphics-batik
xmlgraphics-batik copied to clipboard
BATIK-1361: Animated `rotate()` tranform ignores y-origin at exactly 270 degrees
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());
}
}
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".
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;