fabricjs / fabric.js

Javascript Canvas Library, SVG-to-Canvas (& canvas-to-SVG) Parser
http://fabricjs.com
Other
28.73k stars 3.49k forks source link

About Gradients #1601

Closed asturur closed 10 years ago

asturur commented 10 years ago

I have a "talkabout" on gradients and i need @kangax comment. FabricJs is a canvas library that imports even SVG and with SVG export But mainly is a canvas oriented library.

Gradients on Canvas don't have the gradientUnits attribute. Every gradient is relative to the canvas cooridnates itself. So on gradient class we should not import the gradientUnit attribute from SVG,instead we should convert the coordinates of the gradient in the "userSpaceOnUse" coordinates when objectBoundingBox is found ( that is default value ). When exporting we should just specify userSpaceOnUse and export without normalize.

Also the spreadMethod = "pad | reflect | repeat" attribute. Canvas doesn't have it. So when we import a svg that specify spreadMethod we should add colorstops ( according to spread method value reflect or repeat ) till we manage to fill up the region we are interested. But everything should finish in the "fromElement function" we should not have in the class those properties just to reconvert units and export in toSVG method.

I'm gonna pull some example and decide for the right PR.

asturur commented 10 years ago

Example in 4 rows: Row 1 gradient is userSpaceOnUse from 0 to 1000, svg is 1000px width. You see that every object has just a portion of the gradient. Gradient is relative to canvas coordinates.

Row 2 gradient is on objectBoundingBox. coordinates are from 0 to 1000. i would expect to see the red and green part only, but i see flat red. That is because later i understood that in this case x1 and x2 , y1 and y2 are relative to object width and height. So if you write 1 it will be 100% , 2 is 200% and so on.

Row 3 gradient is on objectBoundingBox. coordinates are from 0 to 100% Gradient start and finish in the object.

Row 4 you see a path that is translated, and in that case even if it is "useonspaceuse" the gradient gets translated.

That brings me some concern: path bounding box for example, is it perfect or has still some issue. Also gradients on canvas are all gradients coordinates related, but when we move the object around with fabric we bring the gradient with it around, so when we want to export we have to translate it as it will be originated as userSpaceonUse, but it will be then used like objectBoundingBox. Otherwise we will drag the object and the gradient will be stopped on the background.

Changed image to illustrate better.

image

And still missing the spreadMethod attribute.

asturur commented 10 years ago

Current FabricJs situation ( after some fixes )

image

I changed final gradient color with hot pink, cause white was causing "missing pieces look-like" As you can see, something is not ready yet.

So the problems are: Interpreting gradient x1, x2 ,y1, y2 when not percentage and gradientUnit is on boundingbox, interpreting polygon width, maybe even path width.

Export didn't try yet.

asturur commented 10 years ago

image

I cannot see any information on why a radial gradient in a circle is round, in a ellipse it gets deformed. THe Svg is very simple

<?xml version="1.0" standalone="no" ?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" width="220" height="300" version="1.1">
<defs>
<radialGradient id="grey_blue" cx="50%" cy="50%" r="50%" fx="50%" fy="50%">
  <stop offset="0%" style="stop-color:red;stop-opacity:0.8"/>
  <stop offset="30%" style="stop-color:#0000ff;"/>
  <stop offset="60%" style="stop-color:rgba(0,153,153,0.5);"/>
  <stop offset="100%" style="stop-color:blue;stop-opacity:1"/>
</radialGradient>
</defs>
<ellipse cx="110" cy="150" rx="110" ry="150" style="fill:url(#grey_blue)"/>
</svg>

Moving crt.restore after render fill in _render method of ellipse class... doesn't help.

asturur commented 10 years ago

So with the approach of putting every gradient on 'useSpaceonUse' i can render and export majority of gradients good: image

But this elliptical gradient

image

Can be rendered good in svg just if it setted to "objectboudingbox". But a way to render it good in fabric is still missing, i'm thinking of moving cx,cy fx,fy following the shape of the ellipse if i can manage. The problem is that SVG consider the "target shape" and deform the radial gradient with it.

Anyway still lot of work to do on polygons ( they don't have top and left? , on path for missing width and height ).

kangax commented 10 years ago

@asturur thanks so much for doing this! :)

asturur commented 10 years ago

Basically we have to choose between 2 roads, and we should do that instead of fixing what is wrong with patches. We should choose a road ( word "path" is ambiguos here ) and change gradient class to reflect the decision.

But I have some other checks to do, and i'm going to holydays. If i manage to complete the checks before leaving i will explain here and ask for your guidelines.

asturur commented 10 years ago

Ok i solved the ellipse problem. When converting the values of a gradient, if a found an ellipse with rx != ry , i apply a gradientTransform to the gradient to scale it properly and i apply the inverse of scale factor to the y properties ( y1 and y2 ) in this way the gradient is transformed from round to elliptical and is exported then to svg in same way.

asturur commented 10 years ago

i don t know what is happening. i was sure i wrote good code to complete gradient. now i notice that if i render gradients from svg everything is fine. i fi i apply them to obects on canvas copying code from some of your tutorial i get gradients misplaced by half dimension of the object. i m mainly trying to eliminate those differences in fabricjs but it looks hard, very hard.

any idea?

the only working good is rect class, that has a translating of half dimension during render.

asturur commented 10 years ago

ok i checked past version of fabric, 1.4.4 and 1.4.0 and gradient situation was same. if you create an object and assign gradient to it, the result is not as expected and how showed in tutorials. gradients start from center instead of top of the object ( vertical gradient ).

so i think it got broken long time ago and no one noticed?

again i see is not yet possible to handle gradients same way for objects and objects in path-groups.

asturur commented 10 years ago

@kangax: Finally i finished, or at least i got to a point where a PR is ok. I checked as much as different case as possible and obtained that:

Gradients can be created with the tutorial example, or can be imported from SVG grouped or not grouped. They can be exported to SVG without problems. Before no one of this three options was giving the same result. Some code is weird ( like the check for ellipse gradientTransform ) and maybe it will be better with additional paramenters.

Some changes to polygon ( and polylines ) and a very small change to rect are necessary.

SpreadMethod property from SVG is not supported yet.

johnrees commented 9 years ago

Did the misplacement problem get fixed? I am still experiencing the issue of the gradient starting at 50% of the object's width, you can see it happening here http://jsfiddle.net/nkrgf4ba/

johnrees commented 9 years ago

Ah, I was using an old version from cdnjs. https://rawgit.com/kangax/fabric.js/master/dist/fabric.min.js fixed it. Thanks