Closed Bug 1064151 Opened 10 years ago Closed 10 years ago

Nested SVG: getTransformToElement returns incorrect transformation matrix

Categories

(Core :: SVG, defect)

32 Branch
x86_64
Windows 7
defect
Not set
normal

Tracking

()

VERIFIED INVALID

People

(Reporter: simon.eu, Unassigned)

Details

Attachments

(8 files, 4 obsolete files)

User Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:27.0) Gecko/20100101 Firefox/27.0
Build ID: 20140212131424

Steps to reproduce:

Firefox' implementation of SVGLocatable.getTransformToElement() is incorrect when transforming to something else than the root SVG element.
Attached image SVG-Firefox26.png
Rendering in Firefox with incorrect circle location
Attached file matrixTranformTo.html
Test case
Please refer to the test case for more details.
Attached file matrixTransformToTest.html (obsolete) —
Short JavaScript test to test for the bug
Source code comment added, test fixed
Attachment #8485608 - Attachment is obsolete: true
I'm afraid you've been led astray by Chrome.

The "sub" element is at 100,100 in the co-ordinate system established by its parent. That needs to be added in if you want to transform to a child. It looks like you want a transform from a child of "sub".
Status: UNCONFIRMED → RESOLVED
Closed: 10 years ago
Resolution: --- → INVALID
Status: RESOLVED → VERIFIED
I think I disagree. Maybe I do not understand the specs correctly. But from my understanding, the second SVG element has its own coordinate system A, and the group element defines another coordinate system B. Now if I have coordinates in B and want to convert them to the coordinate system of A, I apply a transformation matrix from B to A.

I agree that sub is at 100,100 relative to the root SVG. But note that I did not add the circles to the root element, but to the sub and its group child. The way it is at the moment in Firefox is that there is a sub SVG in a SVG. The sub SVG contains circles, which change their position *in sub's coordinate system* depending on where the sub SVG is located in the root SVG. In other words, the sub SVG looks different depending on where you put it. 

This is not related to Chrome. I did the test first and then wondered why the transformation did not work as expected. It was rather by accident that I took a look at other browsers.
Attached file matrixTransformToBugDemo.html (obsolete) —
Bug demo
I have added another test case. The JavaScript code adds four times *exactly the same* SVG element to the root SVG, but in Firefox they all look different.
One issue here is that the SVG spec does not adequately define what an element's "user space" is, or in the case of getTransformToElement() specifically, what "user coordinate system on the current element" means. Take the <svg> element for example - there are four coordinate spaces of relevance for the following <svg>:

  <svg x="10" y="10" width="10" height="10"
       transform="translate(10,10)"
       viewBox="-10 -10 10 10">

0. we start with the coordinate system that the <svg>'s parent
   establishes for its children

1. the {10,10} translation due to the 'transform' attribute
   is then applied to establishes a second coordinate system

2. next the {10,10} translation due to the 'x' and 'y'
   attributes is applied to establish a third coordinate system

3. final the {10,10} translation due to the 'viewBox' attribute
   is applied to create a fourth coordinate system (for the
   <svg>'s children)

(I numbered from 0 because 1-3 are coordinate spaces created by properties of the element, whereas 0 is preexisting, and unaffected by anything the element does. In other words I'm trying to distinguish the fact that there are four coordinate spaces of interested for an element, but only three sources of coordinate space changes brought about by that element.)

In computer graphics as a field, when you talk about the "user space"/"user coordinate system" for an object you are talking about the coordinate system that is "used" when the basic properties of the object that give the object its shape are used to place it into the scene. So for a <circle> it's the coordinate space that the "cx", "cy" and "r" attributes are relative to. Or for an <svg> it's the coordinate space that the "x", "y", "width" and "height" attributes are relative to. For that reason I consider an element's "user space" to be the coordinate space established _by_ #1 above.

The spec. seems to back that up in the text describing getTransformToElement(), where it says "user coordinate system on the current element (after application of the ‘transform’ property)". So it's saying the method returns the transform from one element's user space to another element's user space, and it clarifies in the parenthetical that user space is indeed "after application of the ‘transform’ property" - so the space established by #1 above.

So, in the case of your example:

  <svg id="sub" x="100" y="100">
    <g id="subgroup" transform="translate(50 50) scale(2 2)">

the transform from "subgroup" to "sub" includes "sub"s #2 and #3 transforms and "subgroup"s #1 transforms ("sub"s #3 establishing "subgroup"s #0). More specifically, the transforms due to "sub"s 'x' and 'y' attributes ({100,100}) and 'viewBox' attribute (none), and "subgroup"s 'transform' attribute (translate(50 50) scale(2 2)). So the 'x' and 'y' attributes on the <svg> should be included in the result.

Now, all this said, I understand that getting a transform to/from an <svg> element's user space is not very helpful. Since the <svg>'s "user space" is partway through the coordinate system changes that the <svg> element's attributes can make, this behavior is neither useful for positioning something as a child of the <svg>, nor as a child of the <svg>'s parent. So the question is why does the spec. say what it says?

I would speculate that the reason is that the authors had in mind shapes like <circle> and the grouping element <g> and, without clarity on #0-3 above, it didn't occur to them that having getTransformToElement() get transforms from user space to user space gives poor results when one of the to/from elements is an <svg>. With only basic shapes and <g> in mind what they wanted was more likely the transform from "the coordinate system established by an element for its children" from one element to another. Note that in the case of basic shapes and <g> that happens to be exactly the same as "user space" to "user space" because #2 and #3 transforms don't exist for those types of elements.

Perhaps the spec. text can be changed to use the phrase "the coordinate system established by an element for its children" rather than talking about user space, and then we can change to have the same behavior as Chrome. I'd need to think through the consequences of that a bit more though.
I agree with you that the specification is not very clear in this point. Do you have experience in changing it to your wording?

Regarding the example, I'm not sure if I understand you correctly. Let me take a different approach:
a) "sub"s user space origin is at 100,100 relative to the root SVG.
b) "subgroup"s user space origin is at 150,150, again relative to the root SVG.

