Bug 14499 - Need ability to reset Canvas clipping region without resetting all other Canvas state
Summary: Need ability to reset Canvas clipping region without resetting all other Canv...
Status: CLOSED FIXED
Alias: None
Product: HTML WG
Classification: Unclassified
Component: HTML Canvas 2D Context (show other bugs)
Version: unspecified
Hardware: All All
: P2 enhancement
Target Milestone: ---
Assignee: Ian 'Hixie' Hickson
QA Contact: HTML WG Bugzilla archive list
URL:
Whiteboard: canvas v5
Keywords:
Depends on:
Blocks:
 
Reported: 2011-10-18 16:37 UTC by Simon Sarris
Modified: 2012-07-06 02:40 UTC (History)
9 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Simon Sarris 2011-10-18 16:37:26 UTC
The clipping region is defined such that it can only get smaller and never larger.   

"The clip() method must create a new clipping region by calculating the intersection of the current clipping region and the area described by the current path, using the non-zero winding number rule."

In other words, one cannot define a clipping region of (50,50,50,50) and then define a larger one of (0,0,200,200). The clipping region will still be (50,50,50,50).

This rule itself is fine. But if one wants to clip and then reset the clipping region back to default (the entire canvas) there is currently no way to do that short of resetting all canvas state, either by changing the size of the canvas (destroys all state) or using save() and restore().

But there are plenty of reasons for wanting the canvas state to persist otherwise while resetting the clipping region. Most of them are performance/caching reasons that can make a significant difference. If one wanted to draw 7,000 strings with a single font but wanted a different clipping region for each, one would have to set the font property of the canvas 7,000 times instead of just once. This is murder on performance when it need not be necessary.
Comment 1 Ian 'Hixie' Hickson 2011-10-18 22:48:07 UTC
Just set the font before you do the save/clip/filltext/restore loop. Why would you set the font 7000 times?
Comment 2 Simon Sarris 2011-10-18 23:06:38 UTC
That only works if your object drawing structure is flat. Suppose you have 7,000 text objects "nested" inside of 7,000 shapes or panels of some kind, and each object (text or panel) along the way needed to clip a unique region. So the drawing would go:

Panel save+clip
  Text setFont+save+clip
  Text draw
  restore
restore

Panel save+clip
  Text setFont+save+clip // would have to set text again here anyway :(
  Text draw
  restore
restore

etc.

In a case like this the panel's clip/save/restore would interfere with the setting of the font. A save/restore would not be necessary if there were a way of arbitrarily resetting the clipping region.
Comment 3 Ian 'Hixie' Hickson 2011-10-25 03:25:54 UTC
If they're all setting the same font, just restructure your code to not set the font each time. Doing it like that is really inefficient, regardless of the clipping region thing.

If they're all setting different fonts then you're screwed either way.

In any case, do you have any data showing that the performance problem is in setting the font? I would have imagined that if you're drawing 7000 shapes each with their own text, the drawing of the text would vastly outweigh any cost of setting the font, making this a non-issue.

It's hard to tell exactly what the right solution is here without a better understanding of the problem. Can you provide a link to the page that you think demonstrates what is inefficient about the current API?
Comment 4 Simon Sarris 2011-10-25 04:23:35 UTC
If one wants to make heavy use of clipping, one cannot (as effectively as one would like) cache attributes, with font being the one of the biggest offenders. (Caching other attributes leads to non-trivial performance gains too).

That kind of scenario comes up when you have complex panels, for example in a diagramming app where a parent panel may arrange and clip child panels, which may in turn arrange and clip text strings and other objects.

Here is a distilled working example of why one loses the ability to cache font in such a scenario:

http://jsfiddle.net/PwAGz/

Having no way to reset the clipping region makes these kind of optimizations harder when you do have a scenario such as nested-and-clipped objects.

Setting the font is very slow in some browsers. For just how big of an offender it is, here's a simple test of fillText vs setting font + fillText:

http://jsperf.com/set-font-perf

In short, having to set the font each time is:

- 14% slower on Firefox

- 57% slower on Chrome 16 (dev and nightly). That is to say, if you do not set the font, then execution is more than twice as fast.

- About the same on IE9

I have mentioned that font is not the only thing that results in non-trivial performance gains when cached. If these performance hits alone are not enough to cause you to consider having some kind of resetClip(), I can provide more evidence.

Thank you.
Comment 5 Ian 'Hixie' Hickson 2011-10-25 05:47:29 UTC
Those performance differences seem like low-hanging fruit that browsers should fix. I really see no reason why setting the font to a previously-set value need be _that_ slow. I recommend filing bugs with the browsers in question.

All this talk of panels is making me wonder what you're using <canvas> for, though. Are you sure you're not abusing it for something that it's not really intended for? Wouldn't CSS be the better way to do this?
Comment 6 Simon Sarris 2011-10-25 17:19:11 UTC
The performance problem here is not about setting a font to an already-set value. That's easy for programmers to remedy by checking and having a no-op, as my example code already does.

