Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .pubignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@
/doc/hero.png
/build/
.git/
.github/
.github/
/doc/
56 changes: 56 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,59 @@
## 0.1.0

Targets `layer_canvas: ^0.1.0` (was `^0.1.0-beta.4`) and surfaces everything the
core gained since — still with only Flutter types.

* **Stroke cap/join/miter/dash** — `Layers.rectangle`/`Layers.path` gain
`strokeCap`/`strokeJoin`/`strokeMiterLimit`, using `dart:ui`'s own
`StrokeCap`/`StrokeJoin`; `Layers.path` additionally accepts
`dashArray`/`dashOffset` (scaled by `pixelRatio` like every other length),
since dashing only applies to a `PathLayer`'s stroke in the core.
* **`clipBehavior`** — `Layers.rectangle`/`text`/`image`/`path` accept a
`Clip clipBehavior` (any value but `Clip.none` clips the layer to its
`size`), mapping to the core's `Layer.clipToBounds`. Not on `Layers.group`/
`Layers.svg`: the core expands a `Group` into its concrete descendants
before rendering, leaving no single surface to clip.
* **`scale`/`alignment`** — every `Layers` factory now accepts `scale`
(uniform, via the core's `LayerTransform.scale`) and `alignment` (via the
existing `AlignmentGeometryX.toFractionalPoint2D()`, so `rotation`/`scale`
can pivot around a corner instead of the center), alongside `position`/
`rotation`.
* **Word-wrap** — `Layers.text` word-wraps into a `size` with a width set
(greedily, only at spaces), vertically centered within `size`'s height —
no new parameter, this was already reachable, just newly documented.
* **Exact `FontWeight` mapping** — `FontWeightX.toTextWeight()` now uses the
core's `TextWeight.fromValue` (added in `layer_canvas` 0.1.0-beta.6) for an
exact 1:1 conversion, instead of snapping to the nearest of 7 named
constants.
* **`AssetImageSource`** — a `LayerImageSource` that lazily loads a Flutter
asset by key (optionally from a `package`) instead of eagerly reading bytes
like `ImageSources.asset`, so it's cheap to build ahead of time and
compact to serialize (a short string instead of a base64 blob). Every
widget in this package (and the new `Scenes.encode`/`saveToFile`) resolves
it against the ambient `AssetBundle` before rendering; registers its own
`LayerRegistry` decoder automatically, so `Scene.fromJson` reconstructs it
too.
* **Scene persistence** — `Scene.toJson()`/`Scene.fromJson()` (added in
`layer_canvas` 0.1.0-beta.6) work as-is through this package's re-exported
`Scene`; only documented here, no new API.
* **Export** — `Scenes.encode(scene, {format})` and
`Scenes.saveToFile(scene, path, {format})` rasterize a `Scene` to
`png`/`bmp`/`qoi` bytes or a file (via the re-exported `OutputFormat`),
resolving any `AssetImageSource` first — for exporting a scene instead of
displaying it, which every widget still does as PNG.
* Re-exports `OutputFormat` and `LayerRegistry` (+ its `LayerFromJson`/
`ImageSourceFromJson` typedefs) alongside the previously re-exported core
types.
* **Rendering never blocks the UI isolate** — `LayerCanvas`, `SceneWidget`,
`SvgLayer`, and `Scenes.encode`/`saveToFile` now run the native render
call on a background isolate internally (`renderOffMainIsolate`), instead
of on the calling isolate. The core `layer_canvas` package's own
`Renderer.render` stays synchronous-under-the-hood on purpose — it's
plain Dart with no UI thread to protect, so a CLI/batch consumer isn't
charged isolate-spawn overhead for a benefit only a Flutter app needs;
this package is where that trade makes sense, so it's where the offload
lives.

## 0.1.0-beta.1