Ignoring the scaling for now, the transformation matrix from b) to a) should then be a translation matrix doing exactly translate(50 50).


From the recommendation:

current transformation matrix (CTM)
    […] The current transformation matrix (CTM) defines the mapping from the user coordinate system into the viewport coordinate system.

SVGMatrix getScreenCTM()
    Returns the transformation matrix from current user units (i.e., after application of the ‘transform’ attribute, if any) to the parent user agent's notice of a "pixel".

For a circle, user units should again be the cx and cy coordinates. In the updated bug demo (next comment) I do exactly this transformation from screen coordinates to user units. The second demo (CTM) does the reverse, and I receive two different screen coordinates for two points with exactly the same screen coordinates.
Attachment #8485653 - Attachment is obsolete: true
Attached file matrixTransformToCTM.html (obsolete) —
Result in Firefox:

    Point 1 is at (78,234)
    Point 2 is at (28,184)
(In reply to Simon A. Eugster from comment #11)
> I agree with you that the specification is not very clear in this point. Do
> you have experience in changing it to your wording?
> 
> Regarding the example, I'm not sure if I understand you correctly. Let me
> take a different approach:
> a) "sub"s user space origin is at 100,100 relative to the root SVG.
> b) "subgroup"s user space origin is at 150,150, again relative to the root
> SVG.
> 
> Ignoring the scaling for now, the transformation matrix from b) to a) should
> then be a translation matrix doing exactly translate(50 50).
> 

No, 150, 150 as sub's user space is the root user space. sub is at 100, 100 within that co-ordinate system though.
Sorry, I did not write what I meant. It should read:
a) The origin of the user space created by "sub"
b) […] by "subgroup"
Attachment #8487357 - Attachment mime type: text/plain → image/svg+xml
Attached image better illustration
Attachment #8487357 - Attachment is obsolete: true
Attachment #8487528 - Attachment mime type: text/plain → image/svg+xml
So in my testcase (which runs on Chrome and IE should you wish to try).

a) Do you think that getScreenCTM and getTransformToElement should return the same values for r and inner? If not why not since they have the same attributes.

r2 is then transformed by the x of inner. Is that clearer?
I believe I have found an answer in the SVG specs.

  7.4 Coordinate system transformations
  A new user space (i.e., a new current coordinate system) can be established 
  by specifying transformations in the form of a ‘transform’ attribute on a 
  container element or graphics element or a ‘viewBox’ attribute on an ‘svg’, 
  ‘symbol’, ‘marker’, ‘pattern’ and the ‘view’ element. The ‘transform’ and 
  ‘viewBox’ attributes transform user space coordinates and lengths on sibling 
  attributes on the given element […] and all of its descendants.

user space == user coordinate system,
'transform' creates a new user coordinate system on container/graphics elements

  7.6 The ‘transform’ attribute
  The ‘transform’ attribute is applied to an element before processing any other 
  coordinate or length values supplied for that element. In the element
    <rect x="10" y="10" width="20" height="20" transform="scale(2)"/>
  the x, y, width and height values are processed after the current coordinate 
  system has been scaled uniformly by a factor of 2 by the ‘transform’ attribute. 
  Attributes x, y, width and height (and any other attributes or properties) 
  are treated as values in the new user coordinate system, not the previous 
  user coordinate system.
  