The problem is not something browsers can currently fix. The context's font starts at value "A" and gets set to a value "B" inside a nested series of clipped objects. Then the panel housing the text object has restore() called because there is no other way to grow the clipping region, and so the font becomes "A" again. It would be nice if it could remain "B" so that one would not be required to set the font again, because perhaps the next 500 text objects to be drawn (which are nested in clipped panels) have the "B" font. The example shows how keeping the ctx.font "B" is not possible if you have nested objects clipping.

For the record, what I am doing is creating a professional diagramming library that supports greater than 10,000 nodes and links of different complexities, with layouts and animation and other bells and whistles. The performance is very good, much better than similar products out for canvas (so far), and the kind of performance I have is not possible if I use SVG or HTML/CSS to do it, as 10,000 DOM elements is a performance nightmare from the get-go! My nodes can contain any number of nested panels and any of these can have a maximum size, which might necessitate clipping their children in some way. Thus this problem arises. I'm sure there are other real-world examples of successive clipping used in such a way that disallow effective property caching.

Over the past year I have amassed a large number of canvas performance findings (I've blogged a few of them) that have allowed me to create fast canvas apps. In the future hope to publish them and dispel the notion that canvas is necessarily too slow. This issue at hand is one of the few problems that have no programmatic remedy except to avoid several potentially legitimate uses of clipping.

It is important to remember here that font is merely an example as it is just one of the properties that resetting a clipping region will clear. In general I think we should consider it a bad thing that there is simply no way to reset a clipping region without also resetting the whole state of the canvas. That performance can suffer so much because of it only compounds the problem.

Adding a resetClip or setClip or similar method would also be more in line with the rest of the context's functionality. After all, one is able to reset the transform, or indeed anything else without having to reset the rest of the context state. Why shouldn't the same be possible with a clipping region?
Comment 7 Ian 'Hixie' Hickson 2011-10-28 23:02:33 UTC
The transform is the exception, actually, and that feature was only added because there were transformations you couldn't otherwise achieve.

Setting the font to A then B 500 times should not be any slower than setting it to A 500 times than B 500 times, and both should be not significnatly slower than setting it to A and B once. That _can_ be fixed by browsers.

Your described use case doesn't seem unreasonable, though making it accessible will be a lot harder than it would be using SVG.
Comment 8 Ian 'Hixie' Hickson 2011-11-01 17:31:41 UTC
Anyone else got any opinions?
Comment 9 Leonard Rosenthol 2011-11-01 17:36:39 UTC
I would comment simply that if Canvas is to be usable for rendering rich vector graphics, must as PDF/EPS/SVG are today - then you need the ability to push & pop the graphic state (incl. the clip path).  So perhaps that would a better (and more generic) solution to the problem.
Comment 10 Robert O'Callahan (Mozilla) 2011-11-01 21:22:54 UTC
I don't see any implementation problems here. putImageData already has to be able to reset the clip temporarily.

cairo supports this via cairo_reset_clip, which suggests the need for such an API has come up before. (Although maybe we added it to support putImageData!)
Comment 11 Robert O'Callahan (Mozilla) 2011-11-01 21:28:26 UTC
By the way, Bas Schouten points out that in hardware accelerated canvas implementations there's usually a large performance penalty for changing the clipping region frequently.
Comment 12 Ian 'Hixie' Hickson 2011-11-03 15:46:36 UTC
EDITOR'S RESPONSE: This is an Editor's Response to your comment. If you are satisfied with this response, please change the state of this bug to CLOSED. If you have additional information and would like the editor to reconsider, please reopen this bug. If you would like to escalate the issue to the full HTML Working Group, please add the TrackerRequest keyword to this bug, and suggest title and text for the tracker issue; or you may create a tracker issue yourself, if you are able to do so. For more details, see this document:
   http://dev.w3.org/html5/decision-policy/decision-policy.html

Status: Partially Accepted
Change Description: none yet
Rationale: I have marked this LATER so that we can look at this again once browsers have caught up with what we've specified so far, based in particular on how much demand this request gets.
Comment 13 Ian 'Hixie' Hickson 2012-02-29 00:33:12 UTC
context.resetClip()
Comment 14 Simon Sarris 2012-02-29 01:21:42 UTC
Does this mean resetClip has been accepted? Or is it back up for discussion?
Comment 15 Ian 'Hixie' Hickson 2012-02-29 22:49:34 UTC
Whether it's accepted or not depends on how the browser vendors feel when they see it in the spec. :-) But it's my intent to add this to the spec in the near future, yes.

Current proposal at: http://wiki.whatwg.org/wiki/Canvas#Misc_other_proposals
Comment 16 Ian 'Hixie' Hickson 2012-03-28 21:45:56 UTC
EDITOR'S RESPONSE: This is an Editor's Response to your comment. If you are satisfied with this response, please change the state of this bug to CLOSED. If you have additional information and would like the editor to reconsider, please reopen this bug. If you would like to escalate the issue to the full HTML Working Group, please add the TrackerRequest keyword to this bug, and suggest title and text for the tracker issue; or you may create a tracker issue yourself, if you are able to do so. For more details, see this document:
   http://dev.w3.org/html5/decision-policy/decision-policy.html

Status: Accepted
Change Description: resetClip()
Rationale: Concurred with reporter's comments.
Comment 17 Simon Sarris 2012-07-06 02:40:41 UTC
Thanks!