Brian Osman | 0351d5a | 2021-04-07 16:01:52 -0400 | [diff] [blame^] | 1 | --- |
| 2 | title: 'Skia Coordinate Spaces' |
| 3 | linkTitle: 'Coordinates' |
| 4 | --- |
| 5 | |
| 6 | ## <span id="overview">Overview</span> |
| 7 | |
| 8 | Skia generally refers to two different coordinate spaces: **device** and **local**. Device |
| 9 | coordinates are defined by the surface (or other device) that you're rendering to. They range from |
| 10 | `(0, 0)` in the upper-left corner of the surface, to `(w, h)` in the bottom-right corner - they are |
| 11 | effectively measured in pixels. |
| 12 | |
| 13 | --- |
| 14 | |
| 15 | ## <span>Local Coordinates</span> |
| 16 | |
| 17 | The local coordinate space is how all geometry and shaders are supplied to the `SkCanvas`. By |
| 18 | default, the local and device coordinate systems are the same. This means that geometry is |
| 19 | typically specified in pixel units. Here, we position a rectangle at `(100, 50)`, and specify that |
| 20 | it is `50` units wide and tall: |
| 21 | |
| 22 | <fiddle-embed name='96f782b723c5240aab440242f4c7cbfb'></fiddle-embed> |
| 23 | |
| 24 | Local coordinates are also used to define and evaluate any `SkShader` on the paint. Here, we define |
| 25 | a linear gradient shader that goes from green (when `x == 0`) to blue (when `x == 50`): |
| 26 | |
| 27 | <fiddle-embed name='97cf81a465fdeff01d2298e07a0802a3'></fiddle-embed> |
| 28 | |
| 29 | --- |
| 30 | |
| 31 | ## <span>Shaders Do Not Move With Geometry</span> |
| 32 | |
| 33 | Now, let's try to draw the gradient-filled square at `(100, 50)`: |
| 34 | |
| 35 | <fiddle-embed name='3adc73d23d57084f954f52c6b14c8772'></fiddle-embed> |
| 36 | |
| 37 | What happened? Remember, the local coordinate space has not changed. The origin is still in the |
| 38 | upper-left corner of the surface. We have specified that the geometry should be positioned at |
| 39 | `(100, 50)`, but the `SkShader` is still producing a gradient as `x` goes from `0` to `50`. We have |
| 40 | slid the rectangle across the gradient defined by the `SkShader`. Shaders do not move with the |
| 41 | geometry. |
| 42 | |
| 43 | --- |
| 44 | |
| 45 | ## <span>Transforming Local Coordinate Space</span> |
| 46 | |
| 47 | To get the desired effect, we could create a new gradient shader, with the positions moved to |
| 48 | `100` and `150`. That makes our shaders difficult to reuse. Instead, we can use methods on |
| 49 | `SkCanvas` to **change the local coordinate space**. This causes all local coordinates (geometry |
| 50 | and shaders) to be evaluated in the new space defined by the canvas' transformation matrix: |
| 51 | |
| 52 | <fiddle-embed name='ce89b326b2bbe41587eec738706bf155'></fiddle-embed> |
| 53 | |
| 54 | --- |
| 55 | |
| 56 | ## <span>Transforming Shader Coordinate Space</span> |
| 57 | |
| 58 | Finally, it is possible to transform the coordinate space of the `SkShader`, relative to the canvas |
| 59 | local coordinate space. To do this, you supply a `localMatrix` parameter when creating the |
| 60 | `SkShader`. In this situation, the geometry is transformed by the `SkCanvas` matrix. The `SkShader` |
| 61 | is transformed by the `SkCanvas` matrix **and** the `localMatrix` for that shader. The other way to |
| 62 | think about this: The `localMatrix` defines a transform that maps the shader's coordinates to the |
| 63 | coordinate space of the geometry. |
| 64 | |
| 65 | To help illustrate the difference, here's our gradient-filled box. It's first been translated `50` |
| 66 | units over and down. Then, we apply a `45` degree rotation (pivoting on the center of the box) to |
| 67 | the canvas. This rotates the geometry of the box, and the gradient inside it: |
| 68 | |
| 69 | <fiddle-embed name='d4b52d94342f1b55900d489c7ba8fd21'></fiddle-embed> |
| 70 | |
| 71 | Compare that to the second example. We still translate `50` units over and down. Here, though, we |
| 72 | apply the `45` degree rotation *only to the shader*, by specifying it as a `localMatrix` to the |
| 73 | `SkGradientShader::MakeLinear` function. Now, the box remains un-rotated, but the gradient rotates |
| 74 | inside the box: |
| 75 | |
| 76 | <fiddle-embed name='886fa46943b67e0d6aa78486dcfbcc2c'></fiddle-embed> |