[whatwg] Drawing shapes on canvas

Rik Cabanier cabanier at gmail.com
Mon Jan 27 18:28:10 PST 2014


On Mon, Jan 27, 2014 at 3:15 PM, Joe Gregorio <jcgregorio at google.com> wrote:

>
>
>
> On Mon, Jan 27, 2014 at 1:26 PM, Rik Cabanier <cabanier at gmail.com> wrote:
>
>>
>>
>>
>> On Fri, Jan 24, 2014 at 12:08 PM, Joe Gregorio <jcgregorio at google.com>wrote:
>>
>>> Rik,
>>>   From the Skia perspective we have the following feedback on the
>>> proposal.
>>>
>>
>> Thanks for your feedback!
>>
>>
>>>
>>>   While we can see how Shape2D can be implemented, it isn't clear that
>>> it's
>>>   such a large performance benefit over what Path provides, so we aren't
>>>   opposed to Shape2D, but don't believe implementing Path should be held
>>> up
>>>   for the sake of Shape2D.  Path itself is a huge win for performance
>>> over
>>>   having only a single current default path and has utility without the
>>> need
>>>   for Shape2D.
>>>
>>
>> I agree.
>> Maybe it's OK to have a staged approach where we introduce the concept of
>> Path first and possible add shapes later.
>>
>>
>>>   In general we would like to see a layered approach to drawing objects,
>>>   starting with something primitive like Path, and at the most general a
>>>   Display List[1] that contains the full set of capabilities of
>>>   CanvasRenderingContext2D.  That layering could be done in two or three
>>>   objects, either [Path, DisplayList] or [Path, Shape2D, DisplayList]. In
>>> all
>>>   cases you can use the lower level objects to construct higher level
>>> objects,
>>>   i.e. use one or more Paths to build a Shape2D, and use Paths and
>>> Shape2Ds
>>> to
>>>   define a DisplayList.
>>>
>>
>> That is an interesting concept. Do you have a more detailed proposal or a
>> prototype in the works?
>>
>
> My tentative plan for this quarter includes prototyping to inform a
> proposal.
>
>
>> At first blush, this seems more like streamlined SVG :-)
>>
>>   What we'd like to see happen:
>>>     1. Review the Path add* methods and possibly simplify them, see
>>> below.
>>>     2. Keep the rest of the Path object and the Path related methods on
>>> the
>>>        CanvasRenderingContext2D.
>>>     3. If Shape2D moves forward do it by adding different versions of
>>> fill,
>>>        clip, etc to CanvasRenderingContext2D, such as:
>>>
>>>        void fill(optional CanvasFillRule fillRule = "nonzero");
>>>        void fill(Path path, optional CanvasFillRule fillRule =
>>> "nonzero");
>>>        void fill(Shape2D shape);
>>>
>>>     4. Possibly work on a DisplayList design, but only after some
>>>        experimentation.
>>>
>>>   We have some particular feedback on the Shape2D design that is inline
>>> below:
>>>
>>> [1] http://en.wikipedia.org/wiki/Display_list
>>>
>>> > All,
>>> >
>>> > around a year ago, I wrote a blog post [1] that introduced a new
>>> 'Shape'
>>> > class that described a filled or stroked region or an area of text.
>>> Java2D
>>> > has a similar concept that they call 'Area' [2].
>>> >
>>> > We've had some discussions but it doesn't look like there was any sort
>>> of
>>> > conclusion. I'd like to pick it back up now that we have a partial
>>> > implementation of the Path object and people are starting to look into
>>> > extending it.
>>> >
>>> > I'll reiterate my proposal:
>>> > 1. remove all the addxxx methods from the Path2D object [3]
>>> > Path object are just containers for segments. Aggregating segments will
>>> > generally not give the desired results since the segments will interact
>>> > (see [1]).
>>> > AddPath *could* be kept if people see a strong use case.
>>>
>>> The add* methods could be simplified to:
>>>
>>>   void addPath(Path path, SVGMatrix? transformation);
>>>   void addPathByStrokingPath(Path path, CanvasDrawingStyles styles,
>>> SVGMatrix? transformation);
>>>   void addText(DOMString text, CanvasDrawingStyles styles, SVGMatrix?
>>> transformation, unrestricted double x, unrestricted double y, optional
>>> unrestricted double maxWidth);
>>>   void addTextAlongPath(DOMString text, CanvasDrawingStyles styles,
>>> SVGMatrix? transformation, Path path, optional unrestricted double
>>> maxWidth);
>>>
>>> The functionality of the addPathByStrokingText methods below can be done
>>> by
>>> applying the above methods.
>>>
>>>   void addPathByStrokingText(DOMString text, CanvasDrawingStyles styles,
>>> SVGMatrix? transformation, unrestricted double x, unrestricted double y,
>>> optional unrestricted double maxWidth);
>>>   void addPathByStrokingText(DOMString text, CanvasDrawingStyles styles,
>>> SVGMatrix? transformation, Path path, optional unrestricted double
>>> maxWidth);
>>>
>>
>> My issue with this add* methods is that they likely will not do what an
>> author expects since the path segments will interact in unexpected ways.
>> In addition, I think no browser follows the spec for "tracing a path"
>> [1]. Since paths will interact with the segments that that algorithm
>> generates, we will get into interoperability issues.
>>
>
> Agreed on the complexity and the interactions. Unfortunately dropping all
> of them but addPath does seem to leave
> Path with a hole in functionality since CanvasRenderingContext2D has both
> a fillText() and strokeText(). How about simplifying
> to just these two add* methods:
>
>   void addPath(Path path, SVGMatrix? transformation);
>   void addText(DOMString text, SVGMatrix? transformation, unrestricted
> double x, unrestricted double y, optional unrestricted double maxWidth);
>
> Note the removal of the CanvasDrawingStyles from addText.
>