Initial release.
Expand Down
169 changes: 161 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,25 @@ other `Image` — no `Color32`, no `Point2D`, nothing from the core package
imported directly. See [Usage](#usage) below for gradients, custom shapes,
SVG, and tap handling.

## Gallery

A few scenes built entirely from `Layers`/`Scenes.of`/`LayerPathBuilder` —
gradients, hand-drawn vector paths, SVG, dashed strokes, and wrapped text —
to give a sense of what a `LayerCanvas` can render.

<table>
<tr>
<td width="33%"><img src="https://raw.githubusercontent.com/code3743/layer_canvas_flutter/main/doc/gallery/gradient-burst.png" alt="Overlapping radial, linear, and sweep gradients"></td>
<td width="33%"><img src="https://raw.githubusercontent.com/code3743/layer_canvas_flutter/main/doc/gallery/vector-blob.png" alt="An organic blob drawn with LayerPathBuilder cubic and quadratic curves"></td>
<td width="33%"><img src="https://raw.githubusercontent.com/code3743/layer_canvas_flutter/main/doc/gallery/bauhaus-grid.png" alt="An abstract Bauhaus-style geometric composition"></td>
</tr>
<tr>
<td width="33%"><img src="https://raw.githubusercontent.com/code3743/layer_canvas_flutter/main/doc/gallery/svg-pattern.png" alt="A parsed SVG document placed as a scattered pattern"></td>
<td width="33%"><img src="https://raw.githubusercontent.com/code3743/layer_canvas_flutter/main/doc/gallery/constellation.png" alt="A dashed-stroke constellation/network diagram"></td>
<td width="33%"><img src="https://raw.githubusercontent.com/code3743/layer_canvas_flutter/main/doc/gallery/editorial-card.png" alt="An editorial card with wrapped text, a clipped image badge, and a gradient background"></td>
</tr>
</table>

## Features

* **`Layers`** — static factories (`rectangle`, `text`, `image`, `path`,
Expand All @@ -51,6 +70,13 @@ SVG, and tap handling.
* **Gradients** — pass a Flutter `LinearGradient`/`RadialGradient`/
`SweepGradient` as `Layers.rectangle`/`Layers.path`'s `gradient:`, no core
gradient types involved.
* **Stroke cap/join/miter/dash** — `strokeCap`/`strokeJoin` (`dart:ui`'s own
enums), `strokeMiterLimit`, and (on `Layers.path`) `dashArray`/
`dashOffset` for a dashed stroke.
* **`clipBehavior`** — clips a sized layer to its own box, same idea as
`Container`'s `clipBehavior`.
* **`scale`/`alignment`** — every factory's `rotation` gets a uniform
`scale` and an `alignment` to pivot around, instead of always the center.
* **`LayerPathBuilder`** — draws a `Layers.path` shape with the same method
names as `dart:ui`'s own `Path` (`moveTo`, `lineTo`, `cubicTo`,
`arcToPoint`, `close`...), so it reads like drawing on a `Canvas`.
Expand All @@ -60,6 +86,12 @@ SVG, and tap handling.
widget; a different rendering path entirely).
* **`Scenes.of`** — builds a `Scene` from a `children` list instead of the
core's mutate-after-construction `Scene(...)..add(...)..add(...)`.
* **`AssetImageSource`** — an image layer/background source that lazily
loads a Flutter asset by key, resolved automatically before every render.
* **Scene persistence** — `Scene.toJson()`/`fromJson()` save and restore a
whole scene, `AssetImageSource` included.
* **`Scenes.encode`/`Scenes.saveToFile`** — export a `Scene` to
`png`/`bmp`/`qoi` bytes or a file, instead of displaying it.
* **`LayerCanvas`** — a widget that renders a `Scene` (fixed, or built from
the widget's measured size and device pixel ratio) as an `Image`, with
render caching, a placeholder while it's rendering, an error builder, and
Expand All @@ -82,7 +114,7 @@ cover the whole surface you need from Flutter code.

```yaml
dependencies:
layer_canvas_flutter: ^0.1.0-beta.1
layer_canvas_flutter: ^0.1.0
```

`layer_canvas` embeds a default font (Roboto) in its native library so text
Expand Down Expand Up @@ -181,6 +213,66 @@ Layers.rectangle(
)
```

### Strokes: cap, join, miter, dash

`strokeCap`/`strokeJoin` take `dart:ui`'s own enums — the same ones a
`Paint` would — and `strokeMiterLimit` controls how far a `StrokeJoin.miter`
corner may extend before it's clamped to a bevel. `Layers.path` additionally
takes `dashArray`/`dashOffset` for a dashed stroke (only `PathLayer`s dash —
a `RectangleLayer` has no path geometry of its own to dash):

```dart
Layers.path(
path: LayerPathBuilder()
..moveTo(const Offset(0, 50))
..lineTo(const Offset(300, 50)),
color: const Color(0xFF4C6EF5),
style: PaintingStyle.stroke,
strokeWidth: 4,
strokeCap: StrokeCap.round,
dashArray: const [12, 8],
pixelRatio: pixelRatio,
)
```

### Clipping with clipBehavior

`clipBehavior` (any value but the default `Clip.none`) clips a sized layer
to its own box — same idea as `Container`'s `clipBehavior`. The natural
case is cropping a `cover`-fit image, exactly like `Image` inside a clipped
box:

```dart
Layers.image(
source: MemoryImageSource(bytes),
size: const Size(200, 120),
fit: BoxFit.cover,
clipBehavior: Clip.hardEdge,
pixelRatio: pixelRatio,
)
```

Not available on `Layers.group`/`Layers.svg`: the core expands a `Group`
into its concrete descendants before rendering, leaving no single surface
to clip — clip an individual child via its own factory instead.

### scale and alignment

Every factory's `rotation` gets two companions: `scale` (uniform) and
`alignment` (where `rotation`/`scale` pivot from — `Alignment.center` by
default, same as the core):

```dart
Layers.rectangle(
size: const Size(80, 80),
color: const Color(0xFFFF6B6B),
rotation: 0.3,
scale: 1.2,
alignment: Alignment.topLeft, // pivot from the corner, not the center
pixelRatio: pixelRatio,
)
```

### Custom shapes with LayerPathBuilder

`LayerPathBuilder` mirrors `dart:ui`'s `Path` — the same method names, in
Expand Down Expand Up @@ -248,6 +340,25 @@ the document at its own natural size and lets Flutter's ordinary layout
scale/position that result, the same way it would any other
fixed-aspect-ratio child.

### Word-wrap

`Layers.text` word-wraps into a `size` with a width set — greedily,
breaking only at spaces (a single word wider than the box overflows on its
own line rather than being split mid-word) — and the wrapped block is
vertically centered within `size`'s height:

```dart
Layers.text(
text: 'A longer caption that should wrap across a few lines.',
size: const Size(220, 80),
fontSize: 16,
pixelRatio: pixelRatio,
)
```

Leave `size` unset (or give it no width) for a single, possibly overflowing
line — the same as before this existed.

### Tap handling with onLayerTap

`LayerCanvas.onLayerTap` reports which `Layer` (if any) was under a tap,
Expand Down Expand Up @@ -310,21 +421,63 @@ one in place, pass a changing `rebuildKey` to force a re-render:
LayerCanvas(scene: scene, rebuildKey: generation)
```

### Saving and loading a Scene

`Scene` round-trips through JSON as-is — `toJson()`/`fromJson` recurse
through every layer, paint, gradient, transform, and image source:

```dart
final json = jsonEncode(scene.toJson());
// ...later, or on another device:
final restored = Scene.fromJson(jsonDecode(json) as Map<String, Object?>);
```

For an image that should serialize as a short asset key instead of a
base64 blob, use `AssetImageSource` instead of `ImageSources.asset`
(which reads the bytes immediately):

```dart
Layers.image(
source: AssetImageSource('assets/logo.png'),
size: const Size(120, 40),
)
```

Every widget in this package resolves an `AssetImageSource` against the
ambient `AssetBundle` right before rendering, and its `LayerRegistry`
decoder is registered automatically the first time one is built or
deserialized — no setup call needed.

### Exporting a Scene

Every widget in this package always displays a `Scene` as PNG. To export
one instead — to a file, or as bytes in another format — use `Scenes`:

```dart
final pngBytes = await Scenes.encode(scene); // png by default
await Scenes.saveToFile(scene, '/path/to/export.qoi', format: OutputFormat.qoi);
```

Both resolve any `AssetImageSource` in `scene` first, same as `LayerCanvas`.

## Additional information

This package only depends on `layer_canvas` and re-exports only the pieces
of its API that this package's own public API surfaces as parameters or
return types: `Scene`, `Layer` and its subclasses (`RectangleLayer`,
`TextLayer`, `ImageLayer`, `PathLayer`, `Group`), `LayerImageSource` (with
`FileImageSource`/`MemoryImageSource`), `SvgDocument`/`SvgParseException`,
`Renderer`/`RenderException`, and `FontRegistry`/`FontRegistrationException`.
Value types the core exposes that `Layers` and the adapters exist
specifically to shield you from (`Color32`, `Point2D`/`Size2D`,
`TextWeight`, `TextAlignment`, `ImageFit`, `LayerPaint`, `LayerTransform`,
`FillRule`, and the core's own `Gradient`/`LinearGradient`/
`Renderer`/`RenderException`, `OutputFormat`,
`FontRegistry`/`FontRegistrationException`, and `LayerRegistry` (with its
`LayerFromJson`/`ImageSourceFromJson` typedefs, for registering a custom
`Layer`/`LayerImageSource` subclass of your own). Value types the core
exposes that `Layers` and the adapters exist specifically to shield you
from (`Color32`, `Point2D`/`Size2D`, `TextWeight`, `TextAlignment`,
`ImageFit`, `LayerPaint`, `LayerTransform`, `FillRule`, the core's own
`StrokeCap`/`StrokeJoin`, and its `Gradient`/`LinearGradient`/
`RadialGradient`/`ConicGradient`, which would otherwise collide with
Flutter's own same-named gradient types) are intentionally not re-exported
building UI with this package should never require importing
Flutter's own same-named types) are intentionally not re-exported
building UI with this package should never require importing
`package:layer_canvas` directly.

See [`layer_canvas`](https://pub.dev/packages/layer_canvas) and its
Expand Down
Binary file added doc/gallery/bauhaus-grid.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/gallery/constellation.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/gallery/editorial-card.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/gallery/gradient-burst.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/gallery/svg-pattern.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/gallery/vector-blob.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added example/assets/images/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading