├── .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 | Bitmap Canvas - Render bitmap pixels with canvas-style APIs 3 |

4 | 5 |

6 | 7 | Built by the Flutter Bounty Hunters 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 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 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 --------------------------------------------------------------------------------