Unfortunately, that doesn't really help.
For instance, let's say you request the path for the letter 'o'. It's
represented by 2 closed paths.
What winding rule should you use when you fill that path? Should the paths
be oriented so you have to use a NZO fill or so you can use any winding
rule?

What happens if the path for a letter intersect with another path (or
another letter)?


> >
>>> > 2. introduce a new class 'Shape2D'
>>> > Interface:
>>> >
>>> > [Constructor,
>>> >   Constructor(Path2D , CanvasWindingRule = "nonzero"),
>>> >   Constructor(Path2D , CanvasDrawingStyles, SVGMatrix?), // strokes a
>>> path
>>> >   Constructor(DomString text, CanvasDrawingStyles, SVGMatrix?,
>>> unrestricted
>>> > double, unrestricted double, boolean isStroked = false, optional
>>> > unrestricted double)]
>>> > interface Shape2D{
>>> >     Shape2D transform(matrix); // returns a transformed path
>>>
>>> Why not do this as another constructor?
>>>
>>>   Constructor(Shape2D, SVGMatrix)
>>>
>>
>> No particular reason. It's a bit more compact to do it with a method so
>> you don't have to call 'new'.
>>
>>
>>>
>>> >     Shape2D add(Shape2D); // returns a path that is the union of the 2
>>> paths
>>>
>>> Just to clarify, add() means a union of the two shapes, with no
>>> side-effects
>>> resulting from winding rules of the individual path?
>>>
>>
>> Yes
>>
>>
>>>
>>> Shape2D seems reasonable and useful just as an immutable capture of all
>>> "coverage" aspects of geometry. If add() is hard for platforms to
>>> support,
>>> or
>>> is expensive and clients don't know that, perhaps we can leave it out of
>>> this
>>> version. If we really want to have add(), why not include the full
>>> compliment
>>> of Set operations  [ diff, xor, intersect ], which are no harder to
>>> implement
>>> (afaik) once you've implemented add().
>>>
>>
>> The reason I didn't add them, is that it's straightforward for
>> implementation that don't have access to such a library.
>>  For instance, if there was a union of a fill and a stroke, you could
>> call 'fill' and 'stroke' under the hood.
>>
>>
>>>
>>> > }
>>> >
>>> > This class will represent a painted area. Because it knows the winding
>>> and
>>> > stroking rules, the browser will be able to do expensive math in
>>> advance.
>>> > It can also cache the region on the GPU.
>>> > constructors:
>>> > a. default constructor that creates an empty region
>>> > b. constructor that take a path and a winding rule. This represents a
>>> > filled region
>>> > c. constructor that takes a path, canvasDrawingStyles object and a
>>> matrix.
>>> > This represent a stroked region.
>>> > d. constructor that takes text + canvasDrawingStyles. This represent a
>>> > region of filled or stroked text.
>>> >
>>> > methods:
>>> > a. transform -> transform the shape by the matrix and returns a new
>>> shape
>>> > b. add -> add the region of the shape to the current shape and return
>>> as a
>>> > new shape
>>> >
>>> > 3. Add new methods:
>>> fill(Shape2D)/clip(Shape2D)/isPointInShape(Shape2D,...)
>>> >
>>> > 4. remove stroke(path), fill(path), clip(path), isPointInPath(path,...)
>>> >
>>> > 5. HitRegionOptions takes a Shape2D instead of a path + winding rule
>>> >
>>> > What do people think?
>>> >
>>> >
>>> > 1: http://blogs.adobe.com/webplatform/2013/01/31/revised-canvas-paths/
>>> > 2:
>>> http://docs.oracle.com/javase/tutorial/2d/advanced/complexshapes.html
>>> > 3:
>>>
>>
>> 1:
>> http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#trace-a-path
>>
>>
>
>



More information about the whatwg mailing list