├── .gitignore ├── .idea ├── .gitignore ├── .name ├── libraries │ ├── Dart_SDK.xml │ └── Flutter_Plugins.xml ├── misc.xml ├── modules.xml ├── runConfigurations │ └── example_lib_main_dart.xml └── workspace.xml ├── .metadata ├── .packages ├── CHANGELOG.md ├── LICENSE ├── README.md ├── android ├── .gitignore ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── settings.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── lijiaqi │ └── image_editor │ └── ImageEditorPlugin.java ├── example ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ │ └── com │ │ │ │ │ └── lijiaqi │ │ │ │ │ └── image_editor_example │ │ │ │ │ └── MainActivity.java │ │ │ └── res │ │ │ │ ├── drawable-v21 │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── values-night │ │ │ │ └── styles.xml │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h ├── lib │ └── main.dart ├── pubspec.lock ├── pubspec.yaml └── test │ └── widget_test.dart ├── image_editor.iml ├── ios ├── .gitignore ├── Assets │ └── .gitkeep ├── Classes │ ├── ImageEditorPlugin.h │ ├── ImageEditorPlugin.m │ └── SwiftImageEditorPlugin.swift └── image_editor_dove.podspec ├── lib ├── extension │ └── num_extension.dart ├── flutter_image_editor.dart ├── image_editor.dart ├── model │ └── float_text_model.dart └── widget │ ├── drawing_board.dart │ ├── editor_panel_controller.dart │ ├── float_text_widget.dart │ ├── image_editor_delegate.dart │ ├── slider_widget.dart │ └── text_editor_page.dart ├── pubspec.lock ├── pubspec.yaml └── test └── image_editor_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.aar 4 | *.ap_ 5 | *.aab 6 | 7 | # Files for the ART/Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | out/ 17 | # Uncomment the following line in case you need and you don't have the release build type files in your app 18 | # release/ 19 | 20 | # Gradle files 21 | .gradle/ 22 | build/ 23 | 24 | # Local configuration file (sdk path, etc) 25 | local.properties 26 | 27 | # Proguard folder generated by Eclipse 28 | proguard/ 29 | 30 | # Log Files 31 | *.log 32 | 33 | # Android Studio Navigation editor temp files 34 | .navigation/ 35 | 36 | # Android Studio captures folder 37 | captures/ 38 | 39 | # IntelliJ 40 | #*.iml 41 | #.idea/workspace.xml 42 | #.idea/tasks.xml 43 | #.idea/gradle.xml 44 | #.idea/assetWizardSettings.xml 45 | #.idea/dictionaries 46 | #.idea/libraries 47 | ## Android Studio 3 in .gitignore file. 48 | #.idea/caches 49 | #.idea/modules.xml 50 | ## Comment next line if keeping position of elements in Navigation Editor is relevant for you 51 | #.idea/navEditor.xml 52 | 53 | # Keystore files 54 | # Uncomment the following lines if you do not want to check your keystore files in. 55 | #*.jks 56 | #*.keystore 57 | 58 | # External native build folder generated in Android Studio 2.2 and later 59 | .externalNativeBuild 60 | .cxx/ 61 | 62 | # Google Services (e.g. APIs or Firebase) 63 | # google-services.json 64 | 65 | # Freeline 66 | freeline.py 67 | freeline/ 68 | freeline_project_description.json 69 | 70 | # fastlane 71 | fastlane/report.xml 72 | fastlane/Preview.html 73 | fastlane/screenshots 74 | fastlane/test_output 75 | fastlane/readme.md 76 | 77 | # Version control 78 | vcs.xml 79 | 80 | # lint 81 | lint/intermediates/ 82 | lint/generated/ 83 | lint/outputs/ 84 | lint/tmp/ 85 | # lint/reports/ 86 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladeofgod/flutter_image_editor/9f0aeb5d025a60650e5daf2dd36329fc062e2b82/.idea/.gitignore -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | flutter_image_editor -------------------------------------------------------------------------------- /.idea/libraries/Dart_SDK.xml: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /.idea/libraries/Flutter_Plugins.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/runConfigurations/example_lib_main_dart.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 27 | 1642388073606 28 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /.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: d79295af24c3ed621c33713ecda14ad196fd9c31 8 | channel: stable 9 | 10 | project_type: plugin 11 | -------------------------------------------------------------------------------- /.packages: -------------------------------------------------------------------------------- 1 | # This file is deprecated. Tools should instead consume 2 | # `.dart_tool/package_config.json`. 3 | # 4 | # For more info see: https://dart.dev/go/dot-packages-deprecation 5 | # 6 | # Generated by pub on 2022-02-03 10:24:03.727670. 7 | archive:file:///H:/Users/45010/AppData/Roaming/Pub/Cache/hosted/pub.flutter-io.cn/archive-3.1.11/lib/ 8 | async:file:///H:/Users/45010/AppData/Roaming/Pub/Cache/hosted/pub.flutter-io.cn/async-2.8.1/lib/ 9 | boolean_selector:file:///H:/Users/45010/AppData/Roaming/Pub/Cache/hosted/pub.flutter-io.cn/boolean_selector-2.1.0/lib/ 10 | characters:file:///H:/Users/45010/AppData/Roaming/Pub/Cache/hosted/pub.flutter-io.cn/characters-1.1.0/lib/ 11 | charcode:file:///H:/Users/45010/AppData/Roaming/Pub/Cache/hosted/pub.flutter-io.cn/charcode-1.3.1/lib/ 12 | clock:file:///H:/Users/45010/AppData/Roaming/Pub/Cache/hosted/pub.flutter-io.cn/clock-1.1.0/lib/ 13 | collection:file:///H:/Users/45010/AppData/Roaming/Pub/Cache/hosted/pub.flutter-io.cn/collection-1.15.0/lib/ 14 | crypto:file:///H:/Users/45010/AppData/Roaming/Pub/Cache/hosted/pub.flutter-io.cn/crypto-3.0.1/lib/ 15 | fake_async:file:///H:/Users/45010/AppData/Roaming/Pub/Cache/hosted/pub.flutter-io.cn/fake_async-1.2.0/lib/ 16 | ffi:file:///H:/Users/45010/AppData/Roaming/Pub/Cache/hosted/pub.flutter-io.cn/ffi-1.1.2/lib/ 17 | file:file:///H:/Users/45010/AppData/Roaming/Pub/Cache/hosted/pub.flutter-io.cn/file-6.1.2/lib/ 18 | flutter:file:///I:/flutter/flutter/packages/flutter/lib/ 19 | flutter_test:file:///I:/flutter/flutter/packages/flutter_test/lib/ 20 | image:file:///H:/Users/45010/AppData/Roaming/Pub/Cache/hosted/pub.flutter-io.cn/image-3.1.1/lib/ 21 | matcher:file:///H:/Users/45010/AppData/Roaming/Pub/Cache/hosted/pub.flutter-io.cn/matcher-0.12.10/lib/ 22 | meta:file:///H:/Users/45010/AppData/Roaming/Pub/Cache/hosted/pub.flutter-io.cn/meta-1.7.0/lib/ 23 | path:file:///H:/Users/45010/AppData/Roaming/Pub/Cache/hosted/pub.flutter-io.cn/path-1.8.0/lib/ 24 | path_provider:file:///H:/Users/45010/AppData/Roaming/Pub/Cache/hosted/pub.flutter-io.cn/path_provider-2.0.8/lib/ 25 | path_provider_android:file:///H:/Users/45010/AppData/Roaming/Pub/Cache/hosted/pub.flutter-io.cn/path_provider_android-2.0.11/lib/ 26 | path_provider_ios:file:///H:/Users/45010/AppData/Roaming/Pub/Cache/hosted/pub.flutter-io.cn/path_provider_ios-2.0.7/lib/ 27 | path_provider_linux:file:///H:/Users/45010/AppData/Roaming/Pub/Cache/hosted/pub.flutter-io.cn/path_provider_linux-2.1.5/lib/ 28 | path_provider_macos:file:///H:/Users/45010/AppData/Roaming/Pub/Cache/hosted/pub.flutter-io.cn/path_provider_macos-2.0.5/lib/ 29 | path_provider_platform_interface:file:///H:/Users/45010/AppData/Roaming/Pub/Cache/hosted/pub.flutter-io.cn/path_provider_platform_interface-2.0.3/lib/ 30 | path_provider_windows:file:///H:/Users/45010/AppData/Roaming/Pub/Cache/hosted/pub.flutter-io.cn/path_provider_windows-2.0.5/lib/ 31 | petitparser:file:///H:/Users/45010/AppData/Roaming/Pub/Cache/hosted/pub.flutter-io.cn/petitparser-4.4.0/lib/ 32 | platform:file:///H:/Users/45010/AppData/Roaming/Pub/Cache/hosted/pub.flutter-io.cn/platform-3.1.0/lib/ 33 | plugin_platform_interface:file:///H:/Users/45010/AppData/Roaming/Pub/Cache/hosted/pub.flutter-io.cn/plugin_platform_interface-2.1.2/lib/ 34 | process:file:///H:/Users/45010/AppData/Roaming/Pub/Cache/hosted/pub.flutter-io.cn/process-4.2.4/lib/ 35 | screenshot:file:///H:/Users/45010/AppData/Roaming/Pub/Cache/hosted/pub.flutter-io.cn/screenshot-1.2.3/lib/ 36 | sky_engine:file:///I:/flutter/flutter/bin/cache/pkg/sky_engine/lib/ 37 | source_span:file:///H:/Users/45010/AppData/Roaming/Pub/Cache/hosted/pub.flutter-io.cn/source_span-1.8.1/lib/ 38 | stack_trace:file:///H:/Users/45010/AppData/Roaming/Pub/Cache/hosted/pub.flutter-io.cn/stack_trace-1.10.0/lib/ 39 | stream_channel:file:///H:/Users/45010/AppData/Roaming/Pub/Cache/hosted/pub.flutter-io.cn/stream_channel-2.1.0/lib/ 40 | string_scanner:file:///H:/Users/45010/AppData/Roaming/Pub/Cache/hosted/pub.flutter-io.cn/string_scanner-1.1.0/lib/ 41 | term_glyph:file:///H:/Users/45010/AppData/Roaming/Pub/Cache/hosted/pub.flutter-io.cn/term_glyph-1.2.0/lib/ 42 | test_api:file:///H:/Users/45010/AppData/Roaming/Pub/Cache/hosted/pub.flutter-io.cn/test_api-0.4.2/lib/ 43 | typed_data:file:///H:/Users/45010/AppData/Roaming/Pub/Cache/hosted/pub.flutter-io.cn/typed_data-1.3.0/lib/ 44 | vector_math:file:///H:/Users/45010/AppData/Roaming/Pub/Cache/hosted/pub.flutter-io.cn/vector_math-2.1.0/lib/ 45 | win32:file:///H:/Users/45010/AppData/Roaming/Pub/Cache/hosted/pub.flutter-io.cn/win32-2.3.6/lib/ 46 | xdg_directories:file:///H:/Users/45010/AppData/Roaming/Pub/Cache/hosted/pub.flutter-io.cn/xdg_directories-0.2.0/lib/ 47 | xml:file:///H:/Users/45010/AppData/Roaming/Pub/Cache/hosted/pub.flutter-io.cn/xml-5.3.1/lib/ 48 | image_editor_dove:lib/ 49 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | ## 0.0.3 3 | feat : add interact for image and allow cancel operation 4 | ## 0.0.2 5 | fix: bugs 6 | ## 0.0.1 7 | 8 | * TODO: Describe initial release. 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 bladeofgod 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 SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # image_editor_dove 2 | 3 | A high-performance image editor with crop, scribble, mosaic, add-text, flip, rotated functions. 4 | 5 | Support custom ui style. 6 | 7 | - drawing 8 | 9 | ![draw.gif](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/390c7ccae86b438094272590dff8ecc8~tplv-k3u1fbpfcp-watermark.image?) 10 | 11 | 12 | - rotate 13 | 14 | 15 | ![flip&rotate.gif](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/59509d75d54c453facf87146adf16656~tplv-k3u1fbpfcp-watermark.image?) 16 | 17 | 18 | - mosaic 19 | 20 | 21 | ![mosaic.gif](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/34fd160b855c40bdb16ff4a7d95cec28~tplv-k3u1fbpfcp-watermark.image?) 22 | 23 | - add or delete text 24 | 25 | 26 | ![text_delete.gif](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bd9ba17fb43e4819a6e97489ee1ee2d1~tplv-k3u1fbpfcp-watermark.image?) 27 | 28 | 29 | ![addtext_done.gif](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1388e96b13e64705a8c6ecaf56c5b976~tplv-k3u1fbpfcp-watermark.image?) 30 | 31 | 32 | ## Getting Started 33 | 34 | First, add image_editor: as a 35 | [dependency in your pubspec.yaml file.](https://flutter.cn/development/packages-and-plugins/using-packages) 36 | 37 | import 38 | ``` 39 | import 'package:image_editor/flutter_image_editor.dart'; 40 | ``` 41 | 42 | iOS 43 | Add the following keys to your Info.plist file, located in /ios/Runner/Info.plist: 44 | 45 | - NSPhotoLibraryUsageDescription - describe why your app needs permission for the photo library. This is called Privacy - Photo Library Usage Description in the visual editor. 46 | - NSCameraUsageDescription - describe why your app needs access to the camera. This is called Privacy - Camera Usage Description in the visual editor. 47 | - NSMicrophoneUsageDescription - describe why your app needs access to the microphone, if you intend to record videos. This is called Privacy - Microphone Usage Description in the visual editor. 48 | Or in text format add the key: 49 | ``` 50 | NSPhotoLibraryUsageDescription 51 | Used to demonstrate image picker plugin 52 | NSCameraUsageDescription 53 | Used to demonstrate image picker plugin 54 | NSMicrophoneUsageDescription 55 | Used to capture audio for image picker plugin 56 | ``` 57 | 58 | Android 59 | 60 | No configuration required - the plugin should work out of the box. 61 | 62 | ## Usage 63 | 64 | pick an image that wanna edit, and pass it to image editor like this: 65 | 66 | ``` 67 | Future toImageEditor(File origin) async { 68 | return Navigator.push(context, MaterialPageRoute(builder: (context) { 69 | return ImageEditor( 70 | originImage: origin, 71 | //this is nullable, you can custom new file's save postion 72 | savePath: customDirectory 73 | ); 74 | })).then((result) { 75 | if (result is EditorImageResult) { 76 | setState(() { 77 | _image = result.newFile; 78 | }); 79 | } 80 | }).catchError((er) { 81 | debugPrint(er); 82 | }); 83 | } 84 | ``` 85 | 86 | ## custom ui style 87 | 88 | You can extends ImageEditorDelegate and custom editor's widget: 89 | 90 | ``` 91 | ImageEditor.uiDelegate = YouUiDelegate(); 92 | 93 | class YouUiDelegate extends ImageEditorDelegate{ 94 | ... 95 | } 96 | ``` 97 | 98 | 99 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | group 'com.lijiaqi.image_editor' 2 | version '1.0' 3 | 4 | buildscript { 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:4.1.0' 12 | } 13 | } 14 | 15 | rootProject.allprojects { 16 | repositories { 17 | google() 18 | jcenter() 19 | } 20 | } 21 | 22 | apply plugin: 'com.android.library' 23 | 24 | android { 25 | compileSdkVersion 30 26 | 27 | defaultConfig { 28 | minSdkVersion 16 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip 6 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'image_editor' 2 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /android/src/main/java/com/lijiaqi/image_editor/ImageEditorPlugin.java: -------------------------------------------------------------------------------- 1 | package com.lijiaqi.image_editor; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import io.flutter.embedding.engine.plugins.FlutterPlugin; 6 | import io.flutter.plugin.common.MethodCall; 7 | import io.flutter.plugin.common.MethodChannel; 8 | import io.flutter.plugin.common.MethodChannel.MethodCallHandler; 9 | import io.flutter.plugin.common.MethodChannel.Result; 10 | 11 | /** ImageEditorPlugin */ 12 | public class ImageEditorPlugin implements FlutterPlugin, MethodCallHandler { 13 | /// The MethodChannel that will the communication between Flutter and native Android 14 | /// 15 | /// This local reference serves to register the plugin with the Flutter Engine and unregister it 16 | /// when the Flutter Engine is detached from the Activity 17 | //private MethodChannel channel; 18 | 19 | @Override 20 | public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) { 21 | // channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "image_editor"); 22 | // channel.setMethodCallHandler(this); 23 | } 24 | 25 | @Override 26 | public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { 27 | // if (call.method.equals("getPlatformVersion")) { 28 | // result.success("Android " + android.os.Build.VERSION.RELEASE); 29 | // } else { 30 | // result.notImplemented(); 31 | // } 32 | } 33 | 34 | @Override 35 | public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { 36 | //channel.setMethodCallHandler(null); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | -------------------------------------------------------------------------------- /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 and should not be manually edited. 5 | 6 | version: 7 | revision: d79295af24c3ed621c33713ecda14ad196fd9c31 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # image_editor_example 2 | 3 | Demonstrates how to use the image_editor plugin. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | 27 | android { 28 | compileSdkVersion 31 29 | 30 | defaultConfig { 31 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 32 | applicationId "com.lijiaqi.image_editor_example" 33 | minSdkVersion 16 34 | targetSdkVersion 31 35 | versionCode flutterVersionCode.toInteger() 36 | versionName flutterVersionName 37 | } 38 | 39 | buildTypes { 40 | release { 41 | // TODO: Add your own signing config for the release build. 42 | // Signing with the debug keys for now, so `flutter run --release` works. 43 | signingConfig signingConfigs.debug 44 | } 45 | } 46 | } 47 | 48 | flutter { 49 | source '../..' 50 | } 51 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 14 | 18 | 22 | 27 | 31 | 32 | 33 | 34 | 35 | 36 | 38 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/lijiaqi/image_editor_example/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.lijiaqi.image_editor_example; 2 | 3 | import io.flutter.embedding.android.FlutterActivity; 4 | 5 | public class MainActivity extends FlutterActivity { 6 | } 7 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladeofgod/flutter_image_editor/9f0aeb5d025a60650e5daf2dd36329fc062e2b82/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladeofgod/flutter_image_editor/9f0aeb5d025a60650e5daf2dd36329fc062e2b82/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladeofgod/flutter_image_editor/9f0aeb5d025a60650e5daf2dd36329fc062e2b82/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladeofgod/flutter_image_editor/9f0aeb5d025a60650e5daf2dd36329fc062e2b82/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladeofgod/flutter_image_editor/9f0aeb5d025a60650e5daf2dd36329fc062e2b82/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | maven { url 'https://maven.aliyun.com/repository/public' } 4 | maven { url 'https://maven.aliyun.com/repository/google' } 5 | maven { url 'https://maven.aliyun.com/repository/jcenter' } 6 | maven {url 'http://download.flutter.io'} 7 | google() 8 | jcenter() 9 | } 10 | 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:4.1.0' 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | maven { url 'https://maven.aliyun.com/repository/public' } 19 | maven { url 'https://maven.aliyun.com/repository/google' } 20 | maven { url 'https://maven.aliyun.com/repository/jcenter' } 21 | maven {url 'http://download.flutter.io'} 22 | google() 23 | jcenter() 24 | } 25 | } 26 | 27 | rootProject.buildDir = '../build' 28 | subprojects { 29 | project.buildDir = "${rootProject.buildDir}/${project.name}" 30 | project.evaluationDependsOn(':app') 31 | } 32 | 33 | task clean(type: Delete) { 34 | delete rootProject.buildDir 35 | } 36 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/ephemeral/ 22 | Flutter/app.flx 23 | Flutter/app.zip 24 | Flutter/flutter_assets/ 25 | Flutter/flutter_export_environment.sh 26 | ServiceDefinitions.json 27 | Runner/GeneratedPluginRegistrant.* 28 | 29 | # Exceptions to above rules. 30 | !default.mode1v3 31 | !default.mode2v3 32 | !default.pbxuser 33 | !default.perspectivev3 34 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 13 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 14 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 15 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXCopyFilesBuildPhase section */ 19 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 20 | isa = PBXCopyFilesBuildPhase; 21 | buildActionMask = 2147483647; 22 | dstPath = ""; 23 | dstSubfolderSpec = 10; 24 | files = ( 25 | ); 26 | name = "Embed Frameworks"; 27 | runOnlyForDeploymentPostprocessing = 0; 28 | }; 29 | /* End PBXCopyFilesBuildPhase section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 33 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 34 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 35 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 36 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 37 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 38 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 39 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 40 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 42 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 43 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 44 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | ); 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | /* End PBXFrameworksBuildPhase section */ 56 | 57 | /* Begin PBXGroup section */ 58 | 9740EEB11CF90186004384FC /* Flutter */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 62 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 63 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 64 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 65 | ); 66 | name = Flutter; 67 | sourceTree = ""; 68 | }; 69 | 97C146E51CF9000F007C117D = { 70 | isa = PBXGroup; 71 | children = ( 72 | 9740EEB11CF90186004384FC /* Flutter */, 73 | 97C146F01CF9000F007C117D /* Runner */, 74 | 97C146EF1CF9000F007C117D /* Products */, 75 | ); 76 | sourceTree = ""; 77 | }; 78 | 97C146EF1CF9000F007C117D /* Products */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | 97C146EE1CF9000F007C117D /* Runner.app */, 82 | ); 83 | name = Products; 84 | sourceTree = ""; 85 | }; 86 | 97C146F01CF9000F007C117D /* Runner */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 90 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 91 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 92 | 97C147021CF9000F007C117D /* Info.plist */, 93 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 94 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 95 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 96 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 97 | ); 98 | path = Runner; 99 | sourceTree = ""; 100 | }; 101 | /* End PBXGroup section */ 102 | 103 | /* Begin PBXNativeTarget section */ 104 | 97C146ED1CF9000F007C117D /* Runner */ = { 105 | isa = PBXNativeTarget; 106 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 107 | buildPhases = ( 108 | 9740EEB61CF901F6004384FC /* Run Script */, 109 | 97C146EA1CF9000F007C117D /* Sources */, 110 | 97C146EB1CF9000F007C117D /* Frameworks */, 111 | 97C146EC1CF9000F007C117D /* Resources */, 112 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 113 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 114 | ); 115 | buildRules = ( 116 | ); 117 | dependencies = ( 118 | ); 119 | name = Runner; 120 | productName = Runner; 121 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 122 | productType = "com.apple.product-type.application"; 123 | }; 124 | /* End PBXNativeTarget section */ 125 | 126 | /* Begin PBXProject section */ 127 | 97C146E61CF9000F007C117D /* Project object */ = { 128 | isa = PBXProject; 129 | attributes = { 130 | LastUpgradeCheck = 1020; 131 | ORGANIZATIONNAME = ""; 132 | TargetAttributes = { 133 | 97C146ED1CF9000F007C117D = { 134 | CreatedOnToolsVersion = 7.3.1; 135 | LastSwiftMigration = 1100; 136 | }; 137 | }; 138 | }; 139 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 140 | compatibilityVersion = "Xcode 9.3"; 141 | developmentRegion = en; 142 | hasScannedForEncodings = 0; 143 | knownRegions = ( 144 | en, 145 | Base, 146 | ); 147 | mainGroup = 97C146E51CF9000F007C117D; 148 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 149 | projectDirPath = ""; 150 | projectRoot = ""; 151 | targets = ( 152 | 97C146ED1CF9000F007C117D /* Runner */, 153 | ); 154 | }; 155 | /* End PBXProject section */ 156 | 157 | /* Begin PBXResourcesBuildPhase section */ 158 | 97C146EC1CF9000F007C117D /* Resources */ = { 159 | isa = PBXResourcesBuildPhase; 160 | buildActionMask = 2147483647; 161 | files = ( 162 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 163 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 164 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 165 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 166 | ); 167 | runOnlyForDeploymentPostprocessing = 0; 168 | }; 169 | /* End PBXResourcesBuildPhase section */ 170 | 171 | /* Begin PBXShellScriptBuildPhase section */ 172 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 173 | isa = PBXShellScriptBuildPhase; 174 | buildActionMask = 2147483647; 175 | files = ( 176 | ); 177 | inputPaths = ( 178 | ); 179 | name = "Thin Binary"; 180 | outputPaths = ( 181 | ); 182 | runOnlyForDeploymentPostprocessing = 0; 183 | shellPath = /bin/sh; 184 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 185 | }; 186 | 9740EEB61CF901F6004384FC /* Run Script */ = { 187 | isa = PBXShellScriptBuildPhase; 188 | buildActionMask = 2147483647; 189 | files = ( 190 | ); 191 | inputPaths = ( 192 | ); 193 | name = "Run Script"; 194 | outputPaths = ( 195 | ); 196 | runOnlyForDeploymentPostprocessing = 0; 197 | shellPath = /bin/sh; 198 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 199 | }; 200 | /* End PBXShellScriptBuildPhase section */ 201 | 202 | /* Begin PBXSourcesBuildPhase section */ 203 | 97C146EA1CF9000F007C117D /* Sources */ = { 204 | isa = PBXSourcesBuildPhase; 205 | buildActionMask = 2147483647; 206 | files = ( 207 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 208 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 209 | ); 210 | runOnlyForDeploymentPostprocessing = 0; 211 | }; 212 | /* End PBXSourcesBuildPhase section */ 213 | 214 | /* Begin PBXVariantGroup section */ 215 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 216 | isa = PBXVariantGroup; 217 | children = ( 218 | 97C146FB1CF9000F007C117D /* Base */, 219 | ); 220 | name = Main.storyboard; 221 | sourceTree = ""; 222 | }; 223 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 224 | isa = PBXVariantGroup; 225 | children = ( 226 | 97C147001CF9000F007C117D /* Base */, 227 | ); 228 | name = LaunchScreen.storyboard; 229 | sourceTree = ""; 230 | }; 231 | /* End PBXVariantGroup section */ 232 | 233 | /* Begin XCBuildConfiguration section */ 234 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 235 | isa = XCBuildConfiguration; 236 | buildSettings = { 237 | ALWAYS_SEARCH_USER_PATHS = NO; 238 | CLANG_ANALYZER_NONNULL = YES; 239 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 240 | CLANG_CXX_LIBRARY = "libc++"; 241 | CLANG_ENABLE_MODULES = YES; 242 | CLANG_ENABLE_OBJC_ARC = YES; 243 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 244 | CLANG_WARN_BOOL_CONVERSION = YES; 245 | CLANG_WARN_COMMA = YES; 246 | CLANG_WARN_CONSTANT_CONVERSION = YES; 247 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 248 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 249 | CLANG_WARN_EMPTY_BODY = YES; 250 | CLANG_WARN_ENUM_CONVERSION = YES; 251 | CLANG_WARN_INFINITE_RECURSION = YES; 252 | CLANG_WARN_INT_CONVERSION = YES; 253 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 254 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 255 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 256 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 257 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 258 | CLANG_WARN_STRICT_PROTOTYPES = YES; 259 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 260 | CLANG_WARN_UNREACHABLE_CODE = YES; 261 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 262 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 263 | COPY_PHASE_STRIP = NO; 264 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 265 | ENABLE_NS_ASSERTIONS = NO; 266 | ENABLE_STRICT_OBJC_MSGSEND = YES; 267 | GCC_C_LANGUAGE_STANDARD = gnu99; 268 | GCC_NO_COMMON_BLOCKS = YES; 269 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 270 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 271 | GCC_WARN_UNDECLARED_SELECTOR = YES; 272 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 273 | GCC_WARN_UNUSED_FUNCTION = YES; 274 | GCC_WARN_UNUSED_VARIABLE = YES; 275 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 276 | MTL_ENABLE_DEBUG_INFO = NO; 277 | SDKROOT = iphoneos; 278 | SUPPORTED_PLATFORMS = iphoneos; 279 | TARGETED_DEVICE_FAMILY = "1,2"; 280 | VALIDATE_PRODUCT = YES; 281 | }; 282 | name = Profile; 283 | }; 284 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 285 | isa = XCBuildConfiguration; 286 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 287 | buildSettings = { 288 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 289 | CLANG_ENABLE_MODULES = YES; 290 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 291 | ENABLE_BITCODE = NO; 292 | INFOPLIST_FILE = Runner/Info.plist; 293 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 294 | PRODUCT_BUNDLE_IDENTIFIER = com.lijiaqi.imageEditorExample; 295 | PRODUCT_NAME = "$(TARGET_NAME)"; 296 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 297 | SWIFT_VERSION = 5.0; 298 | VERSIONING_SYSTEM = "apple-generic"; 299 | }; 300 | name = Profile; 301 | }; 302 | 97C147031CF9000F007C117D /* Debug */ = { 303 | isa = XCBuildConfiguration; 304 | buildSettings = { 305 | ALWAYS_SEARCH_USER_PATHS = NO; 306 | CLANG_ANALYZER_NONNULL = YES; 307 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 308 | CLANG_CXX_LIBRARY = "libc++"; 309 | CLANG_ENABLE_MODULES = YES; 310 | CLANG_ENABLE_OBJC_ARC = YES; 311 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 312 | CLANG_WARN_BOOL_CONVERSION = YES; 313 | CLANG_WARN_COMMA = YES; 314 | CLANG_WARN_CONSTANT_CONVERSION = YES; 315 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 316 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 317 | CLANG_WARN_EMPTY_BODY = YES; 318 | CLANG_WARN_ENUM_CONVERSION = YES; 319 | CLANG_WARN_INFINITE_RECURSION = YES; 320 | CLANG_WARN_INT_CONVERSION = YES; 321 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 322 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 323 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 324 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 325 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 326 | CLANG_WARN_STRICT_PROTOTYPES = YES; 327 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 328 | CLANG_WARN_UNREACHABLE_CODE = YES; 329 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 330 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 331 | COPY_PHASE_STRIP = NO; 332 | DEBUG_INFORMATION_FORMAT = dwarf; 333 | ENABLE_STRICT_OBJC_MSGSEND = YES; 334 | ENABLE_TESTABILITY = YES; 335 | GCC_C_LANGUAGE_STANDARD = gnu99; 336 | GCC_DYNAMIC_NO_PIC = NO; 337 | GCC_NO_COMMON_BLOCKS = YES; 338 | GCC_OPTIMIZATION_LEVEL = 0; 339 | GCC_PREPROCESSOR_DEFINITIONS = ( 340 | "DEBUG=1", 341 | "$(inherited)", 342 | ); 343 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 344 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 345 | GCC_WARN_UNDECLARED_SELECTOR = YES; 346 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 347 | GCC_WARN_UNUSED_FUNCTION = YES; 348 | GCC_WARN_UNUSED_VARIABLE = YES; 349 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 350 | MTL_ENABLE_DEBUG_INFO = YES; 351 | ONLY_ACTIVE_ARCH = YES; 352 | SDKROOT = iphoneos; 353 | TARGETED_DEVICE_FAMILY = "1,2"; 354 | }; 355 | name = Debug; 356 | }; 357 | 97C147041CF9000F007C117D /* Release */ = { 358 | isa = XCBuildConfiguration; 359 | buildSettings = { 360 | ALWAYS_SEARCH_USER_PATHS = NO; 361 | CLANG_ANALYZER_NONNULL = YES; 362 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 363 | CLANG_CXX_LIBRARY = "libc++"; 364 | CLANG_ENABLE_MODULES = YES; 365 | CLANG_ENABLE_OBJC_ARC = YES; 366 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 367 | CLANG_WARN_BOOL_CONVERSION = YES; 368 | CLANG_WARN_COMMA = YES; 369 | CLANG_WARN_CONSTANT_CONVERSION = YES; 370 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 371 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 372 | CLANG_WARN_EMPTY_BODY = YES; 373 | CLANG_WARN_ENUM_CONVERSION = YES; 374 | CLANG_WARN_INFINITE_RECURSION = YES; 375 | CLANG_WARN_INT_CONVERSION = YES; 376 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 377 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 378 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 379 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 380 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 381 | CLANG_WARN_STRICT_PROTOTYPES = YES; 382 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 383 | CLANG_WARN_UNREACHABLE_CODE = YES; 384 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 385 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 386 | COPY_PHASE_STRIP = NO; 387 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 388 | ENABLE_NS_ASSERTIONS = NO; 389 | ENABLE_STRICT_OBJC_MSGSEND = YES; 390 | GCC_C_LANGUAGE_STANDARD = gnu99; 391 | GCC_NO_COMMON_BLOCKS = YES; 392 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 393 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 394 | GCC_WARN_UNDECLARED_SELECTOR = YES; 395 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 396 | GCC_WARN_UNUSED_FUNCTION = YES; 397 | GCC_WARN_UNUSED_VARIABLE = YES; 398 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 399 | MTL_ENABLE_DEBUG_INFO = NO; 400 | SDKROOT = iphoneos; 401 | SUPPORTED_PLATFORMS = iphoneos; 402 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 403 | TARGETED_DEVICE_FAMILY = "1,2"; 404 | VALIDATE_PRODUCT = YES; 405 | }; 406 | name = Release; 407 | }; 408 | 97C147061CF9000F007C117D /* Debug */ = { 409 | isa = XCBuildConfiguration; 410 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 411 | buildSettings = { 412 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 413 | CLANG_ENABLE_MODULES = YES; 414 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 415 | ENABLE_BITCODE = NO; 416 | INFOPLIST_FILE = Runner/Info.plist; 417 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 418 | PRODUCT_BUNDLE_IDENTIFIER = com.lijiaqi.imageEditorExample; 419 | PRODUCT_NAME = "$(TARGET_NAME)"; 420 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 421 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 422 | SWIFT_VERSION = 5.0; 423 | VERSIONING_SYSTEM = "apple-generic"; 424 | }; 425 | name = Debug; 426 | }; 427 | 97C147071CF9000F007C117D /* Release */ = { 428 | isa = XCBuildConfiguration; 429 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 430 | buildSettings = { 431 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 432 | CLANG_ENABLE_MODULES = YES; 433 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 434 | ENABLE_BITCODE = NO; 435 | INFOPLIST_FILE = Runner/Info.plist; 436 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 437 | PRODUCT_BUNDLE_IDENTIFIER = com.lijiaqi.imageEditorExample; 438 | PRODUCT_NAME = "$(TARGET_NAME)"; 439 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 440 | SWIFT_VERSION = 5.0; 441 | VERSIONING_SYSTEM = "apple-generic"; 442 | }; 443 | name = Release; 444 | }; 445 | /* End XCBuildConfiguration section */ 446 | 447 | /* Begin XCConfigurationList section */ 448 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 449 | isa = XCConfigurationList; 450 | buildConfigurations = ( 451 | 97C147031CF9000F007C117D /* Debug */, 452 | 97C147041CF9000F007C117D /* Release */, 453 | 249021D3217E4FDB00AE95B9 /* Profile */, 454 | ); 455 | defaultConfigurationIsVisible = 0; 456 | defaultConfigurationName = Release; 457 | }; 458 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 459 | isa = XCConfigurationList; 460 | buildConfigurations = ( 461 | 97C147061CF9000F007C117D /* Debug */, 462 | 97C147071CF9000F007C117D /* Release */, 463 | 249021D4217E4FDB00AE95B9 /* Profile */, 464 | ); 465 | defaultConfigurationIsVisible = 0; 466 | defaultConfigurationName = Release; 467 | }; 468 | /* End XCConfigurationList section */ 469 | }; 470 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 471 | } 472 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladeofgod/flutter_image_editor/9f0aeb5d025a60650e5daf2dd36329fc062e2b82/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladeofgod/flutter_image_editor/9f0aeb5d025a60650e5daf2dd36329fc062e2b82/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladeofgod/flutter_image_editor/9f0aeb5d025a60650e5daf2dd36329fc062e2b82/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladeofgod/flutter_image_editor/9f0aeb5d025a60650e5daf2dd36329fc062e2b82/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladeofgod/flutter_image_editor/9f0aeb5d025a60650e5daf2dd36329fc062e2b82/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladeofgod/flutter_image_editor/9f0aeb5d025a60650e5daf2dd36329fc062e2b82/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladeofgod/flutter_image_editor/9f0aeb5d025a60650e5daf2dd36329fc062e2b82/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladeofgod/flutter_image_editor/9f0aeb5d025a60650e5daf2dd36329fc062e2b82/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladeofgod/flutter_image_editor/9f0aeb5d025a60650e5daf2dd36329fc062e2b82/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladeofgod/flutter_image_editor/9f0aeb5d025a60650e5daf2dd36329fc062e2b82/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladeofgod/flutter_image_editor/9f0aeb5d025a60650e5daf2dd36329fc062e2b82/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladeofgod/flutter_image_editor/9f0aeb5d025a60650e5daf2dd36329fc062e2b82/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladeofgod/flutter_image_editor/9f0aeb5d025a60650e5daf2dd36329fc062e2b82/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladeofgod/flutter_image_editor/9f0aeb5d025a60650e5daf2dd36329fc062e2b82/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladeofgod/flutter_image_editor/9f0aeb5d025a60650e5daf2dd36329fc062e2b82/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladeofgod/flutter_image_editor/9f0aeb5d025a60650e5daf2dd36329fc062e2b82/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladeofgod/flutter_image_editor/9f0aeb5d025a60650e5daf2dd36329fc062e2b82/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladeofgod/flutter_image_editor/9f0aeb5d025a60650e5daf2dd36329fc062e2b82/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | image_editor_example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:image_editor_dove/image_editor.dart'; 4 | import 'dart:async'; 5 | 6 | import 'package:image_picker/image_picker.dart'; 7 | 8 | 9 | void main() { 10 | runApp(MyApp()); 11 | } 12 | class MyApp extends StatefulWidget { 13 | @override 14 | _MyAppState createState() => _MyAppState(); 15 | } 16 | 17 | class _MyAppState extends State { 18 | 19 | @override 20 | void initState() { 21 | super.initState(); 22 | } 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | return MaterialApp( 27 | home: Scaffold( 28 | appBar: AppBar( 29 | title: const Text('Plugin example app'), 30 | ), 31 | body: HomePage(), 32 | ), 33 | ); 34 | } 35 | } 36 | 37 | class HomePage extends StatefulWidget { 38 | @override 39 | _HomePageState createState() => _HomePageState(); 40 | } 41 | 42 | class _HomePageState extends State { 43 | 44 | File? _image; 45 | 46 | final picker = ImagePicker(); 47 | 48 | Future toImageEditor(File origin) async { 49 | return Navigator.push(context, MaterialPageRoute(builder: (context) { 50 | return ImageEditor( 51 | originImage: origin, 52 | ); 53 | })).then((result) { 54 | if (result is EditorImageResult) { 55 | setState(() { 56 | _image = result.newFile; 57 | }); 58 | } 59 | }).catchError((er) { 60 | debugPrint(er); 61 | }); 62 | } 63 | 64 | void getImage() async { 65 | PickedFile? image = 66 | await picker.getImage(source: ImageSource.gallery); 67 | if(image != null) { 68 | final File origin = File(image.path); 69 | toImageEditor(origin); 70 | } 71 | } 72 | 73 | @override 74 | Widget build(BuildContext context) { 75 | return Material( 76 | color: Colors.white, 77 | child: Container( 78 | width: double.infinity, height: double.infinity, 79 | child: Column( 80 | children: [ 81 | if(_image != null) 82 | Expanded(child: Image.file(_image!)), 83 | ElevatedButton( 84 | onPressed: getImage, 85 | child: Text('edit image'), 86 | ), 87 | ], 88 | ), 89 | ), 90 | ); 91 | } 92 | } 93 | 94 | Widget condition({required bool condtion, required Widget isTrue, required Widget isFalse}) { 95 | return condtion ? isTrue : isFalse; 96 | } 97 | -------------------------------------------------------------------------------- /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 | sha256: "7e0d52067d05f2e0324268097ba723b71cb41ac8a6a2b24d1edf9c536b987b03" 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "3.4.6" 12 | async: 13 | dependency: transitive 14 | description: 15 | name: async 16 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "2.11.0" 20 | boolean_selector: 21 | dependency: transitive 22 | description: 23 | name: boolean_selector 24 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "2.1.1" 28 | characters: 29 | dependency: transitive 30 | description: 31 | name: characters 32 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "1.3.0" 36 | clock: 37 | dependency: transitive 38 | description: 39 | name: clock 40 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "1.1.1" 44 | collection: 45 | dependency: transitive 46 | description: 47 | name: collection 48 | sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "1.17.1" 52 | convert: 53 | dependency: transitive 54 | description: 55 | name: convert 56 | sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" 57 | url: "https://pub.dev" 58 | source: hosted 59 | version: "3.1.1" 60 | cross_file: 61 | dependency: transitive 62 | description: 63 | name: cross_file 64 | sha256: "445db18de832dba8d851e287aff8ccf169bed30d2e94243cb54c7d2f1ed2142c" 65 | url: "https://pub.dev" 66 | source: hosted 67 | version: "0.3.3+6" 68 | crypto: 69 | dependency: transitive 70 | description: 71 | name: crypto 72 | sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab 73 | url: "https://pub.dev" 74 | source: hosted 75 | version: "3.0.3" 76 | cupertino_icons: 77 | dependency: "direct main" 78 | description: 79 | name: cupertino_icons 80 | sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d 81 | url: "https://pub.dev" 82 | source: hosted 83 | version: "1.0.6" 84 | fake_async: 85 | dependency: transitive 86 | description: 87 | name: fake_async 88 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" 89 | url: "https://pub.dev" 90 | source: hosted 91 | version: "1.3.1" 92 | ffi: 93 | dependency: transitive 94 | description: 95 | name: ffi 96 | sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" 97 | url: "https://pub.dev" 98 | source: hosted 99 | version: "2.1.0" 100 | flutter: 101 | dependency: "direct main" 102 | description: flutter 103 | source: sdk 104 | version: "0.0.0" 105 | flutter_plugin_android_lifecycle: 106 | dependency: transitive 107 | description: 108 | name: flutter_plugin_android_lifecycle 109 | sha256: b068ffc46f82a55844acfa4fdbb61fad72fa2aef0905548419d97f0f95c456da 110 | url: "https://pub.dev" 111 | source: hosted 112 | version: "2.0.17" 113 | flutter_test: 114 | dependency: "direct dev" 115 | description: flutter 116 | source: sdk 117 | version: "0.0.0" 118 | flutter_web_plugins: 119 | dependency: transitive 120 | description: flutter 121 | source: sdk 122 | version: "0.0.0" 123 | http: 124 | dependency: transitive 125 | description: 126 | name: http 127 | sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" 128 | url: "https://pub.dev" 129 | source: hosted 130 | version: "1.1.0" 131 | http_parser: 132 | dependency: transitive 133 | description: 134 | name: http_parser 135 | sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" 136 | url: "https://pub.dev" 137 | source: hosted 138 | version: "4.0.2" 139 | image: 140 | dependency: transitive 141 | description: 142 | name: image 143 | sha256: "8e9d133755c3e84c73288363e6343157c383a0c6c56fc51afcc5d4d7180306d6" 144 | url: "https://pub.dev" 145 | source: hosted 146 | version: "3.3.0" 147 | image_editor_dove: 148 | dependency: "direct main" 149 | description: 150 | path: ".." 151 | relative: true 152 | source: path 153 | version: "0.0.2" 154 | image_picker: 155 | dependency: "direct main" 156 | description: 157 | name: image_picker 158 | sha256: b4f02353277b39f350093a2460b32b43eafaa3bfc4f92e8d90926698d8d78df6 159 | url: "https://pub.dev" 160 | source: hosted 161 | version: "0.7.5+4" 162 | image_picker_for_web: 163 | dependency: transitive 164 | description: 165 | name: image_picker_for_web 166 | sha256: "869fe8a64771b7afbc99fc433a5f7be2fea4d1cb3d7c11a48b6b579eb9c797f0" 167 | url: "https://pub.dev" 168 | source: hosted 169 | version: "2.2.0" 170 | image_picker_platform_interface: 171 | dependency: transitive 172 | description: 173 | name: image_picker_platform_interface 174 | sha256: ed9b00e63977c93b0d2d2b343685bed9c324534ba5abafbb3dfbd6a780b1b514 175 | url: "https://pub.dev" 176 | source: hosted 177 | version: "2.9.1" 178 | js: 179 | dependency: transitive 180 | description: 181 | name: js 182 | sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 183 | url: "https://pub.dev" 184 | source: hosted 185 | version: "0.6.7" 186 | matcher: 187 | dependency: transitive 188 | description: 189 | name: matcher 190 | sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" 191 | url: "https://pub.dev" 192 | source: hosted 193 | version: "0.12.15" 194 | material_color_utilities: 195 | dependency: transitive 196 | description: 197 | name: material_color_utilities 198 | sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 199 | url: "https://pub.dev" 200 | source: hosted 201 | version: "0.2.0" 202 | meta: 203 | dependency: transitive 204 | description: 205 | name: meta 206 | sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" 207 | url: "https://pub.dev" 208 | source: hosted 209 | version: "1.9.1" 210 | mime: 211 | dependency: transitive 212 | description: 213 | name: mime 214 | sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e 215 | url: "https://pub.dev" 216 | source: hosted 217 | version: "1.0.4" 218 | path: 219 | dependency: transitive 220 | description: 221 | name: path 222 | sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" 223 | url: "https://pub.dev" 224 | source: hosted 225 | version: "1.8.3" 226 | path_provider: 227 | dependency: transitive 228 | description: 229 | name: path_provider 230 | sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa 231 | url: "https://pub.dev" 232 | source: hosted 233 | version: "2.1.1" 234 | path_provider_android: 235 | dependency: transitive 236 | description: 237 | name: path_provider_android 238 | sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72 239 | url: "https://pub.dev" 240 | source: hosted 241 | version: "2.2.1" 242 | path_provider_foundation: 243 | dependency: transitive 244 | description: 245 | name: path_provider_foundation 246 | sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" 247 | url: "https://pub.dev" 248 | source: hosted 249 | version: "2.3.1" 250 | path_provider_linux: 251 | dependency: transitive 252 | description: 253 | name: path_provider_linux 254 | sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 255 | url: "https://pub.dev" 256 | source: hosted 257 | version: "2.2.1" 258 | path_provider_platform_interface: 259 | dependency: transitive 260 | description: 261 | name: path_provider_platform_interface 262 | sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" 263 | url: "https://pub.dev" 264 | source: hosted 265 | version: "2.1.1" 266 | path_provider_windows: 267 | dependency: transitive 268 | description: 269 | name: path_provider_windows 270 | sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" 271 | url: "https://pub.dev" 272 | source: hosted 273 | version: "2.2.1" 274 | petitparser: 275 | dependency: transitive 276 | description: 277 | name: petitparser 278 | sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 279 | url: "https://pub.dev" 280 | source: hosted 281 | version: "5.4.0" 282 | platform: 283 | dependency: transitive 284 | description: 285 | name: platform 286 | sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59" 287 | url: "https://pub.dev" 288 | source: hosted 289 | version: "3.1.3" 290 | plugin_platform_interface: 291 | dependency: transitive 292 | description: 293 | name: plugin_platform_interface 294 | sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d 295 | url: "https://pub.dev" 296 | source: hosted 297 | version: "2.1.6" 298 | pointycastle: 299 | dependency: transitive 300 | description: 301 | name: pointycastle 302 | sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" 303 | url: "https://pub.dev" 304 | source: hosted 305 | version: "3.7.3" 306 | screenshot: 307 | dependency: transitive 308 | description: 309 | name: screenshot 310 | sha256: "455284ff1f5b911d94a43c25e1385485cf6b4f288293eba68f15dad711c7b81c" 311 | url: "https://pub.dev" 312 | source: hosted 313 | version: "2.1.0" 314 | sky_engine: 315 | dependency: transitive 316 | description: flutter 317 | source: sdk 318 | version: "0.0.99" 319 | source_span: 320 | dependency: transitive 321 | description: 322 | name: source_span 323 | sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 324 | url: "https://pub.dev" 325 | source: hosted 326 | version: "1.9.1" 327 | stack_trace: 328 | dependency: transitive 329 | description: 330 | name: stack_trace 331 | sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 332 | url: "https://pub.dev" 333 | source: hosted 334 | version: "1.11.0" 335 | stream_channel: 336 | dependency: transitive 337 | description: 338 | name: stream_channel 339 | sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" 340 | url: "https://pub.dev" 341 | source: hosted 342 | version: "2.1.1" 343 | string_scanner: 344 | dependency: transitive 345 | description: 346 | name: string_scanner 347 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" 348 | url: "https://pub.dev" 349 | source: hosted 350 | version: "1.2.0" 351 | term_glyph: 352 | dependency: transitive 353 | description: 354 | name: term_glyph 355 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 356 | url: "https://pub.dev" 357 | source: hosted 358 | version: "1.2.1" 359 | test_api: 360 | dependency: transitive 361 | description: 362 | name: test_api 363 | sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb 364 | url: "https://pub.dev" 365 | source: hosted 366 | version: "0.5.1" 367 | typed_data: 368 | dependency: transitive 369 | description: 370 | name: typed_data 371 | sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c 372 | url: "https://pub.dev" 373 | source: hosted 374 | version: "1.3.2" 375 | vector_math: 376 | dependency: transitive 377 | description: 378 | name: vector_math 379 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 380 | url: "https://pub.dev" 381 | source: hosted 382 | version: "2.1.4" 383 | win32: 384 | dependency: transitive 385 | description: 386 | name: win32 387 | sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3" 388 | url: "https://pub.dev" 389 | source: hosted 390 | version: "5.0.9" 391 | xdg_directories: 392 | dependency: transitive 393 | description: 394 | name: xdg_directories 395 | sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2" 396 | url: "https://pub.dev" 397 | source: hosted 398 | version: "1.0.3" 399 | xml: 400 | dependency: transitive 401 | description: 402 | name: xml 403 | sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" 404 | url: "https://pub.dev" 405 | source: hosted 406 | version: "6.3.0" 407 | sdks: 408 | dart: ">=3.0.0 <4.0.0" 409 | flutter: ">=3.10.0" 410 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: image_editor_dove_example 2 | description: Demonstrates how to use the image_editor plugin. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | environment: 9 | sdk: ">=2.12.0 <3.0.0" 10 | 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | 15 | image_editor_dove: 16 | # When depending on this package from a real application you should use: 17 | # image_editor: ^x.y.z 18 | # See https://dart.dev/tools/pub/dependencies#version-constraints 19 | # The example app is bundled with the plugin so we use a path dependency on 20 | # the parent directory to use the current plugin's version. 21 | path: ../ 22 | 23 | # The following adds the Cupertino Icons font to your application. 24 | # Use with the CupertinoIcons class for iOS style icons. 25 | cupertino_icons: ^1.0.2 26 | 27 | image_picker: ^0.7.4 28 | 29 | 30 | dev_dependencies: 31 | flutter_test: 32 | sdk: flutter 33 | 34 | # For information on the generic Dart part of this file, see the 35 | # following page: https://dart.dev/tools/pub/pubspec 36 | 37 | # The following section is specific to Flutter. 38 | flutter: 39 | 40 | # The following line ensures that the Material Icons font is 41 | # included with your application, so that you can use the icons in 42 | # the material Icons class. 43 | uses-material-design: true 44 | 45 | # To add assets to your application, add an assets section, like this: 46 | # assets: 47 | # - images/a_dot_burr.jpeg 48 | # - images/a_dot_ham.jpeg 49 | 50 | # An image asset can refer to one or more resolution-specific "variants", see 51 | # https://flutter.dev/assets-and-images/#resolution-aware. 52 | 53 | # For details regarding adding assets from package dependencies, see 54 | # https://flutter.dev/assets-and-images/#from-packages 55 | 56 | # To add custom fonts to your application, add a fonts section here, 57 | # in this "flutter" section. Each entry in this list should have a 58 | # "family" key with the font family name, and a "fonts" key with a 59 | # list giving the asset and other descriptors for the font. For 60 | # example: 61 | # fonts: 62 | # - family: Schyler 63 | # fonts: 64 | # - asset: fonts/Schyler-Regular.ttf 65 | # - asset: fonts/Schyler-Italic.ttf 66 | # style: italic 67 | # - family: Trajan Pro 68 | # fonts: 69 | # - asset: fonts/TrajanPro.ttf 70 | # - asset: fonts/TrajanPro_Bold.ttf 71 | # weight: 700 72 | # 73 | # For details regarding fonts from package dependencies, 74 | # see https://flutter.dev/custom-fonts/#from-packages 75 | -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | import 'package:image_editor_dove_example/main.dart'; 11 | 12 | 13 | void main() { 14 | testWidgets('Verify Platform version', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that platform version is retrieved. 19 | expect( 20 | find.byWidgetPredicate( 21 | (Widget widget) => widget is Text && 22 | widget.data!.startsWith('Running on:'), 23 | ), 24 | findsOneWidget, 25 | ); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /image_editor.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/Generated.xcconfig 37 | /Flutter/ephemeral/ 38 | /Flutter/flutter_export_environment.sh -------------------------------------------------------------------------------- /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladeofgod/flutter_image_editor/9f0aeb5d025a60650e5daf2dd36329fc062e2b82/ios/Assets/.gitkeep -------------------------------------------------------------------------------- /ios/Classes/ImageEditorPlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface ImageEditorPlugin : NSObject 4 | @end 5 | -------------------------------------------------------------------------------- /ios/Classes/ImageEditorPlugin.m: -------------------------------------------------------------------------------- 1 | #import "ImageEditorPlugin.h" 2 | #if __has_include() 3 | #import 4 | #else 5 | // Support project import fallback if the generated compatibility header 6 | // is not copied when this plugin is created as a library. 7 | // https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816 8 | #import "image_editor_dove-Swift.h" 9 | #endif 10 | 11 | @implementation ImageEditorPlugin 12 | + (void)registerWithRegistrar:(NSObject*)registrar { 13 | [SwiftImageEditorPlugin registerWithRegistrar:registrar]; 14 | } 15 | @end 16 | -------------------------------------------------------------------------------- /ios/Classes/SwiftImageEditorPlugin.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | 4 | public class SwiftImageEditorPlugin: NSObject, FlutterPlugin { 5 | public static func register(with registrar: FlutterPluginRegistrar) { 6 | let channel = FlutterMethodChannel(name: "image_editor", binaryMessenger: registrar.messenger()) 7 | let instance = SwiftImageEditorPlugin() 8 | registrar.addMethodCallDelegate(instance, channel: channel) 9 | } 10 | 11 | public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { 12 | result("iOS " + UIDevice.current.systemVersion) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /ios/image_editor_dove.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. 3 | # Run `pod lib lint image_editor.podspec` to validate before publishing. 4 | # 5 | Pod::Spec.new do |s| 6 | s.name = 'image_editor_dove' 7 | s.version = '0.0.1' 8 | s.summary = 'An image editor with crop, scribble, mosaic, add-text, flip, rotated functions.' 9 | s.description = <<-DESC 10 | An image editor with crop, scribble, mosaic, add-text, flip, rotated functions. 11 | DESC 12 | s.homepage = 'http://example.com' 13 | s.license = { :file => '../LICENSE' } 14 | s.author = { 'Your Company' => 'email@example.com' } 15 | s.source = { :path => '.' } 16 | s.source_files = 'Classes/**/*' 17 | s.dependency 'Flutter' 18 | s.platform = :ios, '8.0' 19 | 20 | # Flutter.framework does not contain a i386 slice. 21 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } 22 | s.swift_version = '5.0' 23 | end 24 | -------------------------------------------------------------------------------- /lib/extension/num_extension.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | import 'package:flutter/cupertino.dart'; 4 | 5 | extension EditorNum on num{ 6 | 7 | Widget get vGap => SizedBox(height: this.toDouble()); 8 | 9 | Widget get hGap => SizedBox(width: this.toDouble()); 10 | 11 | } 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /lib/flutter_image_editor.dart: -------------------------------------------------------------------------------- 1 | 2 | export 'image_editor.dart'; 3 | export 'widget/image_editor_delegate.dart'; 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /lib/image_editor.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:math' as math; 3 | 4 | import 'package:flutter/material.dart'; 5 | 6 | import 'dart:ui' as ui; 7 | 8 | import 'extension/num_extension.dart'; 9 | import 'package:path_provider/path_provider.dart'; 10 | import 'package:screenshot/screenshot.dart'; 11 | 12 | import 'model/float_text_model.dart'; 13 | import 'widget/drawing_board.dart'; 14 | import 'widget/editor_panel_controller.dart'; 15 | import 'widget/float_text_widget.dart'; 16 | import 'widget/image_editor_delegate.dart'; 17 | import 'widget/text_editor_page.dart'; 18 | 19 | const CanvasLauncher _defaultLauncher = CanvasLauncher(mosaicWidth: 5.0, pStrockWidth: 5.0, pColor: Colors.red); 20 | 21 | ///The editor's result. 22 | class EditorImageResult { 23 | ///image width 24 | final int imgWidth; 25 | 26 | ///image height 27 | final int imgHeight; 28 | 29 | ///new file after edit 30 | final File newFile; 31 | 32 | EditorImageResult(this.imgWidth, this.imgHeight, this.newFile); 33 | } 34 | 35 | 36 | ///The launcher provides some initial-values for canvas; 37 | class CanvasLauncher{ 38 | 39 | factory CanvasLauncher.auto() { 40 | return const CanvasLauncher(mosaicWidth: 5.0, pStrockWidth: 5.0, pColor: Colors.red); 41 | } 42 | 43 | const CanvasLauncher({required this.mosaicWidth, required this.pStrockWidth, required this.pColor}); 44 | 45 | ///mosaic pixel's width 46 | final double mosaicWidth; 47 | 48 | ///painter stroke width. 49 | final double pStrockWidth; 50 | 51 | ///painter color 52 | final Color pColor; 53 | 54 | } 55 | 56 | class ImageEditor extends StatefulWidget { 57 | 58 | const ImageEditor({Key? key, 59 | required this.originImage, 60 | this.savePath, 61 | this.launcher = _defaultLauncher}) 62 | : super(key: key); 63 | 64 | ///origin image 65 | /// * input for edit 66 | final File originImage; 67 | 68 | ///edited-file's save path. 69 | /// * if null will save in temporary file. 70 | final Directory? savePath; 71 | 72 | ///provide some initial value 73 | final CanvasLauncher launcher; 74 | 75 | ///[uiDelegate] is determine the editor's ui style. 76 | ///You can extends [ImageEditorDelegate] and custome it by youself. 77 | static ImageEditorDelegate uiDelegate = DefaultImageEditorDelegate(); 78 | 79 | @override 80 | State createState() { 81 | return ImageEditorState(); 82 | } 83 | } 84 | 85 | class ImageEditorState extends State 86 | with SignatureBinding, ScreenShotBinding, TextCanvasBinding, RotateCanvasBinding, LittleWidgetBinding, WindowUiBinding { 87 | final EditorPanelController _panelController = EditorPanelController(); 88 | 89 | double get headerHeight => windowStatusBarHeight; 90 | 91 | double get bottomBarHeight => 105 + windowBottomBarHeight; 92 | 93 | ///Edit area height. 94 | double get canvasHeight => screenHeight - bottomBarHeight - headerHeight; 95 | 96 | ///Operation panel button's horizontal space. 97 | Widget get controlBtnSpacing => 5.hGap; 98 | 99 | ///Save the edited-image to [widget.savePath] or [getTemporaryDirectory()]. 100 | void saveImage() { 101 | _panelController.takeShot.value = true; 102 | screenshotController.capture(pixelRatio: 1.0).then((value) async { 103 | final paths = widget.savePath ?? await getTemporaryDirectory(); 104 | final file = await File('${paths.path}/' + DateTime.now().toString() + '.jpg').create(); 105 | file.writeAsBytes(value ?? []); 106 | decodeImg().then((value) { 107 | if (value == null) { 108 | Navigator.pop(context); 109 | } else { 110 | Navigator.pop(context, EditorImageResult(value.width, value.height, file)); 111 | } 112 | }).catchError((e) { 113 | Navigator.pop(context); 114 | }); 115 | }); 116 | } 117 | 118 | Future decodeImg() async { 119 | return await decodeImageFromList(widget.originImage.readAsBytesSync()); 120 | } 121 | 122 | static ImageEditorState? of(BuildContext context) { 123 | return context.findAncestorStateOfType(); 124 | } 125 | 126 | @override 127 | void initState() { 128 | super.initState(); 129 | initPainter(); 130 | } 131 | 132 | @override 133 | Widget build(BuildContext context) { 134 | _panelController.screenSize ??= windowSize; 135 | 136 | final appBar = ValueListenableBuilder( 137 | valueListenable: _panelController.showAppBar, 138 | builder: (ctx, value, child) { 139 | return AnimatedPositioned( 140 | top: value ? 0 : -headerHeight, 141 | left: 0, right: 0, 142 | child: ValueListenableBuilder( 143 | valueListenable: _panelController.takeShot, 144 | builder: (ctx, value, child) { 145 | return Opacity( 146 | opacity: value ? 0 : 1, 147 | child: AppBar( 148 | iconTheme: IconThemeData(color: Colors.white, size: 16), 149 | leading: backWidget(), 150 | backgroundColor: Colors.transparent, 151 | actions: [ 152 | resetWidget(onTap: resetCanvasPlate) 153 | ], 154 | ), 155 | ); 156 | }), 157 | duration: _panelController.panelDuration); 158 | }); 159 | 160 | final paintCanvas = Positioned.fromRect( 161 | rect: Rect.fromLTWH(0, headerHeight, screenWidth, canvasHeight), 162 | child: RotatedBox( 163 | quarterTurns: rotateValue, 164 | child: Stack( 165 | alignment: Alignment.center, 166 | children: [ 167 | InteractiveViewer(child: _buildImage()), 168 | _buildBrushCanvas(), 169 | //buildTextCanvas(), 170 | ], 171 | ), 172 | )); 173 | 174 | final textCanvas = Positioned.fromRect( 175 | rect: Rect.fromLTWH(0, headerHeight, screenWidth, screenHeight), 176 | child: RotatedBox( 177 | quarterTurns: rotateValue, 178 | child: buildTextCanvas(), 179 | )); 180 | 181 | //bottom operation(control) bar 182 | final bottomOpBar = ValueListenableBuilder( 183 | valueListenable: _panelController.showBottomBar, 184 | builder: (ctx, value, child) { 185 | return AnimatedPositioned( 186 | bottom: value ? 0 : -bottomBarHeight, 187 | child: SizedBox( 188 | width: screenWidth, 189 | child: ValueListenableBuilder( 190 | valueListenable: _panelController.takeShot, 191 | builder: (ctx, value, child) { 192 | return Opacity( 193 | opacity: value ? 0 : 1, 194 | child: _buildControlBar(), 195 | ); 196 | }), 197 | ), 198 | duration: _panelController.panelDuration); 199 | }); 200 | 201 | //trash bin 202 | final trashBin = ValueListenableBuilder( 203 | valueListenable: _panelController.showTrashCan, 204 | builder: (ctx, value, child) { 205 | return AnimatedPositioned( 206 | bottom: value ? _panelController.trashCanPosition.dy : -headerHeight, 207 | left: _panelController.trashCanPosition.dx, 208 | child: _buildTrashCan(), 209 | duration: _panelController.panelDuration); 210 | }); 211 | 212 | return Material( 213 | color: Colors.black, 214 | child: Listener( 215 | onPointerMove: (v) { 216 | _panelController.pointerMoving(v); 217 | }, 218 | child: Screenshot( 219 | controller: screenshotController, 220 | child: Stack( 221 | children: [ 222 | appBar, 223 | //canvas 224 | paintCanvas, 225 | bottomOpBar, 226 | trashBin, 227 | textCanvas, 228 | ], 229 | ), 230 | ), 231 | ), 232 | ); 233 | } 234 | 235 | Widget _buildControlBar() { 236 | return Container( 237 | color: Colors.black, 238 | width: screenWidth, 239 | height: bottomBarHeight, 240 | padding: EdgeInsets.only(left: 16, right: 16, bottom: windowBottomBarHeight), 241 | child: Column( 242 | children: [ 243 | Expanded( 244 | child: ValueListenableBuilder( 245 | valueListenable: _panelController.operateType, 246 | builder: (ctx, value, child) { 247 | return Opacity( 248 | opacity: _panelController.show2ndPanel() ? 1 : 0, 249 | child: Row( 250 | mainAxisAlignment: value == OperateType.brush ? 251 | MainAxisAlignment.spaceAround : MainAxisAlignment.end, 252 | children: [ 253 | if(value == OperateType.brush) 254 | ..._panelController.brushColor 255 | .map((e) => CircleColorWidget( 256 | color: e, 257 | valueListenable: _panelController.colorSelected, 258 | onColorSelected: (color) { 259 | if(pColor.value == color.value) return; 260 | changePainterColor(color); 261 | }, 262 | )) 263 | .toList(), 264 | 35.hGap, 265 | unDoWidget(onPressed: undo), 266 | if(value == OperateType.mosaic) 267 | 7.hGap, 268 | ], 269 | ), 270 | ); 271 | }, 272 | ), 273 | ), 274 | Expanded( 275 | child: Row( 276 | children: [ 277 | _buildButton(OperateType.brush, 'Draw', onPressed: () { 278 | switchPainterMode(DrawStyle.normal); 279 | }), 280 | controlBtnSpacing, 281 | _buildButton(OperateType.text, 'Text', onPressed: toTextEditorPage), 282 | controlBtnSpacing, 283 | _buildButton(OperateType.flip, 'Flip', onPressed: flipCanvas), 284 | controlBtnSpacing, 285 | _buildButton(OperateType.rotated, 'Rotate', onPressed: rotateCanvasPlate), 286 | controlBtnSpacing, 287 | _buildButton(OperateType.mosaic, 'Mosaic', onPressed: () { 288 | switchPainterMode(DrawStyle.mosaic); 289 | }), 290 | Expanded(child: SizedBox()), 291 | doneButtonWidget(onPressed: saveImage), 292 | ], 293 | ), 294 | ) 295 | ], 296 | ), 297 | ); 298 | } 299 | 300 | Widget _buildImage() { 301 | return Transform( 302 | alignment: Alignment.center, 303 | transform: Matrix4.rotationY(flipValue), 304 | child: Container( 305 | alignment: Alignment.center, 306 | child: Image.file(widget.originImage), 307 | ), 308 | ); 309 | } 310 | 311 | Widget _buildTrashCan() { 312 | return ValueListenableBuilder( 313 | valueListenable: _panelController.trashColor, 314 | builder: (ctx, value, child) { 315 | final bool isActive = value.value == EditorPanelController.defaultTrashColor.value; 316 | return Container( 317 | width: _panelController.tcSize.width, 318 | height: _panelController.tcSize.height, 319 | decoration: BoxDecoration( 320 | color: value, 321 | borderRadius: BorderRadius.all(Radius.circular(8)) 322 | ), 323 | child: Column( 324 | children: [ 325 | 12.vGap, 326 | Icon(Icons.delete_outline, size: 32, color: Colors.white,), 327 | 4.vGap, 328 | Text(isActive ? 'move here for delete' : 'release to delete', 329 | style: TextStyle(color: Colors.white, fontSize: 12),) 330 | ], 331 | ), 332 | ); 333 | }); 334 | } 335 | 336 | Widget _buildButton(OperateType type, String txt, {VoidCallback? onPressed}) { 337 | return GestureDetector( 338 | onTap: () { 339 | if(type == _panelController.currentOperateType) { 340 | _panelController.cancelOperateType(); 341 | } else { 342 | _panelController.switchOperateType(type); 343 | onPressed?.call(); 344 | } 345 | }, 346 | child: ValueListenableBuilder( 347 | valueListenable: _panelController.operateType, 348 | builder: (ctx, value, child) { 349 | return SizedBox( 350 | width: 44, 351 | height: 41, 352 | child: Column( 353 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 354 | children: [ 355 | getOperateTypeRes(type, choosen: _panelController.isCurrentOperateType(type)), 356 | Text( 357 | txt, 358 | style: TextStyle( 359 | color: _panelController.isCurrentOperateType(type) 360 | ? const Color(0xFFFA4D32) : const Color(0xFF999999), fontSize: 11), 361 | ) 362 | ], 363 | ), 364 | ); 365 | }, 366 | ), 367 | ); 368 | } 369 | 370 | 371 | } 372 | 373 | ///Little widget binding is for unified manage the widgets that has common style. 374 | /// * If you wanna custom this part, see [ImageEditorDelegate] 375 | mixin LittleWidgetBinding on State { 376 | 377 | ///go back widget 378 | Widget backWidget({VoidCallback? onPressed}) { 379 | return GestureDetector( 380 | onTap: onPressed ?? () { 381 | Navigator.pop(context); 382 | }, 383 | child: ImageEditor.uiDelegate.buildBackWidget(), 384 | ); 385 | } 386 | 387 | ///operation button in control bar 388 | Widget getOperateTypeRes(OperateType type, {required bool choosen}) { 389 | return ImageEditor.uiDelegate.buildOperateWidget(type, choosen: choosen); 390 | } 391 | 392 | ///action done widget 393 | Widget doneButtonWidget({VoidCallback? onPressed}) { 394 | return GestureDetector( 395 | onTap: onPressed, 396 | child: ImageEditor.uiDelegate.buildDoneWidget(), 397 | ); 398 | } 399 | 400 | ///undo action 401 | Widget unDoWidget({VoidCallback? onPressed}) { 402 | return GestureDetector( 403 | onTap: onPressed, 404 | child: ImageEditor.uiDelegate.buildUndoWidget(), 405 | ); 406 | } 407 | 408 | ///Ignore pointer evenet by [OperateType] 409 | Widget ignoreWidgetByType(OperateType type, Widget child) { 410 | return ValueListenableBuilder( 411 | valueListenable: realState?._panelController.operateType ?? ValueNotifier(OperateType.non), 412 | builder: (ctx, type, c) { 413 | return IgnorePointer( 414 | ignoring: type != OperateType.brush && type != OperateType.mosaic, 415 | child: child, 416 | ); 417 | }); 418 | } 419 | 420 | ///reset button 421 | Widget resetWidget({VoidCallback? onTap}) { 422 | return Padding( 423 | padding: EdgeInsets.only(top: 6, bottom: 6 , right: 16), 424 | child: ValueListenableBuilder( 425 | valueListenable: realState?._panelController.operateType ?? ValueNotifier(OperateType.non), 426 | builder: (ctx, value, child) { 427 | return Offstage( 428 | offstage: value != OperateType.rotated, 429 | child: GestureDetector( 430 | onTap: onTap, 431 | child: ImageEditor.uiDelegate.resetWidget, 432 | ),); 433 | }, 434 | ), 435 | ); 436 | } 437 | 438 | } 439 | 440 | ///This binding can make editor to roatate canvas 441 | /// * for now, the paint-path,will change his relative position of canvas 442 | /// * when canvas rotated. because the paint-path area it's full canvas and 443 | /// * origin image is not maybe. if force to keep the relative path, will reduce 444 | /// * the paint-path area. 445 | mixin RotateCanvasBinding on State { 446 | 447 | ///canvas rotate value 448 | /// * 90 angle each time. 449 | int rotateValue = 0; 450 | 451 | ///canvas flip value 452 | /// * 180 angle each time. 453 | double flipValue = 0; 454 | 455 | ///flip canvas 456 | void flipCanvas() { 457 | flipValue = flipValue == 0 ? math.pi : 0; 458 | setState(() {}); 459 | } 460 | 461 | ///routate canvas 462 | /// * will effect image, text, drawing board 463 | void rotateCanvasPlate() { 464 | rotateValue++; 465 | setState(() {}); 466 | } 467 | 468 | ///reset canvas 469 | void resetCanvasPlate() { 470 | rotateValue = 0; 471 | setState(() { 472 | 473 | }); 474 | } 475 | 476 | } 477 | 478 | ///text painting 479 | mixin TextCanvasBinding on State { 480 | late StateSetter textSetter; 481 | 482 | final List textModels = []; 483 | 484 | void addText(FloatTextModel model) { 485 | textModels.add(model); 486 | refreshTextCanvas(); 487 | } 488 | 489 | ///delete a text from canvas 490 | void deleteTextWidget(FloatTextModel target) { 491 | textModels.remove(target); 492 | refreshTextCanvas(); 493 | } 494 | 495 | void toTextEditorPage() { 496 | realState?._panelController.hidePanel(); 497 | Navigator.of(context) 498 | .push(PageRouteBuilder( 499 | opaque: false, 500 | pageBuilder: (BuildContext context, Animation animation, Animation secondaryAnimation) { 501 | return TextEditorPage(); 502 | })) 503 | .then((value) { 504 | realState?._panelController.showPanel(); 505 | if (value is FloatTextModel) { 506 | addText(value); 507 | } 508 | }); 509 | } 510 | 511 | void refreshTextCanvas() { 512 | textSetter.call(() {}); 513 | } 514 | 515 | Widget buildTextCanvas() { 516 | return StatefulBuilder(builder: (tCtx, setter) { 517 | textSetter = setter; 518 | return Stack( 519 | alignment: Alignment.center, 520 | children: textModels 521 | .map((e) => Positioned( 522 | child: _wrapWithGesture( 523 | FloatTextWidget( 524 | textModel: e, 525 | ), 526 | e), 527 | left: e.left, 528 | top: e.top, 529 | )) 530 | .toList(), 531 | ); 532 | }); 533 | } 534 | 535 | Widget _wrapWithGesture(Widget child, FloatTextModel model) { 536 | void pointerDetach(DragEndDetails? details) { 537 | if (details != null) { 538 | //touch event up 539 | realState?._panelController.releaseText(details, model, () { 540 | deleteTextWidget(model); 541 | }); 542 | } else { 543 | //touch event cancel 544 | realState?._panelController.doIdle(); 545 | } 546 | model.isSelected = false; 547 | refreshTextCanvas(); 548 | realState?._panelController.showPanel(); 549 | } 550 | 551 | return GestureDetector( 552 | child: child, 553 | onPanStart: (_) { 554 | realState?._panelController.moveText(model); 555 | }, 556 | onPanUpdate: (details) { 557 | model.isSelected = true; 558 | model.left += details.delta.dx; 559 | model.top += details.delta.dy; 560 | refreshTextCanvas(); 561 | realState?._panelController.hidePanel(); 562 | }, 563 | onPanEnd: (d) { 564 | pointerDetach(d); 565 | }, 566 | onPanCancel: () { 567 | pointerDetach(null); 568 | }, 569 | ); 570 | } 571 | } 572 | 573 | ///drawing board 574 | mixin SignatureBinding on State { 575 | 576 | DrawStyle get lastDrawStyle => painterController.drawStyle; 577 | 578 | ///Canvas layer for each draw action action. 579 | /// * e.g. First draw some path with white color, than change the color and draw some path again. 580 | /// * After this [pathRecord] will save 2 layes in it. 581 | final List pathRecord = []; 582 | 583 | late StateSetter canvasSetter; 584 | 585 | ///mosaic pixel's width 586 | double mosaicWidth = 5.0; 587 | 588 | ///painter stroke width. 589 | double pStrockWidth = 5; 590 | 591 | ///painter color 592 | Color pColor = Colors.redAccent; 593 | 594 | ///painter controller 595 | late SignatureController painterController; 596 | 597 | @override 598 | void initState() { 599 | super.initState(); 600 | pColor = widget.launcher.pColor; 601 | mosaicWidth = widget.launcher.mosaicWidth; 602 | pStrockWidth = widget.launcher.pStrockWidth; 603 | } 604 | 605 | ///switch painter's style 606 | /// * e.g. color、mosaic 607 | void switchPainterMode(DrawStyle style) { 608 | if(lastDrawStyle == style) return; 609 | changePainterColor(pColor); 610 | painterController.drawStyle = style; 611 | } 612 | 613 | ///change painter's color 614 | void changePainterColor(Color color) async { 615 | pColor = color; 616 | realState?._panelController.selectColor(color); 617 | pathRecord.insert( 618 | 0, 619 | RepaintBoundary( 620 | child: CustomPaint( 621 | painter: SignaturePainter(painterController), 622 | child: ConstrainedBox( 623 | constraints: const BoxConstraints( 624 | minWidth: double.infinity, minHeight: double.infinity, maxWidth: double.infinity, maxHeight: double.infinity), 625 | )), 626 | )); 627 | //savePath(); 628 | initPainter(); 629 | _refreshBrushCanvas(); 630 | } 631 | 632 | void _refreshBrushCanvas() { 633 | pathRecord.removeLast(); 634 | //add new layer. 635 | pathRecord.add(Signature( 636 | controller: painterController, 637 | backgroundColor: Colors.transparent, 638 | )); 639 | _refreshCanvas(); 640 | } 641 | 642 | ///undo last drawing. 643 | void undo() { 644 | painterController.undo(); 645 | } 646 | 647 | ///refresh canvas. 648 | void _refreshCanvas() { 649 | canvasSetter(() {}); 650 | } 651 | 652 | void initPainter() { 653 | painterController = SignatureController(penStrokeWidth: pStrockWidth, penColor: pColor, mosaicWidth: mosaicWidth); 654 | } 655 | 656 | Widget _buildBrushCanvas() { 657 | if (pathRecord.isEmpty) { 658 | pathRecord.add(Signature( 659 | controller: painterController, 660 | backgroundColor: Colors.transparent, 661 | )); 662 | } 663 | return StatefulBuilder(builder: (ctx, canvasSetter) { 664 | this.canvasSetter = canvasSetter; 665 | return realState?.ignoreWidgetByType(OperateType.brush, Stack( 666 | children: pathRecord, 667 | )) ?? SizedBox(); 668 | }); 669 | } 670 | 671 | @override 672 | void dispose() { 673 | pathRecord.clear(); 674 | super.dispose(); 675 | } 676 | } 677 | 678 | mixin ScreenShotBinding on State { 679 | final ScreenshotController screenshotController = ScreenshotController(); 680 | } 681 | 682 | ///information about window 683 | mixin WindowUiBinding on State { 684 | 685 | Size get windowSize => MediaQuery.of(context).size; 686 | 687 | double get windowStatusBarHeight => View.of(context).padding.top; 688 | 689 | double get windowBottomBarHeight => View.of(context).padding.bottom; 690 | 691 | double get screenWidth => windowSize.width; 692 | 693 | double get screenHeight => windowSize.height; 694 | 695 | } 696 | 697 | extension _BaseImageEditorState on State { 698 | ImageEditorState? get realState { 699 | if (this is ImageEditorState) { 700 | return this as ImageEditorState; 701 | } 702 | return null; 703 | } 704 | } 705 | 706 | ///the color selected. 707 | typedef OnColorSelected = void Function(Color color); 708 | 709 | class CircleColorWidget extends StatefulWidget { 710 | final Color color; 711 | 712 | final ValueNotifier valueListenable; 713 | 714 | final OnColorSelected onColorSelected; 715 | 716 | const CircleColorWidget({Key? key, required this.color, required this.valueListenable, required this.onColorSelected}) : super(key: key); 717 | 718 | @override 719 | State createState() { 720 | return CircleColorWidgetState(); 721 | } 722 | } 723 | 724 | class CircleColorWidgetState extends State { 725 | @override 726 | Widget build(BuildContext context) { 727 | return GestureDetector( 728 | onTap: () { 729 | widget.onColorSelected(widget.color); 730 | }, 731 | child: ValueListenableBuilder( 732 | valueListenable: widget.valueListenable, 733 | builder: (ctx, value, child) { 734 | final double size = value == widget.color.value ? 25 : 21; 735 | return Container( 736 | width: size, 737 | height: size, 738 | decoration: BoxDecoration( 739 | border: Border.all(color: Colors.white, width: value == widget.color.value ? 4 : 2), 740 | shape: BoxShape.circle, 741 | color: widget.color, 742 | ), 743 | ); 744 | }, 745 | ), 746 | ); 747 | } 748 | 749 | 750 | } 751 | -------------------------------------------------------------------------------- /lib/model/float_text_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | 3 | ///Text widget model, for help to move text-widget. 4 | class FloatTextModel extends BaseFloatModel{ 5 | 6 | String text; 7 | 8 | TextStyle? style; 9 | 10 | ///the top of position 11 | double top; 12 | ///the left of position 13 | double left; 14 | ///widget's size 15 | Size? size; 16 | 17 | bool isSelected; 18 | 19 | FloatTextModel({ 20 | required this.text, 21 | this.style, 22 | required this.top, 23 | required this.left, 24 | this.isSelected = false, 25 | }); 26 | 27 | @override 28 | Size? get floatSize => size; 29 | } 30 | 31 | abstract class BaseFloatModel{ 32 | Size? get floatSize; 33 | } 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /lib/widget/drawing_board.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | import 'dart:async'; 4 | import 'dart:math'; 5 | import 'dart:ui' as ui; 6 | 7 | import 'package:flutter/foundation.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:image/image.dart' as img; 10 | 11 | 12 | /// signature canvas. Controller is required, other parameters are optional. 13 | /// widget/canvas expands to maximum by default. 14 | /// this behaviour can be overridden using width and/or height parameters. 15 | /// 16 | /// This is from https://pub.flutter-io.cn/packages/signature 17 | class Signature extends StatefulWidget { 18 | /// constructor 19 | const Signature({ 20 | required this.controller, 21 | Key? key, 22 | this.backgroundColor = Colors.grey, 23 | this.width, 24 | this.height, 25 | }) : super(key: key); 26 | 27 | /// signature widget controller 28 | final SignatureController controller; 29 | 30 | /// signature widget width 31 | final double? width; 32 | 33 | /// signature widget height 34 | final double? height; 35 | 36 | /// signature widget background color 37 | final Color backgroundColor; 38 | 39 | @override 40 | State createState() => SignatureState(); 41 | } 42 | 43 | /// signature widget state 44 | class SignatureState extends State { 45 | /// Helper variable indicating that user has left the canvas so we can prevent linking next point 46 | /// with straight line. 47 | bool _isOutsideDrawField = false; 48 | 49 | /// Active pointer to prevent multitouch drawing 50 | int? activePointerId; 51 | 52 | @override 53 | Widget build(BuildContext context) { 54 | final double maxWidth = widget.width ?? double.infinity; 55 | final double maxHeight = widget.height ?? double.infinity; 56 | final GestureDetector signatureCanvas = GestureDetector( 57 | onVerticalDragUpdate: (DragUpdateDetails details) { 58 | //NO-OP 59 | }, 60 | child: Container( 61 | decoration: BoxDecoration(color: widget.backgroundColor), 62 | child: Listener( 63 | onPointerDown: (PointerDownEvent event) { 64 | if (activePointerId == null || activePointerId == event.pointer) { 65 | activePointerId = event.pointer; 66 | widget.controller.onDrawStart?.call(); 67 | _addPoint(event, PointType.tap); 68 | } 69 | }, 70 | onPointerUp: (PointerUpEvent event) { 71 | if (activePointerId == event.pointer) { 72 | _addPoint(event, PointType.tap); 73 | widget.controller.pushCurrentStateToUndoStack(); 74 | widget.controller.onDrawEnd?.call(); 75 | activePointerId = null; 76 | } 77 | }, 78 | onPointerCancel: (PointerCancelEvent event) { 79 | if (activePointerId == event.pointer) { 80 | _addPoint(event, PointType.tap); 81 | widget.controller.pushCurrentStateToUndoStack(); 82 | widget.controller.onDrawEnd?.call(); 83 | activePointerId = null; 84 | } 85 | }, 86 | onPointerMove: (PointerMoveEvent event) { 87 | if (activePointerId == event.pointer) { 88 | _addPoint(event, PointType.move); 89 | widget.controller.onDrawMove?.call(); 90 | } 91 | }, 92 | child: RepaintBoundary( 93 | child: CustomPaint( 94 | painter: SignaturePainter(widget.controller), 95 | child: ConstrainedBox( 96 | constraints: BoxConstraints( 97 | minWidth: maxWidth, 98 | minHeight: maxHeight, 99 | maxWidth: maxWidth, 100 | maxHeight: maxHeight), 101 | ), 102 | ), 103 | )), 104 | ), 105 | ); 106 | 107 | if (widget.width != null || widget.height != null) { 108 | //IF DOUNDARIES ARE DEFINED, USE LIMITED BOX 109 | return Center( 110 | child: LimitedBox( 111 | maxWidth: maxWidth, 112 | maxHeight: maxHeight, 113 | child: signatureCanvas, 114 | ), 115 | ); 116 | } else { 117 | //IF NO BOUNDARIES ARE DEFINED, RETURN THE WIDGET AS IS 118 | return signatureCanvas; 119 | } 120 | } 121 | 122 | void _addPoint(PointerEvent event, PointType type) { 123 | final Offset o = event.localPosition; 124 | //SAVE POINT ONLY IF IT IS IN THE SPECIFIED BOUNDARIES 125 | if ((widget.width == null || o.dx > 0 && o.dx < widget.width!) && 126 | (widget.height == null || o.dy > 0 && o.dy < widget.height!)) { 127 | // IF USER LEFT THE BOUNDARY AND AND ALSO RETURNED BACK 128 | // IN ONE MOVE, RETYPE IT AS TAP, AS WE DO NOT WANT TO 129 | // LINK IT WITH PREVIOUS POINT 130 | 131 | PointType t = type; 132 | if (_isOutsideDrawField) { 133 | t = PointType.tap; 134 | } 135 | setState(() { 136 | //IF USER WAS OUTSIDE OF CANVAS WE WILL RESET THE HELPER VARIABLE AS HE HAS RETURNED 137 | _isOutsideDrawField = false; 138 | widget.controller.addPoint(Point(o, t, event.pointer)); 139 | }); 140 | } else { 141 | //NOTE: USER LEFT THE CANVAS!!! WE WILL SET HELPER VARIABLE 142 | //WE ARE NOT UPDATING IN setState METHOD BECAUSE WE DO NOT NEED TO RUN BUILD METHOD 143 | _isOutsideDrawField = true; 144 | } 145 | } 146 | } 147 | 148 | /// type of user display finger movement 149 | enum PointType { 150 | /// one touch on specific place - tap 151 | tap, 152 | 153 | /// finger touching the display and moving around 154 | move, 155 | } 156 | 157 | /// one point on canvas represented by offset and type 158 | class Point { 159 | /// constructor 160 | Point(this.offset, this.type, this.eventId); 161 | 162 | /// x and y value on 2D canvas 163 | Offset offset; 164 | 165 | /// type of user display finger movement 166 | PointType type; 167 | 168 | int eventId; 169 | } 170 | 171 | class SignaturePainter extends CustomPainter { 172 | SignaturePainter(this._controller) 173 | : _penStyle = Paint(), 174 | super(repaint: _controller) { 175 | _penStyle 176 | ..color = _controller.penColor 177 | ..style = PaintingStyle.stroke 178 | ..strokeWidth = _controller.penStrokeWidth; 179 | 180 | } 181 | 182 | final SignatureController _controller; 183 | final Paint _penStyle; 184 | 185 | 186 | @override 187 | void paint(Canvas canvas, _) { 188 | final List points = _controller.value; 189 | if (points.isEmpty) { 190 | return; 191 | } 192 | //for draw [DrawStyle.mosaic] 193 | void paintMosaic(Offset center) { 194 | final ui.Paint paint = ui.Paint()..color = Colors.black26; 195 | final double size = _controller.mosaicWidth; 196 | final double halfSize = size/2; 197 | final ui.Rect b1 = Rect.fromCenter(center: center.translate(-halfSize, -halfSize),width: size,height: size); 198 | //0,0 199 | canvas.drawRect(b1, paint); 200 | paint.color = Colors.grey.withOpacity(0.5); 201 | //0,1 202 | canvas.drawRect(b1.translate(0, size), paint); 203 | paint.color = Colors.black38; 204 | //0,2 205 | canvas.drawRect(b1.translate(0, size*2), paint); 206 | paint.color = Colors.black12; 207 | //1,0 208 | canvas.drawRect(b1.translate(size, 0), paint); 209 | paint.color = Colors.black26; 210 | //1,1 211 | canvas.drawRect(b1.translate(size, size), paint); 212 | paint.color = Colors.black45; 213 | //1,2 214 | canvas.drawRect(b1.translate(size, size*2), paint); 215 | paint.color = Colors.grey.withOpacity(0.5); 216 | //2,0 217 | canvas.drawRect(b1.translate(size*2, 0), paint); 218 | paint.color = Colors.black12; 219 | //2,1 220 | canvas.drawRect(b1.translate(size*2, size), paint); 221 | paint.color = Colors.black26; 222 | //2,2 223 | canvas.drawRect(b1.translate(size*2, size*2), paint); 224 | 225 | } 226 | 227 | //for draw [DrawStyle.normal] 228 | Path paintPath() { 229 | final Path path = Path(); 230 | final Map> pathM = {}; 231 | points.forEach((element) { 232 | if(pathM[element.eventId] == null) 233 | pathM[element.eventId] = []; 234 | pathM[element.eventId]!.add(element); 235 | }); 236 | 237 | pathM.forEach((key, value) { 238 | final first = value.first; 239 | path.moveTo(first.offset.dx, first.offset.dy); 240 | if(value.length <= 3) { 241 | _penStyle.style = PaintingStyle.fill; 242 | canvas.drawCircle(first.offset, _controller.penStrokeWidth,_penStyle); 243 | _penStyle.style = PaintingStyle.stroke; 244 | } else { 245 | value.forEach((e) { 246 | path.lineTo(e.offset.dx, e.offset.dy); 247 | }); 248 | } 249 | }); 250 | return path; 251 | } 252 | 253 | switch(_controller.drawStyle) { 254 | case DrawStyle.normal: 255 | canvas.drawPath(paintPath(), _penStyle); 256 | break; 257 | case DrawStyle.mosaic: 258 | //reduce the frequency of mosaic drawing. 259 | for(int i=0; i < points.length; i+=2) { 260 | paintMosaic(points[i].offset); 261 | } 262 | break; 263 | } 264 | 265 | } 266 | 267 | @override 268 | bool shouldRepaint(CustomPainter other) => true; 269 | } 270 | 271 | 272 | enum DrawStyle{ 273 | ///use penColor 274 | normal, 275 | mosaic, 276 | } 277 | 278 | /// class for interaction with signature widget 279 | /// manages points representing signature on canvas 280 | /// provides signature manipulation functions (export, clear) 281 | class SignatureController extends ValueNotifier> { 282 | /// constructor 283 | SignatureController({ 284 | List? points, 285 | this.penColor = Colors.black, 286 | this.penStrokeWidth = 3.0, 287 | this.exportBackgroundColor, 288 | this.onDrawStart, 289 | this.onDrawMove, 290 | this.onDrawEnd, 291 | this.mosaicWidth = 5.0 292 | }) : super(points ?? []); 293 | 294 | /// color of a signature line 295 | final Color penColor; 296 | 297 | /// boldness of a signature line 298 | final double penStrokeWidth; 299 | 300 | /// background color to be used in exported png image 301 | final Color? exportBackgroundColor; 302 | 303 | ///mosaic pixel's width 304 | final double mosaicWidth; 305 | 306 | ///draw style 307 | /// * [DrawStyle.normal] : paint with some color 308 | DrawStyle drawStyle = DrawStyle.normal; 309 | 310 | /// callback to notify when drawing has started 311 | VoidCallback? onDrawStart; 312 | 313 | /// callback to notify when the pointer was moved while drawing. 314 | VoidCallback? onDrawMove; 315 | 316 | /// callback to notify when drawing has stopped 317 | VoidCallback? onDrawEnd; 318 | 319 | /// getter for points representing signature on 2D canvas 320 | List get points => value; 321 | 322 | /// stack-like list of point to save user's latest action 323 | final List> _latestActions = >[]; 324 | 325 | /// stack-like list that use to save points when user undo the signature 326 | final List> _revertedActions = >[]; 327 | 328 | /// setter for points representing signature on 2D canvas 329 | set points(List points) { 330 | value = points; 331 | } 332 | 333 | /// add point to point collection 334 | void addPoint(Point point) { 335 | value.add(point); 336 | notifyListeners(); 337 | } 338 | 339 | /// REMEMBERS CURRENT CANVAS STATE IN UNDO STACK 340 | void pushCurrentStateToUndoStack() { 341 | _latestActions.add([...points]); 342 | //CLEAR ANY UNDO-ED ACTIONS. IF USER UNDO-ED ANYTHING HE ALREADY MADE 343 | // ANOTHER CHANGE AND LEFT THAT OLD PATH. 344 | _revertedActions.clear(); 345 | } 346 | 347 | /// check if canvas is empty (opposite of isNotEmpty method for convenience) 348 | bool get isEmpty { 349 | return value.isEmpty; 350 | } 351 | 352 | /// check if canvas is not empty (opposite of isEmpty method for convenience) 353 | bool get isNotEmpty { 354 | return value.isNotEmpty; 355 | } 356 | 357 | /// clear the canvas 358 | void clear() { 359 | value = []; 360 | _latestActions.clear(); 361 | _revertedActions.clear(); 362 | } 363 | 364 | /// It will remove last action from [_latestActions]. 365 | /// The last action will be saved to [_revertedActions] 366 | /// that will be used to do redo-ing. 367 | /// Then, it will modify the real points with the last action. 368 | void undo() { 369 | if (_latestActions.isNotEmpty) { 370 | final List lastAction = _latestActions.removeLast(); 371 | _revertedActions.add([...lastAction]); 372 | if (_latestActions.isNotEmpty) { 373 | points = [..._latestActions.last]; 374 | return; 375 | } 376 | points = []; 377 | notifyListeners(); 378 | } 379 | } 380 | 381 | /// It will remove last reverted actions and add it into [_latestActions] 382 | /// Then, it will modify the real points with the last reverted action. 383 | void redo() { 384 | if (_revertedActions.isNotEmpty) { 385 | final List lastRevertedAction = _revertedActions.removeLast(); 386 | _latestActions.add([...lastRevertedAction]); 387 | points = [...lastRevertedAction]; 388 | notifyListeners(); 389 | return; 390 | } 391 | } 392 | 393 | /// convert to 394 | Future toImage() async { 395 | if (isEmpty) { 396 | return null; 397 | } 398 | 399 | double minX = double.infinity, minY = double.infinity; 400 | double maxX = 0, maxY = 0; 401 | for (Point point in points) { 402 | if (point.offset.dx < minX) { 403 | minX = point.offset.dx; 404 | } 405 | if (point.offset.dy < minY) { 406 | minY = point.offset.dy; 407 | } 408 | if (point.offset.dx > maxX) { 409 | maxX = point.offset.dx; 410 | } 411 | if (point.offset.dy > maxY) { 412 | maxY = point.offset.dy; 413 | } 414 | } 415 | 416 | final ui.PictureRecorder recorder = ui.PictureRecorder(); 417 | final ui.Canvas canvas = Canvas(recorder) 418 | ..translate(-(minX - penStrokeWidth), -(minY - penStrokeWidth)); 419 | if (exportBackgroundColor != null) { 420 | final ui.Paint paint = Paint()..color = exportBackgroundColor!; 421 | canvas.drawPaint(paint); 422 | } 423 | SignaturePainter(this).paint(canvas, Size.infinite); 424 | final ui.Picture picture = recorder.endRecording(); 425 | return picture.toImage( 426 | (maxX - minX + penStrokeWidth * 2).toInt(), 427 | (maxY - minY + penStrokeWidth * 2).toInt(), 428 | ); 429 | } 430 | 431 | 432 | /// convert canvas to dart:ui Image and then to PNG represented in Uint8List 433 | Future toPngBytes() async { 434 | if (!kIsWeb) { 435 | final ui.Image? image = await toImage(); 436 | if (image == null) { 437 | return null; 438 | } 439 | final ByteData? bytes = await image.toByteData( 440 | format: ui.ImageByteFormat.png, 441 | ); 442 | return bytes?.buffer.asUint8List(); 443 | } else { 444 | return _toPngBytesForWeb(); 445 | } 446 | } 447 | 448 | // 'image.toByteData' is not available for web. So we are using the package 449 | // 'image' to create an image which works on web too 450 | Uint8List? _toPngBytesForWeb() { 451 | if (isEmpty) { 452 | return null; 453 | } 454 | final int pColor = img.getColor( 455 | penColor.red, 456 | penColor.green, 457 | penColor.blue, 458 | ); 459 | 460 | final Color backgroundColor = exportBackgroundColor ?? Colors.transparent; 461 | final int bColor = img.getColor(backgroundColor.red, backgroundColor.green, 462 | backgroundColor.blue, backgroundColor.alpha.toInt()); 463 | 464 | double minX = double.infinity; 465 | double maxX = 0; 466 | double minY = double.infinity; 467 | double maxY = 0; 468 | 469 | for (Point point in points) { 470 | minX = min(point.offset.dx, minX); 471 | maxX = max(point.offset.dx, maxX); 472 | minY = min(point.offset.dy, minY); 473 | maxY = max(point.offset.dy, maxY); 474 | } 475 | 476 | //point translation 477 | final List translatedPoints = []; 478 | for (Point point in points) { 479 | translatedPoints.add(Point( 480 | Offset( 481 | point.offset.dx - minX + penStrokeWidth, 482 | point.offset.dy - minY + penStrokeWidth, 483 | ), 484 | point.type, 485 | point.eventId, 486 | )); 487 | } 488 | 489 | final int width = (maxX - minX + penStrokeWidth * 2).toInt(); 490 | final int height = (maxY - minY + penStrokeWidth * 2).toInt(); 491 | 492 | // create the image with the given size 493 | final img.Image signatureImage = img.Image(width, height); 494 | // set the image background color 495 | img.fill(signatureImage, bColor); 496 | 497 | // read the drawing points list and draw the image 498 | // it uses the same logic as the CustomPainter Paint function 499 | for (int i = 0; i < translatedPoints.length - 1; i++) { 500 | if (translatedPoints[i + 1].type == PointType.move) { 501 | img.drawLine( 502 | signatureImage, 503 | translatedPoints[i].offset.dx.toInt(), 504 | translatedPoints[i].offset.dy.toInt(), 505 | translatedPoints[i + 1].offset.dx.toInt(), 506 | translatedPoints[i + 1].offset.dy.toInt(), 507 | pColor, 508 | thickness: penStrokeWidth); 509 | } else { 510 | // draw the point to the image 511 | img.fillCircle( 512 | signatureImage, 513 | translatedPoints[i].offset.dx.toInt(), 514 | translatedPoints[i].offset.dy.toInt(), 515 | penStrokeWidth.toInt(), 516 | pColor, 517 | ); 518 | } 519 | } 520 | // encode the image to PNG 521 | return Uint8List.fromList(img.encodePng(signatureImage)); 522 | } 523 | } 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | -------------------------------------------------------------------------------- /lib/widget/editor_panel_controller.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'dart:ui'; 3 | 4 | import 'package:flutter/material.dart'; 5 | import 'package:image_editor_dove/model/float_text_model.dart'; 6 | 7 | ///The object taht are moving. 8 | enum MoveStuff{ 9 | non, 10 | text, 11 | } 12 | 13 | 14 | enum OperateType{ 15 | non, 16 | brush,//drawing path 17 | text,//add text to canvas 18 | flip,//flip image 19 | rotated,//rotate canvas 20 | mosaic,//draw mosaic 21 | } 22 | 23 | class EditorPanelController { 24 | 25 | static const defaultTrashColor = const Color(0x26ffffff); 26 | 27 | EditorPanelController() { 28 | colorSelected = ValueNotifier(brushColor.first.value); 29 | } 30 | 31 | Size? screenSize; 32 | 33 | ///take shot action listener 34 | /// * it's for hide some non-relative ui. 35 | /// * e.g. hide status bar, hide bottom bar 36 | ValueNotifier takeShot = ValueNotifier(false); 37 | 38 | ValueNotifier showTrashCan = ValueNotifier(false); 39 | 40 | ///trash background color 41 | ValueNotifier trashColor = ValueNotifier(defaultTrashColor); 42 | 43 | ValueNotifier showAppBar = ValueNotifier(true); 44 | 45 | ValueNotifier showBottomBar = ValueNotifier(true); 46 | 47 | ValueNotifier operateType = ValueNotifier(OperateType.non); 48 | 49 | ///Is current operate type 50 | bool isCurrentOperateType(OperateType type) => type.index == operateType.value.index; 51 | 52 | /// is need to show second panel. 53 | /// * in some operate type like drawing path, it need a 2nd panel for change color. 54 | bool show2ndPanel() => operateType.value == OperateType.brush || operateType.value == OperateType.mosaic; 55 | 56 | final List brushColor = const [ 57 | Color(0xFFFA4D32), 58 | Color(0xFFFF7F1E), 59 | Color(0xFF2DA24A), 60 | Color(0xFFF2F2F2), 61 | Color(0xFF222222), 62 | Color(0xFF1F8BE5), 63 | Color(0xFF4E43DB), 64 | ]; 65 | 66 | late ValueNotifier colorSelected; 67 | 68 | void selectColor(Color color) { 69 | colorSelected.value = color.value; 70 | } 71 | 72 | ///switch operate type 73 | void switchOperateType(OperateType type) { 74 | operateType.value = type; 75 | } 76 | 77 | void cancelOperateType() { 78 | operateType.value = OperateType.non; 79 | } 80 | 81 | OperateType get currentOperateType => operateType.value; 82 | 83 | 84 | ///moving object 85 | /// * non : not moving. 86 | MoveStuff moveStuff = MoveStuff.non; 87 | 88 | ///trash can position 89 | Offset trashCanPosition = Offset(111, (20 + (PlatformDispatcher.instance.implicitView?.padding.bottom ?? 0))); 90 | 91 | ///trash can size. 92 | final Size tcSize = Size(153, 77); 93 | 94 | ///The top and bottom panel's slide duration. 95 | final Duration panelDuration = const Duration(milliseconds: 300); 96 | 97 | ///hide bottom and top(app) bar. 98 | void hidePanel() { 99 | showAppBar.value = false; 100 | showBottomBar.value = false; 101 | switchTrashCan(true); 102 | } 103 | 104 | ///show bottom and top(app) bar. 105 | void showPanel() { 106 | showAppBar.value = true; 107 | showBottomBar.value = true; 108 | switchTrashCan(false); 109 | } 110 | 111 | ///hide/show trash can. 112 | void switchTrashCan(bool show) { 113 | showTrashCan.value = show; 114 | } 115 | 116 | ///switch trash can's color. 117 | void switchTrashCanColor(bool isInside) { 118 | trashColor.value = isInside ? Colors.red : defaultTrashColor; 119 | } 120 | 121 | ///move text. 122 | void moveText(FloatTextModel model) { 123 | moveStuff = MoveStuff.text; 124 | movingTarget = model; 125 | } 126 | 127 | ///release the moving-text. 128 | void releaseText(DragEndDetails details, FloatTextModel model, Function throwCall) { 129 | if(isThrowText(pointerUpPosition??Offset.zero, model)) { 130 | throwCall.call(); 131 | } 132 | doIdle(); 133 | } 134 | 135 | ///stop moving. 136 | void doIdle() { 137 | movingTarget = null; 138 | pointerUpPosition = null; 139 | moveStuff = MoveStuff.non; 140 | switchTrashCanColor(false); 141 | } 142 | 143 | ///moving object. 144 | /// * must based on [BaseFloatModel]. 145 | /// * most time it's used to find the [movingTarget] that who just realeased. 146 | BaseFloatModel? movingTarget; 147 | 148 | ///cache the target taht just released. 149 | Offset? pointerUpPosition; 150 | 151 | ///pointer moving's callback 152 | void pointerMoving(PointerMoveEvent event) { 153 | pointerUpPosition = event.localPosition; 154 | switch(moveStuff) { 155 | case MoveStuff.non: 156 | break; 157 | case MoveStuff.text: 158 | if(movingTarget is FloatTextModel) { 159 | switchTrashCanColor(isThrowText(event.localPosition, movingTarget!)); 160 | } 161 | break; 162 | } 163 | } 164 | 165 | ///decided whether the text is deleted or not. 166 | bool isThrowText(Offset pointer,BaseFloatModel target) { 167 | final Rect textR = Rect.fromCenter(center: pointer, 168 | width: target.floatSize?.width??1, 169 | height: target.floatSize?.height??1); 170 | final Rect tcR = Rect.fromLTWH( 171 | screenSize!.width - trashCanPosition.dx, 172 | screenSize!.height - trashCanPosition.dy - tcSize.height, 173 | tcSize.width, 174 | tcSize.height); 175 | return textR.overlaps(tcR); 176 | } 177 | 178 | } 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | -------------------------------------------------------------------------------- /lib/widget/float_text_widget.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math' as math; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:image_editor_dove/model/float_text_model.dart'; 5 | 6 | import '../image_editor.dart'; 7 | 8 | 9 | 10 | class FloatTextWidget extends StatefulWidget{ 11 | 12 | final FloatTextModel textModel; 13 | 14 | const FloatTextWidget({Key? key, 15 | required this.textModel}) : super(key: key); 16 | 17 | @override 18 | State createState() { 19 | return FloatTextWidgetState(); 20 | } 21 | } 22 | 23 | class FloatTextWidgetState extends State { 24 | 25 | FloatTextModel get model => widget.textModel; 26 | 27 | @override 28 | void initState() { 29 | super.initState(); 30 | WidgetsBinding.instance.addPostFrameCallback((timeStamp) { 31 | if(mounted) { 32 | RenderObject? ro = context.findRenderObject(); 33 | if(ro is RenderBox) { 34 | widget.textModel.size ??= ro.size; 35 | } 36 | } 37 | }); 38 | } 39 | 40 | @override 41 | Widget build(BuildContext context) { 42 | return Container( 43 | padding: EdgeInsets.all(4), 44 | constraints: BoxConstraints( 45 | minWidth: 10, maxWidth: 335 46 | ), 47 | decoration: BoxDecoration( 48 | border: model.isSelected ? 49 | ImageEditor.uiDelegate.textSelectedBorder 50 | : null 51 | ), 52 | child: Text( 53 | model.text, 54 | style: model.style, 55 | ), 56 | ); 57 | } 58 | 59 | } 60 | 61 | 62 | ///Draw dash border. 63 | class DashBorder extends Border{ 64 | 65 | DashBorder({ 66 | this.gap = 4.0, 67 | this.strokeWidth = 2.0, 68 | this.dashColor = Colors.white, 69 | BorderSide top = BorderSide.none, 70 | BorderSide right = BorderSide.none, 71 | BorderSide bottom = BorderSide.none, 72 | BorderSide left = BorderSide.none, 73 | }) : super(top: top, bottom: bottom, left: left, right: right); 74 | 75 | final double gap; 76 | 77 | final double strokeWidth; 78 | 79 | final Color dashColor; 80 | 81 | @override 82 | void paint( 83 | Canvas canvas, 84 | Rect rect, { 85 | TextDirection? textDirection, 86 | BoxShape shape = BoxShape.rectangle, 87 | BorderRadius? borderRadius, 88 | }) { 89 | 90 | Path getDashedPath({ 91 | required math.Point a, 92 | required math.Point b, 93 | required gap, 94 | }) { 95 | final Size size = Size(b.x - a.x, b.y - a.y); 96 | final Path path = Path(); 97 | path.moveTo(a.x, a.y); 98 | bool shouldDraw = true; 99 | math.Point currentPoint = math.Point(a.x, a.y); 100 | 101 | final num radians = math.atan(size.height / size.width); 102 | 103 | final num dx = math.cos(radians) * gap < 0 104 | ? math.cos(radians) * gap * -1 105 | : math.cos(radians) * gap; 106 | 107 | final num dy = math.sin(radians) * gap < 0 108 | ? math.sin(radians) * gap * -1 109 | : math.sin(radians) * gap; 110 | 111 | while (currentPoint.x <= b.x && currentPoint.y <= b.y) { 112 | shouldDraw 113 | ? path.lineTo(currentPoint.x, currentPoint.y) 114 | : path.moveTo(currentPoint.x, currentPoint.y); 115 | shouldDraw = !shouldDraw; 116 | currentPoint = math.Point( 117 | currentPoint.x + dx, 118 | currentPoint.y + dy, 119 | ); 120 | } 121 | return path; 122 | } 123 | if (isUniform) { 124 | final Paint dashedPaint = Paint() 125 | ..color = dashColor 126 | ..strokeWidth = strokeWidth 127 | ..style = PaintingStyle.stroke; 128 | // top line 129 | final Path _topPath = getDashedPath( 130 | a: math.Point(rect.topLeft.dx, rect.topLeft.dy), 131 | b: math.Point(rect.topRight.dx, rect.topRight.dy), 132 | gap: gap, 133 | ); 134 | // right line 135 | final Path _rightPath = getDashedPath( 136 | a: math.Point(rect.topRight.dx, rect.topRight.dy), 137 | b: math.Point(rect.bottomRight.dx, rect.bottomRight.dy), 138 | gap: gap, 139 | ); 140 | // bottom line 141 | final Path _bottomPath = getDashedPath( 142 | a: math.Point(rect.bottomLeft.dx, rect.bottomLeft.dy), 143 | b: math.Point(rect.bottomRight.dx, rect.bottomRight.dy), 144 | gap: gap, 145 | ); 146 | // left line 147 | final Path _leftPath = getDashedPath( 148 | a: math.Point(rect.topLeft.dx, rect.topLeft.dy), 149 | b: math.Point(rect.bottomLeft.dx, rect.bottomLeft.dy), 150 | gap: gap, 151 | ); 152 | 153 | canvas.drawPath(_topPath, dashedPaint); 154 | canvas.drawPath(_rightPath, dashedPaint); 155 | canvas.drawPath(_bottomPath, dashedPaint); 156 | canvas.drawPath(_leftPath, dashedPaint); 157 | } 158 | 159 | paintBorder(canvas, rect, top: top, right: right, bottom: bottom, left: left); 160 | } 161 | 162 | } 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /lib/widget/image_editor_delegate.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:image_editor_dove/flutter_image_editor.dart'; 5 | import 'package:image_editor_dove/widget/float_text_widget.dart'; 6 | import 'package:image_editor_dove/widget/text_editor_page.dart'; 7 | 8 | import 'editor_panel_controller.dart'; 9 | import 'slider_widget.dart'; 10 | 11 | 12 | class DefaultTextConfigModel extends TextConfigModel{ 13 | @override 14 | double get initSize => 14; 15 | 16 | @override 17 | double get sliderBottomLimit => 14; 18 | 19 | @override 20 | double get sliderUpLimit => 36; 21 | 22 | @override 23 | Color get cursorColor => const Color(0xFFF83112); 24 | 25 | } 26 | 27 | class DefaultImageEditorDelegate extends ImageEditorDelegate{ 28 | 29 | Color operatorStatuscolor(bool choosen) => choosen ? Colors.red : Colors.white; 30 | 31 | @override 32 | List get brushColors => const [ 33 | Color(0xFFFA4D32), 34 | Color(0xFFFF7F1E), 35 | Color(0xFF2DA24A), 36 | Color(0xFFF2F2F2), 37 | Color(0xFF222222), 38 | Color(0xFF1F8BE5), 39 | Color(0xFF4E43DB), 40 | ]; 41 | 42 | @override 43 | List get textColors => const [ 44 | Color(0xFFFA4D32), 45 | Color(0xFFFF7F1E), 46 | Color(0xFF2DA24A), 47 | Color(0xFFF2F2F2), 48 | Color(0xFF222222), 49 | Color(0xFF1F8BE5), 50 | Color(0xFF4E43DB), 51 | ]; 52 | 53 | @override 54 | Widget backBtnWidget(double limitSize) { 55 | return Icon(Icons.arrow_back_ios_new, size: limitSize); 56 | } 57 | 58 | @override 59 | Widget doneWidget(BoxConstraints constraints) { 60 | return Container( 61 | constraints: constraints, 62 | alignment: Alignment.center, 63 | decoration: BoxDecoration( 64 | borderRadius: BorderRadius.all(Radius.circular(6)), 65 | gradient: const LinearGradient(colors: [Colors.green, Colors.greenAccent])), 66 | child: Text( 67 | 'Done', 68 | style: TextStyle(fontSize: 15, color: Colors.white), 69 | ), 70 | ); 71 | } 72 | 73 | @override 74 | Widget brushWidget(double limitSize, OperateType type, {required bool choosen}) { 75 | return Icon(Icons.brush_outlined, size: limitSize, color: operatorStatuscolor(choosen)); 76 | } 77 | 78 | @override 79 | Widget addTextWidget(double limitSize, OperateType type, {required bool choosen}) { 80 | return Icon(Icons.notes, size: limitSize, color: operatorStatuscolor(choosen)); 81 | } 82 | 83 | @override 84 | Widget rotateWidget(double limitSize, OperateType type, {required bool choosen}) { 85 | return Icon(Icons.rotate_right, size: limitSize, color: operatorStatuscolor(choosen)); 86 | } 87 | 88 | @override 89 | Widget flipWidget(double limitSize, OperateType type, {required bool choosen}) { 90 | return Icon(Icons.flip, size: limitSize, color: operatorStatuscolor(choosen)); 91 | } 92 | 93 | @override 94 | Widget mosaicWidget(double limitSize, OperateType type, {required bool choosen}) { 95 | return Icon(Icons.auto_awesome_mosaic, size: limitSize, color: operatorStatuscolor(choosen)); 96 | } 97 | 98 | @override 99 | Widget get resetWidget => Text('Reset', style: TextStyle(color: Colors.white, fontSize: 16)); 100 | 101 | @override 102 | SliderThemeData sliderThemeData(BuildContext context) => SliderTheme.of(context).copyWith( 103 | trackHeight: 2, 104 | thumbColor: Colors.white, 105 | disabledThumbColor: Colors.white, 106 | activeTrackColor: const Color(0xFFF83112), 107 | inactiveTrackColor: Colors.white.withOpacity(0.5), 108 | overlayShape: CustomRoundSliderOverlayShape(), 109 | ); 110 | 111 | @override 112 | TextConfigModel get textConfigModel => DefaultTextConfigModel(); 113 | 114 | @override 115 | Border get textSelectedBorder => DashBorder(); 116 | 117 | @override 118 | Widget undoWidget(double limitSize) => Icon(Icons.undo, size: limitSize, color: Colors.white); 119 | 120 | @override 121 | Widget get boldTagWidget => Text( 122 | 'Bold', 123 | style: TextStyle(fontSize: 15, color: Colors.white, fontWeight: FontWeight.bold), 124 | ); 125 | 126 | @override 127 | Widget get sliderLeftWidget => _txtFlatWidget('small'); 128 | 129 | @override 130 | Widget get sliderRightWidget => _txtFlatWidget('big'); 131 | 132 | Widget _txtFlatWidget(String txt) { 133 | return Text( 134 | txt, 135 | style: TextStyle(color: Colors.white.withOpacity(0.5), fontSize: 13), 136 | ); 137 | } 138 | 139 | } 140 | 141 | 142 | ///This model for [TextEditorPage] to initial text style. 143 | abstract class TextConfigModel{ 144 | ///slider up limit 145 | double get sliderUpLimit; 146 | 147 | ///slider bottom limit 148 | double get sliderBottomLimit; 149 | 150 | ///initial size 151 | double get initSize; 152 | 153 | ///input field's cursor color. 154 | Color get cursorColor; 155 | 156 | bool get isLegal => initSize >= sliderBottomLimit && initSize <= sliderUpLimit; 157 | 158 | 159 | } 160 | 161 | 162 | ///For delegate [ImageEditor]'s ui style. 163 | abstract class ImageEditorDelegate{ 164 | 165 | final BoxConstraints _doneWidgetCons = BoxConstraints(maxHeight: 32, maxWidth: 54); 166 | 167 | final double _operateBtnSize = 24; 168 | 169 | final double _unDoWidgetSize = 20; 170 | 171 | final double _backWidgetSize = 22; 172 | 173 | Widget buildDoneWidget() => doneWidget(_doneWidgetCons); 174 | 175 | Widget buildOperateWidget(OperateType type, {required bool choosen}) { 176 | switch(type) { 177 | case OperateType.non: 178 | return SizedBox(); 179 | case OperateType.brush: 180 | return brushWidget(_operateBtnSize, type, choosen: choosen); 181 | case OperateType.text: 182 | return addTextWidget(_operateBtnSize, type, choosen: choosen); 183 | case OperateType.flip: 184 | return flipWidget(_operateBtnSize, type, choosen: choosen); 185 | case OperateType.rotated: 186 | return rotateWidget(_operateBtnSize, type, choosen: choosen); 187 | case OperateType.mosaic: 188 | return mosaicWidget(_operateBtnSize, type, choosen: choosen); 189 | } 190 | } 191 | 192 | Widget buildUndoWidget() => undoWidget(_unDoWidgetSize); 193 | 194 | Widget buildBackWidget() => backBtnWidget(_backWidgetSize); 195 | 196 | ///Brush colors 197 | /// * color's amount in [1,7] 198 | List get brushColors; 199 | 200 | ///Text Colors 201 | /// * color's amount in [1,7] 202 | List get textColors; 203 | 204 | ///Slider's theme data 205 | SliderThemeData sliderThemeData(BuildContext context); 206 | 207 | ///Slider's tag 208 | /// * left-flag(small) - Slider - right-flag(big) 209 | Widget get sliderLeftWidget; 210 | 211 | ///Slider's tag 212 | /// * left-flag(small) - Slider - right-flag(big) 213 | Widget get sliderRightWidget; 214 | 215 | ///To control the text style of bold. 216 | Widget get boldTagWidget; 217 | 218 | ///Text config model 219 | /// * see also: [TextEditorPage] 220 | TextConfigModel get textConfigModel; 221 | 222 | ///Back button on appbar 223 | Widget backBtnWidget(double limitSize); 224 | 225 | ///Brush widget 226 | /// * for paint color on canvas. 227 | Widget brushWidget(double limitSize, OperateType type, {required bool choosen}); 228 | 229 | ///Add text widget 230 | /// * for add some text on canvas. 231 | Widget addTextWidget(double limitSize, OperateType type, {required bool choosen}); 232 | 233 | ///Flip widget 234 | /// * for flip the canvas. 235 | Widget flipWidget(double limitSize, OperateType type, {required bool choosen}); 236 | 237 | ///Rotate widget 238 | /// * for rotate the canvas(90 angle each tap). 239 | Widget rotateWidget(double limitSize, OperateType type, {required bool choosen}); 240 | 241 | ///Mosaic widget 242 | /// * for paint some mosaic on canvas. 243 | Widget mosaicWidget(double limitSize, OperateType type, {required bool choosen}); 244 | 245 | ///Done widget 246 | /// * for save the edit action and generate a new image as result. 247 | Widget doneWidget(BoxConstraints constraints); 248 | 249 | ///Undo widget 250 | /// * for undo last edit action. 251 | Widget undoWidget(double limitSize); 252 | 253 | ///Reset widget 254 | /// * for reset rotated action. 255 | Widget get resetWidget; 256 | 257 | ///Text selected border 258 | /// * for draw a border the text selected. 259 | Border get textSelectedBorder; 260 | 261 | 262 | 263 | } 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | -------------------------------------------------------------------------------- /lib/widget/slider_widget.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/material.dart'; 3 | 4 | 5 | class CustomRoundSliderOverlayShape extends SliderComponentShape { 6 | /// Create a slider thumb overlay that draws a circle. 7 | const CustomRoundSliderOverlayShape({ this.overlayRadius = 0.0 }); 8 | 9 | /// The preferred radius of the round thumb shape when enabled. 10 | /// 11 | /// If it is not provided, then half of the [SliderThemeData.trackHeight] is 12 | /// used. 13 | final double overlayRadius; 14 | 15 | @override 16 | Size getPreferredSize(bool isEnabled, bool isDiscrete) { 17 | return Size.fromRadius(overlayRadius); 18 | } 19 | 20 | @override 21 | void paint( 22 | PaintingContext context, 23 | Offset center, { 24 | required Animation activationAnimation, 25 | required Animation enableAnimation, 26 | required bool isDiscrete, 27 | required TextPainter labelPainter, 28 | required RenderBox parentBox, 29 | required SliderThemeData sliderTheme, 30 | required TextDirection textDirection, 31 | required double value, 32 | required double textScaleFactor, 33 | required Size sizeWithOverflow, 34 | }) { 35 | 36 | final Canvas canvas = context.canvas; 37 | final Tween radiusTween = Tween( 38 | begin: 0.0, 39 | end: overlayRadius, 40 | ); 41 | 42 | canvas.drawCircle( 43 | center, 44 | radiusTween.evaluate(activationAnimation), 45 | Paint()..color = sliderTheme.overlayColor!, 46 | ); 47 | } 48 | } 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /lib/widget/text_editor_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:image_editor_dove/extension/num_extension.dart'; 5 | import 'package:image_editor_dove/model/float_text_model.dart'; 6 | 7 | import '../image_editor.dart'; 8 | import 'image_editor_delegate.dart'; 9 | 10 | 11 | ///A page for input some text to canvas. 12 | class TextEditorPage extends StatefulWidget { 13 | @override 14 | State createState() { 15 | return TextEditorPageState(); 16 | } 17 | } 18 | 19 | class TextEditorPageState extends State with LittleWidgetBinding, WindowUiBinding{ 20 | 21 | static TextConfigModel get configModel => ImageEditor.uiDelegate.textConfigModel; 22 | 23 | final FocusNode _node = FocusNode(); 24 | 25 | final GlobalKey filedKey = GlobalKey(); 26 | 27 | final TextEditingController _controller = TextEditingController(); 28 | 29 | final List textColorList = ImageEditor.uiDelegate.textColors; 30 | 31 | ///text's color 32 | late ValueNotifier selectedColor; 33 | 34 | Color get _textColor => Color(selectedColor.value); 35 | 36 | ///text's size 37 | double _size = configModel.initSize; 38 | 39 | ///text font weight 40 | FontWeight _fontWeight = FontWeight.normal; 41 | 42 | FloatTextModel buildModel() { 43 | RenderObject? ro = filedKey.currentContext?.findRenderObject(); 44 | Offset offset = Offset(100, 200); 45 | if (ro is RenderBox) { 46 | //adjust text's dy value 47 | offset = ro.localToGlobal(Offset.zero).translate(0, -(44 + windowStatusBarHeight)); 48 | } 49 | return FloatTextModel(text: _controller.text, top: offset.dy, left: offset.dx, style: TextStyle(fontSize: _size, color: _textColor)); 50 | } 51 | 52 | void popWithResult() { 53 | Navigator.pop(context, buildModel()); 54 | } 55 | 56 | void tapBoldBtn() { 57 | _fontWeight = _fontWeight == FontWeight.bold ? FontWeight.normal : FontWeight.bold; 58 | setState(() { 59 | 60 | }); 61 | } 62 | 63 | @override 64 | void initState() { 65 | super.initState(); 66 | assert(configModel.isLegal, "TextConfigModel config size is not legal : " 67 | "initSize must middle in up and bottom limit"); 68 | selectedColor = ValueNotifier(textColorList.first.value); 69 | Future.delayed(const Duration(milliseconds: 160), () { 70 | if (mounted) _node.requestFocus(); 71 | }); 72 | } 73 | 74 | @override 75 | void dispose() { 76 | _node.dispose(); 77 | _controller.dispose(); 78 | super.dispose(); 79 | } 80 | 81 | @override 82 | Widget build(BuildContext context) { 83 | return GestureDetector( 84 | onTap: () { 85 | if(!_node.hasFocus) 86 | _node.requestFocus(); 87 | }, 88 | child: Material( 89 | color: Colors.transparent, 90 | child: Stack( 91 | children: [ 92 | ClipRect( 93 | child: BackdropFilter( 94 | filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), 95 | child: Container( 96 | width: double.infinity, height: double.infinity, 97 | color: Colors.black38,), 98 | ), 99 | ), 100 | Scaffold( 101 | backgroundColor: Colors.transparent, 102 | resizeToAvoidBottomInset: true, 103 | appBar: AppBar( 104 | backgroundColor: Colors.transparent, 105 | leading: GestureDetector( 106 | onTap: () { 107 | Navigator.pop(context); 108 | }, 109 | child: Container( 110 | height: 44, 111 | alignment: Alignment.center, 112 | padding: EdgeInsets.only(top: 12, bottom: 12), 113 | child: Text('Cancel', style: TextStyle(color: Colors.white, fontSize: 16),), 114 | ), 115 | ), 116 | actions: [ 117 | Padding( 118 | padding: EdgeInsets.only(top: 12, bottom: 12 , right: 16), 119 | child: doneButtonWidget(onPressed: popWithResult), 120 | ), 121 | ], 122 | ), 123 | body: Column( 124 | mainAxisAlignment: MainAxisAlignment.center, 125 | children: [ 126 | Expanded(child: SizedBox()), 127 | //text area 128 | Container( 129 | width: screenWidth, 130 | padding: EdgeInsets.symmetric(horizontal: 20), 131 | child: TextField( 132 | key: filedKey, 133 | maxLines: 50, 134 | minLines: 1, 135 | controller: _controller, 136 | focusNode: _node, 137 | cursorColor: configModel.cursorColor, 138 | style: TextStyle(color: _textColor, fontSize: _size, fontWeight: _fontWeight), 139 | decoration: InputDecoration( 140 | isCollapsed: true, 141 | border: InputBorder.none 142 | ), 143 | ), 144 | ), 145 | Expanded(child: SizedBox()), 146 | //slider 147 | Container( 148 | height: 36, 149 | padding: EdgeInsets.symmetric(horizontal: 20), 150 | width: double.infinity, 151 | //color: Colors.white, 152 | child: Row( 153 | children: [ 154 | GestureDetector( 155 | onTap: tapBoldBtn, 156 | child: ImageEditor.uiDelegate.boldTagWidget, 157 | ), 158 | 24.hGap, 159 | ImageEditor.uiDelegate.sliderLeftWidget, 160 | 8.hGap, 161 | Expanded(child: _buildSlider()), 162 | 8.hGap, 163 | ImageEditor.uiDelegate.sliderRightWidget, 164 | 2.hGap, 165 | ], 166 | ), 167 | ), 168 | //color selector 169 | Container( 170 | height: 41, 171 | padding: EdgeInsets.symmetric(horizontal: 20), 172 | width: double.infinity, 173 | //color: Colors.white, 174 | child: Row( 175 | mainAxisAlignment: MainAxisAlignment.spaceAround, 176 | children: textColorList 177 | .map((e) => CircleColorWidget( 178 | color: e, 179 | valueListenable: selectedColor, 180 | onColorSelected: (color) { 181 | setState(() { 182 | selectedColor.value = color.value; 183 | }); 184 | }, 185 | )) 186 | .toList(), 187 | ), 188 | ), 189 | ], 190 | ), 191 | ), 192 | ], 193 | ), 194 | ), 195 | ); 196 | } 197 | 198 | Widget _buildSlider() { 199 | return SliderTheme( 200 | data: ImageEditor.uiDelegate.sliderThemeData(context), 201 | child: Slider( 202 | value: _size, 203 | max: configModel.sliderUpLimit, 204 | min: configModel.sliderBottomLimit, 205 | onChanged: (v) { 206 | _size = v; 207 | setState(() {}); 208 | }, 209 | )); 210 | } 211 | 212 | 213 | 214 | } 215 | -------------------------------------------------------------------------------- /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 | sha256: "7e0d52067d05f2e0324268097ba723b71cb41ac8a6a2b24d1edf9c536b987b03" 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "3.4.6" 12 | async: 13 | dependency: transitive 14 | description: 15 | name: async 16 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "2.11.0" 20 | boolean_selector: 21 | dependency: transitive 22 | description: 23 | name: boolean_selector 24 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "2.1.1" 28 | characters: 29 | dependency: transitive 30 | description: 31 | name: characters 32 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "1.3.0" 36 | clock: 37 | dependency: transitive 38 | description: 39 | name: clock 40 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "1.1.1" 44 | collection: 45 | dependency: transitive 46 | description: 47 | name: collection 48 | sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "1.17.1" 52 | convert: 53 | dependency: transitive 54 | description: 55 | name: convert 56 | sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" 57 | url: "https://pub.dev" 58 | source: hosted 59 | version: "3.1.1" 60 | crypto: 61 | dependency: transitive 62 | description: 63 | name: crypto 64 | sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab 65 | url: "https://pub.dev" 66 | source: hosted 67 | version: "3.0.3" 68 | fake_async: 69 | dependency: transitive 70 | description: 71 | name: fake_async 72 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" 73 | url: "https://pub.dev" 74 | source: hosted 75 | version: "1.3.1" 76 | ffi: 77 | dependency: transitive 78 | description: 79 | name: ffi 80 | sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" 81 | url: "https://pub.dev" 82 | source: hosted 83 | version: "2.1.0" 84 | flutter: 85 | dependency: "direct main" 86 | description: flutter 87 | source: sdk 88 | version: "0.0.0" 89 | flutter_test: 90 | dependency: "direct dev" 91 | description: flutter 92 | source: sdk 93 | version: "0.0.0" 94 | image: 95 | dependency: "direct main" 96 | description: 97 | name: image 98 | sha256: "8e9d133755c3e84c73288363e6343157c383a0c6c56fc51afcc5d4d7180306d6" 99 | url: "https://pub.dev" 100 | source: hosted 101 | version: "3.3.0" 102 | js: 103 | dependency: transitive 104 | description: 105 | name: js 106 | sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 107 | url: "https://pub.dev" 108 | source: hosted 109 | version: "0.6.7" 110 | matcher: 111 | dependency: transitive 112 | description: 113 | name: matcher 114 | sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" 115 | url: "https://pub.dev" 116 | source: hosted 117 | version: "0.12.15" 118 | material_color_utilities: 119 | dependency: transitive 120 | description: 121 | name: material_color_utilities 122 | sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 123 | url: "https://pub.dev" 124 | source: hosted 125 | version: "0.2.0" 126 | meta: 127 | dependency: transitive 128 | description: 129 | name: meta 130 | sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" 131 | url: "https://pub.dev" 132 | source: hosted 133 | version: "1.9.1" 134 | path: 135 | dependency: transitive 136 | description: 137 | name: path 138 | sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" 139 | url: "https://pub.dev" 140 | source: hosted 141 | version: "1.8.3" 142 | path_provider: 143 | dependency: "direct main" 144 | description: 145 | name: path_provider 146 | sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa 147 | url: "https://pub.dev" 148 | source: hosted 149 | version: "2.1.1" 150 | path_provider_android: 151 | dependency: transitive 152 | description: 153 | name: path_provider_android 154 | sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72 155 | url: "https://pub.dev" 156 | source: hosted 157 | version: "2.2.1" 158 | path_provider_foundation: 159 | dependency: transitive 160 | description: 161 | name: path_provider_foundation 162 | sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" 163 | url: "https://pub.dev" 164 | source: hosted 165 | version: "2.3.1" 166 | path_provider_linux: 167 | dependency: transitive 168 | description: 169 | name: path_provider_linux 170 | sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 171 | url: "https://pub.dev" 172 | source: hosted 173 | version: "2.2.1" 174 | path_provider_platform_interface: 175 | dependency: transitive 176 | description: 177 | name: path_provider_platform_interface 178 | sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" 179 | url: "https://pub.dev" 180 | source: hosted 181 | version: "2.1.1" 182 | path_provider_windows: 183 | dependency: transitive 184 | description: 185 | name: path_provider_windows 186 | sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" 187 | url: "https://pub.dev" 188 | source: hosted 189 | version: "2.2.1" 190 | petitparser: 191 | dependency: transitive 192 | description: 193 | name: petitparser 194 | sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 195 | url: "https://pub.dev" 196 | source: hosted 197 | version: "5.4.0" 198 | platform: 199 | dependency: transitive 200 | description: 201 | name: platform 202 | sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59" 203 | url: "https://pub.dev" 204 | source: hosted 205 | version: "3.1.3" 206 | plugin_platform_interface: 207 | dependency: transitive 208 | description: 209 | name: plugin_platform_interface 210 | sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d 211 | url: "https://pub.dev" 212 | source: hosted 213 | version: "2.1.6" 214 | pointycastle: 215 | dependency: transitive 216 | description: 217 | name: pointycastle 218 | sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" 219 | url: "https://pub.dev" 220 | source: hosted 221 | version: "3.7.3" 222 | screenshot: 223 | dependency: "direct main" 224 | description: 225 | name: screenshot 226 | sha256: "455284ff1f5b911d94a43c25e1385485cf6b4f288293eba68f15dad711c7b81c" 227 | url: "https://pub.dev" 228 | source: hosted 229 | version: "2.1.0" 230 | sky_engine: 231 | dependency: transitive 232 | description: flutter 233 | source: sdk 234 | version: "0.0.99" 235 | source_span: 236 | dependency: transitive 237 | description: 238 | name: source_span 239 | sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 240 | url: "https://pub.dev" 241 | source: hosted 242 | version: "1.9.1" 243 | stack_trace: 244 | dependency: transitive 245 | description: 246 | name: stack_trace 247 | sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 248 | url: "https://pub.dev" 249 | source: hosted 250 | version: "1.11.0" 251 | stream_channel: 252 | dependency: transitive 253 | description: 254 | name: stream_channel 255 | sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" 256 | url: "https://pub.dev" 257 | source: hosted 258 | version: "2.1.1" 259 | string_scanner: 260 | dependency: transitive 261 | description: 262 | name: string_scanner 263 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" 264 | url: "https://pub.dev" 265 | source: hosted 266 | version: "1.2.0" 267 | term_glyph: 268 | dependency: transitive 269 | description: 270 | name: term_glyph 271 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 272 | url: "https://pub.dev" 273 | source: hosted 274 | version: "1.2.1" 275 | test_api: 276 | dependency: transitive 277 | description: 278 | name: test_api 279 | sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb 280 | url: "https://pub.dev" 281 | source: hosted 282 | version: "0.5.1" 283 | typed_data: 284 | dependency: transitive 285 | description: 286 | name: typed_data 287 | sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c 288 | url: "https://pub.dev" 289 | source: hosted 290 | version: "1.3.2" 291 | vector_math: 292 | dependency: transitive 293 | description: 294 | name: vector_math 295 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 296 | url: "https://pub.dev" 297 | source: hosted 298 | version: "2.1.4" 299 | win32: 300 | dependency: transitive 301 | description: 302 | name: win32 303 | sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3" 304 | url: "https://pub.dev" 305 | source: hosted 306 | version: "5.0.9" 307 | xdg_directories: 308 | dependency: transitive 309 | description: 310 | name: xdg_directories 311 | sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2" 312 | url: "https://pub.dev" 313 | source: hosted 314 | version: "1.0.3" 315 | xml: 316 | dependency: transitive 317 | description: 318 | name: xml 319 | sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" 320 | url: "https://pub.dev" 321 | source: hosted 322 | version: "6.3.0" 323 | sdks: 324 | dart: ">=3.0.0 <4.0.0" 325 | flutter: ">=3.10.0" 326 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: image_editor_dove 2 | description: An image editor with crop, scribble, mosaic, add-text, flip, rotated functions. 3 | version: 0.0.3 4 | homepage: https://github.com/bladeofgod/flutter_image_editor 5 | 6 | environment: 7 | sdk: '>=2.18.0 <4.0.0' 8 | flutter: ">=3.7.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | 14 | path_provider: ^2.1.1 15 | image: ^3.0.0 16 | screenshot: ^2.1.0 17 | 18 | dev_dependencies: 19 | flutter_test: 20 | sdk: flutter 21 | 22 | # For information on the generic Dart part of this file, see the 23 | # following page: https://dart.dev/tools/pub/pubspec 24 | 25 | # The following section is specific to Flutter. 26 | flutter: 27 | # This section identifies this Flutter project as a plugin project. 28 | # The 'pluginClass' and Android 'package' identifiers should not ordinarily 29 | # be modified. They are used by the tooling to maintain consistency when 30 | # adding or updating assets for this project. 31 | plugin: 32 | platforms: 33 | android: 34 | package: com.lijiaqi.image_editor 35 | pluginClass: ImageEditorPlugin 36 | ios: 37 | pluginClass: ImageEditorPlugin 38 | 39 | # To add assets to your plugin package, add an assets section, like this: 40 | # assets: 41 | # - images/a_dot_burr.jpeg 42 | # - images/a_dot_ham.jpeg 43 | # 44 | # For details regarding assets in packages, see 45 | # https://flutter.dev/assets-and-images/#from-packages 46 | # 47 | # An image asset can refer to one or more resolution-specific "variants", see 48 | # https://flutter.dev/assets-and-images/#resolution-aware. 49 | 50 | # To add custom fonts to your plugin package, add a fonts section here, 51 | # in this "flutter" section. Each entry in this list should have a 52 | # "family" key with the font family name, and a "fonts" key with a 53 | # list giving the asset and other descriptors for the font. For 54 | # example: 55 | # fonts: 56 | # - family: Schyler 57 | # fonts: 58 | # - asset: fonts/Schyler-Regular.ttf 59 | # - asset: fonts/Schyler-Italic.ttf 60 | # style: italic 61 | # - family: Trajan Pro 62 | # fonts: 63 | # - asset: fonts/TrajanPro.ttf 64 | # - asset: fonts/TrajanPro_Bold.ttf 65 | # weight: 700 66 | # 67 | # For details regarding fonts in packages, see 68 | # https://flutter.dev/custom-fonts/#from-packages 69 | -------------------------------------------------------------------------------- /test/image_editor_test.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter_test/flutter_test.dart'; 3 | 4 | 5 | void main() { 6 | //const MethodChannel channel = MethodChannel('image_editor'); 7 | 8 | TestWidgetsFlutterBinding.ensureInitialized(); 9 | 10 | setUp(() { 11 | // channel.setMockMethodCallHandler((MethodCall methodCall) async { 12 | // return '42'; 13 | // }); 14 | }); 15 | 16 | tearDown(() { 17 | //channel.setMockMethodCallHandler(null); 18 | }); 19 | 20 | 21 | } 22 | --------------------------------------------------------------------------------