'x', 'y', 'width', and 'height' are applied after application of 'transform'

  7.9 Establishing a new viewport
  At any point in an SVG drawing, you can establish a new viewport into which 
  all contained graphics is drawn by including an ‘svg’ element inside SVG 
  content. By establishing a new viewport, you also implicitly establish a 
  new viewport coordinate system, a new user coordinate system, and, 
  potentially, a new clipping path[…]
  
  The bounds of the new viewport are defined by the ‘x’, ‘y’, ‘width’ and 
  ‘height’ attributes on the element establishing the new viewport, such as an
  ‘svg’ element. Both the new viewport coordinate system and the new user 
  coordinate system have their origins at (‘x’, ‘y’), where ‘x’ and ‘y’ 
  represent the value of the corresponding attributes on the element 
  establishing the viewport.
  
An 'svg' element nested in another 'svg' element creates a new user coordinate
system which has its origins at ('x', 'y').
This clearly specifies that for your example
  <svg>
    <svg id="inner" x="1" y="0">
    </svg>
	<rect id="r" x="1" y="0"/>
  </svg>
"inner" has its origin at (1,0) and not at (0,0).
  
  4.5.23 Interface SVGLocatable
  SVGMatrix getTransformToElement(in SVGElement element)
    Returns the transformation matrix from the user coordinate system on the
	current element (after application of the ‘transform’ attribute, if any) 
	to the user coordinate system on parameter element (after application of 
	its ‘transform’ attribute, if any). 
	
In the above example, the rect element does not define a new user coordinate
system because there is no transform on it, so the parent’s is used.
The SVG element defines the user coordinate system with its origin at (1,0).

Conclusion is that Firefox’ implementation is not correct.
There's no transform on inner either.
Yes. First the transform is applied, which is nothing in this example, then the origin is moved to the coordinates specified by the 'x' and 'y' attributes, according to section 7.9.
Attributes x, y, width and height (and any other attributes or properties) 
  are treated as values in the new user coordinate system, not the previous 
  user coordinate system.

Which we interpret as x, y are not establishing a new coordinate system they are values in the transform-established coordinate system.

7.9 applies to children so they are all in that co-ordinate system which is that of the children, not of the element itself.

You'd be much better off taking all of this discussion to w3c (http://lists.w3.org/Archives/Public/www-svg/)
(In reply to Simon A. Eugster from comment #21)
> Yes. First the transform is applied, which is nothing in this example, then
> the origin is moved to the coordinates specified by the 'x' and 'y'
> attributes, according to section 7.9.

The origin for children, not for the element itself.
Updated with matrix transform from circle to root SVG
Attachment #8487034 - Attachment is obsolete: true
For those who want to follow, the thread on the SVG mailing list is here: 
http://lists.w3.org/Archives/Public/www-svg/2014Sep/0023.html

In the meantime, could you please show me how I can get the third circle centered in Firefox using getTransformToElement() in Attachment #8489324 [details] (https://bugzilla.mozilla.org/attachment.cgi?id=8489324)? 

I cannot see how to do it. Thanks for your help!
Your problem is that you're trying to do things back to front.

    // Draw a circle in the svg "root" element with the same center as "target"
    var circRoot = circle(13);
    root.appendChild(circRoot);
    var matrixRoot = target.getTransformToElement(circRoot);
    var circRootTransform = pTarget.matrixTransform(matrixRoot);
    circRoot.setAttribute("cx", circRootTransform.x);
    circRoot.setAttribute("cy", circRootTransform.y);

    // Draw a circle in the svg "inner" element with the same center as "target"
    // in all browsers
    var circInner = circle(6);
    inner.appendChild(circInner);
    var matrixInner = target.getTransformToElement(circInner);
    var circInnerTransform = pTarget.matrixTransform(matrixInner);
    circInner.setAttribute("cx", circInnerTransform.x);
    circInner.setAttribute("cy", circInnerTransform.y);
Oh and remove the first argument out from the circle function and everything that uses that argument.
Thanks a lot, this works indeed!
Short update on this feature: The Chrome team decided to remove SVG.getTransformToElement() due to the unclear specification, and it is gone in version 48.

https://www.chromestatus.com/feature/5736166087196672
https://lists.w3.org/Archives/Public/www-svg/2015Aug/att-0009/SVGWG-F2F-minutes-20150824.html#item02

I would suggest doing the same in Firefox due to the same reasons.
Just a short remark – getTransformToElement has now been removed from SVG2:
https://www.w3.org/TR/SVG2/changes.html#types
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: