From b0c63b8fa873e264c3db16de2b94623fd8e4d1ba Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Thu, 24 Nov 2022 11:18:26 +0100 Subject: [PATCH] [flutter] Add SkeletonDrawable.renderToRawImageData(), clean-up dress up example. --- spine-flutter/example/lib/dress_up.dart | 47 +++-------- spine-flutter/example/lib/flame_example.dart | 12 ++- spine-flutter/example/lib/main.dart | 13 --- spine-flutter/example/lib/skins.dart | 85 -------------------- spine-flutter/example/pubspec.lock | 9 +-- spine-flutter/example/pubspec.yaml | 1 - spine-flutter/lib/spine_flutter.dart | 23 +++++- spine-flutter/lib/spine_widget.dart | 12 +-- spine-flutter/pubspec.yaml | 2 +- 9 files changed, 53 insertions(+), 151 deletions(-) delete mode 100644 spine-flutter/example/lib/skins.dart diff --git a/spine-flutter/example/lib/dress_up.dart b/spine-flutter/example/lib/dress_up.dart index 9ae0b842f..559d1df53 100644 --- a/spine-flutter/example/lib/dress_up.dart +++ b/spine-flutter/example/lib/dress_up.dart @@ -16,8 +16,7 @@ class DressUp extends StatefulWidget { class DressUpState extends State { static const double thumbnailSize = 200; - late SpineWidgetController _controller; - SkeletonDrawable? _drawable; + late SkeletonDrawable _drawable; Skin? _customSkin; final Map _skinImages = {}; final Map _selectedSkins = {}; @@ -27,42 +26,18 @@ class DressUpState extends State { reportLeaks(); super.initState(); SkeletonDrawable.fromAsset("assets/mix-and-match.atlas", "assets/mix-and-match-pro.skel").then((drawable) async { + _drawable = drawable; for (var skin in drawable.skeletonData.getSkins()) { if (skin.getName() == "default") continue; - var skeleton = drawable.skeleton; skeleton.setSkin(skin); skeleton.setToSetupPose(); skeleton.updateWorldTransform(); - var bounds = skeleton.getBounds(); - var scale = 1 / (bounds.width > bounds.height ? bounds.width / thumbnailSize : bounds.height / thumbnailSize); - - var recorder = ui.PictureRecorder(); - var canvas = Canvas(recorder); - var bgColor = Random().nextInt(0xffffffff) | 0xff0000000; - var paint = Paint() - ..color = ui.Color(bgColor) - ..style = PaintingStyle.fill; - canvas.drawRect(const Rect.fromLTWH(0, 0, thumbnailSize, thumbnailSize), paint); - canvas.translate(thumbnailSize / 2, thumbnailSize / 2); - canvas.scale(scale, scale); - canvas.translate(-(bounds.x + bounds.width / 2), -(bounds.y + bounds.height / 2)); - canvas.drawRect(Rect.fromLTRB(-5, -5, 5, -5), paint..color = Colors.red); - drawable.renderToCanvas(canvas); - - var rawImageData = (await (await recorder.endRecording().toImage(thumbnailSize.toInt(), thumbnailSize.toInt())).toByteData(format: ui.ImageByteFormat.rawRgba))!.buffer.asUint8List(); - _skinImages[skin.getName()] = (RawImageData(rawImageData, thumbnailSize.toInt(), thumbnailSize.toInt())); + _skinImages[skin.getName()] = await drawable.renderToRawImageData(thumbnailSize, thumbnailSize); _selectedSkins[skin.getName()] = false; } - _drawable = drawable; - _controller = SpineWidgetController(onInitialized: (controller) { - controller.animationState.setAnimationByName(0, "dance", true); - }); - setState(() { - _selectedSkins["full-skins/girl"] = true; - drawable.skeleton.setSkinByName("full-skins/girl"); - drawable.skeleton.setToSetupPose(); - }); + _toggleSkin("full-skins/girl"); + setState(() {}); }); } @@ -72,16 +47,20 @@ class DressUpState extends State { _customSkin = Skin("custom-skin"); for (var skinName in _selectedSkins.keys) { if (_selectedSkins[skinName] == true) { - var skin = _controller.skeletonData.findSkin(skinName); + var skin = _drawable.skeletonData.findSkin(skinName); if (skin != null) _customSkin?.addSkin(skin); } } - _controller.skeleton.setSkin(_customSkin!); - _controller.skeleton.setToSetupPose(); + _drawable.skeleton.setSkin(_customSkin!); + _drawable.skeleton.setToSetupPose(); } @override Widget build(BuildContext context) { + final controller = SpineWidgetController(onInitialized: (controller) { + controller.animationState.setAnimationByName(0, "dance", true); + }); + return Scaffold( appBar: AppBar(title: const Text('Dress Up')), body: _skinImages.isEmpty @@ -107,7 +86,7 @@ class DressUpState extends State { ), ), Expanded( - child: SpineWidget.drawable(_drawable, _controller, boundsProvider: SkinAndAnimationBounds(skins: ["full-skins/girl"]),) + child: SpineWidget.drawable(_drawable, controller, boundsProvider: SkinAndAnimationBounds(skins: ["full-skins/girl"]),) ) ] ) diff --git a/spine-flutter/example/lib/flame_example.dart b/spine-flutter/example/lib/flame_example.dart index 3acc95ec9..9e9c51a61 100644 --- a/spine-flutter/example/lib/flame_example.dart +++ b/spine-flutter/example/lib/flame_example.dart @@ -37,9 +37,8 @@ class SpineComponent extends PositionComponent { Iterable? children, int? priority, }) async { - final drawable = await SkeletonDrawable.fromAsset(atlasFile, skeletonFile, bundle: bundle); return SpineComponent( - drawable, + await SkeletonDrawable.fromAsset(atlasFile, skeletonFile, bundle: bundle), ownsDrawable: true, boundsProvider: boundsProvider, position: position, @@ -54,6 +53,11 @@ class SpineComponent extends PositionComponent { if (_ownsDrawable) { _drawable.dispose(); } + + @override + void onDetach() { + + } } @override @@ -133,9 +137,11 @@ class PreloadAndShareSpineDataExample extends FlameGame { @override void onDetach() { - // Dispose the pre-loaded atlas and skeleton data when the game/scene is removed + // Dispose the pre-loaded atlas and skeleton data when the game/scene is removed. cachedAtlas.dispose(); cachedSkeletonData.dispose(); + + // Dispose each spineboy and its internal SkeletonDrawable. for (final spineboy in spineboys) { spineboy.dispose(); } diff --git a/spine-flutter/example/lib/main.dart b/spine-flutter/example/lib/main.dart index 37863dbfd..907d8b5a1 100644 --- a/spine-flutter/example/lib/main.dart +++ b/spine-flutter/example/lib/main.dart @@ -6,7 +6,6 @@ import 'flame_example.dart'; import 'simple_animation.dart'; import 'animation_state_events.dart'; import 'pause_play_animation.dart'; -import 'skins.dart'; import 'dress_up.dart'; import 'ik_following.dart'; @@ -70,18 +69,6 @@ class ExampleSelector extends StatelessWidget { }, ), spacer, - ElevatedButton( - child: const Text('Skins'), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const Skins(), - ), - ); - }, - ), - spacer, ElevatedButton( child: const Text('Dress Up'), onPressed: () { diff --git a/spine-flutter/example/lib/skins.dart b/spine-flutter/example/lib/skins.dart deleted file mode 100644 index 53920819b..000000000 --- a/spine-flutter/example/lib/skins.dart +++ /dev/null @@ -1,85 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:esotericsoftware_spine_flutter/spine_flutter.dart'; - -class Skins extends StatefulWidget { - const Skins({Key? key}) : super(key: key); - - @override - SkinsState createState() => SkinsState(); -} - -class SkinsState extends State { - SkeletonDrawable? _drawable; - late SpineWidgetController _controller; - final Map _selectedSkins = {}; - Skin? _customSkin; - - @override - void initState() { - super.initState(); - SkeletonDrawable.fromAsset("assets/mix-and-match.atlas", "assets/mix-and-match-pro.skel").then((drawable) { - for (var skin in drawable.skeletonData.getSkins()) { - _selectedSkins[skin.getName()] = false; - } - _controller = SpineWidgetController(onInitialized: (controller) { - controller.animationState.setAnimationByName(0, "walk", true); - }); - drawable.skeleton.setSkinByName("full-skins/girl"); - _selectedSkins["full-skins/girl"] = true; - _drawable = drawable; - setState(() {}); - }); - } - - void _toggleSkin(String skinName) { - _selectedSkins[skinName] = !_selectedSkins[skinName]!; - - if (_customSkin != null) _customSkin?.dispose(); - - _customSkin = Skin("custom-skin"); - for (var skinName in _selectedSkins.keys) { - if (_selectedSkins[skinName] == true) { - var skin = _controller.skeletonData.findSkin(skinName); - if (skin != null) _customSkin?.addSkin(skin); - } - } - _controller.skeleton.setSkin(_customSkin!); - _controller.skeleton.setToSetupPose(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('Skins')), - body: _drawable == null - ? const SizedBox() - : Row( - children: [ - Expanded( - child:ListView( - children: _selectedSkins.keys.map((skinName) { - return CheckboxListTile( - title: Text(skinName), - value: _selectedSkins[skinName], - onChanged: (bool? value) { - _toggleSkin(skinName); - setState(() => {}); - }, - ); - }).toList() - ) - ), - Expanded( - child: SpineWidget.drawable(_drawable, _controller, boundsProvider: SkinAndAnimationBounds(skins: ["full-skins/girl"])) - ) - ] - ) - ); - } - - @override - void dispose() { - super.dispose(); - _drawable?.dispose(); - } -} \ No newline at end of file diff --git a/spine-flutter/example/pubspec.lock b/spine-flutter/example/pubspec.lock index 61325d7bd..7e3e5cae1 100644 --- a/spine-flutter/example/pubspec.lock +++ b/spine-flutter/example/pubspec.lock @@ -132,15 +132,8 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.2" - plugin_platform_interface: - dependency: transitive - description: - name: plugin_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.2" raw_image_provider: - dependency: "direct main" + dependency: transitive description: name: raw_image_provider url: "https://pub.dartlang.org" diff --git a/spine-flutter/example/pubspec.yaml b/spine-flutter/example/pubspec.yaml index eb4be6a6b..b86d1c410 100644 --- a/spine-flutter/example/pubspec.yaml +++ b/spine-flutter/example/pubspec.yaml @@ -13,7 +13,6 @@ dependencies: esotericsoftware_spine_flutter: path: ../ cupertino_icons: ^1.0.2 - raw_image_provider: ^0.2.0 flame: ^1.4.0 dev_dependencies: diff --git a/spine-flutter/lib/spine_flutter.dart b/spine-flutter/lib/spine_flutter.dart index 81718a0de..95182b325 100644 --- a/spine-flutter/lib/spine_flutter.dart +++ b/spine-flutter/lib/spine_flutter.dart @@ -1,5 +1,6 @@ import 'dart:convert' as convert; import 'dart:io'; +import 'dart:math'; import 'dart:typed_data'; import 'dart:ui'; @@ -8,6 +9,7 @@ import 'package:flutter/rendering.dart' as rendering; import 'package:flutter/services.dart'; import 'package:http/http.dart' as http; import 'package:path/path.dart' as path; +import 'package:raw_image_provider/raw_image_provider.dart'; import 'spine_flutter_bindings_generated.dart'; import 'ffi_proxy.dart'; @@ -2595,7 +2597,6 @@ class Skeleton { } } -// FIXME expose timelines and apply? class Animation { final spine_animation _animation; @@ -3307,6 +3308,26 @@ class SkeletonDrawable { } } + Future renderToRawImageData(double width, double height) async { + var bounds = skeleton.getBounds(); + var scale = 1 / (bounds.width > bounds.height ? bounds.width / width : bounds.height / height); + + var recorder = PictureRecorder(); + var canvas = Canvas(recorder); + var bgColor = Random().nextInt(0xffffffff) | 0xff0000000; + var paint = Paint() + ..color = material.Color(bgColor) + ..style = PaintingStyle.fill; + canvas.drawRect(Rect.fromLTWH(0, 0, width, height), paint); + canvas.translate(width / 2, height / 2); + canvas.scale(scale, scale); + canvas.translate(-(bounds.x + bounds.width / 2), -(bounds.y + bounds.height / 2)); + canvas.drawRect(const Rect.fromLTRB(-5, -5, 5, -5), paint..color = material.Colors.red); + renderToCanvas(canvas); + var rawImageData = (await (await recorder.endRecording().toImage(width.toInt(), height.toInt())).toByteData(format: ImageByteFormat.rawRgba))!.buffer.asUint8List(); + return RawImageData(rawImageData, width.toInt(), height.toInt()); + } + void dispose() { if (_disposed) return; _disposed = true; diff --git a/spine-flutter/lib/spine_widget.dart b/spine-flutter/lib/spine_widget.dart index cffbbb077..031dbe299 100644 --- a/spine-flutter/lib/spine_widget.dart +++ b/spine-flutter/lib/spine_widget.dart @@ -106,17 +106,18 @@ class SkinAndAnimationBounds extends BoundsProvider { @override Bounds computeBounds(SkeletonDrawable drawable) { - var data = drawable.skeletonData; - var customSkin = Skin("custom-skin"); - for (var skinName in skins) { - var skin = data.findSkin(skinName); + final data = drawable.skeletonData; + final oldSkin = drawable.skeleton.getSkin(); + final customSkin = Skin("custom-skin"); + for (final skinName in skins) { + final skin = data.findSkin(skinName); if (skin == null) continue; customSkin.addSkin(skin); } drawable.skeleton.setSkin(customSkin); drawable.skeleton.setToSetupPose(); - final animation = data.findAnimation(this.animation!); + final animation = this.animation != null ? data.findAnimation(this.animation!) : null; double minX = double.infinity; double minY = double.infinity; double maxX = double.negativeInfinity; @@ -142,6 +143,7 @@ class SkinAndAnimationBounds extends BoundsProvider { customSkin.dispose(); drawable.skeleton.setSkinByName("default"); drawable.animationState.clearTracks(); + if (oldSkin != null) drawable.skeleton.setSkin(oldSkin); drawable.skeleton.setToSetupPose(); drawable.update(0); return Bounds(minX, minY, maxX - minX, maxY - minY); diff --git a/spine-flutter/pubspec.yaml b/spine-flutter/pubspec.yaml index f8eed36a0..f29905823 100644 --- a/spine-flutter/pubspec.yaml +++ b/spine-flutter/pubspec.yaml @@ -13,7 +13,6 @@ environment: dependencies: flutter: sdk: flutter - plugin_platform_interface: ^2.0.2 ffi: ^2.0.1 web_ffi_fork: ^0.7.4 inject_js: ^2.0.0 @@ -21,6 +20,7 @@ dependencies: meta: ^1.3.0 http: ^0.13.5 path: ^1.8.2 + raw_image_provider: ^0.2.0 dev_dependencies: ffigen: ^6.1.2