Summary:  Implement webkitlineargradient and webkitradialgradient  

Product:  WebKit  Reporter:  Simon Fraser (smfr) <simon.fraser>  
Component:  CSS  Assignee:  Simon Fraser (smfr) <simon.fraser>  
Status:  RESOLVED FIXED  
Severity:  Normal  CC:  abarth, arv, bugs.webkit.org, buildbot, cyberskull, daniel, dglazkov, eae, eric, Justin, mathias, mitz, ml, phiw2, pornel, rik, robin, simon.fraser, tqsub, webkitews, webkit.review.bot, webmaster  
Priority:  P2  
Version:  528+ (Nightly build)  
Hardware:  All  
OS:  OS X 10.5  
Attachments: 

Description
Simon Fraser (smfr)
20090810 10:25:23 PDT
Created attachment 34670 [details]
Patch, testcases, changelog
This patch add support for parsing the new gradient types. It does not handle backgroundrepeat, which I'll do separately.
Created attachment 34748 [details]
Work in progress for repeating gradients
Comment on attachment 34670 [details]
Patch, testcases, changelog
Remove review flag awaiting csswg decisions on gradients.
*** Bug 32302 has been marked as a duplicate of this bug. *** Patches attached seem to implement Mozilla's old syntax, which has been dropped, and replaced with a new, IMHO much more userfriendly syntax: https://bugzilla.mozilla.org/show_bug.cgi?id=513395 At the moment Mozilla's syntax is aligned with CSS3 draft: http://dev.w3.org/csswg/css3images/#gradients lineargradient([<bgposition>  <angle>,]? <colorstop>, <colorstop>[, <colorstop>]*); colorstop = <color> [ <percentage>  <length> ]? I hope you're aware of this development. I'd love WebKit to adopt the new syntax. We're very aware of the Mozilla implementation, but have raised issues in wwwstyle about the proposed syntax. There is no final proposal yet. Just some feedback… Personally, I don't like the proposed syntax of using angles in the current W3C draft (also implemented in FireFox 3.6). Here it is again, quoted from previous post for clarity: lineargradient([<bgposition>  <angle>,]? <colorstop>, <colorstop>[, <colorstop>]*); colorstop = <color> [ <percentage>  <length> ]? To me, that is not "user friendly" because it's way to mathematical, and that in itself suggests too much structure for a medium (web pages) inherently flexible. I tend to be perceptual, and as such I quite often set dimensions with em and % sizes for automatic proportioning to fit whatever shape or size the container (window, or body or div etc.) happens to be. This makes development a lot easier because I don't have to constantly adjust and tweak by pixel or angle, and everything stretches and squashes when simply resizing a window or changing the dimension of a container (perhaps because of more content). Maintenance becomes even more cumbersome when using radial gradients. (BTW, radial gradients are my main focus with this, but I think this will equally apply to linear, too, since they can be rotated.) When it comes to gradients, I quite often want the gradient to fit the shape. In fact, as I ponder this realization, I'm having a hard time thinking of any other approach I use when applying a gradient. It's very important that certain colors make it to certain areas of the shape. But what if I widen the shape? Then that would mean the gradient would need to spread out in such a way as to… well, stretch with the container. Similarly if I shorten the width, the gradient would need to squish and reduce to give a similar appearance for the shape. This is part of what I love about computers: fluidity, softness, infinite reusability of resources (code), etc. Yet, such a gradient can not be easily reused if it can't be proportionally defined. That is, it won't be selfadapting. For a gradient to be proportionally defined, every dimension and coordinate needs to be able to accept proportional measurements, too. With that said, if the bgposition, length of colorstop, and the angles all could accept a proportional dimension (e.g. percentage % or maybe em) in the proposed syntax then it would be possible to define completely proportional and selfadapting gradients. (Of course, this in addition to exact dimensions.) OTOH, a proportional angle seems too analytical, something I don't really want to think about when staring at and refining a gradient. The computer can think about how to make it happen, I just want to lay it out, and stretch and squash and shape it. IOW, being able to have everything within a container automatically adjust itself when I simply change the height/width is supreme. Essentially, I look for where I want something to begin or end, or where I want something to appear. I see lines and curves, endpoints and edges. I can identify where something starts and ends, I don't need a ruler for an exact measurement, I can just look at it proportionally, such as where x,y is proportionally located to the widthheight. But to what would an angle be proportional? Mixing polar coordinates with perpendicular coordinates is just too confusing, for me. One or the other, not both at the same time. Designing proportionally makes programming easier because I only have to define the beginning size and the ending size when intentionally changing the shape of a container. Without that simplicity, coding becomes almost imponderable having to set the size for every object (and their objects, and their objects…) when changing the outer dimension. I want to design the layout and control the macroobjects with CSS and JavaScript, not code atoms and molecules forever. With this perspective, I'd say the proposed W3C syntax (above) is not user friendly but instead programmer friendly for the people who code the rendering engine, i.e. deep programming, console cowboys. Yet, it's not programmer friendly for those who deal with the superficial, that is, actually design and implement the user interfaces for the web, craft the GUI with CSS and JavaScript. I think this is why the current syntax in the Webkit has started to grow on me (i.e. I like it), for example: gradient: radial, 64 64, 100, 100 44, 40, from(#fcfcfc), to(#cf0c13), colorstop(.6,#e88787) or in other words: gradient: type, x y, radius, x y, radius, colorstop* It's very similar to defining a linear gradient in that two edges are declared and then the colors blend from one to the other. I didn't see it this way at first, and it seemed a little abstract, but I think it was because the documentation wasn't clear in how the points were to be defined. Once I found a source that described that (a coordinate is space separated, not parenthesized and comma delimited), everything fell into place. The main difference between a linear gradient and a radial gradient, from this perspective, is that the edges for a radial gradient are the circumferences of circles. Kind of like taking one of the beginning edges of a square linear gradient and then shrinking it to a singular point (say, towards the center point of the edge) which results in a triangle. Then, taking the other edge (opposite this point) and lengthening it circularly around the that new point. Though, it's much simpler to just start with a line representing the gradient colors, fixate one end as the origin and then spin the line around that endpoint. Either way, a radial gradient is formed, and it's essentially based on two concentric circles being the boundaries (originendpoint/circumferenceendpoint). With the Webkit's current syntax (as of r54749), it allows a different origin for each circle and so they don't have to be concentric. As the documentation suggests, this can help in giving a spherical appearance, imitating the shapes of the light reflected format the surface of a sphere. In particular, animating the origin of the inner circle would give the suggestive appearance of the light source moving across it. Anyways, I see a lot of flexibility with this approach of defining the origin and radius of circles as if they were lines. In fact, I'd like to see that expanded where any number of circles could be defined. However, that might also mean specifying the "distance" between each circle, that is, how far along the total gradient a color should be located (e.g. from 0.0 to 1.0). Honestly, I'm not really sure what kind of effect this will produce. Nonetheless, I really prefer the Webkit's current approach of defining circles (origin, radius), especially if the radiuses can be proportional lengths. I believe it's much more feasible to define a proportional radius than a proportional angle. Why? Because the containers to which I apply gradients have a shape based on a set of linear dimensions whose values could change. That is, the ratio of widthheight are inherently dynamic simply because people can change the size of the windows, and the contents of a page can change from day to day or from topic to topic. A radius length is similar to height or width since it's a length or a breadth of a linear distance. An angle is a measurement around a singular point, but web pages are not circular so angular measurement doesn't have any relation to anchor it upon. For example, even if you define a point, angle, and length, from where does the angle begin and then end? Mathematically, a horizontal line has been declared zero starting from the right end and rotating counterclockwise. So, to have true flexibility, it seems like one would have to define a starting angle and an ending angle in order to be able to direct the opening towards any direction. Now consider defining a linear gradient with an angle so it isn't simply horizontal or vertical. This means spinning it around some point at a certain angle. Great, but what happens with the container changes shape in one dimension and not the other? IOW, when the ratio of widthheight changes, how will this be reflected in the angle of the gradient? In essence, without adjustment the gradient will not maintain a similar appearance. It might as well be merely a projection on a wall, i.e. independent and not merged. If the syntax is going to be easy to use, personally, it's easier for me to define both endpoints of a slanted line than to guess its angle. This is especially so when the widthheight ratio changes for a container since a corresponding adjustment of the gradient would mean the angle changes. Again, how does one proportionally define an angle? I say it can be specified indirectly by merely defining the endpoints of the line proportionally. For example, two coordinates like (0, 0) and (100%, 100%) for a linear gradient would obviously go from one corner to the diagonally opposite corner of a rectangle. If it were a very special rectangle by the name of "square" then the line would be 45 degrees. For every other rectangle, it would be a different angle. Considering that content can affect the size of the container (more added, font size increased/decreased on demand, etc.), that means that angle can change at anytime. The gradient won't be any better than a static image if it doesn't adapt like this. Defining the coordinates for the endpoints of the line would be a very easy way of dynamically defining the angle. Similarly with a radial gradient, the two origins could be thought of as the endpoints of the imaginary line between them, as suggested on a Mozilla documentation page. However, the approach of defining an angle and length for that line runs into the same problem of not being easily defined as proportional. Once again, simply defining the coordinates of origins implicitly defines the angle and length of that imaginary line. With proportional coordinates (and proportional radii), the radial gradient automatically become selfadapting when the container is resized. In this way, if I want a radial gradient, all I have to think about is "how big do I want the circles" and "where are their origins." That leads me to think of: radialgradient: [origin], [radius], [origin], [radius], [colorstop], [colorstop]*…; which just happens to be what is defined in Webkit for the radial type of gradient. I can't help but wonder if they thought along the same lines. Admittedly, a circle is a special case of an ellipse, so having elliptical gradients might require a little more syntax. Mathematically, an ellipse can be said to have two origins and two diameters. One diameter would conjoin the origins and further span to the circumference, while the other would be perpendicular and run through the center of that one to the circumference: the longest and shortest breadths. Or, it could be said to have one radius (at each origin) and a distance between the origins, assuming it's not an irregular ellipse (i.e. an egg). Personally, just looking at it perceptually, I'd be inclined to focus on the length and width, i.e. the narrowest and widest points of the ellipse. The only other characteristic I'd be concerned about is the curvature at the ends of the widest breadth. IOW, I'd describe it simply as length and width perpendicularly crossing at the very center, and a single radius for the curvature endpoints (sort of like borderradius for rounding a rectangle's corner). Three dimensions, plus origin. That's probably a lot of deduction by the rendering engine to translate, but something as dynamically flexible as a web page doesn't really lend itself to compasses and protractors. radialgradient: [origin] [width] [length] [radius] … er…hmm… Yes, well…hmm. Something else to also consider, an irregular ellipse with endpoints that have different curvatures (e.g. an egg) can be defined simply by allowing a second radius. So, the perpendicular length and width of the irregular ellipse is still easily perceived, and the curvature at either end (lengthwise of ellipse) would each have its own radius, i.e. an egg is pointier at one end than the other. What this really gets me thinking is that maybe there needs to be an ellipse value for defining ellipses, including the special cases for circles and perhaps for simple irregular ellipses, too. That would really help with the syntax: radialgradient: ellipse(…) ellipse(…) [colorstop] [colorstop]*… (with * meaning "as many as desired") In a way, this resembles Webkit's currently implemented syntax but with the circle stuff encapsulated just like colorstop( ) or url( ) and rgba( ) values. This makes radialgradient syntax much more approachable since it's clear what is being asked for, and at the same time much more readable. (I don't know if commas would be preferred in that, but doesn't seem necessary.) Hmm, so how about: ellipse( [origin], [ diameter(s) [, curvature(s)] ] ) So a circle would simply be: ellipse( x y, diameter ) a regular ellipse would be: ellipse( x y, width height, radius ) and an irregular ellipse (e.g. eggshaped): ellipse( x y, width height, radius radius ) With optional components thus [?]: ellipse( origin, diameterhorizontal [diametervertical, radius [radius]? ]? ) So, the coordinate itself is the origin of a circle or the center of an ellipse. Keywords would be nice so someone could just specify "center" and it'd assume center of container. When two diameters are specified, it's obviously an ellipse, with the first as the width and the second as the height to correspond with the concept of horizontal/vertical coordinates like x/y. The curvature "radius" is only needed for ellipses so is only defined when a second diameter is defined. Note that this radius lies along the longest diameter. Pretty simply, (longest [diameter])  2*[radius] gives the distance between centers which should be centered around the given [origin]. The only tricky part is what to do with the second radius when defining an irregular ellipse like an egg. I mean, it's the same idea, so no problem with the formula, just add the radii together instead of multiplying by two (because they aren't expected to be the same value). The question would be consistency: which end of the longest diameter gets which radius? I realize now that I've written this with the idea that the first radius would go with the first diameter, and the second radius with the second diameter. That makes no sense whatsoever because both radii are on the same diameter, at either end. Argh, what was I thinking?! 8) How about assuming top left quadrant? That is, if width diameter is longest then apply the radii from left to right along the width. If height diameter is longest then apply the radii from top to bottom along the height. IOW, when width is longest (first diameter) then it's: endpoint > first radius > rest of width < second radius < endpoint. Endpoints are the outer edges and the "rest of width" is the distance between the derived centers for the ellipse. And that brings up another question for irregular ellipses: is the given [origin] the center of the longest diameter or the halfway between the ellipse's center? That is the same point for a regular ellipse because the radii are the same at either end, but it's not the same point when the radii are different. This makes a difference only in appearance, that is, how to align the irregular ellipse. Well, if irregular ellipses are left out, I won't be crying. They'd be interesting though. So, here's what ellipse( ) would look like with only circles and regular ellipses in mind: ellipse( [origin], [diameter [diameterheight, radius]? ) or as an example for an ellipse: ellipse( x y, width height, radius ) Really simple now. Origin is still center for both a circle or ellipse. If one diameter, then it's a circle. If a second diameter, then it's the height of the ellipse with the first diameter the width. Same as before. Also, the curvature radius would have to be defined if a second diameter is defined. Everything needed for calculations could be derived from that info. Also, everything should be able to accept percentages and pixel. (Maybe em, too.) The percentages are especially important for proportionally defined gradients. I'm not really sure what the diameter would be proportional to when it's just a circle. Perhaps the shortest of widthheight for its container, that is, the implicit "square" within the rectangle? An ellipse can simply have it's diameters proportioned to the corresponding widthheight of it's container. Hmm, even without the irregular ellipse option, there is still one other detail because of ellipses. The rotation of the ellipse itself. This would suggest adding one more component for the angle. However, thinking back about how I was against having the user/designer trying to figure out an angle for a linear gradient or for the imaginary line between origins of two circles, I'd have to seriously consider whether this would be appropriate for an ellipse. It seems like it would be ideal to have an angle, at first, because an ellipse has a center, unlike a line which gets rotated around a center at some distance from it. Consequently, angled linear gradients always seem reversed because positive rotation is counterclockwise (in Firefox, at least, and math). Seems counterintuitive. As for a regular ellipse, that really only has a pertinent rotation of 180 degrees because of it's symmetry. Circles don't reveal themselves as rotated in a gradient, which makes sense. Hmm, it'd be really simple to just tack on another little argument for angle at the very end of ellipse ( ). However, that would bring up the issue of proportional gradients once again. If the size of a container is changed, then the gradient background is stuck being the same as before the change. That can be fine, it may even be desired. Yet, if someone wants the gradient to be truly proportional and adjust with the content, then that means the angle of the ellipse is in reality only general and not exact. Once again, it seems like defining proportional coordinates for either end of the ellipse (along the longest diameter) could be the solution. But how? Wouldn't they need to be the centers of the ellipse? Aren't those centers hard to derive? Is there perhaps a different way of looking at an ellipse, of describing it? How about two sources of light perpendicularly shining at the same surface, like two flashlights close to a table or wall? Pull one further back and its circle gets bigger, simulating an irregular ellipse. Admittedly, there's some fuzzy visuals happening to suggest the longest curves (opposite the shortest diameter perpendicular to the alignment of the light circles) filling out. However, for the purposes of obtaining relevant information for an ellipse, two circles could easily divulge such information, whether a regular or irregular ellipse. So, with that in mind, what about this: ellipse( [origin, radius] [ , origin, radius]? ) For the most basic ellipse, the circle, just specify the origin and radius. (Yes, I switched from diameter.) For either a regular or irregular ellipse, just specify that a second time. That's all. Hmm, it could be sensible to allow the radius of the second circle to be optional and assumed from the first since the radii are the same for regular ellipses. Spelled out here's a circle: ellipse( x y, radius ) either a regular ellipse or an egg is pretty simple: ellipse( x y, radius, x y, radius ) the optional regular ellipse format: ellipse( x y, radius, x y ) So, different origins implicitly provide the angle of the imaginary line between the two circles, if needed by the rendering engine (not sure why). Yet, simply having the centers of an ellipse already available ought to be plenty for the equation of ellipse. And here, we've sneakily gotten the user to provide that by having them make the simple observation of two overlapping or nearby circles. Does it get simpler than that? Hmm, I do wonder… Anyways, just as a reminder, here's the radial gradient with the ellipse( ) value from way above: radialgradient: ellipse( ) ellipse( ) colorstop( )* Also keep in mind the possibility of more than two ellipses. I honestly don't know what that would be like for gradients, but it might be interesting to find out. With this new simplified syntax (so I think of it) it makes defining multiple ellipses (circles, ovals, eggs, etc.) a simple possibility. Remember, there's no need to specify the distance between ellipses because they have their coordinates builtin as their origins, so no more values would be needed. It'd just be a matter of stretching the gradient along the extra ellipses in the middle. The colorstops already have a component for stating where along the gradient to shift colors. Though, it might be desirable to be able to associate a specific color with a specific ellipse. Again, that's probably only something to consider _if_ more than two ellipses are going to be interpreted. Hmm, maybe something comma delimited to associate an ellipse with a color like this: radialgradient: ellipse( ) [color]?, ellipse( ) [color]? [, ellipse( ) [color]? ]* [, colorstop( ) ]* There'd have to be at least one color defined either as a color or colorstop, and at least two ellipses. Any ellipse can optionally have a simple color associated with it, and colorstops can be defined, too. For the case of more than two ellipses, I'd think it'd be nice to allow colorstops between the defined ellipses as meaning to stop between those ellipses, i.e. those two ellipses on either side in the syntax would be 0.0 and 1.0 and the colorstop would be inbetween. Hmm, you know, considering the ellipses would be defined as selfcontained values, it may seem more readable anyways to have colorstops( ) between ellipses( ) (comma delimited to still allow associating a color with an ellipse) and so an ellipse would be at either end of the statement with colorstops in the middle (kind of symbolic). I'm not sure how to write that syntactically, so maybe that's a sign it's too complicated to get across to users. It seems easy enough to express in words. Well, this is long enough. But I hope this is useful. I've left it as is since it has a lot of my backandforth thinking which might help show what I've considered and why I'm leaning one way more than the other. I'm mostly trying to express what would seem easy to use going from visualizing a design to describing it in CSS, not from what would be easy to program if only users knew the correct mathematics (and had protractors). So, I regret this may have sounded intimidating since I don't know the code underneath and what it might take. Yet, I think the last bit about the radialgradient syntax (for just two ellipses) might be quite easy to program since it gets the basic coordinates needed for the ellipse formulas (maybe, haven't seen the code, only math books). Should be easy to explain it in documentation for users by suggesting an ellipse can be visualized as two circles. And it'd have the advantage of handling not just circles, but also ellipses and eggs! :) And at any rotation without having to resort to polar coordinates! Yes? Maybe? :) Hmm, I hope this is the right place to post this… seems relevant, sorry if it wasn't. :( I see your point about proportional gradients. This could possibly be achieved with backgroundsize: 100% 100% (makes sense when gradient is assumed to be a special image), but it's not very intuitive. So I think it would be useful if linear gradient accepted two points (with coordinates given as <length> or keyword) as an alternative to position/angle. I also see use cases for angle  e.g. sometimes I'd like 45degree gradient regardless of container's aspect ratio, or to have multiple boxes of different sizes with matching gradient angles. There's room for both. My main dislike about WebKit's syntax are from(), to() and colorstop() functions. I don't like colors given in order different than they appear visually. This is just confusing. Having middle color appear after last color is illogical to me. I know I could specify them in any order I'd like, but that's not a feature I want, and it comes at cost of syntax verbosity. Most of all I dislike being forced to calculate all distances. If I want 7 evenlyspaced colors, I have to use a calculator! And when I add/remove one color, I have to recalculate all stops again. Implied order or color stops makes specialcase from(), to() pseudofunctions entirely unnecessary and enables interpolation of color stops. To me these two features are most compelling. With interpolation I found that often I can just insert another color to the gradient or specify position of just one color stop to achieve desired effect (the rest is automatically shifted). This is much easier to use than editing series of fractions in multiple colorstop() functions. I find two centers and radii for radial gradient a bit confusing. My mental model of radial (circular) gradient is the same as in Photoshop  a line from center of the gradient, i.e. two points or (point, angle, radius). I'm not sure how that should work with elliptical gradients. Usecase with shiny sphere is nice, but I think the syntax shouldn't require thinking about it in all cases. I prefer syntax that is very simple in basic cases and can grow to more complex cases, instead of requiring all parameters in all cases. I see you're suggesting separate gradient property. I've assumed that gradient as background image was done deal, but if that can be changed, it'd be great. I think of gradient as special case of color fill, rather than an image. I also wouldn't mind if that was layer above solid color fill and below backgroundimage. lineargradient: [<point>, <point>  <bgposition> [, <angle>]? ,]? <colorstop>[, <colorstop>]*; point = [<length>  left  right  center], [<length>  top  bottom  center]]; (I need to think about radial gradients more) This bug is not the right place to debate the proposed CSS gradients syntax. You should participate in the wwwstyle mailing list if you wish to provide feedback. Created attachment 77756 [details]
Implement nonrepeating linear CSS3 gradients.
Attachment 77756 [details] did not build on chromium: Build output: http://queues.webkit.org/results/7224350 Attachment 77756 [details] did not build on qt: Build output: http://queues.webkit.org/results/7338218 Created attachment 77758 [details]
Implement nonrepeating linear CSS3 gradients.
Comment on attachment 77758 [details]
Implement nonrepeating linear CSS3 gradients.
Fix warnings.
Attachment 77758 [details] did not build on chromium: Build output: http://queues.webkit.org/results/7288302 Attachment 77758 [details] did not build on qt: Build output: http://queues.webkit.org/results/7255296 Created attachment 77759 [details]
Implement nonrepeating linear CSS3 gradients
Comment on attachment 77759 [details]
Implement nonrepeating linear CSS3 gradients
Fix warning.
Attachment 77758 [details] did not build on win: Build output: http://queues.webkit.org/results/7197297 Attachment 77756 [details] did not build on mac: Build output: http://queues.webkit.org/results/7210337 Attachment 77756 [details] did not build on chromium: Build output: http://queues.webkit.org/results/7333285 Attachment 77759 [details] did not build on win: Build output: http://queues.webkit.org/results/7255299 Created attachment 77762 [details]
Implement nonrepeating linear CSS3 gradients
Comment on attachment 77762 [details]
Implement nonrepeating linear CSS3 gradients
Fix Windows build.
Attachment 77759 [details] did not build on mac: Build output: http://queues.webkit.org/results/7335219 Attachment 77759 [details] did not build on chromium: Build output: http://queues.webkit.org/results/7300308 Created attachment 77764 [details]
Implement nonrepeating linear CSS3 gradients
Comment on attachment 77764 [details]
Implement nonrepeating linear CSS3 gradients
Fix linkage issue.
Attachment 77762 [details] did not build on mac: Build output: http://queues.webkit.org/results/7290328 Attachment 77762 [details] did not build on chromium: Build output: http://queues.webkit.org/results/7289302 Created attachment 77768 [details]
Add radialgradient support
Comment on attachment 77768 [details]
Add radialgradient support
Note that this patch depends on the previous one, so will fail to build.
Created attachment 77782 [details]
Add radialgradient support
Comment on attachment 77782 [details]
Add radialgradient support
This patch includes support for elliptical gradients.
Comment on attachment 77782 [details] Add radialgradient support View in context: https://bugs.webkit.org/attachment.cgi?id=77782&action=review Make sure to communicate to webkitdev so that ports know they need to update gradient code. > WebCore/css/CSSGradientValue.cpp:229 > + if (numStops > 1 && (stops[0].offset < 0  stops[numStops  1].offset > 1)) { Extra blank line after this if opens that you can get rid of. Comment on attachment 77764 [details]
Implement nonrepeating linear CSS3 gradients
r=me
http://trac.webkit.org/changeset/74912 might have broken Leopard Intel Debug (Build) And bug 51845. 