W3C home > Mailing lists > Public > whatwg@whatwg.org > April 2014

Re: [whatwg] <canvas> feedback

From: Rik Cabanier <cabanier@gmail.com>
Date: Tue, 8 Apr 2014 09:14:50 -0700
Message-ID: <CAGN7qDAaNR6_TgZMQq2tnE8NgAoWqBDrUB+_ELZqMCPxGWe6Mg@mail.gmail.com>
To: Ian Hickson <ian@hixie.ch>
Cc: WHAT Working Group <whatwg@whatwg.org>
On Mon, Apr 7, 2014 at 3:35 PM, Ian Hickson <ian@hixie.ch> wrote:

> > > > > So this is not how most implementations currently have it defined.
> > > >
> > > > I'm unsure what you mean. Browser implementations? If so, they
> > > > definitely do store the path in user coordinates. The spec currently
> > > > says otherwise [1] though.
> > >
> > > I'm not sure what you're referring to here.
> >
> > All graphics backends for canvas that I can inspect, don't apply the CTM
> > to the current path when you call a painting operator. Instead, the path
> > is passed as segments in the current CTM and the graphics library will
> > apply the transform to the segments.
> Right. That's what the spec says too, for the current default path.

No, the spec says this:

For CanvasRenderingContext2D objects, the points passed to the methods, and
the resulting lines added to current default path by these methods, must be
transformed according to the current transformation matrix before being
added to the path.

> This is the confusing behaviour to which I was referring. The "Path" API
> (or
> Path2D or whatever we call it) doesn't have this problem.

That is correct. The Path2D object is in user space and can be passed
directly to the graphics API (along with the CTM).

> ...
> > > > var s = new Shape();
> > > >
> > > > ctx.beginPath(); ctx.lineTo(...); ...; ctx.fill(); s.add(new
> > > > Shape(ctx.currentPath));
> > > > ...
> > > > ctx.beginPath(); ctx.lineTo(...); ...; ctx.stroke(); s.add(new
> > > > Shape(ctx.currentPath, ctx.currentDrawingStyle));
> > > >
> > > > ctx.addHitRegion({shape: s, id: "control"});
> > >
> > > Why not just add ctx.addHitRegion() calls after the fill and stroke
> calls?
> >
> > That does not work as the second addHitRegion will remove the control and
> > id from the first one.
> > The 'add' operation is needed to get a union of the region shapes.
> Just use two different IDs with two different addHitRegion() calls. That's
> a lot less complicated than having a whole new API.

That doesn't work if you want to have the same control for the 2 areas,
from the spec for addHitRegion:

If there is a previous region with this control, remove it from the scratch
bitmap's hit region list; then, if it had a parent region, decrement that
hit region's child count by one.

Even if you don't use the control, it would be strange to have 2 separate
hit regions for something that represents 1 object.

> > > On Fri, 6 Dec 2013, Jürg Lehni wrote:
> ...
> > > > copy, and would help memory consummation and performance.
> > >
> > > I don't really understand the use case here.
> >
> > Jurg was just talking about an optimization (so you don't have to make
> > an internal copy)
> Sure, but that doesn't answer the question of what the use case is.

