├── .gitignore
├── .metadata
├── CHANGELOG.md
├── LICENSE
├── README.md
├── analysis_options.yaml
├── example
├── .gitignore
├── .metadata
├── README.md
├── analysis_options.yaml
├── integration_test
│ ├── bitmap_canvas_test.dart
│ └── goldens
│ │ └── random_noise.png
├── lib
│ ├── bitmap_canvas_demo.dart
│ ├── bitmap_painter_flow_field.dart
│ ├── bitmap_painter_meta_balls.dart
│ ├── bitmap_painter_perlin_noise.dart
│ ├── bitmap_painter_white_noise.dart
│ └── main.dart
├── macos
│ ├── .gitignore
│ ├── Flutter
│ │ ├── Flutter-Debug.xcconfig
│ │ ├── Flutter-Release.xcconfig
│ │ └── GeneratedPluginRegistrant.swift
│ ├── Podfile
│ ├── Podfile.lock
│ ├── Runner.xcodeproj
│ │ ├── project.pbxproj
│ │ ├── project.xcworkspace
│ │ │ └── xcshareddata
│ │ │ │ └── IDEWorkspaceChecks.plist
│ │ └── xcshareddata
│ │ │ └── xcschemes
│ │ │ └── Runner.xcscheme
│ ├── Runner.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── Runner
│ │ ├── AppDelegate.swift
│ │ ├── Assets.xcassets
│ │ └── AppIcon.appiconset
│ │ │ ├── Contents.json
│ │ │ ├── app_icon_1024.png
│ │ │ ├── app_icon_128.png
│ │ │ ├── app_icon_16.png
│ │ │ ├── app_icon_256.png
│ │ │ ├── app_icon_32.png
│ │ │ ├── app_icon_512.png
│ │ │ └── app_icon_64.png
│ │ ├── Base.lproj
│ │ └── MainMenu.xib
│ │ ├── Configs
│ │ ├── AppInfo.xcconfig
│ │ ├── Debug.xcconfig
│ │ ├── Release.xcconfig
│ │ └── Warnings.xcconfig
│ │ ├── DebugProfile.entitlements
│ │ ├── Info.plist
│ │ ├── MainFlutterWindow.swift
│ │ └── Release.entitlements
├── pubspec.lock
└── pubspec.yaml
├── lib
├── bitmap_canvas.dart
├── bitmap_canvas_logging.dart
└── src
│ ├── bitmap_canvas.dart
│ ├── bitmap_paint.dart
│ └── logging.dart
└── pubspec.yaml
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 | migrate_working_dir/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # The .vscode folder contains launch configuration and tasks you configure in
20 | # VS Code which you may wish to be included in version control, so this line
21 | # is commented out by default.
22 | #.vscode/
23 |
24 | # Flutter/Dart/Pub related
25 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
26 | /pubspec.lock
27 | **/doc/api/
28 | .dart_tool/
29 | .packages
30 | build/
31 |
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
8 | channel: stable
9 |
10 | project_type: package
11 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 0.0.1 - Initial release (August, 2022)
2 |
3 | * `BitmapCanvas` - paint pixels with a `Canvas`-style API
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Declarative, Inc
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | ---
12 | `bitmap_canvas` is a package that provides easy-to-use APIs for pixel painting with Dart, along with widgets to easily display those paintings.
13 |
14 |
15 |
16 |
17 |
18 |
19 | ## In the wild
20 | `bitmap_canvas` is the renderer for `flutter_processing`.
21 |
22 | ## Examples
23 | Paint animated static noise, where every pixel has a random brightness.
24 |
25 | ```dart
26 | Widget build(context) {
27 | // BitmapPaint is like CustomPaint, except that you can paint
28 | // individual pixels, too.
29 | return BitmapPaint(
30 | size: const Size(100, 100),
31 | painter: BitmapPainter.fromCallback((bitmapContext) async {
32 | final canvas = paintingContext.canvas;
33 | final size = paintingContext.size;
34 | final random = Random();
35 |
36 | await canvas.startBitmapTransaction();
37 |
38 | for (int x = 0; x < size.width; x += 1) {
39 | for (int y = 0; y < size.height; y += 1) {
40 | // This is where we paint an individual pixel.
41 | canvas.set(x: x, y: y, color: HSVColor.fromAHSV(1.0, 0, 0, random.nextDouble()).toColor());
42 | }
43 | }
44 |
45 | await canvas.endBitmapTransaction();
46 | }),
47 | );
48 | }
49 | ```
50 |
51 | ## Why do we need a bitmap canvas in Flutter?
52 | Flutter is built on top of SKIA, a portable rendering system, which supports hardware acceleration with shaders.
53 |
54 | You might wonder, if we want to paint individual pixels, shouldn't we use shaders? Software rendering is so slow!
55 |
56 | There are a few reasons that you might choose software rendering (i.e., painting pixels with Dart) over shaders:
57 |
58 | 1. Learning how to paint pixels in Dart is easier than with a shader language, like GLSL.
59 | 2. Shaders can't implement every style of pixel painting. For example, any pixel painting where one pixel depends on the value of another pixel is unsupported in shaders.
60 | 3. Flutter doesn't fully support custom shaders, which means that most pixel painting behaviors can't be implemented with shaders in Flutter.
61 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | include: package:flutter_lints/flutter.yaml
2 |
3 | # Additional information about this file can be found at
4 | # https://dart.dev/guides/language/analysis-options
5 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 | migrate_working_dir/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # The .vscode folder contains launch configuration and tasks you configure in
20 | # VS Code which you may wish to be included in version control, so this line
21 | # is commented out by default.
22 | #.vscode/
23 |
24 | # Flutter/Dart/Pub related
25 | **/doc/api/
26 | **/ios/Flutter/.last_build_id
27 | .dart_tool/
28 | .flutter-plugins
29 | .flutter-plugins-dependencies
30 | .packages
31 | .pub-cache/
32 | .pub/
33 | /build/
34 |
35 | # Web related
36 | lib/generated_plugin_registrant.dart
37 |
38 | # Symbolication related
39 | app.*.symbols
40 |
41 | # Obfuscation related
42 | app.*.map.json
43 |
44 | # Android Studio will place build artifacts here
45 | /android/app/debug
46 | /android/app/profile
47 | /android/app/release
48 |
--------------------------------------------------------------------------------
/example/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled.
5 |
6 | version:
7 | revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268
8 | channel: stable
9 |
10 | project_type: app
11 |
12 | # Tracks metadata for the flutter migrate command
13 | migration:
14 | platforms:
15 | - platform: root
16 | create_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268
17 | base_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268
18 | - platform: macos
19 | create_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268
20 | base_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268
21 |
22 | # User provided section
23 |
24 | # List of Local paths (relative to this file) that should be
25 | # ignored by the migrate tool.
26 | #
27 | # Files that are not part of the templates will be ignored by default.
28 | unmanaged_files:
29 | - 'lib/main.dart'
30 | - 'ios/Runner.xcodeproj/project.pbxproj'
31 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # Example App
2 | An example app that demonstrates `bitmap_canvas` in action.
3 |
--------------------------------------------------------------------------------
/example/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # This file configures the analyzer, which statically analyzes Dart code to
2 | # check for errors, warnings, and lints.
3 | #
4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled
5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
6 | # invoked from the command line by running `flutter analyze`.
7 |
8 | # The following line activates a set of recommended lints for Flutter apps,
9 | # packages, and plugins designed to encourage good coding practices.
10 | include: package:flutter_lints/flutter.yaml
11 |
12 | linter:
13 | # The lint rules applied to this project can be customized in the
14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml`
15 | # included above or to enable additional rules. A list of all available lints
16 | # and their documentation is published at
17 | # https://dart-lang.github.io/linter/lints/index.html.
18 | #
19 | # Instead of disabling a lint rule for the entire project in the
20 | # section below, it can also be suppressed for a single line of code
21 | # or a specific dart file by using the `// ignore: name_of_lint` and
22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file
23 | # producing the lint.
24 | rules:
25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule
26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
27 |
28 | # Additional information about this file can be found at
29 | # https://dart.dev/guides/language/analysis-options
30 |
--------------------------------------------------------------------------------
/example/integration_test/bitmap_canvas_test.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:math';
3 |
4 | import 'package:bitmap_canvas/src/bitmap_paint.dart';
5 | import 'package:flutter/material.dart';
6 | import 'package:flutter_test/flutter_test.dart';
7 |
8 | import 'package:bitmap_canvas/bitmap_canvas.dart';
9 | import 'package:golden_toolkit/golden_toolkit.dart';
10 | import 'package:integration_test/integration_test.dart';
11 |
12 | void main() {
13 | IntegrationTestWidgetsFlutterBinding.ensureInitialized();
14 |
15 | group("BitmapCanvas", () {
16 | testGoldens("paints random noise", (tester) async {
17 | await _runAtSize(tester, const Size(100, 100), (tester) async {
18 | final framePaintCompleter = Completer();
19 |
20 | runApp(
21 | MaterialApp(
22 | home: Scaffold(
23 | body: Center(
24 | child: BitmapPaint(
25 | size: const Size(100, 100),
26 | playbackMode: PlaybackMode.singleFrame,
27 | painter: BitmapPainter.fromCallback((paintingContext) async {
28 | print("TEST 1: Painting bitmap canvas");
29 | final canvas = paintingContext.canvas;
30 | final size = paintingContext.size;
31 |
32 | final random = Random(_randomSeed);
33 | print("TEST 2: Starting bitmap transaction");
34 | await canvas.startBitmapTransaction();
35 | print("TEST 3: Painting pixels");
36 | for (int x = 0; x < size.width; x += 1) {
37 | for (int y = 0; y < size.height; y += 1) {
38 | canvas.set(x: x, y: y, color: HSVColor.fromAHSV(1.0, 0, 0, random.nextDouble()).toColor());
39 | }
40 | }
41 | print("TEST 4: Ending bitmap transaction");
42 | await canvas.endBitmapTransaction();
43 |
44 | print("TEST 5: Done painting a frame");
45 | if (!framePaintCompleter.isCompleted) {
46 | framePaintCompleter.complete();
47 | }
48 | }),
49 | ),
50 | ),
51 | ),
52 | debugShowCheckedModeBanner: false,
53 | ),
54 | );
55 | // await tester.pump();
56 | // await tester.pump();
57 | // await tester.pump();
58 | // await tester.pump();
59 | // await tester.pump();
60 | // await tester.pump();
61 | // await tester.pump();
62 | // await tester.pump();
63 | await tester.pumpAndSettle();
64 |
65 | print("Waiting for frame paint completer to complete");
66 | // await framePaintCompleter.future;
67 | await Future.delayed(const Duration(seconds: 10));
68 |
69 | print("Painting golden");
70 | await screenMatchesGolden(tester, "random_noise", customPump: (tester) async {
71 | // no pump-and-settle
72 | });
73 | });
74 | });
75 | });
76 | }
77 |
78 | const _randomSeed = 123456;
79 |
80 | Future _runAtSize(WidgetTester tester, Size size, Future Function(WidgetTester) test) async {
81 | tester.binding.window
82 | ..physicalSizeTestValue = size
83 | ..devicePixelRatioTestValue = 1.0;
84 |
85 | await test(tester);
86 |
87 | tester.binding.window.clearAllTestValues();
88 | }
89 |
--------------------------------------------------------------------------------
/example/integration_test/goldens/random_noise.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Flutter-Bounty-Hunters/bitmap_canvas/720db9c4d12361c3235ef339fa5acf044c8b5de7/example/integration_test/goldens/random_noise.png
--------------------------------------------------------------------------------
/example/lib/bitmap_canvas_demo.dart:
--------------------------------------------------------------------------------
1 | import 'package:bitmap_canvas/bitmap_canvas.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | class BitmapCanvasDemo extends StatefulWidget {
5 | const BitmapCanvasDemo({
6 | Key? key,
7 | required this.bitmapPainter,
8 | }) : super(key: key);
9 |
10 | final BitmapPainter bitmapPainter;
11 |
12 | @override
13 | State createState() => _BitmapCanvasDemoState();
14 | }
15 |
16 | class _BitmapCanvasDemoState extends State implements BitmapPainter {
17 | final _frameTimes = [];
18 | int _frameRate = 0;
19 |
20 | @override
21 | Future paint(BitmapPaintingContext paintingContext) async {
22 | if (mounted) {
23 | setState(() {
24 | _frameTimes.add(paintingContext.timeSinceLastFrame);
25 | final millisPerFrame =
26 | _frameTimes.fold(0, (sum, frameTime) => sum + frameTime.inMilliseconds) / _frameTimes.length;
27 | if (millisPerFrame > 0) {
28 | _frameRate = (1000 / millisPerFrame).floor();
29 | }
30 | });
31 | }
32 |
33 | await widget.bitmapPainter.paint(paintingContext);
34 |
35 | // Draw bars at top and bottom
36 | final canvas = paintingContext.canvas;
37 | final size = paintingContext.size;
38 | canvas.drawRect(Offset.zero & Size(size.width, 10), Paint()..color = Colors.black);
39 | canvas.drawRect(Rect.fromLTWH(0, size.height - 10, size.width, 10), Paint()..color = Colors.black);
40 | }
41 |
42 | @override
43 | Widget build(BuildContext context) {
44 | return Stack(
45 | children: [
46 | BitmapPaint(
47 | size: const Size(100, 100),
48 | painter: this,
49 | playbackMode: PlaybackMode.continuous,
50 | ),
51 | Positioned(
52 | right: 0,
53 | bottom: 0,
54 | child: Container(
55 | padding: const EdgeInsets.all(8),
56 | color: Colors.black.withOpacity(0.5),
57 | child: Text(
58 | "$_frameRate FPS",
59 | style: const TextStyle(
60 | color: Colors.white,
61 | fontSize: 14,
62 | ),
63 | ),
64 | ),
65 | ),
66 | ],
67 | );
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/example/lib/bitmap_painter_flow_field.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | import 'package:bitmap_canvas/bitmap_canvas.dart';
4 | import 'package:fast_noise/fast_noise.dart';
5 | import 'package:flutter/material.dart';
6 |
7 | class FlowFieldPainter implements BitmapPainter {
8 | static const _pixelsPerFlowGrid = 10;
9 | static const _particleCount = 50;
10 |
11 | late List> _flowField;
12 | late List<_Particle> _particles;
13 | PerlinNoise? _perlinNoise;
14 | final int _perlinNoiseSeed = 1337;
15 | final int _perlinNoiseOctaves = 4;
16 | final double _perlinNoiseFalloff = 0.5;
17 | bool _hasPaintedFirstFrame = false;
18 |
19 | @override
20 | Future paint(BitmapPaintingContext paintingContext) async {
21 | if (!_hasPaintedFirstFrame) {
22 | _paintFirstFrame(paintingContext);
23 | }
24 |
25 | final width = paintingContext.size.width;
26 | final height = paintingContext.size.height;
27 |
28 | for (final particle in _particles) {
29 | final flowFieldX = (particle.position.dx.clamp(0, width - 1) / _pixelsPerFlowGrid).floor();
30 | final flowFieldY = (particle.position.dy.clamp(0, height - 1) / _pixelsPerFlowGrid).floor();
31 | particle.applyForce(_flowField[flowFieldX][flowFieldY]);
32 | particle.move();
33 |
34 | // Draw a line between the particles previous position and
35 | // current position.
36 | paintingContext.canvas.drawLine(
37 | particle.previousPosition,
38 | particle.position,
39 | Paint()
40 | ..color = Colors.purpleAccent.withOpacity(0.1)
41 | ..strokeWidth = 2
42 | ..style = PaintingStyle.stroke,
43 | );
44 |
45 | // If the particle moved off-screen, move it back on.
46 | if (particle.left >= width) {
47 | particle.position = Offset(0, particle.position.dy);
48 | particle.previousPosition = particle.position;
49 | }
50 | if (particle.right <= 0) {
51 | particle.position = Offset(width, particle.position.dy);
52 | particle.previousPosition = particle.position;
53 | }
54 | if (particle.top >= height) {
55 | particle.position = Offset(particle.position.dx, 0);
56 | particle.previousPosition = particle.position;
57 | }
58 | if (particle.bottom <= 0) {
59 | particle.position = Offset(particle.position.dx, height);
60 | particle.previousPosition = particle.position;
61 | }
62 | }
63 | }
64 |
65 | Future _paintFirstFrame(BitmapPaintingContext paintingContext) async {
66 | const width = 100;
67 | const height = 100;
68 | final cols = (width / _pixelsPerFlowGrid).round();
69 | final rows = (height / _pixelsPerFlowGrid).round();
70 |
71 | _flowField = List>.generate(
72 | cols,
73 | (_) => List.filled(
74 | rows,
75 | const Offset(0, 0),
76 | ),
77 | );
78 | for (int y = 0; y < rows; y += 1) {
79 | for (int x = 0; x < cols; x += 1) {
80 | final flowFieldVector = Offset.fromDirection(
81 | // Change the "z" value to get a different flow field pattern.
82 | noise(x: x * 1, y: y * 1, z: 0) * 2 * pi,
83 | );
84 |
85 | _flowField[x][y] = flowFieldVector;
86 | }
87 | }
88 |
89 | _particles = <_Particle>[];
90 | for (int i = 0; i < _particleCount; i += 1) {
91 | _particles.add(
92 | _Particle(
93 | position: Offset(random(0, width), random(0, height)),
94 | velocity: Offset.fromDirection(random(0, pi * 2) * 1),
95 | maxSpeed: 1,
96 | ),
97 | );
98 | }
99 |
100 | paintingContext.canvas.drawRect(
101 | Offset.zero & paintingContext.size,
102 | Paint()..color = Colors.deepPurple,
103 | );
104 |
105 | _hasPaintedFirstFrame = true;
106 | }
107 |
108 | double random(num bound1, [num? bound2]) {
109 | final lowerBound = bound2 != null ? bound1 : 0;
110 | final upperBound = bound2 ?? bound1;
111 |
112 | if (upperBound < lowerBound) {
113 | throw Exception('random() lower bound must be less than upper bound');
114 | }
115 |
116 | return Random().nextDouble() * (upperBound - lowerBound) + lowerBound;
117 | }
118 |
119 | double noise({
120 | required double x,
121 | double y = 0,
122 | double z = 0,
123 | }) {
124 | if (_perlinNoise == null) {
125 | _initializePerlinNoise();
126 | }
127 |
128 | return _perlinNoise!.getPerlin3(x, y, z);
129 | }
130 |
131 | void _initializePerlinNoise() {
132 | _perlinNoise = PerlinNoise(
133 | seed: _perlinNoiseSeed,
134 | octaves: _perlinNoiseOctaves,
135 | gain: _perlinNoiseFalloff,
136 | );
137 | }
138 | }
139 |
140 | class _Particle {
141 | _Particle({
142 | required this.position,
143 | required Offset velocity,
144 | double maxSpeed = 5.0,
145 | Offset? acceleration,
146 | }) : previousPosition = position,
147 | _velocity = velocity,
148 | _maxSpeed = maxSpeed,
149 | _acceleration = acceleration ?? const Offset(0, 0);
150 |
151 | Offset position;
152 | Offset previousPosition;
153 |
154 | double get left => position.dx - 1;
155 | double get right => position.dx + 1;
156 | double get top => position.dy - 1;
157 | double get bottom => position.dy + 1;
158 |
159 | Offset _velocity;
160 | final double _maxSpeed;
161 |
162 | Offset _acceleration;
163 |
164 | void move() {
165 | previousPosition = position;
166 |
167 | _velocity += _acceleration;
168 | _velocity = _limit(_velocity, _maxSpeed);
169 | position += _velocity;
170 | _acceleration = Offset.zero;
171 | }
172 |
173 | Offset _limit(Offset offset, double maxLength) {
174 | return Offset(
175 | maxLength * cos(offset.direction),
176 | maxLength * sin(offset.direction),
177 | );
178 | }
179 |
180 | void applyForce(Offset force) {
181 | _acceleration += force;
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/example/lib/bitmap_painter_meta_balls.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | import 'package:bitmap_canvas/bitmap_canvas.dart';
4 | import 'package:flutter/material.dart';
5 |
6 | class MetaBallsPainter implements BitmapPainter {
7 | MetaBallsPainter() {
8 | const width = 100;
9 | const height = 100;
10 | final random = Random();
11 | for (int i = 0; i < _blobCount; i += 1) {
12 | _blobs.add(
13 | Blob(
14 | offset: Offset(random.nextDouble() * width, random.nextDouble() * height),
15 | velocity: Offset.fromDirection(random.nextDouble() * (2 * pi), _blobSpeed),
16 | radius: _blobRadius,
17 | ),
18 | );
19 | }
20 | }
21 |
22 | final _blobCount = 3;
23 | final _blobRadius = 10.0;
24 | final _blobSpeed = 1.0;
25 |
26 | final _blobs = [];
27 |
28 | @override
29 | Future paint(BitmapPaintingContext paintingContext) async {
30 | final width = paintingContext.size.width;
31 | final height = paintingContext.size.height;
32 |
33 | paintingContext.canvas.drawRect(
34 | Offset.zero & paintingContext.size,
35 | Paint()..color = Colors.black,
36 | );
37 |
38 | await paintingContext.canvas.startBitmapTransaction();
39 |
40 | for (int col = 0; col < width; col += 1) {
41 | for (int row = 0; row < height; row += 1) {
42 | double sum = 0;
43 | for (final blob in _blobs) {
44 | final distance = (Offset(col.toDouble(), row.toDouble()) - blob.offset).distance;
45 |
46 | // Add to brightness
47 | sum += 0.75 * blob._radius / distance;
48 |
49 | // Colors
50 | // sum += 50 * blob.radius / distance;
51 | }
52 |
53 | // Brightness
54 | paintingContext.canvas
55 | .set(x: col, y: row, color: HSVColor.fromAHSV(1.0, 0.0, 0.0, sum.clamp(0.0, 1.0)).toColor());
56 |
57 | // Colors
58 | // set(x: col, y: row, color: HSVColor.fromAHSV(1.0, sum % 360, 1.0, 1.0).toColor());
59 | }
60 | }
61 |
62 | await paintingContext.canvas.endBitmapTransaction();
63 |
64 | final screenSize = Size(width.toDouble(), height.toDouble());
65 | for (final blob in _blobs) {
66 | blob.move(screenSize);
67 | // blob.paint(this);
68 | }
69 | }
70 | }
71 |
72 | class Blob {
73 | Blob({
74 | required Offset offset,
75 | required Offset velocity,
76 | required double radius,
77 | }) : _offset = offset,
78 | _velocity = velocity,
79 | _radius = radius;
80 |
81 | Offset _offset;
82 | Offset get offset => _offset;
83 |
84 | Offset _velocity;
85 |
86 | final double _radius;
87 | double get radius => _radius;
88 |
89 | void move(Size screenSize) {
90 | if (_offset.dx <= 0 || _offset.dx >= screenSize.width) {
91 | _velocity = Offset(-_velocity.dx, _velocity.dy);
92 | }
93 | if (_offset.dy <= 0 || _offset.dy >= screenSize.height) {
94 | _velocity = Offset(_velocity.dx, -_velocity.dy);
95 | }
96 |
97 | _offset += _velocity;
98 | }
99 |
100 | void paint(Canvas canvas) {
101 | canvas.drawCircle(
102 | _offset,
103 | _radius,
104 | Paint()
105 | ..color = Colors.white
106 | ..strokeWidth = 2
107 | ..style = PaintingStyle.stroke,
108 | );
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/example/lib/bitmap_painter_perlin_noise.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 | import 'dart:ui';
3 |
4 | import 'package:bitmap_canvas/bitmap_canvas.dart';
5 | import 'package:fast_noise/fast_noise.dart';
6 |
7 | class PerlinNoisePainter implements BitmapPainter {
8 | final int _perlinNoiseSeed = 1337;
9 | int _perlinNoiseOctaves = 4;
10 | double _perlinNoiseFalloff = 0.5;
11 | PerlinNoise? _perlinNoise;
12 |
13 | double z = 0.0;
14 |
15 | @override
16 | Future paint(BitmapPaintingContext paintingContext) async {
17 | await paintingContext.canvas.startBitmapTransaction();
18 |
19 | noiseDetail(octaves: 8);
20 |
21 | const increment = 1.5;
22 | double x = 0;
23 | double y = 0;
24 | for (int col = 0; col < paintingContext.size.width; col += 1) {
25 | for (int row = 0; row < paintingContext.size.height; row += 1) {
26 | // final grayAmount = s.random(0, 256).floor();
27 |
28 | final perlinNoiseValue = noise(x: x, y: y, z: z);
29 | final grayAmount = (((perlinNoiseValue + 1.0) / 2.0) * 255).round();
30 |
31 | final color = Color.fromARGB(255, grayAmount, grayAmount, grayAmount);
32 |
33 | paintingContext.canvas.set(x: col, y: row, color: color);
34 |
35 | y += increment;
36 | }
37 | x += increment;
38 | y = 0;
39 | }
40 |
41 | await paintingContext.canvas.endBitmapTransaction();
42 |
43 | z += 50 / max(paintingContext.timeSinceLastFrame.inMilliseconds, 1);
44 | }
45 |
46 | void noiseDetail({
47 | int? octaves,
48 | double? falloff,
49 | }) {
50 | _perlinNoiseOctaves = octaves ?? 4;
51 | _perlinNoiseFalloff = falloff ?? 0.5;
52 |
53 | _initializePerlinNoise();
54 | }
55 |
56 | double noise({
57 | required double x,
58 | double y = 0,
59 | double z = 0,
60 | }) {
61 | if (_perlinNoise == null) {
62 | _initializePerlinNoise();
63 | }
64 |
65 | return _perlinNoise!.getPerlin3(x, y, z);
66 | }
67 |
68 | void _initializePerlinNoise() {
69 | _perlinNoise = PerlinNoise(
70 | seed: _perlinNoiseSeed,
71 | octaves: _perlinNoiseOctaves,
72 | gain: _perlinNoiseFalloff,
73 | );
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/example/lib/bitmap_painter_white_noise.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | import 'package:bitmap_canvas/bitmap_canvas.dart';
4 | import 'package:flutter/material.dart';
5 |
6 | class NoiseBitmapPainter implements BitmapPainter {
7 | @override
8 | Future paint(BitmapPaintingContext paintingContext) async {
9 | final canvas = paintingContext.canvas;
10 | final size = paintingContext.size;
11 | final random = Random();
12 | await canvas.startBitmapTransaction();
13 | for (int x = 0; x < size.width; x += 1) {
14 | for (int y = 0; y < size.height; y += 1) {
15 | canvas.set(x: x, y: y, color: HSVColor.fromAHSV(1.0, 0, 0, random.nextDouble()).toColor());
16 | }
17 | }
18 | await canvas.endBitmapTransaction();
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/example/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:example/bitmap_painter_flow_field.dart';
2 | import 'package:example/bitmap_painter_meta_balls.dart';
3 | import 'package:example/bitmap_painter_perlin_noise.dart';
4 | import 'package:flutter/material.dart';
5 |
6 | import 'bitmap_canvas_demo.dart';
7 | import 'bitmap_painter_white_noise.dart';
8 |
9 | // TODO: If I change the scale of the BitmapPaint and hot reload, the
10 | // scale doesn't update. I have to hot restart.
11 |
12 | // TODO: add option to pause rendering when the widget is off-screen.
13 | // Use visibility_detector 0.3.3
14 |
15 | void main() {
16 | runApp(
17 | MaterialApp(
18 | home: Scaffold(
19 | body: Center(
20 | child: Wrap(
21 | spacing: 48,
22 | runSpacing: 48,
23 | children: [
24 | BitmapCanvasDemo(
25 | bitmapPainter: NoiseBitmapPainter(),
26 | ),
27 | BitmapCanvasDemo(
28 | bitmapPainter: FlowFieldPainter(),
29 | ),
30 | BitmapCanvasDemo(
31 | bitmapPainter: MetaBallsPainter(),
32 | ),
33 | BitmapCanvasDemo(
34 | bitmapPainter: PerlinNoisePainter(),
35 | ),
36 | ],
37 | ),
38 | ),
39 | ),
40 | ),
41 | );
42 | }
43 |
--------------------------------------------------------------------------------
/example/macos/.gitignore:
--------------------------------------------------------------------------------
1 | # Flutter-related
2 | **/Flutter/ephemeral/
3 | **/Pods/
4 |
5 | # Xcode-related
6 | **/dgph
7 | **/xcuserdata/
8 |
--------------------------------------------------------------------------------
/example/macos/Flutter/Flutter-Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "ephemeral/Flutter-Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/example/macos/Flutter/Flutter-Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "ephemeral/Flutter-Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/example/macos/Flutter/GeneratedPluginRegistrant.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | import FlutterMacOS
6 | import Foundation
7 |
8 |
9 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
10 | }
11 |
--------------------------------------------------------------------------------
/example/macos/Podfile:
--------------------------------------------------------------------------------
1 | platform :osx, '10.11'
2 |
3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
5 |
6 | project 'Runner', {
7 | 'Debug' => :debug,
8 | 'Profile' => :release,
9 | 'Release' => :release,
10 | }
11 |
12 | def flutter_root
13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
14 | unless File.exist?(generated_xcode_build_settings_path)
15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
16 | end
17 |
18 | File.foreach(generated_xcode_build_settings_path) do |line|
19 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
20 | return matches[1].strip if matches
21 | end
22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
23 | end
24 |
25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
26 |
27 | flutter_macos_podfile_setup
28 |
29 | target 'Runner' do
30 | use_frameworks!
31 | use_modular_headers!
32 |
33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
34 | end
35 |
36 | post_install do |installer|
37 | installer.pods_project.targets.each do |target|
38 | flutter_additional_macos_build_settings(target)
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/example/macos/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - FlutterMacOS (1.0.0)
3 |
4 | DEPENDENCIES:
5 | - FlutterMacOS (from `Flutter/ephemeral`)
6 |
7 | EXTERNAL SOURCES:
8 | FlutterMacOS:
9 | :path: Flutter/ephemeral
10 |
11 | SPEC CHECKSUMS:
12 | FlutterMacOS: 57701585bf7de1b3fc2bb61f6378d73bbdea8424
13 |
14 | PODFILE CHECKSUM: 6eac6b3292e5142cfc23bdeb71848a40ec51c14c
15 |
16 | COCOAPODS: 1.11.3
17 |
--------------------------------------------------------------------------------
/example/macos/Runner.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 51;
7 | objects = {
8 |
9 | /* Begin PBXAggregateTarget section */
10 | 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = {
11 | isa = PBXAggregateTarget;
12 | buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */;
13 | buildPhases = (
14 | 33CC111E2044C6BF0003C045 /* ShellScript */,
15 | );
16 | dependencies = (
17 | );
18 | name = "Flutter Assemble";
19 | productName = FLX;
20 | };
21 | /* End PBXAggregateTarget section */
22 |
23 | /* Begin PBXBuildFile section */
24 | 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
25 | 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
26 | 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
27 | 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
28 | 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
29 | 75C627CE5C19E3087D601D9F /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 179B10FB7D364C9DFE2799BC /* Pods_Runner.framework */; };
30 | /* End PBXBuildFile section */
31 |
32 | /* Begin PBXContainerItemProxy section */
33 | 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = {
34 | isa = PBXContainerItemProxy;
35 | containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
36 | proxyType = 1;
37 | remoteGlobalIDString = 33CC111A2044C6BA0003C045;
38 | remoteInfo = FLX;
39 | };
40 | /* End PBXContainerItemProxy section */
41 |
42 | /* Begin PBXCopyFilesBuildPhase section */
43 | 33CC110E2044A8840003C045 /* Bundle Framework */ = {
44 | isa = PBXCopyFilesBuildPhase;
45 | buildActionMask = 2147483647;
46 | dstPath = "";
47 | dstSubfolderSpec = 10;
48 | files = (
49 | );
50 | name = "Bundle Framework";
51 | runOnlyForDeploymentPostprocessing = 0;
52 | };
53 | /* End PBXCopyFilesBuildPhase section */
54 |
55 | /* Begin PBXFileReference section */
56 | 179B10FB7D364C9DFE2799BC /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
57 | 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; };
58 | 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; };
59 | 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example.app; sourceTree = BUILT_PRODUCTS_DIR; };
60 | 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
61 | 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; };
62 | 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; };
63 | 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; };
64 | 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; };
65 | 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; };
66 | 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; };
67 | 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; };
68 | 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; };
69 | 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; };
70 | 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; };
71 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; };
72 | 80A0443629BDAA5CE22CBE3F /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
73 | 863779B46C207396177D9097 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
74 | 962B327B384CBF25E08640BE /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
75 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; };
76 | /* End PBXFileReference section */
77 |
78 | /* Begin PBXFrameworksBuildPhase section */
79 | 33CC10EA2044A3C60003C045 /* Frameworks */ = {
80 | isa = PBXFrameworksBuildPhase;
81 | buildActionMask = 2147483647;
82 | files = (
83 | 75C627CE5C19E3087D601D9F /* Pods_Runner.framework in Frameworks */,
84 | );
85 | runOnlyForDeploymentPostprocessing = 0;
86 | };
87 | /* End PBXFrameworksBuildPhase section */
88 |
89 | /* Begin PBXGroup section */
90 | 0E40F85C126AA70CE5AC5B22 /* Pods */ = {
91 | isa = PBXGroup;
92 | children = (
93 | 80A0443629BDAA5CE22CBE3F /* Pods-Runner.debug.xcconfig */,
94 | 863779B46C207396177D9097 /* Pods-Runner.release.xcconfig */,
95 | 962B327B384CBF25E08640BE /* Pods-Runner.profile.xcconfig */,
96 | );
97 | name = Pods;
98 | path = Pods;
99 | sourceTree = "";
100 | };
101 | 33BA886A226E78AF003329D5 /* Configs */ = {
102 | isa = PBXGroup;
103 | children = (
104 | 33E5194F232828860026EE4D /* AppInfo.xcconfig */,
105 | 9740EEB21CF90195004384FC /* Debug.xcconfig */,
106 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
107 | 333000ED22D3DE5D00554162 /* Warnings.xcconfig */,
108 | );
109 | path = Configs;
110 | sourceTree = "";
111 | };
112 | 33CC10E42044A3C60003C045 = {
113 | isa = PBXGroup;
114 | children = (
115 | 33FAB671232836740065AC1E /* Runner */,
116 | 33CEB47122A05771004F2AC0 /* Flutter */,
117 | 33CC10EE2044A3C60003C045 /* Products */,
118 | D73912EC22F37F3D000D13A0 /* Frameworks */,
119 | 0E40F85C126AA70CE5AC5B22 /* Pods */,
120 | );
121 | sourceTree = "";
122 | };
123 | 33CC10EE2044A3C60003C045 /* Products */ = {
124 | isa = PBXGroup;
125 | children = (
126 | 33CC10ED2044A3C60003C045 /* example.app */,
127 | );
128 | name = Products;
129 | sourceTree = "";
130 | };
131 | 33CC11242044D66E0003C045 /* Resources */ = {
132 | isa = PBXGroup;
133 | children = (
134 | 33CC10F22044A3C60003C045 /* Assets.xcassets */,
135 | 33CC10F42044A3C60003C045 /* MainMenu.xib */,
136 | 33CC10F72044A3C60003C045 /* Info.plist */,
137 | );
138 | name = Resources;
139 | path = ..;
140 | sourceTree = "";
141 | };
142 | 33CEB47122A05771004F2AC0 /* Flutter */ = {
143 | isa = PBXGroup;
144 | children = (
145 | 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */,
146 | 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */,
147 | 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */,
148 | 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */,
149 | );
150 | path = Flutter;
151 | sourceTree = "";
152 | };
153 | 33FAB671232836740065AC1E /* Runner */ = {
154 | isa = PBXGroup;
155 | children = (
156 | 33CC10F02044A3C60003C045 /* AppDelegate.swift */,
157 | 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */,
158 | 33E51913231747F40026EE4D /* DebugProfile.entitlements */,
159 | 33E51914231749380026EE4D /* Release.entitlements */,
160 | 33CC11242044D66E0003C045 /* Resources */,
161 | 33BA886A226E78AF003329D5 /* Configs */,
162 | );
163 | path = Runner;
164 | sourceTree = "";
165 | };
166 | D73912EC22F37F3D000D13A0 /* Frameworks */ = {
167 | isa = PBXGroup;
168 | children = (
169 | 179B10FB7D364C9DFE2799BC /* Pods_Runner.framework */,
170 | );
171 | name = Frameworks;
172 | sourceTree = "";
173 | };
174 | /* End PBXGroup section */
175 |
176 | /* Begin PBXNativeTarget section */
177 | 33CC10EC2044A3C60003C045 /* Runner */ = {
178 | isa = PBXNativeTarget;
179 | buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
180 | buildPhases = (
181 | A08AB93599E785432788BBF7 /* [CP] Check Pods Manifest.lock */,
182 | 33CC10E92044A3C60003C045 /* Sources */,
183 | 33CC10EA2044A3C60003C045 /* Frameworks */,
184 | 33CC10EB2044A3C60003C045 /* Resources */,
185 | 33CC110E2044A8840003C045 /* Bundle Framework */,
186 | 3399D490228B24CF009A79C7 /* ShellScript */,
187 | );
188 | buildRules = (
189 | );
190 | dependencies = (
191 | 33CC11202044C79F0003C045 /* PBXTargetDependency */,
192 | );
193 | name = Runner;
194 | productName = Runner;
195 | productReference = 33CC10ED2044A3C60003C045 /* example.app */;
196 | productType = "com.apple.product-type.application";
197 | };
198 | /* End PBXNativeTarget section */
199 |
200 | /* Begin PBXProject section */
201 | 33CC10E52044A3C60003C045 /* Project object */ = {
202 | isa = PBXProject;
203 | attributes = {
204 | LastSwiftUpdateCheck = 0920;
205 | LastUpgradeCheck = 1300;
206 | ORGANIZATIONNAME = "";
207 | TargetAttributes = {
208 | 33CC10EC2044A3C60003C045 = {
209 | CreatedOnToolsVersion = 9.2;
210 | LastSwiftMigration = 1100;
211 | ProvisioningStyle = Automatic;
212 | SystemCapabilities = {
213 | com.apple.Sandbox = {
214 | enabled = 1;
215 | };
216 | };
217 | };
218 | 33CC111A2044C6BA0003C045 = {
219 | CreatedOnToolsVersion = 9.2;
220 | ProvisioningStyle = Manual;
221 | };
222 | };
223 | };
224 | buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */;
225 | compatibilityVersion = "Xcode 9.3";
226 | developmentRegion = en;
227 | hasScannedForEncodings = 0;
228 | knownRegions = (
229 | en,
230 | Base,
231 | );
232 | mainGroup = 33CC10E42044A3C60003C045;
233 | productRefGroup = 33CC10EE2044A3C60003C045 /* Products */;
234 | projectDirPath = "";
235 | projectRoot = "";
236 | targets = (
237 | 33CC10EC2044A3C60003C045 /* Runner */,
238 | 33CC111A2044C6BA0003C045 /* Flutter Assemble */,
239 | );
240 | };
241 | /* End PBXProject section */
242 |
243 | /* Begin PBXResourcesBuildPhase section */
244 | 33CC10EB2044A3C60003C045 /* Resources */ = {
245 | isa = PBXResourcesBuildPhase;
246 | buildActionMask = 2147483647;
247 | files = (
248 | 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */,
249 | 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */,
250 | );
251 | runOnlyForDeploymentPostprocessing = 0;
252 | };
253 | /* End PBXResourcesBuildPhase section */
254 |
255 | /* Begin PBXShellScriptBuildPhase section */
256 | 3399D490228B24CF009A79C7 /* ShellScript */ = {
257 | isa = PBXShellScriptBuildPhase;
258 | buildActionMask = 2147483647;
259 | files = (
260 | );
261 | inputFileListPaths = (
262 | );
263 | inputPaths = (
264 | );
265 | outputFileListPaths = (
266 | );
267 | outputPaths = (
268 | );
269 | runOnlyForDeploymentPostprocessing = 0;
270 | shellPath = /bin/sh;
271 | shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n";
272 | };
273 | 33CC111E2044C6BF0003C045 /* ShellScript */ = {
274 | isa = PBXShellScriptBuildPhase;
275 | buildActionMask = 2147483647;
276 | files = (
277 | );
278 | inputFileListPaths = (
279 | Flutter/ephemeral/FlutterInputs.xcfilelist,
280 | );
281 | inputPaths = (
282 | Flutter/ephemeral/tripwire,
283 | );
284 | outputFileListPaths = (
285 | Flutter/ephemeral/FlutterOutputs.xcfilelist,
286 | );
287 | outputPaths = (
288 | );
289 | runOnlyForDeploymentPostprocessing = 0;
290 | shellPath = /bin/sh;
291 | shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
292 | };
293 | A08AB93599E785432788BBF7 /* [CP] Check Pods Manifest.lock */ = {
294 | isa = PBXShellScriptBuildPhase;
295 | buildActionMask = 2147483647;
296 | files = (
297 | );
298 | inputFileListPaths = (
299 | );
300 | inputPaths = (
301 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
302 | "${PODS_ROOT}/Manifest.lock",
303 | );
304 | name = "[CP] Check Pods Manifest.lock";
305 | outputFileListPaths = (
306 | );
307 | outputPaths = (
308 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
309 | );
310 | runOnlyForDeploymentPostprocessing = 0;
311 | shellPath = /bin/sh;
312 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
313 | showEnvVarsInLog = 0;
314 | };
315 | /* End PBXShellScriptBuildPhase section */
316 |
317 | /* Begin PBXSourcesBuildPhase section */
318 | 33CC10E92044A3C60003C045 /* Sources */ = {
319 | isa = PBXSourcesBuildPhase;
320 | buildActionMask = 2147483647;
321 | files = (
322 | 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */,
323 | 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */,
324 | 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */,
325 | );
326 | runOnlyForDeploymentPostprocessing = 0;
327 | };
328 | /* End PBXSourcesBuildPhase section */
329 |
330 | /* Begin PBXTargetDependency section */
331 | 33CC11202044C79F0003C045 /* PBXTargetDependency */ = {
332 | isa = PBXTargetDependency;
333 | target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */;
334 | targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */;
335 | };
336 | /* End PBXTargetDependency section */
337 |
338 | /* Begin PBXVariantGroup section */
339 | 33CC10F42044A3C60003C045 /* MainMenu.xib */ = {
340 | isa = PBXVariantGroup;
341 | children = (
342 | 33CC10F52044A3C60003C045 /* Base */,
343 | );
344 | name = MainMenu.xib;
345 | path = Runner;
346 | sourceTree = "";
347 | };
348 | /* End PBXVariantGroup section */
349 |
350 | /* Begin XCBuildConfiguration section */
351 | 338D0CE9231458BD00FA5F75 /* Profile */ = {
352 | isa = XCBuildConfiguration;
353 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
354 | buildSettings = {
355 | ALWAYS_SEARCH_USER_PATHS = NO;
356 | CLANG_ANALYZER_NONNULL = YES;
357 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
358 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
359 | CLANG_CXX_LIBRARY = "libc++";
360 | CLANG_ENABLE_MODULES = YES;
361 | CLANG_ENABLE_OBJC_ARC = YES;
362 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
363 | CLANG_WARN_BOOL_CONVERSION = YES;
364 | CLANG_WARN_CONSTANT_CONVERSION = YES;
365 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
366 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
367 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
368 | CLANG_WARN_EMPTY_BODY = YES;
369 | CLANG_WARN_ENUM_CONVERSION = YES;
370 | CLANG_WARN_INFINITE_RECURSION = YES;
371 | CLANG_WARN_INT_CONVERSION = YES;
372 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
373 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
374 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
375 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
376 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
377 | CODE_SIGN_IDENTITY = "-";
378 | COPY_PHASE_STRIP = NO;
379 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
380 | ENABLE_NS_ASSERTIONS = NO;
381 | ENABLE_STRICT_OBJC_MSGSEND = YES;
382 | GCC_C_LANGUAGE_STANDARD = gnu11;
383 | GCC_NO_COMMON_BLOCKS = YES;
384 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
385 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
386 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
387 | GCC_WARN_UNUSED_FUNCTION = YES;
388 | GCC_WARN_UNUSED_VARIABLE = YES;
389 | MACOSX_DEPLOYMENT_TARGET = 10.11;
390 | MTL_ENABLE_DEBUG_INFO = NO;
391 | SDKROOT = macosx;
392 | SWIFT_COMPILATION_MODE = wholemodule;
393 | SWIFT_OPTIMIZATION_LEVEL = "-O";
394 | };
395 | name = Profile;
396 | };
397 | 338D0CEA231458BD00FA5F75 /* Profile */ = {
398 | isa = XCBuildConfiguration;
399 | baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
400 | buildSettings = {
401 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
402 | CLANG_ENABLE_MODULES = YES;
403 | CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
404 | CODE_SIGN_STYLE = Automatic;
405 | COMBINE_HIDPI_IMAGES = YES;
406 | INFOPLIST_FILE = Runner/Info.plist;
407 | LD_RUNPATH_SEARCH_PATHS = (
408 | "$(inherited)",
409 | "@executable_path/../Frameworks",
410 | );
411 | PROVISIONING_PROFILE_SPECIFIER = "";
412 | SWIFT_VERSION = 5.0;
413 | };
414 | name = Profile;
415 | };
416 | 338D0CEB231458BD00FA5F75 /* Profile */ = {
417 | isa = XCBuildConfiguration;
418 | buildSettings = {
419 | CODE_SIGN_STYLE = Manual;
420 | PRODUCT_NAME = "$(TARGET_NAME)";
421 | };
422 | name = Profile;
423 | };
424 | 33CC10F92044A3C60003C045 /* Debug */ = {
425 | isa = XCBuildConfiguration;
426 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
427 | buildSettings = {
428 | ALWAYS_SEARCH_USER_PATHS = NO;
429 | CLANG_ANALYZER_NONNULL = YES;
430 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
431 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
432 | CLANG_CXX_LIBRARY = "libc++";
433 | CLANG_ENABLE_MODULES = YES;
434 | CLANG_ENABLE_OBJC_ARC = YES;
435 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
436 | CLANG_WARN_BOOL_CONVERSION = YES;
437 | CLANG_WARN_CONSTANT_CONVERSION = YES;
438 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
439 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
440 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
441 | CLANG_WARN_EMPTY_BODY = YES;
442 | CLANG_WARN_ENUM_CONVERSION = YES;
443 | CLANG_WARN_INFINITE_RECURSION = YES;
444 | CLANG_WARN_INT_CONVERSION = YES;
445 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
446 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
447 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
448 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
449 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
450 | CODE_SIGN_IDENTITY = "-";
451 | COPY_PHASE_STRIP = NO;
452 | DEBUG_INFORMATION_FORMAT = dwarf;
453 | ENABLE_STRICT_OBJC_MSGSEND = YES;
454 | ENABLE_TESTABILITY = YES;
455 | GCC_C_LANGUAGE_STANDARD = gnu11;
456 | GCC_DYNAMIC_NO_PIC = NO;
457 | GCC_NO_COMMON_BLOCKS = YES;
458 | GCC_OPTIMIZATION_LEVEL = 0;
459 | GCC_PREPROCESSOR_DEFINITIONS = (
460 | "DEBUG=1",
461 | "$(inherited)",
462 | );
463 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
464 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
465 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
466 | GCC_WARN_UNUSED_FUNCTION = YES;
467 | GCC_WARN_UNUSED_VARIABLE = YES;
468 | MACOSX_DEPLOYMENT_TARGET = 10.11;
469 | MTL_ENABLE_DEBUG_INFO = YES;
470 | ONLY_ACTIVE_ARCH = YES;
471 | SDKROOT = macosx;
472 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
473 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
474 | };
475 | name = Debug;
476 | };
477 | 33CC10FA2044A3C60003C045 /* Release */ = {
478 | isa = XCBuildConfiguration;
479 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
480 | buildSettings = {
481 | ALWAYS_SEARCH_USER_PATHS = NO;
482 | CLANG_ANALYZER_NONNULL = YES;
483 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
484 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
485 | CLANG_CXX_LIBRARY = "libc++";
486 | CLANG_ENABLE_MODULES = YES;
487 | CLANG_ENABLE_OBJC_ARC = YES;
488 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
489 | CLANG_WARN_BOOL_CONVERSION = YES;
490 | CLANG_WARN_CONSTANT_CONVERSION = YES;
491 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
492 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
493 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
494 | CLANG_WARN_EMPTY_BODY = YES;
495 | CLANG_WARN_ENUM_CONVERSION = YES;
496 | CLANG_WARN_INFINITE_RECURSION = YES;
497 | CLANG_WARN_INT_CONVERSION = YES;
498 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
499 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
500 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
501 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
502 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
503 | CODE_SIGN_IDENTITY = "-";
504 | COPY_PHASE_STRIP = NO;
505 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
506 | ENABLE_NS_ASSERTIONS = NO;
507 | ENABLE_STRICT_OBJC_MSGSEND = YES;
508 | GCC_C_LANGUAGE_STANDARD = gnu11;
509 | GCC_NO_COMMON_BLOCKS = YES;
510 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
511 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
512 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
513 | GCC_WARN_UNUSED_FUNCTION = YES;
514 | GCC_WARN_UNUSED_VARIABLE = YES;
515 | MACOSX_DEPLOYMENT_TARGET = 10.11;
516 | MTL_ENABLE_DEBUG_INFO = NO;
517 | SDKROOT = macosx;
518 | SWIFT_COMPILATION_MODE = wholemodule;
519 | SWIFT_OPTIMIZATION_LEVEL = "-O";
520 | };
521 | name = Release;
522 | };
523 | 33CC10FC2044A3C60003C045 /* Debug */ = {
524 | isa = XCBuildConfiguration;
525 | baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
526 | buildSettings = {
527 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
528 | CLANG_ENABLE_MODULES = YES;
529 | CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
530 | CODE_SIGN_STYLE = Automatic;
531 | COMBINE_HIDPI_IMAGES = YES;
532 | INFOPLIST_FILE = Runner/Info.plist;
533 | LD_RUNPATH_SEARCH_PATHS = (
534 | "$(inherited)",
535 | "@executable_path/../Frameworks",
536 | );
537 | PROVISIONING_PROFILE_SPECIFIER = "";
538 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
539 | SWIFT_VERSION = 5.0;
540 | };
541 | name = Debug;
542 | };
543 | 33CC10FD2044A3C60003C045 /* Release */ = {
544 | isa = XCBuildConfiguration;
545 | baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
546 | buildSettings = {
547 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
548 | CLANG_ENABLE_MODULES = YES;
549 | CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
550 | CODE_SIGN_STYLE = Automatic;
551 | COMBINE_HIDPI_IMAGES = YES;
552 | INFOPLIST_FILE = Runner/Info.plist;
553 | LD_RUNPATH_SEARCH_PATHS = (
554 | "$(inherited)",
555 | "@executable_path/../Frameworks",
556 | );
557 | PROVISIONING_PROFILE_SPECIFIER = "";
558 | SWIFT_VERSION = 5.0;
559 | };
560 | name = Release;
561 | };
562 | 33CC111C2044C6BA0003C045 /* Debug */ = {
563 | isa = XCBuildConfiguration;
564 | buildSettings = {
565 | CODE_SIGN_STYLE = Manual;
566 | PRODUCT_NAME = "$(TARGET_NAME)";
567 | };
568 | name = Debug;
569 | };
570 | 33CC111D2044C6BA0003C045 /* Release */ = {
571 | isa = XCBuildConfiguration;
572 | buildSettings = {
573 | CODE_SIGN_STYLE = Automatic;
574 | PRODUCT_NAME = "$(TARGET_NAME)";
575 | };
576 | name = Release;
577 | };
578 | /* End XCBuildConfiguration section */
579 |
580 | /* Begin XCConfigurationList section */
581 | 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = {
582 | isa = XCConfigurationList;
583 | buildConfigurations = (
584 | 33CC10F92044A3C60003C045 /* Debug */,
585 | 33CC10FA2044A3C60003C045 /* Release */,
586 | 338D0CE9231458BD00FA5F75 /* Profile */,
587 | );
588 | defaultConfigurationIsVisible = 0;
589 | defaultConfigurationName = Release;
590 | };
591 | 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = {
592 | isa = XCConfigurationList;
593 | buildConfigurations = (
594 | 33CC10FC2044A3C60003C045 /* Debug */,
595 | 33CC10FD2044A3C60003C045 /* Release */,
596 | 338D0CEA231458BD00FA5F75 /* Profile */,
597 | );
598 | defaultConfigurationIsVisible = 0;
599 | defaultConfigurationName = Release;
600 | };
601 | 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = {
602 | isa = XCConfigurationList;
603 | buildConfigurations = (
604 | 33CC111C2044C6BA0003C045 /* Debug */,
605 | 33CC111D2044C6BA0003C045 /* Release */,
606 | 338D0CEB231458BD00FA5F75 /* Profile */,
607 | );
608 | defaultConfigurationIsVisible = 0;
609 | defaultConfigurationName = Release;
610 | };
611 | /* End XCConfigurationList section */
612 | };
613 | rootObject = 33CC10E52044A3C60003C045 /* Project object */;
614 | }
615 |
--------------------------------------------------------------------------------
/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
52 |
54 |
60 |
61 |
62 |
63 |
69 |
71 |
77 |
78 |
79 |
80 |
82 |
83 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/example/macos/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/macos/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 | import FlutterMacOS
3 |
4 | @NSApplicationMain
5 | class AppDelegate: FlutterAppDelegate {
6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
7 | return true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "16x16",
5 | "idiom" : "mac",
6 | "filename" : "app_icon_16.png",
7 | "scale" : "1x"
8 | },
9 | {
10 | "size" : "16x16",
11 | "idiom" : "mac",
12 | "filename" : "app_icon_32.png",
13 | "scale" : "2x"
14 | },
15 | {
16 | "size" : "32x32",
17 | "idiom" : "mac",
18 | "filename" : "app_icon_32.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "32x32",
23 | "idiom" : "mac",
24 | "filename" : "app_icon_64.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "128x128",
29 | "idiom" : "mac",
30 | "filename" : "app_icon_128.png",
31 | "scale" : "1x"
32 | },
33 | {
34 | "size" : "128x128",
35 | "idiom" : "mac",
36 | "filename" : "app_icon_256.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "256x256",
41 | "idiom" : "mac",
42 | "filename" : "app_icon_256.png",
43 | "scale" : "1x"
44 | },
45 | {
46 | "size" : "256x256",
47 | "idiom" : "mac",
48 | "filename" : "app_icon_512.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "512x512",
53 | "idiom" : "mac",
54 | "filename" : "app_icon_512.png",
55 | "scale" : "1x"
56 | },
57 | {
58 | "size" : "512x512",
59 | "idiom" : "mac",
60 | "filename" : "app_icon_1024.png",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Flutter-Bounty-Hunters/bitmap_canvas/720db9c4d12361c3235ef339fa5acf044c8b5de7/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Flutter-Bounty-Hunters/bitmap_canvas/720db9c4d12361c3235ef339fa5acf044c8b5de7/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Flutter-Bounty-Hunters/bitmap_canvas/720db9c4d12361c3235ef339fa5acf044c8b5de7/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Flutter-Bounty-Hunters/bitmap_canvas/720db9c4d12361c3235ef339fa5acf044c8b5de7/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Flutter-Bounty-Hunters/bitmap_canvas/720db9c4d12361c3235ef339fa5acf044c8b5de7/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Flutter-Bounty-Hunters/bitmap_canvas/720db9c4d12361c3235ef339fa5acf044c8b5de7/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Flutter-Bounty-Hunters/bitmap_canvas/720db9c4d12361c3235ef339fa5acf044c8b5de7/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png
--------------------------------------------------------------------------------
/example/macos/Runner/Base.lproj/MainMenu.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
--------------------------------------------------------------------------------
/example/macos/Runner/Configs/AppInfo.xcconfig:
--------------------------------------------------------------------------------
1 | // Application-level settings for the Runner target.
2 | //
3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the
4 | // future. If not, the values below would default to using the project name when this becomes a
5 | // 'flutter create' template.
6 |
7 | // The application's name. By default this is also the title of the Flutter window.
8 | PRODUCT_NAME = example
9 |
10 | // The application's bundle identifier
11 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example
12 |
13 | // The copyright displayed in application information
14 | PRODUCT_COPYRIGHT = Copyright © 2022 com.example. All rights reserved.
15 |
--------------------------------------------------------------------------------
/example/macos/Runner/Configs/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "../../Flutter/Flutter-Debug.xcconfig"
2 | #include "Warnings.xcconfig"
3 |
--------------------------------------------------------------------------------
/example/macos/Runner/Configs/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "../../Flutter/Flutter-Release.xcconfig"
2 | #include "Warnings.xcconfig"
3 |
--------------------------------------------------------------------------------
/example/macos/Runner/Configs/Warnings.xcconfig:
--------------------------------------------------------------------------------
1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings
2 | GCC_WARN_UNDECLARED_SELECTOR = YES
3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES
4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE
5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES
6 | CLANG_WARN_PRAGMA_PACK = YES
7 | CLANG_WARN_STRICT_PROTOTYPES = YES
8 | CLANG_WARN_COMMA = YES
9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES
10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES
11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES
12 | GCC_WARN_SHADOW = YES
13 | CLANG_WARN_UNREACHABLE_CODE = YES
14 |
--------------------------------------------------------------------------------
/example/macos/Runner/DebugProfile.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.cs.allow-jit
8 |
9 | com.apple.security.network.server
10 |
11 | com.apple.security.temporary-exception.files.absolute-path.read-write
12 |
13 | /Volumes/G-RAID/SuperDeclarative/Properties/flutterbountyhunters_com/products/bitmap_canvas/example/integration_test/goldens/
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/example/macos/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(FLUTTER_BUILD_NAME)
21 | CFBundleVersion
22 | $(FLUTTER_BUILD_NUMBER)
23 | LSMinimumSystemVersion
24 | $(MACOSX_DEPLOYMENT_TARGET)
25 | NSHumanReadableCopyright
26 | $(PRODUCT_COPYRIGHT)
27 | NSMainNibFile
28 | MainMenu
29 | NSPrincipalClass
30 | NSApplication
31 |
32 |
33 |
--------------------------------------------------------------------------------
/example/macos/Runner/MainFlutterWindow.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 | import FlutterMacOS
3 |
4 | class MainFlutterWindow: NSWindow {
5 | override func awakeFromNib() {
6 | let flutterViewController = FlutterViewController.init()
7 | let windowFrame = self.frame
8 | self.contentViewController = flutterViewController
9 | self.setFrame(windowFrame, display: true)
10 |
11 | RegisterGeneratedPlugins(registry: flutterViewController)
12 |
13 | super.awakeFromNib()
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/example/macos/Runner/Release.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/pubspec.lock:
--------------------------------------------------------------------------------
1 | # Generated by pub
2 | # See https://dart.dev/tools/pub/glossary#lockfile
3 | packages:
4 | archive:
5 | dependency: transitive
6 | description:
7 | name: archive
8 | url: "https://pub.dartlang.org"
9 | source: hosted
10 | version: "3.1.11"
11 | async:
12 | dependency: transitive
13 | description:
14 | name: async
15 | url: "https://pub.dartlang.org"
16 | source: hosted
17 | version: "2.8.2"
18 | bitmap_canvas:
19 | dependency: "direct main"
20 | description:
21 | path: ".."
22 | relative: true
23 | source: path
24 | version: "0.0.1"
25 | boolean_selector:
26 | dependency: transitive
27 | description:
28 | name: boolean_selector
29 | url: "https://pub.dartlang.org"
30 | source: hosted
31 | version: "2.1.0"
32 | characters:
33 | dependency: transitive
34 | description:
35 | name: characters
36 | url: "https://pub.dartlang.org"
37 | source: hosted
38 | version: "1.2.0"
39 | charcode:
40 | dependency: transitive
41 | description:
42 | name: charcode
43 | url: "https://pub.dartlang.org"
44 | source: hosted
45 | version: "1.3.1"
46 | clock:
47 | dependency: transitive
48 | description:
49 | name: clock
50 | url: "https://pub.dartlang.org"
51 | source: hosted
52 | version: "1.1.0"
53 | collection:
54 | dependency: transitive
55 | description:
56 | name: collection
57 | url: "https://pub.dartlang.org"
58 | source: hosted
59 | version: "1.16.0"
60 | crypto:
61 | dependency: transitive
62 | description:
63 | name: crypto
64 | url: "https://pub.dartlang.org"
65 | source: hosted
66 | version: "3.0.1"
67 | fake_async:
68 | dependency: transitive
69 | description:
70 | name: fake_async
71 | url: "https://pub.dartlang.org"
72 | source: hosted
73 | version: "1.3.0"
74 | fast_noise:
75 | dependency: "direct main"
76 | description:
77 | name: fast_noise
78 | url: "https://pub.dartlang.org"
79 | source: hosted
80 | version: "1.0.1"
81 | file:
82 | dependency: transitive
83 | description:
84 | name: file
85 | url: "https://pub.dartlang.org"
86 | source: hosted
87 | version: "6.1.2"
88 | fixnum:
89 | dependency: transitive
90 | description:
91 | name: fixnum
92 | url: "https://pub.dartlang.org"
93 | source: hosted
94 | version: "1.0.1"
95 | flutter:
96 | dependency: "direct main"
97 | description: flutter
98 | source: sdk
99 | version: "0.0.0"
100 | flutter_driver:
101 | dependency: transitive
102 | description: flutter
103 | source: sdk
104 | version: "0.0.0"
105 | flutter_lints:
106 | dependency: "direct dev"
107 | description:
108 | name: flutter_lints
109 | url: "https://pub.dartlang.org"
110 | source: hosted
111 | version: "2.0.1"
112 | flutter_test:
113 | dependency: "direct dev"
114 | description: flutter
115 | source: sdk
116 | version: "0.0.0"
117 | fuchsia_remote_debug_protocol:
118 | dependency: transitive
119 | description: flutter
120 | source: sdk
121 | version: "0.0.0"
122 | golden_toolkit:
123 | dependency: "direct dev"
124 | description:
125 | name: golden_toolkit
126 | url: "https://pub.dartlang.org"
127 | source: hosted
128 | version: "0.13.0"
129 | integration_test:
130 | dependency: "direct dev"
131 | description: flutter
132 | source: sdk
133 | version: "0.0.0"
134 | lints:
135 | dependency: transitive
136 | description:
137 | name: lints
138 | url: "https://pub.dartlang.org"
139 | source: hosted
140 | version: "2.0.0"
141 | logging:
142 | dependency: transitive
143 | description:
144 | name: logging
145 | url: "https://pub.dartlang.org"
146 | source: hosted
147 | version: "1.0.2"
148 | matcher:
149 | dependency: transitive
150 | description:
151 | name: matcher
152 | url: "https://pub.dartlang.org"
153 | source: hosted
154 | version: "0.12.11"
155 | material_color_utilities:
156 | dependency: transitive
157 | description:
158 | name: material_color_utilities
159 | url: "https://pub.dartlang.org"
160 | source: hosted
161 | version: "0.1.4"
162 | meta:
163 | dependency: transitive
164 | description:
165 | name: meta
166 | url: "https://pub.dartlang.org"
167 | source: hosted
168 | version: "1.7.0"
169 | path:
170 | dependency: transitive
171 | description:
172 | name: path
173 | url: "https://pub.dartlang.org"
174 | source: hosted
175 | version: "1.8.1"
176 | platform:
177 | dependency: transitive
178 | description:
179 | name: platform
180 | url: "https://pub.dartlang.org"
181 | source: hosted
182 | version: "3.1.0"
183 | process:
184 | dependency: transitive
185 | description:
186 | name: process
187 | url: "https://pub.dartlang.org"
188 | source: hosted
189 | version: "4.2.4"
190 | sky_engine:
191 | dependency: transitive
192 | description: flutter
193 | source: sdk
194 | version: "0.0.99"
195 | source_span:
196 | dependency: transitive
197 | description:
198 | name: source_span
199 | url: "https://pub.dartlang.org"
200 | source: hosted
201 | version: "1.8.2"
202 | stack_trace:
203 | dependency: transitive
204 | description:
205 | name: stack_trace
206 | url: "https://pub.dartlang.org"
207 | source: hosted
208 | version: "1.10.0"
209 | stream_channel:
210 | dependency: transitive
211 | description:
212 | name: stream_channel
213 | url: "https://pub.dartlang.org"
214 | source: hosted
215 | version: "2.1.0"
216 | string_scanner:
217 | dependency: transitive
218 | description:
219 | name: string_scanner
220 | url: "https://pub.dartlang.org"
221 | source: hosted
222 | version: "1.1.0"
223 | sync_http:
224 | dependency: transitive
225 | description:
226 | name: sync_http
227 | url: "https://pub.dartlang.org"
228 | source: hosted
229 | version: "0.3.0"
230 | term_glyph:
231 | dependency: transitive
232 | description:
233 | name: term_glyph
234 | url: "https://pub.dartlang.org"
235 | source: hosted
236 | version: "1.2.0"
237 | test_api:
238 | dependency: transitive
239 | description:
240 | name: test_api
241 | url: "https://pub.dartlang.org"
242 | source: hosted
243 | version: "0.4.9"
244 | typed_data:
245 | dependency: transitive
246 | description:
247 | name: typed_data
248 | url: "https://pub.dartlang.org"
249 | source: hosted
250 | version: "1.3.0"
251 | vector_math:
252 | dependency: transitive
253 | description:
254 | name: vector_math
255 | url: "https://pub.dartlang.org"
256 | source: hosted
257 | version: "2.1.2"
258 | vm_service:
259 | dependency: transitive
260 | description:
261 | name: vm_service
262 | url: "https://pub.dartlang.org"
263 | source: hosted
264 | version: "8.2.2"
265 | webdriver:
266 | dependency: transitive
267 | description:
268 | name: webdriver
269 | url: "https://pub.dartlang.org"
270 | source: hosted
271 | version: "3.0.0"
272 | sdks:
273 | dart: ">=2.17.1 <3.0.0"
274 | flutter: ">=1.17.0"
275 |
--------------------------------------------------------------------------------
/example/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: example
2 | description: Example app for bitmap_canvas
3 |
4 | publish_to: 'none'
5 |
6 | version: 1.0.0+1
7 |
8 | environment:
9 | sdk: ">=2.17.1 <3.0.0"
10 |
11 | dependencies:
12 | flutter:
13 | sdk: flutter
14 |
15 | bitmap_canvas:
16 | path: ../
17 | fast_noise: ^1.0.1
18 |
19 | dev_dependencies:
20 | flutter_test:
21 | sdk: flutter
22 | integration_test:
23 | sdk: flutter
24 | flutter_lints: ^2.0.0
25 | golden_toolkit: ^0.13.0
26 |
27 | flutter:
28 | uses-material-design: true
29 |
30 | # To add assets to your application, add an assets section, like this:
31 | # assets:
32 | # - images/a_dot_burr.jpeg
33 | # - images/a_dot_ham.jpeg
34 |
35 | # An image asset can refer to one or more resolution-specific "variants", see
36 | # https://flutter.dev/assets-and-images/#resolution-aware
37 |
38 | # For details regarding adding assets from package dependencies, see
39 | # https://flutter.dev/assets-and-images/#from-packages
40 |
41 | # To add custom fonts to your application, add a fonts section here,
42 | # in this "flutter" section. Each entry in this list should have a
43 | # "family" key with the font family name, and a "fonts" key with a
44 | # list giving the asset and other descriptors for the font. For
45 | # example:
46 | # fonts:
47 | # - family: Schyler
48 | # fonts:
49 | # - asset: fonts/Schyler-Regular.ttf
50 | # - asset: fonts/Schyler-Italic.ttf
51 | # style: italic
52 | # - family: Trajan Pro
53 | # fonts:
54 | # - asset: fonts/TrajanPro.ttf
55 | # - asset: fonts/TrajanPro_Bold.ttf
56 | # weight: 700
57 | #
58 | # For details regarding fonts from package dependencies,
59 | # see https://flutter.dev/custom-fonts/#from-packages
60 |
--------------------------------------------------------------------------------
/lib/bitmap_canvas.dart:
--------------------------------------------------------------------------------
1 | export 'src/bitmap_canvas.dart';
2 | export 'src/bitmap_paint.dart';
3 |
--------------------------------------------------------------------------------
/lib/bitmap_canvas_logging.dart:
--------------------------------------------------------------------------------
1 | export 'src/logging.dart';
2 |
--------------------------------------------------------------------------------
/lib/src/bitmap_canvas.dart:
--------------------------------------------------------------------------------
1 | import 'dart:typed_data';
2 | import 'dart:ui';
3 |
4 | import 'package:bitmap_canvas/src/logging.dart';
5 | import 'package:flutter/foundation.dart';
6 |
7 | /// A painting canvas that includes standard [Canvas] vector operations,
8 | /// as well as bitmap operations.
9 | ///
10 | /// Clients must adhere to the lifecycle contract of this object.
11 | ///
12 | /// **Start a new image:**
13 | /// To begin painting a new image, call [startRecording()], which prepares a new
14 | /// image for painting.
15 | ///
16 | /// **Paint desired content:**
17 | /// Vector commands run on the GPU. Pixel painting commands run on the CPU.
18 | /// As a result, these commands must be issued in phases.
19 | ///
20 | /// When you want to start painting pixels, call [startBitmapTransaction].
21 | ///
22 | /// When you want to shift from pixel painting to vector commands, call
23 | /// [endBitmapTransaction].
24 | ///
25 | /// **Produce the final image:**
26 | /// To produce an image that you can access, call [finishRecording()]. The resulting image
27 | /// is available in [publishedImage].
28 | ///
29 | /// You can display the [publishedImage] in a widget, save it to a file, or
30 | /// send it over the network.
31 | class BitmapCanvas implements Canvas {
32 | BitmapCanvas({
33 | required this.size,
34 | });
35 |
36 | /// The size of the painting region.
37 | final Size size;
38 |
39 | /// Backs our [Canvas], producing a [Picture], which produces a bitmap image.
40 | late PictureRecorder _recorder;
41 |
42 | /// Collects traditional Flutter vector operations, which are painted by
43 | /// Flutter's rendering system.
44 | ///
45 | /// Whenever a bitmap operation is performed after a vector operation, this
46 | /// canvas is rendered to a bitmap, and then the bitmap operation is applied.
47 | @visibleForTesting
48 | Canvas get canvas => _canvas;
49 | late Canvas _canvas;
50 |
51 | /// Whether this canvas is in the process of painting a frame.
52 | bool get isDrawing => _isDrawing;
53 | bool _isDrawing = false;
54 |
55 | /// Bitmap cache that's used to paint pixels during a single painting frame.
56 | ///
57 | /// This cache is needed because we have to switch from [Canvas] vector
58 | /// operations to bitmap operations, which requires a place to perform the
59 | /// bitmap operations.
60 | Image? _intermediateImage;
61 |
62 | /// Pixel buffer that holds all pixel values during a bitmap transaction.
63 | ///
64 | /// Consider using [get] or [getRegion] to query pixel values, and [set] or [setRegion] to
65 | /// set pixel values. Direct access to the [pixels] buffer may be useful in some situations.
66 | ByteData? get pixels => _pixels;
67 | ByteData? _pixels;
68 |
69 | /// Whether our [_canvas] has operations that have yet to be rasterized to
70 | /// a our [_intermediateImage].
71 | bool _hasUnappliedCanvasCommands = false;
72 |
73 | /// Informs this [BitmapCanvas] that there is some number
74 | /// of [Canvas] commands which have not yet been rasterized to
75 | /// [_intermediateImage].
76 | void _markHasUnappliedCanvasCommands() => _hasUnappliedCanvasCommands = true;
77 |
78 | /// The latest image to be produced by this [BitmapPaintingContext].
79 | Image? get publishedImage => _publishedImage;
80 | Image? _publishedImage;
81 |
82 | /// Starts a new image.
83 | void startRecording() {
84 | canvasLifecycleLog.info("Starting a new recording");
85 | assert(!isDrawing);
86 |
87 | _isDrawing = true;
88 | _intermediateImage = null;
89 |
90 | _recorder = PictureRecorder();
91 | _canvas = Canvas(_recorder);
92 |
93 | // Re-paint the previous frame so that new operations are applied on top.
94 | if (publishedImage != null) {
95 | _canvas.drawImage(publishedImage!, Offset.zero, Paint());
96 | }
97 | }
98 |
99 | /// Prepares the [BitmapCanvas] to execute a series of bitmap manipulations.
100 | ///
101 | /// [startBitmapTransaction] must be called before executing any bitmap
102 | /// operations. [startBitmapTransaction] rasterizes any outstanding [Canvas]
103 | /// commands, turning them into pixels, and prepares the pixel buffer for
104 | /// the bitmap operations that you're about to execute.
105 | ///
106 | /// Any traditional [Canvas] operations that you run during a bitmap transaction
107 | /// will be lost. When you're done with your bitmap operations, call
108 | /// [endBitmapTransaction] to shift back to the traditional [Canvas] mode.
109 | Future startBitmapTransaction() async {
110 | canvasLifecycleLog.info("Starting a bitmap transaction");
111 | if (!_hasUnappliedCanvasCommands && _pixels == null) {
112 | // There aren't any unapplied canvas commands. Fill the buffer
113 | // with empty pixels.
114 | final byteCount = size.width.round() * size.height.round() * 8;
115 | _pixels = ByteData(byteCount);
116 | return;
117 | }
118 |
119 | await _doIntermediateRasterization();
120 |
121 | final sourceImage = (_intermediateImage ?? publishedImage)!;
122 | _pixels = await sourceImage.toByteData();
123 | }
124 |
125 | /// Paints the latest [pixels] onto the [canvas].
126 | ///
127 | /// This operation is the logical inverse of [startBitmapTransaction].
128 | // TODO: this method was renamed from udpatePixels(). Some uses of updatePixels()
129 | // might not be the same thing as "starting a bitmap transaction". Figure out
130 | // if this method should be split into multiple methods for different purposes.
131 | Future endBitmapTransaction() async {
132 | canvasLifecycleLog.info("Ending a bitmap transaction");
133 | if (_pixels == null) {
134 | // No pixels to paint.
135 | canvasLifecycleLog.fine(" - No pixels to paint");
136 | return;
137 | }
138 |
139 | canvasLifecycleLog.finer("Encoding pixels to codec");
140 | final pixelsCodec = await ImageDescriptor.raw(
141 | await ImmutableBuffer.fromUint8List(_pixels!.buffer.asUint8List()),
142 | width: size.width.round(),
143 | height: size.height.round(),
144 | pixelFormat: PixelFormat.rgba8888,
145 | ).instantiateCodec();
146 |
147 | canvasLifecycleLog.finer("Encoding image into single frame");
148 | final pixelsImage = (await pixelsCodec.getNextFrame()).image;
149 |
150 | canvasLifecycleLog.finer("Drawing image to canvas");
151 | _canvas.drawImage(pixelsImage, Offset.zero, Paint());
152 | _markHasUnappliedCanvasCommands();
153 | }
154 |
155 | /// Immediately applies any outstanding [canvas] operations to
156 | /// produce a new [intermediateImage] bitmap.
157 | Future _doIntermediateRasterization() async {
158 | canvasLifecycleLog.info("Doing an intermediate rasterization");
159 | if (!_hasUnappliedCanvasCommands && _pixels != null) {
160 | // There are no commands to rasterize
161 | canvasLifecycleLog.fine(" - nothing to rasterize right now");
162 | return;
163 | }
164 |
165 | _intermediateImage = await _rasterize();
166 |
167 | _recorder = PictureRecorder();
168 | _canvas = Canvas(_recorder)..drawImage(_intermediateImage!, Offset.zero, Paint());
169 |
170 | _hasUnappliedCanvasCommands = false;
171 | canvasLifecycleLog.fine("Done with intermediate rasterization");
172 | }
173 |
174 | /// Produces a new [publishedImage] based on all the commands that were
175 | /// run since [startRecording].
176 | Future finishRecording() async {
177 | canvasLifecycleLog.info("Finishing the recording");
178 | if (_recorder.isRecording) {
179 | _publishedImage = await _rasterize();
180 | } else {
181 | _publishedImage = _intermediateImage;
182 | }
183 | _isDrawing = false;
184 | }
185 |
186 | Future _rasterize() async {
187 | final picture = _recorder.endRecording();
188 | return await picture.toImage(size.width.round(), size.height.round());
189 | }
190 |
191 | /// Returns the color of the pixel at the given ([x],[y]).
192 | Future get(int x, int y) async {
193 | await _doIntermediateRasterization();
194 | final sourceImage = (_intermediateImage ?? publishedImage)!;
195 |
196 | final pixelDataOffset = _getBitmapPixelOffset(
197 | imageWidth: sourceImage.width,
198 | x: x,
199 | y: y,
200 | );
201 | final imageData = await sourceImage.toByteData();
202 | final rgbaColor = imageData!.getUint32(pixelDataOffset);
203 | final argbColor = ((rgbaColor & 0x000000FF) << 24) | ((rgbaColor & 0xFFFFFF00) >> 8);
204 | return Color(argbColor);
205 | }
206 |
207 | /// Returns the colors of all pixels in the given region, represented as
208 | /// an [Image].
209 | ///
210 | /// The region is defined by a top-left ([x],[y]), a [width], and a [height].
211 | Future getRegion({
212 | required int x,
213 | required int y,
214 | required int width,
215 | required int height,
216 | }) async {
217 | await _doIntermediateRasterization();
218 | final sourceImage = (_intermediateImage ?? publishedImage)!;
219 |
220 | final sourceData = await sourceImage.toByteData();
221 | final destinationData = Uint8List(width * height * 4);
222 | final rowLength = width * 4;
223 |
224 | for (int row = 0; row < height; row += 1) {
225 | final sourceRowOffset = _getBitmapPixelOffset(
226 | imageWidth: sourceImage.width,
227 | x: x,
228 | y: y + row,
229 | );
230 | final destinationRowOffset = _getBitmapPixelOffset(
231 | imageWidth: width,
232 | x: 0,
233 | y: row,
234 | );
235 |
236 | destinationData.setRange(
237 | destinationRowOffset,
238 | destinationRowOffset + rowLength - 1,
239 | Uint8List.view(sourceData!.buffer, sourceRowOffset, rowLength),
240 | );
241 | }
242 |
243 | final codec = await ImageDescriptor.raw(
244 | await ImmutableBuffer.fromUint8List(destinationData),
245 | width: width,
246 | height: height,
247 | pixelFormat: PixelFormat.rgba8888,
248 | ).instantiateCodec();
249 |
250 | return (await codec.getNextFrame()).image;
251 | }
252 |
253 | /// Sets the pixel at the given ([x],[y]) to the given [color].
254 | void set({
255 | required int x,
256 | required int y,
257 | required Color color,
258 | }) {
259 | if (_pixels == null) {
260 | throw Exception("You must call startBitmapTransaction() before selling set().");
261 | }
262 |
263 | final pixelIndex = _getBitmapPixelOffset(
264 | imageWidth: size.width.round(),
265 | x: x,
266 | y: y,
267 | );
268 |
269 | final argbColorInt = color.value;
270 | final rgbaColorInt = ((argbColorInt & 0xFF000000) >> 24) | ((argbColorInt & 0x00FFFFFF) << 8);
271 | _pixels!.setUint32(pixelIndex, rgbaColorInt);
272 | }
273 |
274 | /// Sets all pixels in the given region to the colors specified by the given [image].
275 | ///
276 | /// The region is defined by the top-left ([x],[y]) and the width and height of
277 | /// the given [image].
278 | Future setRegion({
279 | required Image image,
280 | int x = 0,
281 | int y = 0,
282 | }) async {
283 | if (_pixels == null) {
284 | throw Exception("You must call startBitmapTransaction() before selling setRegion().");
285 | }
286 |
287 | // In theory, this method should copy each pixel in the given image
288 | // into the pixels buffer. But, it's easier for us to utilize the Canvas
289 | // to accomplish the same thing. To use the Canvas, we must first ensure
290 | // that any intermediate values in the pixels buffer are applied back to
291 | // the intermediate image. For example, if the user called set() on any
292 | // pixels but has not yet called endBitmapTransaction(), those pixels would be
293 | // lost during an intermediate rasterization. Therefore, the first thing
294 | // we do is endBitmapTransaction().
295 | await endBitmapTransaction();
296 |
297 | // Use the Canvas to draw the given image at the desired offset.
298 | _canvas.drawImage(image, Offset.zero, Paint());
299 | _markHasUnappliedCanvasCommands();
300 |
301 | // Rasterize the Canvas image command and load the latest image data
302 | // into the pixels buffer.
303 | await startBitmapTransaction();
304 | }
305 |
306 | int _getBitmapPixelOffset({
307 | required int imageWidth,
308 | required int x,
309 | required int y,
310 | }) {
311 | return ((y * imageWidth) + x) * 4;
312 | }
313 |
314 | //---- START Canvas delegations ---
315 | @override
316 | void clipPath(Path path, {bool doAntiAlias = true}) {
317 | _canvas.clipPath(path, doAntiAlias: doAntiAlias);
318 | _markHasUnappliedCanvasCommands();
319 | }
320 |
321 | @override
322 | void clipRRect(RRect rrect, {bool doAntiAlias = true}) {
323 | _canvas.clipRRect(rrect, doAntiAlias: doAntiAlias);
324 | _markHasUnappliedCanvasCommands();
325 | }
326 |
327 | @override
328 | void clipRect(Rect rect, {ClipOp clipOp = ClipOp.intersect, bool doAntiAlias = true}) {
329 | _canvas.clipRect(rect, clipOp: clipOp, doAntiAlias: doAntiAlias);
330 | _markHasUnappliedCanvasCommands();
331 | }
332 |
333 | @override
334 | void drawArc(Rect rect, double startAngle, double sweepAngle, bool useCenter, Paint paint) {
335 | _canvas.drawArc(rect, startAngle, sweepAngle, useCenter, paint);
336 | _markHasUnappliedCanvasCommands();
337 | }
338 |
339 | @override
340 | void drawAtlas(Image atlas, List transforms, List rects, List? colors, BlendMode? blendMode,
341 | Rect? cullRect, Paint paint) {
342 | _canvas.drawAtlas(atlas, transforms, rects, colors, blendMode, cullRect, paint);
343 | _markHasUnappliedCanvasCommands();
344 | }
345 |
346 | @override
347 | void drawCircle(Offset center, double radius, Paint paint) {
348 | _canvas.drawCircle(center, radius, paint);
349 | _markHasUnappliedCanvasCommands();
350 | }
351 |
352 | @override
353 | void drawColor(Color color, BlendMode blendMode) {
354 | _canvas.drawColor(color, blendMode);
355 | _markHasUnappliedCanvasCommands();
356 | }
357 |
358 | @override
359 | void drawDRRect(RRect outer, RRect inner, Paint paint) {
360 | _canvas.drawDRRect(outer, inner, paint);
361 | _markHasUnappliedCanvasCommands();
362 | }
363 |
364 | @override
365 | void drawImage(Image image, Offset offset, Paint paint) {
366 | _canvas.drawImage(image, offset, paint);
367 | _markHasUnappliedCanvasCommands();
368 | }
369 |
370 | @override
371 | void drawImageNine(Image image, Rect center, Rect dst, Paint paint) {
372 | _canvas.drawImageNine(image, center, dst, paint);
373 | _markHasUnappliedCanvasCommands();
374 | }
375 |
376 | @override
377 | void drawImageRect(Image image, Rect src, Rect dst, Paint paint) {
378 | _canvas.drawImageRect(image, src, dst, paint);
379 | _markHasUnappliedCanvasCommands();
380 | }
381 |
382 | @override
383 | void drawLine(Offset p1, Offset p2, Paint paint) {
384 | _canvas.drawLine(p1, p2, paint);
385 | _markHasUnappliedCanvasCommands();
386 | }
387 |
388 | @override
389 | void drawOval(Rect rect, Paint paint) {
390 | _canvas.drawOval(rect, paint);
391 | _markHasUnappliedCanvasCommands();
392 | }
393 |
394 | @override
395 | void drawPaint(Paint paint) {
396 | _canvas.drawPaint(paint);
397 | _markHasUnappliedCanvasCommands();
398 | }
399 |
400 | @override
401 | void drawParagraph(Paragraph paragraph, Offset offset) {
402 | _canvas.drawParagraph(paragraph, offset);
403 | _markHasUnappliedCanvasCommands();
404 | }
405 |
406 | @override
407 | void drawPath(Path path, Paint paint) {
408 | _canvas.drawPath(path, paint);
409 | _markHasUnappliedCanvasCommands();
410 | }
411 |
412 | @override
413 | void drawPicture(Picture picture) {
414 | _canvas.drawPicture(picture);
415 | _markHasUnappliedCanvasCommands();
416 | }
417 |
418 | @override
419 | void drawPoints(PointMode pointMode, List points, Paint paint) {
420 | _canvas.drawPoints(pointMode, points, paint);
421 | _markHasUnappliedCanvasCommands();
422 | }
423 |
424 | @override
425 | void drawRRect(RRect rrect, Paint paint) {
426 | _canvas.drawRRect(rrect, paint);
427 | _markHasUnappliedCanvasCommands();
428 | }
429 |
430 | @override
431 | void drawRawAtlas(Image atlas, Float32List rstTransforms, Float32List rects, Int32List? colors, BlendMode? blendMode,
432 | Rect? cullRect, Paint paint) {
433 | _canvas.drawRawAtlas(atlas, rstTransforms, rects, colors, blendMode, cullRect, paint);
434 | _markHasUnappliedCanvasCommands();
435 | }
436 |
437 | @override
438 | void drawRawPoints(PointMode pointMode, Float32List points, Paint paint) {
439 | _canvas.drawRawPoints(pointMode, points, paint);
440 | _markHasUnappliedCanvasCommands();
441 | }
442 |
443 | @override
444 | void drawRect(Rect rect, Paint paint) {
445 | _canvas.drawRect(rect, paint);
446 | _markHasUnappliedCanvasCommands();
447 | }
448 |
449 | @override
450 | void drawShadow(Path path, Color color, double elevation, bool transparentOccluder) {
451 | _canvas.drawShadow(path, color, elevation, transparentOccluder);
452 | _markHasUnappliedCanvasCommands();
453 | }
454 |
455 | @override
456 | void drawVertices(Vertices vertices, BlendMode blendMode, Paint paint) {
457 | _canvas.drawVertices(vertices, blendMode, paint);
458 | _markHasUnappliedCanvasCommands();
459 | }
460 |
461 | @override
462 | int getSaveCount() {
463 | return _canvas.getSaveCount();
464 | }
465 |
466 | @override
467 | void restore() {
468 | _canvas.restore();
469 | }
470 |
471 | @override
472 | void rotate(double radians) {
473 | _canvas.rotate(radians);
474 | _markHasUnappliedCanvasCommands();
475 | }
476 |
477 | @override
478 | void save() {
479 | _canvas.save();
480 | }
481 |
482 | @override
483 | void saveLayer(Rect? bounds, Paint paint) {
484 | _canvas.saveLayer(bounds, paint);
485 | }
486 |
487 | @override
488 | void scale(double sx, [double? sy]) {
489 | _canvas.scale(sx, sy);
490 | _markHasUnappliedCanvasCommands();
491 | }
492 |
493 | @override
494 | void skew(double sx, double sy) {
495 | _canvas.skew(sx, sy);
496 | _markHasUnappliedCanvasCommands();
497 | }
498 |
499 | @override
500 | void transform(Float64List matrix4) {
501 | _canvas.transform(matrix4);
502 | _markHasUnappliedCanvasCommands();
503 | }
504 |
505 | @override
506 | void translate(double dx, double dy) {
507 | _canvas.translate(dx, dy);
508 | _markHasUnappliedCanvasCommands();
509 | }
510 | //---- END Canvas delegations ---
511 | }
512 |
--------------------------------------------------------------------------------
/lib/src/bitmap_paint.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui';
2 |
3 | import 'package:bitmap_canvas/bitmap_canvas.dart';
4 | import 'package:bitmap_canvas/src/logging.dart';
5 | import 'package:flutter/scheduler.dart';
6 | import 'package:flutter/widgets.dart' hide Image;
7 |
8 | import 'bitmap_canvas.dart';
9 |
10 | /// A widget that displays a bitmap that you paint with a [BitmapPainter].
11 | ///
12 | /// The [BitmapPaint] widget is very similar to a traditional [CustomPaint]
13 | /// widget, except a [BitmapPaint] delegates painting to a [BitmapPainter].
14 | /// A [BitmapPainter] uses a [BitmapCanvas], which supports traditional
15 | /// [Canvas] operations, as well as bitmap operations, e.g., setting and
16 | /// getting individual pixel colors.
17 | class BitmapPaint extends StatefulWidget {
18 | const BitmapPaint({
19 | Key? key,
20 | required this.painter,
21 | required this.size,
22 | this.playbackMode = PlaybackMode.continuous,
23 | }) : super(key: key);
24 |
25 | /// Painting delegate, which paints the pixels that are displayed
26 | /// by this [BitmapPaint].
27 | final BitmapPainter painter;
28 |
29 | /// The size of the painting that's displayed in this [BitmapPaint].
30 | ///
31 | /// Every time the [size] changes, the underlying bitmap caches are
32 | /// regenerated to fit the new size. This is a costly operation. Therefore,
33 | /// you should only change a [BitmapPaint]'s [size] when absolutely
34 | /// necessary.
35 | final Size size;
36 |
37 | /// The playback mode for the [BitmapPaint], e.g., paint a single frame,
38 | /// paint continuously, or pause painting.
39 | final PlaybackMode playbackMode;
40 |
41 | @override
42 | State createState() => _BitmapPaintState();
43 | }
44 |
45 | class _BitmapPaintState extends State with SingleTickerProviderStateMixin {
46 | late Ticker _ticker;
47 | Duration _lastFrameTime = Duration.zero;
48 |
49 | late BitmapCanvas _bitmapCanvas;
50 | Image? _currentImage;
51 |
52 | @override
53 | void initState() {
54 | super.initState();
55 |
56 | _bitmapCanvas = BitmapCanvas(size: widget.size);
57 |
58 | _ticker = createTicker(_onTick);
59 | if (widget.playbackMode == PlaybackMode.continuous) {
60 | _startTicking();
61 | } else if (widget.playbackMode == PlaybackMode.singleFrame) {
62 | WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
63 | _paintFrame(Duration.zero);
64 | });
65 | }
66 | }
67 |
68 | @override
69 | void didUpdateWidget(BitmapPaint oldWidget) {
70 | super.didUpdateWidget(oldWidget);
71 |
72 | if (widget.playbackMode != oldWidget.playbackMode) {
73 | if (widget.playbackMode == PlaybackMode.continuous && !_ticker.isTicking) {
74 | _startTicking();
75 | } else if (oldWidget.playbackMode == PlaybackMode.continuous) {
76 | _ticker.stop();
77 | }
78 | }
79 |
80 | if (widget.size != oldWidget.size) {
81 | _bitmapCanvas = BitmapCanvas(size: widget.size);
82 |
83 | // We always want to repaint at least one frame when the size
84 | // changes, because the new size is incompatible with our last
85 | // image size.
86 | if (!_ticker.isTicking) {
87 | _startTicking();
88 | }
89 | // TODO: write a test that makes sure a singleFrame playback mode repaints when size changes
90 | }
91 | }
92 |
93 | @override
94 | void dispose() {
95 | _ticker.dispose();
96 | super.dispose();
97 | }
98 |
99 | void _startTicking() {
100 | _lastFrameTime = Duration.zero;
101 | _ticker.start();
102 | }
103 |
104 | bool _isPainting = false;
105 | Future _onTick(elapsedTime) async {
106 | if (_isPainting) {
107 | return;
108 | }
109 | _isPainting = true;
110 |
111 | if (widget.playbackMode != PlaybackMode.continuous) {
112 | // The playback mode is either "single frame" or "paused".
113 | // Either way, we don't want to paint another frame after
114 | // this one.
115 | _ticker.stop();
116 | }
117 |
118 | await _paintFrame(elapsedTime);
119 |
120 | if (mounted) {
121 | setState(() {
122 | _lastFrameTime = elapsedTime;
123 | _isPainting = false;
124 | });
125 | }
126 | }
127 |
128 | Future _paintFrame(Duration elapsedTime) async {
129 | // Save a reference to the _bitmapCanvas in this method, in case the
130 | // _bitmapCanvas is replaced while we're painting this frame.
131 | final bitmapCanvas = _bitmapCanvas;
132 |
133 | paintLifecycleLog.finer("On BitmapPaint frame tick. Elapsed time: $elapsedTime");
134 | if (bitmapCanvas.isDrawing) {
135 | paintLifecycleLog.fine(" - Painting already in progress. Ignoring tick.");
136 | return;
137 | }
138 |
139 | paintLifecycleLog.fine("Starting a new recording.");
140 | paintLifecycleLog.fine(" - playback mode: ${widget.playbackMode}");
141 | bitmapCanvas.startRecording();
142 |
143 | paintLifecycleLog.fine("Telling delegate to paint a frame.");
144 | // Ask our delegate to paint a frame. This call may take a while.
145 | await widget.painter.paint(
146 | BitmapPaintingContext(
147 | canvas: bitmapCanvas,
148 | size: widget.size,
149 | elapsedTime: elapsedTime,
150 | timeSinceLastFrame: elapsedTime - _lastFrameTime,
151 | ),
152 | );
153 | paintLifecycleLog.fine("Delegate is done painting a frame.");
154 |
155 | paintLifecycleLog.fine("Ending the recording and producing a new image");
156 | await bitmapCanvas.finishRecording();
157 |
158 | if (mounted) {
159 | setState(() {
160 | _currentImage = bitmapCanvas.publishedImage;
161 | });
162 | }
163 | }
164 |
165 | @override
166 | Widget build(BuildContext context) {
167 | return _currentImage != null
168 | ? SizedBox.fromSize(
169 | size: widget.size,
170 | child: OverflowBox(
171 | maxWidth: double.infinity,
172 | maxHeight: double.infinity,
173 | child: SizedBox(
174 | width: _currentImage!.width.toDouble(),
175 | height: _currentImage!.height.toDouble(),
176 | child: RepaintBoundary(
177 | child: RawImage(
178 | image: _currentImage,
179 | ),
180 | ),
181 | ),
182 | ),
183 | )
184 | : SizedBox.fromSize(size: widget.size);
185 | }
186 | }
187 |
188 | /// The playback mode for a [BitmapPaint] widget.
189 | enum PlaybackMode {
190 | /// Renders only a single frame.
191 | singleFrame,
192 |
193 | /// Continuously renders frames.
194 | continuous,
195 | }
196 |
197 | /// Delegate that paints with a [BitmapCanvas].
198 | ///
199 | /// Simple use-cases can call the [BitmapPainter.fromCallback] constructor and pass
200 | /// a function that paints frames.
201 | ///
202 | /// Use-cases that require internal variable manipulation should implement [BitmapPainter]
203 | /// in their own class.
204 | class BitmapPainter {
205 | const BitmapPainter.fromCallback(this._paint);
206 |
207 | final Future Function(BitmapPaintingContext)? _paint;
208 |
209 | Future paint(BitmapPaintingContext paintingContext) async {
210 | if (_paint == null) {
211 | return;
212 | }
213 |
214 | await _paint!(paintingContext);
215 | }
216 | }
217 |
218 | /// Information provided to a [BitmapPainter] so that the painter can paint a single
219 | /// frame.
220 | class BitmapPaintingContext {
221 | BitmapPaintingContext({
222 | required this.canvas,
223 | required this.size,
224 | required this.elapsedTime,
225 | required this.timeSinceLastFrame,
226 | });
227 |
228 | /// The canvas to paint with.
229 | final BitmapCanvas canvas;
230 |
231 | /// The size of the canvas.
232 | final Size size;
233 |
234 | /// The total time that the owning [BitmapPaint] has been painting frames.
235 | ///
236 | /// This time is reset whenever a [BitmapPaint] pauses rendering.
237 | final Duration elapsedTime;
238 |
239 | /// The delta-time since the last frame was painted.
240 | final Duration timeSinceLastFrame;
241 | }
242 |
--------------------------------------------------------------------------------
/lib/src/logging.dart:
--------------------------------------------------------------------------------
1 | // ignore_for_file: avoid_print
2 |
3 | import 'package:logging/logging.dart' as logging;
4 |
5 | class LogNames {
6 | /// Logs lifecycle events within a `BitmapCanvas`.
7 | static const canvasLifecycle = 'bitmapcanvas.lifecycle';
8 |
9 | /// Logs lifecycle events within a `BitmapPaint` widget.
10 | static const paintLifecycle = 'bitmappaint.lifecycle';
11 | }
12 |
13 | final canvasLifecycleLog = logging.Logger(LogNames.canvasLifecycle);
14 |
15 | final paintLifecycleLog = logging.Logger(LogNames.paintLifecycle);
16 |
17 | final _activeLoggers = {};
18 |
19 | void initAllLogs(logging.Level level) {
20 | initLoggers(level, {logging.Logger.root});
21 | }
22 |
23 | void initLoggers(logging.Level level, Set loggers) {
24 | logging.hierarchicalLoggingEnabled = true;
25 |
26 | for (final logger in loggers) {
27 | if (!_activeLoggers.contains(logger)) {
28 | print('Initializing logger: ${logger.name}');
29 | logger
30 | ..level = level
31 | ..onRecord.listen(printLog);
32 |
33 | _activeLoggers.add(logger);
34 | }
35 | }
36 | }
37 |
38 | void deactivateLoggers(Set loggers) {
39 | for (final logger in loggers) {
40 | if (_activeLoggers.contains(logger)) {
41 | print('Deactivating logger: ${logger.name}');
42 | logger.clearListeners();
43 |
44 | _activeLoggers.remove(logger);
45 | }
46 | }
47 | }
48 |
49 | void printLog(logging.LogRecord record) {
50 | print(
51 | '(${record.time.second}.${record.time.millisecond.toString().padLeft(3, '0')}) ${record.loggerName} > ${record.level.name}: ${record.message}');
52 | }
53 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: bitmap_canvas
2 | description: Render bitmap pixels with canvas-style APIs.
3 | version: 0.0.1+1
4 | repository: https://github.com/Flutter-Bounty-Hunters/bitmap_canvas
5 | homepage: https://github.com/Flutter-Bounty-Hunters/bitmap_canvas
6 |
7 | environment:
8 | sdk: ">=2.17.0 <3.0.0"
9 | flutter: ">=1.17.0"
10 |
11 | dependencies:
12 | flutter:
13 | sdk: flutter
14 | logging: ^1.0.2
15 |
16 | dev_dependencies:
17 | flutter_test:
18 | sdk: flutter
19 | flutter_lints: ^2.0.0
20 |
21 | flutter:
22 | # No Flutter configuration
--------------------------------------------------------------------------------