mattdesl / canvas-sketch

[beta] A framework for making generative artwork in JavaScript and the browser.
MIT License
4.97k stars 393 forks source link

Avoid save/restore when scale not set #172

Closed sfrieson closed 1 year ago

sfrieson commented 1 year ago

Avoids setting save/restore calls when not necessary

The scaleContext: false setting allows currently allows us to keep the the scale from being set, but it doesn't keep the context state from being saved and restored.

While the scaleX and scaleY are often 1 for most basic use cases, resulting in no visible scaling, this functionality has tripped me up a handful of times as I was first learning Canvas, and a still does as I've gotten more comfortable. The main issue is that it invisibly restores state.

The problem

For example, the first time I tried to change the canvas coordinate system (top->bottom, left->right) to work more like the cartesian coordinate system (bottom->top, left->right) I tried this code (Quadrant I only):

const sketch = ({ context, height }) => {
  context.scale(1, -1);
  context.translate(0, -height);

  return () => {
    ...
  };
};

While this does work for the setup phase of the sketch, it is restored in the renders which is confusing since there is so much talk about canvas being a state machine.

The solution is to do something like this instead:

const sketch = ({ context, height }) => {
- context.scale(1, -1);
- context.translate(0, -height);
-
  return () => {
+   context.scale(1, -1);
+   context.translate(0, -height);
    ...
  };
};

While this produces the desired effect with canvas-sketch, it breaks the idea of the state machine because, if it truly were a state machine, this code would imply that the coordinate system gets flipped back and forth each render.

Proposed solution

This code change avoids this issue when the scaleContext setting is set to false. That unfortunately doesn't help newcomers so much since it is true by default.

There is the more opinionated solution to do the same when scaleX and scaleY are both 1 since it will have no effect. This will fix the issue for the common case, but then possibly cause confusion when the context scaling is actually needed.

Also, changing the default experience adds a risk of breaking existing sketches (like my own) that are dependent on this quirk. The solution for those would be to add a save() and restore() in render function.


I'm open to other solutions, updating docs, or any other way that I can help out here!

Thanks for this fantastic tool!