>From my recent experiments with porting canvg (
https://code.google.com/p/canvg/) to use Path2D, they have a routine that
continually plays a path into the context which is called from a routine
that does the fill, clip or stroke.
Because that routine can't simply set the current path, a lot more changes
were needed.
Some pseudocode that shows the added complexity, without currentPath:

function drawpath() {

  if(Path2DSupported) {

    return myPath;

  } else

  for(...) {




function fillpath() {

var p = drawpath();


with currentPath:

function drawpath() {
  if(Path2DSupported) { // only 2 extra lines of code
    ctx.currentPath = myPath;
  } else
  for(...) {
function fillpath() {

> On Wed, 12 Mar 2014, Rik Cabanier wrote:
> ...
> > > You say, here are some paths, here are some fill rules, here are some
> > > operations you should perform, now give me back a path that describes
> > > the result given a particular fill rule.
> >
> > I think you're collapsing a couple of different concepts here:
> >
> > path + fillrule -> shape
> > union of shapes -> shape
> > shape can be converted to a path
> I'm saying "shape" is an unnecessary primitive. You can do it all with
> paths.
>    union of (path + fillrule)s -> path

No, that makes no sense. What would you get when combining a path with a
fillrule and no fillrule?

> > > A shape is just a path with a fill rule, essentially.
> >
> > So, a path can now have a fillrule? Sorry, that makes no sense.
> I'm saying a shape is just the combination of a fill rule and a path. The
> path is just a path, the fill rule is just a fill rule.

After applying a fillrule, there is no longer a path. You can *convert* it
back to a path that describes the outline of the shape if you want, but
that is something different.
The way you've defined things now, you can apply another fill rule on a
path with a fill rule. What would the result of that be?

> > > Anything you can do
> > > with one you can do with the other.
> >
> > You can't add segments from one shape to another as shapes represent
> > regions.
> > Likewise, you can't union, intersect or xor path segments.
> But you can union, intersect, or xor lists of pairs of paths and
> fillrules.

would you start throwing when doing these operations on paths without fill

> ...
> >
> > "Wrong" meaning:
> > if the author has a bunch of geometry and wants to put it in 1 path
> object
> > so he can just execute 1 fill operation, he might be under the impression
> > that "adding" the geometry will just work.
> Well, sure, an author might be under any number of false impressions.
> The API has a way for a bunch of paths to be merged with a single fillrule
> to generate a new path with no crossing subpaths (which is also fillrule
> agnostic), essentially giving you the union of the shapes represented by
> those paths interpreted with that fillrule.

Is this the API you're referring to?

path = new Path2D(paths [, fillRule ] )

The first argument could point to paths that need different winding rules
so this won't work.
What if one of the paths already had a fill rule? This doesn't seem

> There are very few use cases where you want to add partial path segments
> > together but I agree that there are some cases that it's useful to have.
> I disagree that there few such cases. Pretty much any time you create a
> path, you are adding partial path segments together. Whether you do so
> using one Path object all at once or multiple Path objects that you later
> add together is just a matter of programming style.

It's the multiple path objects use case that is unclear to me. Is there any
tool/library that does this?
Looking at Adobe's graphics applications, there isn't anything like it.
Looking at graphics APIs, I don't see any calls that combine paths directly.
hmm, it seems the spec has changed.
It didn't used to start with "Create a new path that describes the edge of
the areas"

With the new wording, the last sentence should be updated:

Subpaths in the newly created path must wind clockwise, regardless of the
direction of paths in path.

Since you now create 'holes', the separate paths need to be reoriented like
you specify in other parts.

> ...
> > > > I want them removed because they will most likely not behave in the
> > > > way that an author expects. When he "adds" 2 paths, he wouldn't
> > > > expect that there is 'interference' between them.
> > >
> > > I don't see why not. It's exactly what happens today if you were to
> > > just add the same path creation statements together into the current
> > > default path and fill or stroke that.
> >
> > Sure but who does that?
> It's how all paths are built, as far as I can tell. I don't see how else
> you could build a path that consists of more than one line.
> addPath() is useful for shifting a path according to a transform.

Why not just transform() then?

addPathByStrokingPath() is for creating a stroked path.
> addText() is for writing text.
> I don't see how removing any of them is a win.

Yes, they are useful. The issue is that they are not implementable as
currently specified.

> > > > > On Mon, 4 Nov 2013, Rik Cabanier wrote:
> ...
> > > >
> > > > How would you get a point? the width is scaled to 0.
> > >
> > > That's how you get a point -- scale(0,0) essentially reverts
> > > everything to a zero dimensional point.
> >
> > OK, but the width of the point is also transformed to 0 so you get
> > nothing.
> Points are always zero-width, by definition.

You can still stroke it though and get a point of the strokewidth.

> ...
> > However the way you defined those APIs does not make sense and will not
> > give the result that authors want.
> The way to make this point would be to start from the use case, describe
> the desired effect, show the "obvious" way to achieve this using the API,
> and then demonstrate how it doesn't match the desired effect.

The obvious way is to go with Shape2D.
It's not because I invented it; many advanced graphics APIs came offer this
(including D2D and skia)

> ...
> > So, it's not realistic to add this to the Path2D object.
> I don't really see why it's unrealistic. In most cases, the user agent
> doesn't actually have to do any work -- e.g. if all that you're doing is
> merging two paths so that you can fill them simultaneously later, the UA
> can just keep the two paths as is and, when necessary, fill them.
> For cases where you really want to have this effect -- e.g. when you want
> to get the outline of the dashed outline of text -- then I don't really
> see any way to work around it.

That is true. That is why I proposed to make the interface more limited for
now until there is a time that this functionality is available.

>From Ian Hickson:

> I don't think the spec should be written with a particular
> implementation in mind, nor should it dictate one.

I agree it shouldn't (and doesn't) dictate one. But it's crazy to not
consider implementations at all when writing a spec. That way lies madness
like requiring O(N^2) algorithms and solving the halting problem and all
kinds of other disasters (all of which I've seen in real proposals).

> > The reason for that is that even though a UA could emulate the union by
> > doing multiple fill operations, Path2D allows you to stroke another path
> > object. At that point, you really have to do planarization. By defining
> > a Shape2D object and not allowing it to be stroked, we can work around
> > this.
> Sure, by limiting the feature set dramatically we can avoid the cases
> where you have to do the hard work, but we also lose a bunch of features.

For now. They can be added later.
Until then, this is confusing implementors.

> ...
> >
> > Where is the union of fill regions specified? All I see is segments
> > aggregation.
> One of the Path constructors takes an array of paths and a fill rule.

See above.

> > > > No one has implemented them and they are confusing the browser
> > > > vendors.
> > >
> > > I don't think they're confusing anyone.
> >
> > The blink people were looking at adding this until they thought it
> > through and realized that it wouldn't work.
> Realised what wouldn't work? As far as I'm aware, there's nothing that
> wouldn't work.

See this thread:
I think you already addressed some of the concerns. Doing the planarization
is now the problem so we can't implement this.

> ...
> > How can you make that statement? No one has implemented them yet.
> What do you mean by "stable"?
> I assumed you meant "hasn't been changing a lot". The spec hasn't been
> changing a lot, so it seems pretty stable.

You've been making a lot of changes lately so this not quite true.
Because you wrote it a long time ago and nobody looked at it until now,
doesn't mean that it is stable.
Received on Tuesday, 8 April 2014 16:20:27 UTC

This archive was generated by hypermail 2.3.1 : Monday, 13 April 2015 23:09:28 UTC