├── .pubignore ├── example ├── ios │ ├── Flutter │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── AppFrameworkInfo.plist │ ├── Runner │ │ ├── Runner-Bridging-Header.h │ │ ├── Assets.xcassets │ │ │ ├── LaunchImage.imageset │ │ │ │ ├── LaunchImage.png │ │ │ │ ├── LaunchImage@2x.png │ │ │ │ ├── LaunchImage@3x.png │ │ │ │ ├── README.md │ │ │ │ └── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ │ ├── 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-1024x1024@1x.png │ │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ │ └── Contents.json │ │ ├── AppDelegate.swift │ │ ├── Base.lproj │ │ │ ├── Main.storyboard │ │ │ └── LaunchScreen.storyboard │ │ └── Info.plist │ ├── Runner.xcodeproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ │ └── IDEWorkspaceChecks.plist │ │ ├── xcshareddata │ │ │ └── xcschemes │ │ │ │ └── Runner.xcscheme │ │ └── project.pbxproj │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ └── IDEWorkspaceChecks.plist │ └── .gitignore ├── images │ └── sample.jpg ├── web │ ├── favicon.png │ ├── icons │ │ ├── Icon-192.png │ │ ├── Icon-512.png │ │ ├── Icon-maskable-192.png │ │ └── Icon-maskable-512.png │ ├── manifest.json │ └── index.html ├── android │ ├── gradle.properties │ ├── app │ │ ├── src │ │ │ ├── main │ │ │ │ ├── res │ │ │ │ │ ├── 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 │ │ │ │ │ ├── drawable │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ ├── drawable-v21 │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ ├── values │ │ │ │ │ │ └── styles.xml │ │ │ │ │ └── values-night │ │ │ │ │ │ └── styles.xml │ │ │ │ ├── kotlin │ │ │ │ │ └── dev │ │ │ │ │ │ └── pub │ │ │ │ │ │ └── crop │ │ │ │ │ │ └── app │ │ │ │ │ │ └── MainActivity.kt │ │ │ │ └── AndroidManifest.xml │ │ │ ├── debug │ │ │ │ └── AndroidManifest.xml │ │ │ └── profile │ │ │ │ └── AndroidManifest.xml │ │ └── build.gradle │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ ├── .gitignore │ ├── settings.gradle │ └── build.gradle ├── analysis_options.yaml ├── .gitignore ├── test │ └── widget_test.dart ├── .metadata ├── pubspec.yaml ├── README.md └── lib │ ├── centered_slider_track_shape.dart │ └── main.dart ├── .metadata ├── .github └── workflows │ ├── publish_pubdev.yml │ └── github-pages.yaml ├── test └── crop_test.dart ├── lib ├── src │ ├── matrix_decomposition.dart │ ├── utils.dart │ ├── crop_render_object_widget.dart │ ├── crop_controller.dart │ ├── crop_render.dart │ └── crop.dart └── crop.dart ├── pubspec.yaml ├── LICENSE ├── README.md ├── .gitignore ├── CHANGELOG.md └── analysis_options.yaml /.pubignore: -------------------------------------------------------------------------------- 1 | docs/ 2 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /example/images/sample.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olutter/crop/HEAD/example/images/sample.jpg -------------------------------------------------------------------------------- /example/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olutter/crop/HEAD/example/web/favicon.png -------------------------------------------------------------------------------- /example/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olutter/crop/HEAD/example/web/icons/Icon-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olutter/crop/HEAD/example/web/icons/Icon-512.png -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /example/web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olutter/crop/HEAD/example/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olutter/crop/HEAD/example/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olutter/crop/HEAD/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/olutter/crop/HEAD/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/olutter/crop/HEAD/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/olutter/crop/HEAD/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/olutter/crop/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olutter/crop/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olutter/crop/HEAD/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/olutter/crop/HEAD/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/olutter/crop/HEAD/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/olutter/crop/HEAD/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/olutter/crop/HEAD/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/olutter/crop/HEAD/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/olutter/crop/HEAD/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/olutter/crop/HEAD/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/olutter/crop/HEAD/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/olutter/crop/HEAD/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/olutter/crop/HEAD/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/olutter/crop/HEAD/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/olutter/crop/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olutter/crop/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olutter/crop/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | # Additional information about this file can be found at 4 | # https://dart.dev/guides/language/analysis-options 5 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olutter/crop/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olutter/crop/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/dev/pub/crop/app/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package dev.pub.crop.app 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/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-7.5-bin.zip 6 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.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/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.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: 5d37de2685b8fcc151f61e121289c3dcbaee4623 8 | channel: master 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /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 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /.github/workflows/publish_pubdev.yml: -------------------------------------------------------------------------------- 1 | name: Publish to Pub.dev 2 | 3 | on: push 4 | 5 | jobs: 6 | publishing: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: 'Checkout' 10 | uses: actions/checkout@v2 # required! 11 | 12 | - name: 'crop' 13 | uses: k-paxian/dart-package-publisher@master 14 | with: 15 | credentialJson: ${{ secrets.CREDENTIAL_JSON }} 16 | -------------------------------------------------------------------------------- /test/crop_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | 3 | //import 'package:crop/crop.dart'; 4 | 5 | void main() { 6 | test('No tests yet.', () { 7 | // final calculator = Calculator(); 8 | // expect(calculator.addOne(2), 3); 9 | // expect(calculator.addOne(-7), -6); 10 | // expect(calculator.addOne(0), 1); 11 | // expect(() => calculator.addOne(null), throwsNoSuchMethodError); 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 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 | -------------------------------------------------------------------------------- /lib/src/matrix_decomposition.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | /// Decomposition of a matrix into [rotation], [scale], [translation]. 4 | class MatrixDecomposition { 5 | /// Construction 6 | MatrixDecomposition({ 7 | required this.scale, 8 | required this.rotation, 9 | required this.translation, 10 | }); 11 | 12 | /// Rotation 13 | final double rotation; 14 | 15 | /// Scale 16 | final double scale; 17 | 18 | /// Translation 19 | final Offset translation; 20 | } 21 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: crop 2 | description: Crop for Flutter. Crop any widget/image in Android, iOS, Web and Desktop with fancy and customizable UI, in 100% pure Dart code. 3 | version: 0.5.5 4 | homepage: https://pwa.ir 5 | repository: https://github.com/xclud/flutter_crop 6 | 7 | environment: 8 | sdk: ">=2.12.0 <3.0.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | collision: ^0.0.3 14 | vector_math: ^2.1.2 15 | 16 | dev_dependencies: 17 | flutter_test: 18 | sdk: flutter 19 | flutter_lints: ^2.0.1 20 | -------------------------------------------------------------------------------- /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/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/crop.dart: -------------------------------------------------------------------------------- 1 | /// Crop for Flutter. Crop any widget/image in Android, iOS, Web and Desktop with fancy and customizable UI, in 100% pure Dart code. 2 | library crop; 3 | 4 | import 'dart:ui' as ui; 5 | import 'dart:math'; 6 | 7 | import 'package:collision/collision.dart'; 8 | import 'package:crop/src/matrix_decomposition.dart'; 9 | import 'package:flutter/material.dart'; 10 | import 'package:flutter/rendering.dart'; 11 | import 'package:vector_math/vector_math_64.dart' as vm; 12 | 13 | export 'src/matrix_decomposition.dart'; 14 | 15 | part 'src/crop.dart'; 16 | part 'src/crop_render.dart'; 17 | part 'src/crop_controller.dart'; 18 | part 'src/utils.dart'; 19 | part 'src/crop_render_object_widget.dart'; 20 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /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 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | 35 | # Exceptions to above rules. 36 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 37 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.7.10' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.2.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /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 | 11.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/src/utils.dart: -------------------------------------------------------------------------------- 1 | part of crop; 2 | 3 | Size _getSizeToFitByRatio(double imageAspectRatio, double containerWidth, 4 | double containerHeight, EdgeInsets padding) { 5 | var targetAspectRatio = containerWidth / containerHeight; 6 | 7 | // no need to adjust the size if current size is square 8 | var adjustedWidth = containerWidth; 9 | var adjustedHeight = containerHeight; 10 | 11 | // get the larger aspect ratio of the two 12 | // if aspect ratio is 1 then no adjustment needed 13 | if (imageAspectRatio > targetAspectRatio) { 14 | adjustedHeight = containerWidth / imageAspectRatio; 15 | } else if (imageAspectRatio < targetAspectRatio) { 16 | adjustedWidth = containerHeight * imageAspectRatio; 17 | } 18 | 19 | // set the adjusted size (same if square) 20 | return Size( 21 | adjustedWidth - padding.horizontal, 22 | adjustedHeight - padding.vertical, 23 | ); 24 | } 25 | 26 | vm.Vector2 _toVector2(Offset offset) => vm.Vector2(offset.dx, offset.dy); 27 | Offset _toOffset(vm.Vector2 v) => Offset(v.x, v.y); 28 | -------------------------------------------------------------------------------- /example/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Crop Demo", 3 | "short_name": "Crop Demo", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-2021 Mahdi K. Fard 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /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 | 11 | //import 'package:app/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // // Build our app and trigger a frame. 16 | // await tester.pumpWidget(const MyApp()); 17 | 18 | // // Verify that our counter starts at 0. 19 | // expect(find.text('0'), findsOneWidget); 20 | // expect(find.text('1'), findsNothing); 21 | 22 | // // Tap the '+' icon and trigger a frame. 23 | // await tester.tap(find.byIcon(Icons.add)); 24 | // await tester.pump(); 25 | 26 | // // Verify that our counter has incremented. 27 | // expect(find.text('0'), findsNothing); 28 | // expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/github-pages.yaml: -------------------------------------------------------------------------------- 1 | name: GitHub Pages 2 | 3 | concurrency: 4 | group: production 5 | cancel-in-progress: true 6 | 7 | on: 8 | push: 9 | branches: [ main ] 10 | 11 | jobs: 12 | build: 13 | name: Build Web App 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Fetch all history for all tags and branches 18 | run: | 19 | git config remote.origin.url https://x-access-token:${{ secrets.GH_TOKEN }}@github.com/${{ github.repository }} 20 | git fetch --prune --depth=10000 21 | - uses: subosito/flutter-action@v2 22 | with: 23 | channel: "master" 24 | - run: cd example && flutter pub get 25 | - run: cd example && flutter test 26 | - name: Build Web App 27 | run: cd example && flutter build web --base-href /flutter_crop/ 28 | - name: Upload Artifacts 29 | uses: actions/upload-pages-artifact@v1 30 | with: 31 | path: example/build/web 32 | deploy: 33 | name: Deploy GitHub Pages 34 | needs: build 35 | runs-on: ubuntu-latest 36 | permissions: 37 | pages: write 38 | id-token: write 39 | environment: 40 | name: github-pages 41 | url: ${{ steps.deployment.outputs.page_url }} 42 | steps: 43 | - name: Deploy to GitHub Pages 44 | id: deployment 45 | uses: actions/deploy-pages@v1 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![pub package](https://img.shields.io/pub/v/crop.svg)](https://pub.dartlang.org/packages/crop) 2 | 3 | A Flutter package for cropping any widget, not only images. This package is entirely written in Dart and supports Android, iOS, Web and Desktop. Also, because of being independent from native platform, it does not increase size of your apps output (e.g. apk). 4 | 5 | ## Supported platforms 6 | 7 | * Flutter Android 8 | * Flutter iOS 9 | * Flutter Web 10 | * Flutter Desktop 11 | 12 | ## Demo 13 | 14 | [Web Demo](https://xclud.github.io/flutter_crop/) | [Install from Google Play](https://play.google.com/store/apps/details?id=dev.pub.crop.app) 15 | 16 | ## Donation 17 | 18 | If you find this project useful, please support me by buying me a pizza 🍕. 19 | 20 | Tron Address: 21 | 22 | ```bash 23 | TLtrEU4KT2bn5J87VWfs1QDrmB1aFQ1bja 24 | ``` 25 | 26 | Ethereum Address: 27 | 28 | ```bash 29 | 0xf8Da77e7BbE39be8c9e527289465Bf7219af58db 30 | ``` 31 | 32 | I do not accept Bitcoin due to its issues with sustainability and global warming. 33 | 34 | ## Getting Started 35 | 36 | In your `pubspec.yaml` file add: 37 | 38 | ```dart 39 | dependencies: 40 | crop: any 41 | ``` 42 | 43 | Then, in your code import: 44 | 45 | ```dart 46 | import 'package:crop/crop.dart'; 47 | ``` 48 | 49 | Now in build function, put a `Crop` widget in the widget tree and you are done. Please don't forget to check ```/example``` folder, there is much more. 50 | -------------------------------------------------------------------------------- /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/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled. 5 | 6 | version: 7 | revision: a707d05e9d4c7ec95403444e04a65cd69c0da3b0 8 | channel: master 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: a707d05e9d4c7ec95403444e04a65cd69c0da3b0 17 | base_revision: a707d05e9d4c7ec95403444e04a65cd69c0da3b0 18 | - platform: android 19 | create_revision: a707d05e9d4c7ec95403444e04a65cd69c0da3b0 20 | base_revision: a707d05e9d4c7ec95403444e04a65cd69c0da3b0 21 | - platform: ios 22 | create_revision: a707d05e9d4c7ec95403444e04a65cd69c0da3b0 23 | base_revision: a707d05e9d4c7ec95403444e04a65cd69c0da3b0 24 | - platform: linux 25 | create_revision: a707d05e9d4c7ec95403444e04a65cd69c0da3b0 26 | base_revision: a707d05e9d4c7ec95403444e04a65cd69c0da3b0 27 | - platform: macos 28 | create_revision: a707d05e9d4c7ec95403444e04a65cd69c0da3b0 29 | base_revision: a707d05e9d4c7ec95403444e04a65cd69c0da3b0 30 | - platform: web 31 | create_revision: a707d05e9d4c7ec95403444e04a65cd69c0da3b0 32 | base_revision: a707d05e9d4c7ec95403444e04a65cd69c0da3b0 33 | - platform: windows 34 | create_revision: a707d05e9d4c7ec95403444e04a65cd69c0da3b0 35 | base_revision: a707d05e9d4c7ec95403444e04a65cd69c0da3b0 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 16 | 20 | 24 | 25 | 26 | 27 | 28 | 29 | 31 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /.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 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | build/ 32 | 33 | # Android related 34 | **/android/**/gradle-wrapper.jar 35 | **/android/.gradle 36 | **/android/captures/ 37 | **/android/gradlew 38 | **/android/gradlew.bat 39 | **/android/local.properties 40 | **/android/**/GeneratedPluginRegistrant.java 41 | 42 | # iOS/XCode related 43 | **/ios/**/*.mode1v3 44 | **/ios/**/*.mode2v3 45 | **/ios/**/*.moved-aside 46 | **/ios/**/*.pbxuser 47 | **/ios/**/*.perspectivev3 48 | **/ios/**/*sync/ 49 | **/ios/**/.sconsign.dblite 50 | **/ios/**/.tags* 51 | **/ios/**/.vagrant/ 52 | **/ios/**/DerivedData/ 53 | **/ios/**/Icon? 54 | **/ios/**/Pods/ 55 | **/ios/**/.symlinks/ 56 | **/ios/**/profile 57 | **/ios/**/xcuserdata 58 | **/ios/.generated/ 59 | **/ios/Flutter/App.framework 60 | **/ios/Flutter/Flutter.framework 61 | **/ios/Flutter/Flutter.podspec 62 | **/ios/Flutter/Generated.xcconfig 63 | **/ios/Flutter/app.flx 64 | **/ios/Flutter/app.zip 65 | **/ios/Flutter/flutter_assets/ 66 | **/ios/Flutter/flutter_export_environment.sh 67 | **/ios/ServiceDefinitions.json 68 | **/ios/Runner/GeneratedPluginRegistrant.* 69 | 70 | # Exceptions to above rules. 71 | !**/ios/**/default.mode1v3 72 | !**/ios/**/default.mode2v3 73 | !**/ios/**/default.pbxuser 74 | !**/ios/**/default.perspectivev3 75 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 76 | /example/pubspec.lock 77 | /pubspec.lock 78 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.5.5 2 | 3 | - Allow users to set their own offset. 4 | 5 | ## 0.5.4 6 | 7 | - More dartdoc comments. 8 | 9 | ## 0.5.3 10 | 11 | - Upgrade to flutter_lints version 2. 12 | 13 | ## 0.5.1 14 | 15 | - Support two-finger rotation gesture. 16 | - Documentations. 17 | 18 | ## 0.5.0 19 | 20 | - Flutter 2.0 and Dart 2.12.0 support. 21 | 22 | ## 0.4.3 23 | 24 | - Migration to null-safety 25 | 26 | ## 0.4.2 27 | 28 | - Web support (via CanvasKit/Skia) 29 | 30 | ## 0.4.1 31 | 32 | - Crop widget gets `onChanged` propery. Thanks to @sanjul. 33 | 34 | ## 0.4.0 35 | 36 | - Adds support for Oval crop area. Special thanks to @songfei for his contribution and inspiration. 37 | - Example project updated with a crop shape selector. 38 | 39 | ## 0.3.2 40 | 41 | - Fixes an issue causing the image go off-canvas when playing with aspect-ratio and panning. 42 | 43 | ## 0.3.1 44 | 45 | - Fixes a bug when the rotation is more than 90 degrees (or less than -90). 46 | - Updated the example project to let the rotation slider move between -180 and +180 degrees. 47 | 48 | ## 0.3.0 49 | 50 | - Introduces `helper` parameter. 51 | - Ditches `borderWidth` and `borderColor` parameters in favor of the new `helper` parameter (Breaking Change). You should see the example project to figure out how to bring borders back to your UI. 52 | 53 | ## 0.2.2 54 | 55 | - Adds Support for background widget. 56 | - Layout and Paint more efficiently. 57 | 58 | ## 0.2.1 59 | 60 | - Fixes a bug when child's aspect-ratio does not match the canvas's aspect ratio. 61 | 62 | ## 0.2.0 63 | 64 | - Introduced CropController (Breaking Change). 65 | 66 | ## 0.1.3 67 | 68 | - Optionally takes pixelRatio for better output quality. 69 | 70 | ## 0.1.1 71 | 72 | - Update documentation and improvements to the package health. 73 | 74 | ## 0.1.0 75 | 76 | - Initial release of the package. 77 | 78 | ## 0.0.1 79 | 80 | - Initialize package placeholder. 81 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | App 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | app 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UIViewControllerBasedStatusBarAppearance 45 | 46 | CADisableMinimumFrameDurationOnPhone 47 | 48 | UIApplicationSupportsIndirectInputEvents 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /example/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | Crop Demo 33 | 34 | 35 | 39 | 40 | 41 | 42 | 43 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | analyzer: 3 | exclude: 4 | - "lib/**/generated/**" 5 | language: 6 | strict-raw-types: false 7 | errors: 8 | always_declare_return_types: error 9 | always_specify_types: error 10 | always_use_package_imports: error 11 | annotate_overrides: error 12 | argument_type_not_assignable: error 13 | avoid_function_literals_in_foreach_calls: error 14 | avoid_renaming_method_parameters: error 15 | avoid_types_on_closure_parameters: error 16 | avoid_unnecessary_containers: error 17 | await_only_futures: error 18 | curly_braces_in_flow_control_structures: error 19 | dead_code: error 20 | duplicate_import: error 21 | file_names: error 22 | implicit_dynamic_function: ignore 23 | implicit_dynamic_parameter: error 24 | implicit_dynamic_list_literal: ignore 25 | implicit_dynamic_map_literal: ignore 26 | implicit_dynamic_method: ignore 27 | implicit_dynamic_type: ignore 28 | implicit_dynamic_variable: ignore 29 | invalid_assignment: error 30 | missing_return: error 31 | prefer_const_constructors: error 32 | prefer_const_constructors_in_immutables: error 33 | prefer_const_declarations: error 34 | prefer_collection_literals: error 35 | prefer_contains: error 36 | prefer_const_literals_to_create_immutables: error 37 | prefer_final_fields: error 38 | prefer_single_quotes: error 39 | public_member_api_docs: error 40 | sized_box_for_whitespace: error 41 | sort_constructors_first: error 42 | sort_unnamed_constructors_first: error 43 | use_function_type_syntax_for_parameters: error 44 | use_key_in_widget_constructors: error 45 | unnecessary_import: error 46 | unnecessary_string_interpolations: error 47 | unnecessary_this: error 48 | unused_import: error 49 | linter: 50 | rules: 51 | always_use_package_imports: true 52 | avoid_print: false 53 | prefer_single_quotes: true 54 | public_member_api_docs: true 55 | sort_constructors_first: true 56 | sort_unnamed_constructors_first: true 57 | -------------------------------------------------------------------------------- /lib/src/crop_render_object_widget.dart: -------------------------------------------------------------------------------- 1 | part of crop; 2 | 3 | /// Render object widget with a [RenderCrop] inside. 4 | class CropRenderObjectWidget extends SingleChildRenderObjectWidget { 5 | /// Constructor. 6 | const CropRenderObjectWidget({ 7 | required Widget child, 8 | required this.aspectRatio, 9 | required this.shape, 10 | Key? key, 11 | this.backgroundColor = Colors.black, 12 | this.dimColor = const Color.fromRGBO(0, 0, 0, 0.8), 13 | this.padding = EdgeInsets.zero, 14 | this.radius, 15 | }) : super(key: key, child: child); 16 | 17 | /// Aspect ratio. 18 | final double aspectRatio; 19 | 20 | /// Dim Color. 21 | final Color dimColor; 22 | 23 | /// Background color. 24 | final Color backgroundColor; 25 | 26 | /// Shape of crop area. 27 | final BoxShape shape; 28 | 29 | /// Padding of crop area. 30 | final EdgeInsets padding; 31 | 32 | /// Radius of crop area. 33 | final Radius? radius; 34 | 35 | @override 36 | RenderObject createRenderObject(BuildContext context) { 37 | return RenderCrop() 38 | ..aspectRatio = aspectRatio 39 | ..dimColor = dimColor 40 | ..backgroundColor = backgroundColor 41 | ..shape = shape 42 | ..padding = padding 43 | ..radius = radius; 44 | } 45 | 46 | @override 47 | void updateRenderObject(BuildContext context, RenderCrop renderObject) { 48 | bool needsPaint = false; 49 | bool needsLayout = false; 50 | 51 | if (renderObject.aspectRatio != aspectRatio) { 52 | renderObject.aspectRatio = aspectRatio; 53 | needsLayout = true; 54 | } 55 | 56 | if (renderObject.dimColor != dimColor) { 57 | renderObject.dimColor = dimColor; 58 | needsPaint = true; 59 | } 60 | 61 | if (renderObject.shape != shape) { 62 | renderObject.shape = shape; 63 | needsPaint = true; 64 | } 65 | 66 | if (renderObject.backgroundColor != backgroundColor) { 67 | renderObject.backgroundColor = backgroundColor; 68 | needsPaint = true; 69 | } 70 | 71 | if (needsLayout) { 72 | renderObject.markNeedsLayout(); 73 | } 74 | if (needsPaint) { 75 | renderObject.markNeedsPaint(); 76 | } 77 | 78 | super.updateRenderObject(context, renderObject); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /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 plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion flutter.compileSdkVersion 30 | ndkVersion flutter.ndkVersion 31 | 32 | compileOptions { 33 | sourceCompatibility JavaVersion.VERSION_1_8 34 | targetCompatibility JavaVersion.VERSION_1_8 35 | } 36 | 37 | kotlinOptions { 38 | jvmTarget = '1.8' 39 | } 40 | 41 | sourceSets { 42 | main.java.srcDirs += 'src/main/kotlin' 43 | } 44 | 45 | defaultConfig { 46 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 47 | applicationId "dev.pub.crop.app" 48 | // You can update the following values to match your application needs. 49 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. 50 | minSdkVersion flutter.minSdkVersion 51 | targetSdkVersion flutter.targetSdkVersion 52 | versionCode flutterVersionCode.toInteger() 53 | versionName flutterVersionName 54 | } 55 | 56 | buildTypes { 57 | release { 58 | // TODO: Add your own signing config for the release build. 59 | // Signing with the debug keys for now, so `flutter run --release` works. 60 | signingConfig signingConfigs.debug 61 | } 62 | } 63 | } 64 | 65 | flutter { 66 | source '../..' 67 | } 68 | 69 | dependencies { 70 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 71 | } 72 | -------------------------------------------------------------------------------- /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/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: app 2 | description: Example project for Crop package. 3 | publish_to: none 4 | 5 | # The following defines the version and build number for your application. 6 | # A version number is three numbers separated by dots, like 1.2.43 7 | # followed by an optional build number separated by a +. 8 | # Both the version and the builder number may be overridden in flutter 9 | # build by specifying --build-name and --build-number, respectively. 10 | # In Android, build-name is used as versionName while build-number used as versionCode. 11 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 12 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 13 | # Read more about iOS versioning at 14 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 15 | version: 1.0.0+1 16 | 17 | environment: 18 | sdk: ">=2.12.0 <3.0.0" 19 | 20 | dependencies: 21 | flutter: 22 | sdk: flutter 23 | crop: 24 | path: ../ 25 | url_launcher: any 26 | image_gallery_saver: any 27 | permission_handler: any 28 | 29 | dev_dependencies: 30 | flutter_test: 31 | sdk: flutter 32 | flutter_lints: ^2.0.0 33 | 34 | 35 | # For information on the generic Dart part of this file, see the 36 | # following page: https://dart.dev/tools/pub/pubspec 37 | 38 | # The following section is specific to Flutter. 39 | flutter: 40 | 41 | # The following line ensures that the Material Icons font is 42 | # included with your application, so that you can use the icons in 43 | # the material Icons class. 44 | uses-material-design: true 45 | 46 | # To add assets to your application, add an assets section, like this: 47 | assets: 48 | - images/sample.jpg 49 | # - images/a_dot_ham.jpeg 50 | 51 | # An image asset can refer to one or more resolution-specific "variants", see 52 | # https://flutter.dev/assets-and-images/#resolution-aware. 53 | 54 | # For details regarding adding assets from package dependencies, see 55 | # https://flutter.dev/assets-and-images/#from-packages 56 | 57 | # To add custom fonts to your application, add a fonts section here, 58 | # in this "flutter" section. Each entry in this list should have a 59 | # "family" key with the font family name, and a "fonts" key with a 60 | # list giving the asset and other descriptors for the font. For 61 | # example: 62 | # fonts: 63 | # - family: Schyler 64 | # fonts: 65 | # - asset: fonts/Schyler-Regular.ttf 66 | # - asset: fonts/Schyler-Italic.ttf 67 | # style: italic 68 | # - family: Trajan Pro 69 | # fonts: 70 | # - asset: fonts/TrajanPro.ttf 71 | # - asset: fonts/TrajanPro_Bold.ttf 72 | # weight: 700 73 | # 74 | # For details regarding fonts from package dependencies, 75 | # see https://flutter.dev/custom-fonts/#from-packages 76 | -------------------------------------------------------------------------------- /lib/src/crop_controller.dart: -------------------------------------------------------------------------------- 1 | part of crop; 2 | 3 | /// The controller used to control the rotation, scale and actual cropping. 4 | class CropController extends ChangeNotifier { 5 | /// Constructor 6 | CropController({ 7 | double aspectRatio = 1.0, 8 | double scale = 1.0, 9 | double rotation = 0, 10 | Offset offset = Offset.zero 11 | }) { 12 | _aspectRatio = aspectRatio; 13 | _scale = scale; 14 | _rotation = rotation; 15 | _offset = offset; 16 | } 17 | double _aspectRatio = 1; 18 | double _rotation = 0; 19 | double _scale = 1; 20 | Offset _offset = Offset.zero; 21 | Future Function(double pixelRatio)? _cropCallback; 22 | 23 | /// Gets the current aspect ratio. 24 | double get aspectRatio => _aspectRatio; 25 | 26 | /// Sets the desired aspect ratio. 27 | set aspectRatio(double value) { 28 | _aspectRatio = value; 29 | notifyListeners(); 30 | } 31 | 32 | /// Gets the current scale. 33 | double get scale => max(_scale, 1); 34 | 35 | /// Sets the desired scale. 36 | set scale(double value) { 37 | _scale = max(value, 1); 38 | notifyListeners(); 39 | } 40 | 41 | /// Gets the current rotation. 42 | double get rotation => _rotation; 43 | 44 | /// Sets the desired rotation. 45 | set rotation(double value) { 46 | _rotation = value; 47 | notifyListeners(); 48 | } 49 | 50 | /// Gets the current offset. 51 | Offset get offset => _offset; 52 | 53 | /// Sets the desired offset. 54 | set offset(Offset value) { 55 | _offset = value; 56 | notifyListeners(); 57 | } 58 | 59 | /// Gets the transformation matrix. 60 | Matrix4 get transform => Matrix4.identity() 61 | ..translate(_offset.dx, _offset.dy, 0) 62 | ..rotateZ(_rotation) 63 | ..scale(_scale, _scale, 1); 64 | 65 | double _getMinScale() { 66 | final r = vm.radians(_rotation % 360); 67 | final rabs = r.abs(); 68 | 69 | final sinr = sin(rabs).abs(); 70 | final cosr = cos(rabs).abs(); 71 | 72 | final x = cosr * _aspectRatio + sinr; 73 | final y = sinr * _aspectRatio + cosr; 74 | 75 | final m = max(x / _aspectRatio, y); 76 | 77 | return m; 78 | } 79 | 80 | /// Capture an image of the current state of this widget and its children. 81 | /// 82 | /// The returned [ui.Image] has uncompressed raw RGBA bytes, will have 83 | /// dimensions equal to the size of the [child] widget multiplied by [pixelRatio]. 84 | /// 85 | /// The [pixelRatio] describes the scale between the logical pixels and the 86 | /// size of the output image. It is independent of the 87 | /// [window.devicePixelRatio] for the device, so specifying 1.0 (the default) 88 | /// will give you a 1:1 mapping between logical pixels and the output pixels 89 | /// in the image. 90 | Future crop({double pixelRatio = 1}) { 91 | if (_cropCallback == null) { 92 | return Future.value(null); 93 | } 94 | 95 | return _cropCallback!.call(pixelRatio); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /lib/src/crop_render.dart: -------------------------------------------------------------------------------- 1 | part of crop; 2 | 3 | /// RenderBox for [Crop]. 4 | class RenderCrop extends RenderBox with RenderObjectWithChildMixin { 5 | /// Aspect ratio of the crop area. 6 | double? aspectRatio; 7 | 8 | /// Dim color of the crop area. 9 | Color? dimColor; 10 | 11 | /// Background color of the crop area. 12 | Color? backgroundColor; 13 | 14 | /// Shape of the crop area. 15 | BoxShape? shape; 16 | 17 | /// Padding of the crop area. 18 | EdgeInsets? padding; 19 | 20 | /// Radius of the crop area. 21 | Radius? radius; 22 | 23 | @override 24 | bool hitTestSelf(Offset position) => false; 25 | 26 | @override 27 | void performLayout() { 28 | final BoxConstraints constraints = this.constraints; 29 | size = constraints.biggest; 30 | 31 | if (child != null) { 32 | final forcedSize = 33 | _getSizeToFitByRatio(aspectRatio!, size.width, size.height, padding!); 34 | child!.layout(BoxConstraints.tight(forcedSize), parentUsesSize: true); 35 | } 36 | } 37 | 38 | Path _getDimClipPath() { 39 | final center = Offset( 40 | size.width / 2, 41 | size.height / 2, 42 | ); 43 | 44 | final forcedSize = 45 | _getSizeToFitByRatio(aspectRatio!, size.width, size.height, padding!); 46 | 47 | final path = Path(); 48 | final baseRect = Rect.fromCenter( 49 | center: center, 50 | width: forcedSize.width, 51 | height: forcedSize.height, 52 | ); 53 | 54 | if (radius != null) { 55 | final rect = RRect.fromRectAndRadius(baseRect, radius!); 56 | path.addRRect(rect); 57 | } else { 58 | final rect = baseRect; 59 | if (shape == BoxShape.circle) { 60 | path.addOval(rect); 61 | } else if (shape == BoxShape.rectangle) { 62 | path.addRect(rect); 63 | } 64 | } 65 | 66 | path.addRect(Rect.fromLTWH(0.0, 0.0, size.width, size.height)); 67 | path.fillType = PathFillType.evenOdd; 68 | return path; 69 | } 70 | 71 | @override 72 | void handleEvent(PointerEvent event, BoxHitTestEntry entry) {} 73 | 74 | @override 75 | void paint(PaintingContext context, Offset offset) { 76 | final bounds = offset & size; 77 | 78 | if (backgroundColor != null) { 79 | context.canvas.drawRect(bounds, Paint()..color = backgroundColor!); 80 | } 81 | 82 | final forcedSize = 83 | _getSizeToFitByRatio(aspectRatio!, size.width, size.height, padding!); 84 | 85 | if (child != null) { 86 | final Offset tmp = (size - forcedSize) as Offset; 87 | context.paintChild(child!, offset + tmp / 2); 88 | 89 | final clipPath = _getDimClipPath(); 90 | 91 | context.pushClipPath( 92 | needsCompositing, 93 | offset, 94 | bounds, 95 | clipPath, 96 | (context, offset) { 97 | context.canvas.drawRect(bounds, Paint()..color = dimColor!); 98 | }, 99 | ); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /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.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # crop 2 | 3 | A Flutter package for cropping any widget, not only images. This package is entirely written in Dart and supports Android, iOS, Web and Desktop. Also, because of being independent from native platform, it does not increase size of your apps output (e.g. apk). 4 | 5 | [![Crop Demo on Google Play](../doc/google-play-badge.png)](https://play.google.com/store/apps/details?id=dev.pub.crop.app) 6 | 7 | ![Demo of Crop](../doc/demo1.gif) 8 | 9 | ## Getting Started 10 | 11 | In your `pubspec.yaml` file add: 12 | 13 | ```dart 14 | dependencies: 15 | crop: any 16 | ``` 17 | Then, in your code import: 18 | ```dart 19 | import 'package:crop/crop.dart'; 20 | ``` 21 | Now in build function, put a Crop widget in the widget tree: 22 | 23 | ```dart 24 | import 'package:app/centered_slider_track_shape.dart'; 25 | import 'package:flutter/material.dart'; 26 | import 'package:crop/crop.dart'; 27 | 28 | void main() => runApp(MyApp()); 29 | 30 | class MyApp extends StatelessWidget { 31 | @override 32 | Widget build(BuildContext context) { 33 | return MaterialApp( 34 | title: 'Crop Demo', 35 | theme: ThemeData( 36 | primarySwatch: Colors.blue, 37 | ), 38 | home: MyHomePage(), 39 | ); 40 | } 41 | } 42 | 43 | class MyHomePage extends StatefulWidget { 44 | @override 45 | _MyHomePageState createState() => _MyHomePageState(); 46 | } 47 | 48 | class _MyHomePageState extends State { 49 | final _cropKey = GlobalKey(); 50 | double _rotation = 0; 51 | 52 | void _cropImage() async { 53 | final cropped = await _cropKey.currentState.crop(); 54 | Navigator.of(context).push( 55 | MaterialPageRoute( 56 | builder: (context) => Scaffold( 57 | appBar: AppBar( 58 | title: Text('Crop Result'), 59 | centerTitle: true, 60 | ), 61 | body: Center( 62 | child: RawImage( 63 | image: cropped, 64 | ), 65 | ), 66 | ), 67 | fullscreenDialog: true, 68 | ), 69 | ); 70 | } 71 | 72 | @override 73 | Widget build(BuildContext context) { 74 | final theme = Theme.of(context); 75 | return Scaffold( 76 | appBar: AppBar( 77 | title: Text('Crop Demo'), 78 | centerTitle: true, 79 | actions: [ 80 | IconButton( 81 | onPressed: _cropImage, 82 | tooltip: 'Crop', 83 | icon: Icon(Icons.crop), 84 | ) 85 | ], 86 | ), 87 | body: Column( 88 | children: [ 89 | Expanded( 90 | child: Crop( 91 | key: _cropKey, 92 | child: Image.asset('images/sample.jpg'), 93 | aspectRatio: 1000 / 667.0, 94 | ), 95 | ), 96 | Row( 97 | children: [ 98 | IconButton( 99 | icon: Icon(Icons.undo), 100 | tooltip: 'Undo', 101 | onPressed: () { 102 | _cropKey.currentState.rotation = 0; 103 | _cropKey.currentState.scale = 1; 104 | _cropKey.currentState.offset = Offset.zero; 105 | setState(() { 106 | _rotation = 0; 107 | }); 108 | }, 109 | ), 110 | Expanded( 111 | child: SliderTheme( 112 | data: theme.sliderTheme.copyWith( 113 | trackShape: CenteredRectangularSliderTrackShape(), 114 | ), 115 | child: Slider( 116 | divisions: 91, 117 | value: _rotation, 118 | min: -45, 119 | max: 45, 120 | label: '$_rotation°', 121 | onChanged: (n) { 122 | setState(() { 123 | _rotation = n.roundToDouble(); 124 | _cropKey.currentState.rotation = _rotation; 125 | }); 126 | }, 127 | ), 128 | ), 129 | ), 130 | IconButton( 131 | icon: Icon(Icons.aspect_ratio), 132 | tooltip: 'Aspect Ratio', 133 | onPressed: () {}, 134 | ), 135 | ], 136 | ), 137 | ], 138 | ), 139 | ); 140 | } 141 | } 142 | ``` 143 | 144 | Please don't forget to check ```/example``` folder, there is much more. 145 | -------------------------------------------------------------------------------- /example/lib/centered_slider_track_shape.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | class CenteredRectangularSliderTrackShape extends RectangularSliderTrackShape { 6 | @override 7 | void paint( 8 | PaintingContext context, 9 | Offset offset, { 10 | required RenderBox parentBox, 11 | required SliderThemeData sliderTheme, 12 | required Animation enableAnimation, 13 | required TextDirection textDirection, 14 | required Offset thumbCenter, 15 | Offset? secondaryOffset, 16 | bool isDiscrete = false, 17 | bool isEnabled = false, 18 | }) { 19 | // If the slider track height is less than or equal to 0, then it makes no 20 | // difference whether the track is painted or not, therefore the painting 21 | // can be a no-op. 22 | if (sliderTheme.trackHeight! <= 0) { 23 | return; 24 | } 25 | 26 | // Assign the track segment paints, which are left: active, right: inactive, 27 | // but reversed for right to left text. 28 | final ColorTween activeTrackColorTween = ColorTween( 29 | begin: sliderTheme.disabledActiveTrackColor, 30 | end: sliderTheme.activeTrackColor); 31 | final ColorTween inactiveTrackColorTween = ColorTween( 32 | begin: sliderTheme.disabledInactiveTrackColor, 33 | end: sliderTheme.inactiveTrackColor); 34 | final Paint activePaint = Paint() 35 | ..color = activeTrackColorTween.evaluate(enableAnimation)!; 36 | final Paint inactivePaint = Paint() 37 | ..color = inactiveTrackColorTween.evaluate(enableAnimation)!; 38 | 39 | final Rect trackRect = getPreferredRect( 40 | parentBox: parentBox, 41 | offset: offset, 42 | sliderTheme: sliderTheme, 43 | isEnabled: isEnabled, 44 | isDiscrete: isDiscrete, 45 | ); 46 | final trackCenter = trackRect.center; 47 | final Size thumbSize = 48 | sliderTheme.thumbShape!.getPreferredSize(isEnabled, isDiscrete); 49 | // final Rect leftTrackSegment = Rect.fromLTRB( 50 | // trackRect.left + trackRect.height / 2, 51 | // trackRect.top, 52 | // thumbCenter.dx - thumbSize.width / 2, 53 | // trackRect.bottom); 54 | // if (!leftTrackSegment.isEmpty) 55 | // context.canvas.drawRect(leftTrackSegment, leftTrackPaint); 56 | // final Rect rightTrackSegment = Rect.fromLTRB( 57 | // thumbCenter.dx + thumbSize.width / 2, 58 | // trackRect.top, 59 | // trackRect.right, 60 | // trackRect.bottom); 61 | // if (!rightTrackSegment.isEmpty) 62 | // context.canvas.drawRect(rightTrackSegment, rightTrackPaint); 63 | 64 | if (trackCenter.dx < thumbCenter.dx) { 65 | final Rect leftTrackSegment = Rect.fromLTRB( 66 | trackRect.left, 67 | trackRect.top, 68 | min(trackCenter.dx, thumbCenter.dx - thumbSize.width / 2), 69 | trackRect.bottom); 70 | if (!leftTrackSegment.isEmpty) { 71 | context.canvas.drawRect(leftTrackSegment, inactivePaint); 72 | } 73 | 74 | final activeRect = Rect.fromLTRB( 75 | trackCenter.dx, trackRect.top, thumbCenter.dx, trackRect.bottom); 76 | if (!activeRect.isEmpty) { 77 | context.canvas.drawRect(activeRect, activePaint); 78 | } 79 | 80 | final Rect rightTrackSegment = Rect.fromLTRB( 81 | thumbCenter.dx + thumbSize.width / 2, 82 | trackRect.top, 83 | trackRect.right, 84 | trackRect.bottom); 85 | if (!rightTrackSegment.isEmpty) { 86 | context.canvas.drawRect(rightTrackSegment, inactivePaint); 87 | } 88 | } else if (trackCenter.dx > thumbCenter.dx) { 89 | final Rect leftTrackSegment = Rect.fromLTRB(trackRect.left, trackRect.top, 90 | thumbCenter.dx + thumbSize.width / 2, trackRect.bottom); 91 | if (!leftTrackSegment.isEmpty) { 92 | context.canvas.drawRect(leftTrackSegment, inactivePaint); 93 | } 94 | 95 | final activeRect = Rect.fromLTRB( 96 | thumbCenter.dx + thumbSize.width / 2, 97 | trackRect.top, 98 | trackRect.center.dx, 99 | trackRect.bottom, 100 | ); 101 | if (!activeRect.isEmpty) { 102 | context.canvas.drawRect(activeRect, activePaint); 103 | } 104 | 105 | final Rect rightTrackSegment = Rect.fromLTRB( 106 | max(trackCenter.dx, thumbCenter.dx - thumbSize.width / 2), 107 | trackRect.top, 108 | trackRect.right, 109 | trackRect.bottom, 110 | ); 111 | 112 | if (!rightTrackSegment.isEmpty) { 113 | context.canvas.drawRect(rightTrackSegment, inactivePaint); 114 | } 115 | } else { 116 | final Rect leftTrackSegment = Rect.fromLTRB( 117 | trackRect.left, 118 | trackRect.top, 119 | min(trackCenter.dx, thumbCenter.dx - thumbSize.width / 2), 120 | trackRect.bottom); 121 | if (!leftTrackSegment.isEmpty) { 122 | context.canvas.drawRect(leftTrackSegment, inactivePaint); 123 | } 124 | 125 | final Rect rightTrackSegment = Rect.fromLTRB( 126 | min(trackCenter.dx, thumbCenter.dx - thumbSize.width / 2), 127 | trackRect.top, 128 | trackRect.right, 129 | trackRect.bottom); 130 | if (!rightTrackSegment.isEmpty) { 131 | context.canvas.drawRect(rightTrackSegment, inactivePaint); 132 | } 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui' as ui; 2 | import 'package:app/centered_slider_track_shape.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:crop/crop.dart'; 5 | import 'package:url_launcher/url_launcher.dart'; 6 | import 'package:image_gallery_saver/image_gallery_saver.dart'; 7 | import 'package:permission_handler/permission_handler.dart'; 8 | 9 | void main() => runApp(const MyApp()); 10 | 11 | class MyApp extends StatelessWidget { 12 | const MyApp({Key? key}) : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return MaterialApp( 17 | title: 'Crop Demo', 18 | theme: ThemeData( 19 | primarySwatch: Colors.blue, 20 | useMaterial3: true, 21 | brightness: Brightness.dark, 22 | ), 23 | home: const HomePage(), 24 | ); 25 | } 26 | } 27 | 28 | class HomePage extends StatefulWidget { 29 | const HomePage({Key? key}) : super(key: key); 30 | @override 31 | State createState() => _HomePageState(); 32 | } 33 | 34 | class _HomePageState extends State { 35 | final controller = CropController(aspectRatio: 1000 / 667.0); 36 | double _rotation = 0; 37 | BoxShape shape = BoxShape.rectangle; 38 | 39 | void _cropImage() async { 40 | final pixelRatio = MediaQuery.of(context).devicePixelRatio; 41 | final cropped = await controller.crop(pixelRatio: pixelRatio); 42 | 43 | if (cropped == null) { 44 | return; 45 | } 46 | 47 | if (!mounted) { 48 | return; 49 | } 50 | 51 | Navigator.of(context).push( 52 | MaterialPageRoute( 53 | builder: (context) => Scaffold( 54 | appBar: AppBar( 55 | title: const Text('Crop Result'), 56 | centerTitle: true, 57 | actions: [ 58 | Builder( 59 | builder: (context) => IconButton( 60 | icon: const Icon(Icons.save), 61 | onPressed: () async { 62 | final status = await Permission.storage.request(); 63 | if (status == PermissionStatus.granted) { 64 | await _saveScreenShot(cropped); 65 | if (!mounted) { 66 | return; 67 | } 68 | ScaffoldMessenger.of(context).showSnackBar( 69 | const SnackBar( 70 | content: Text('Saved to gallery.'), 71 | ), 72 | ); 73 | } 74 | }, 75 | ), 76 | ), 77 | ], 78 | ), 79 | body: Center( 80 | child: RawImage( 81 | image: cropped, 82 | ), 83 | ), 84 | ), 85 | fullscreenDialog: true, 86 | ), 87 | ); 88 | } 89 | 90 | @override 91 | Widget build(BuildContext context) { 92 | final theme = Theme.of(context); 93 | return Scaffold( 94 | appBar: AppBar( 95 | title: const Text('Crop Demo'), 96 | centerTitle: true, 97 | leading: IconButton( 98 | icon: const Icon(Icons.link), 99 | onPressed: () { 100 | launchUrl(Uri.parse('https://github.com/xclud/flutter_crop'), 101 | mode: LaunchMode.externalApplication); 102 | }, 103 | ), 104 | actions: [ 105 | IconButton( 106 | onPressed: _cropImage, 107 | tooltip: 'Crop', 108 | icon: const Icon(Icons.crop), 109 | ) 110 | ], 111 | ), 112 | body: Column( 113 | children: [ 114 | Expanded( 115 | child: Container( 116 | color: Colors.black, 117 | padding: const EdgeInsets.all(8), 118 | child: Crop( 119 | onChanged: (decomposition) { 120 | if (_rotation != decomposition.rotation) { 121 | setState(() { 122 | _rotation = ((decomposition.rotation + 180) % 360) - 180; 123 | }); 124 | } 125 | 126 | // print( 127 | // "Scale : ${decomposition.scale}, Rotation: ${decomposition.rotation}, translation: ${decomposition.translation}"); 128 | }, 129 | controller: controller, 130 | shape: shape, 131 | /* It's very important to set `fit: BoxFit.cover`. 132 | Do NOT remove this line. 133 | There are a lot of issues on github repo by people who remove this line and their image is not shown correctly. 134 | */ 135 | foreground: IgnorePointer( 136 | child: Container( 137 | alignment: Alignment.bottomRight, 138 | child: const Text( 139 | 'Foreground Object', 140 | style: TextStyle(color: Colors.red), 141 | ), 142 | ), 143 | ), 144 | helper: shape == BoxShape.rectangle 145 | ? Container( 146 | decoration: BoxDecoration( 147 | border: Border.all(color: Colors.white, width: 2), 148 | ), 149 | ) 150 | : null, 151 | child: Image.asset( 152 | 'images/sample.jpg', 153 | fit: BoxFit.cover, 154 | ), 155 | ), 156 | ), 157 | ), 158 | Row( 159 | children: [ 160 | IconButton( 161 | icon: const Icon(Icons.undo), 162 | tooltip: 'Undo', 163 | onPressed: () { 164 | controller.rotation = 0; 165 | controller.scale = 1; 166 | controller.offset = Offset.zero; 167 | setState(() { 168 | _rotation = 0; 169 | }); 170 | }, 171 | ), 172 | Expanded( 173 | child: SliderTheme( 174 | data: theme.sliderTheme.copyWith( 175 | trackShape: CenteredRectangularSliderTrackShape(), 176 | ), 177 | child: Slider( 178 | divisions: 360, 179 | value: _rotation, 180 | min: -180, 181 | max: 180, 182 | label: '$_rotation°', 183 | onChanged: (n) { 184 | setState(() { 185 | _rotation = n.roundToDouble(); 186 | controller.rotation = _rotation; 187 | }); 188 | }, 189 | ), 190 | ), 191 | ), 192 | PopupMenuButton( 193 | icon: const Icon(Icons.crop_free), 194 | itemBuilder: (context) => [ 195 | const PopupMenuItem( 196 | value: BoxShape.rectangle, 197 | child: Text("Box"), 198 | ), 199 | const PopupMenuItem( 200 | value: BoxShape.circle, 201 | child: Text("Oval"), 202 | ), 203 | ], 204 | tooltip: 'Crop Shape', 205 | onSelected: (x) { 206 | setState(() { 207 | shape = x; 208 | }); 209 | }, 210 | ), 211 | PopupMenuButton( 212 | icon: const Icon(Icons.aspect_ratio), 213 | itemBuilder: (context) => [ 214 | const PopupMenuItem( 215 | value: 1000 / 667.0, 216 | child: Text("Original"), 217 | ), 218 | const PopupMenuDivider(), 219 | const PopupMenuItem( 220 | value: 16.0 / 9.0, 221 | child: Text("16:9"), 222 | ), 223 | const PopupMenuItem( 224 | value: 4.0 / 3.0, 225 | child: Text("4:3"), 226 | ), 227 | const PopupMenuItem( 228 | value: 1, 229 | child: Text("1:1"), 230 | ), 231 | const PopupMenuItem( 232 | value: 3.0 / 4.0, 233 | child: Text("3:4"), 234 | ), 235 | const PopupMenuItem( 236 | value: 9.0 / 16.0, 237 | child: Text("9:16"), 238 | ), 239 | ], 240 | tooltip: 'Aspect Ratio', 241 | onSelected: (x) { 242 | controller.aspectRatio = x; 243 | setState(() {}); 244 | }, 245 | ), 246 | ], 247 | ), 248 | ], 249 | ), 250 | ); 251 | } 252 | } 253 | 254 | Future _saveScreenShot(ui.Image img) async { 255 | var byteData = await img.toByteData(format: ui.ImageByteFormat.png); 256 | var buffer = byteData!.buffer.asUint8List(); 257 | final result = await ImageGallerySaver.saveImage(buffer); 258 | 259 | return result; 260 | } 261 | -------------------------------------------------------------------------------- /lib/src/crop.dart: -------------------------------------------------------------------------------- 1 | part of crop; 2 | 3 | /// Used for cropping the [child] widget. 4 | class Crop extends StatefulWidget { 5 | /// The constructor. 6 | const Crop({ 7 | Key? key, 8 | required this.child, 9 | required this.controller, 10 | this.padding = const EdgeInsets.all(8), 11 | this.dimColor = const Color.fromRGBO(0, 0, 0, 0.8), 12 | this.backgroundColor = Colors.black, 13 | this.background, 14 | this.foreground, 15 | this.helper, 16 | this.overlay, 17 | this.interactive = true, 18 | this.shape = BoxShape.rectangle, 19 | this.onChanged, 20 | this.animationDuration = const Duration(milliseconds: 200), 21 | this.radius, 22 | }) : super(key: key); 23 | 24 | /// The widget below this widget in the tree. 25 | final Widget child; 26 | 27 | /// Controls the crop area. 28 | final CropController controller; 29 | 30 | /// Background color of the crop area. 31 | final Color backgroundColor; 32 | 33 | /// Dim color of the crop area. 34 | final Color dimColor; 35 | 36 | /// Padding of the crop area. 37 | final EdgeInsets padding; 38 | 39 | /// Background widget displayed on under on the resulting image. 40 | final Widget? background; 41 | 42 | /// Forground widget is displayed on top on the resulting image. 43 | final Widget? foreground; 44 | 45 | /// Helper widget is displayed on top on the crop 46 | /// area, but not included in the resulting image. 47 | /// 48 | /// Useful to display helper lines e.g. golden ratio. 49 | final Widget? helper; 50 | 51 | /// Similar to [helper] but is not transformed. 52 | final Widget? overlay; 53 | 54 | /// If set to false, the widget will not listen for gestures. 55 | final bool interactive; 56 | 57 | /// Shape of the crop area. 58 | final BoxShape shape; 59 | 60 | /// Triggered when a gesture is detected. 61 | final ValueChanged? onChanged; 62 | 63 | /// When dragged out of crop area boundries, it will 64 | /// re-center. This sets the re-center duration. 65 | final Duration animationDuration; 66 | 67 | /// Radius of the crop area. 68 | final Radius? radius; 69 | 70 | @override 71 | State createState() { 72 | return _CropState(); 73 | } 74 | 75 | @override 76 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 77 | super.debugFillProperties(properties); 78 | properties.add(DiagnosticsProperty('padding', padding)); 79 | properties.add(ColorProperty('dimColor', dimColor)); 80 | properties.add(DiagnosticsProperty('child', child)); 81 | properties.add(DiagnosticsProperty('controller', controller)); 82 | properties.add(DiagnosticsProperty('background', background)); 83 | properties.add(DiagnosticsProperty('foreground', foreground)); 84 | properties.add(DiagnosticsProperty('helper', helper)); 85 | properties.add(DiagnosticsProperty('overlay', overlay)); 86 | properties.add(FlagProperty( 87 | 'interactive', 88 | value: interactive, 89 | ifTrue: 'enabled', 90 | ifFalse: 'disabled', 91 | showName: true, 92 | )); 93 | } 94 | } 95 | 96 | class _CropState extends State with TickerProviderStateMixin { 97 | final _key = GlobalKey(); 98 | final _parent = GlobalKey(); 99 | final _repaintBoundaryKey = GlobalKey(); 100 | 101 | double _previousScale = 1; 102 | Offset _previousOffset = Offset.zero; 103 | Offset _startOffset = Offset.zero; 104 | Offset _endOffset = Offset.zero; 105 | double _previousGestureRotation = 0.0; 106 | 107 | /// Store the pointer count (finger involved to perform scaling). 108 | /// 109 | /// This is used to compare with the value in 110 | /// [ScaleUpdateDetails.pointerCount]. Check [_onScaleUpdate] for detail. 111 | int _previousPointerCount = 0; 112 | 113 | late AnimationController _controller; 114 | late CurvedAnimation _animation; 115 | 116 | Future _crop(double pixelRatio) { 117 | final rrb = _repaintBoundaryKey.currentContext?.findRenderObject() 118 | as RenderRepaintBoundary; 119 | 120 | return rrb.toImage(pixelRatio: pixelRatio); 121 | } 122 | 123 | @override 124 | void initState() { 125 | widget.controller._cropCallback = _crop; 126 | widget.controller.addListener(_reCenterImage); 127 | 128 | //Setup animation. 129 | _controller = AnimationController( 130 | vsync: this, 131 | duration: widget.animationDuration, 132 | ); 133 | 134 | _animation = CurvedAnimation(curve: Curves.easeInOut, parent: _controller); 135 | _animation.addListener(() { 136 | if (_animation.isCompleted) { 137 | _reCenterImage(false); 138 | } 139 | setState(() {}); 140 | }); 141 | super.initState(); 142 | } 143 | 144 | void _reCenterImage([bool animate = true]) { 145 | //final totalSize = _parent.currentContext.size; 146 | 147 | final sz = _key.currentContext!.size!; 148 | final s = widget.controller._scale * widget.controller._getMinScale(); 149 | final w = sz.width; 150 | final h = sz.height; 151 | final offset = _toVector2(widget.controller._offset); 152 | final canvas = Rectangle.fromLTWH(0, 0, w, h); 153 | final obb = Obb2( 154 | center: offset + canvas.center, 155 | width: w * s, 156 | height: h * s, 157 | rotation: widget.controller._rotation, 158 | ); 159 | 160 | final bakedObb = obb.bake(); 161 | 162 | _startOffset = widget.controller._offset; 163 | _endOffset = widget.controller._offset; 164 | 165 | final ctl = canvas.topLeft; 166 | final ctr = canvas.topRight; 167 | final cbr = canvas.bottomRight; 168 | final cbl = canvas.bottomLeft; 169 | 170 | final ll = Line(bakedObb.topLeft, bakedObb.bottomLeft); 171 | final tt = Line(bakedObb.topRight, bakedObb.topLeft); 172 | final rr = Line(bakedObb.bottomRight, bakedObb.topRight); 173 | final bb = Line(bakedObb.bottomLeft, bakedObb.bottomRight); 174 | 175 | final tl = ll.project(ctl); 176 | final tr = tt.project(ctr); 177 | final br = rr.project(cbr); 178 | final bl = bb.project(cbl); 179 | 180 | final dtl = ll.distanceToPoint(ctl); 181 | final dtr = tt.distanceToPoint(ctr); 182 | final dbr = rr.distanceToPoint(cbr); 183 | final dbl = bb.distanceToPoint(cbl); 184 | 185 | if (dtl > 0) { 186 | final d = _toOffset(ctl - tl); 187 | _endOffset += d; 188 | } 189 | 190 | if (dtr > 0) { 191 | final d = _toOffset(ctr - tr); 192 | _endOffset += d; 193 | } 194 | 195 | if (dbr > 0) { 196 | final d = _toOffset(cbr - br); 197 | _endOffset += d; 198 | } 199 | if (dbl > 0) { 200 | final d = _toOffset(cbl - bl); 201 | _endOffset += d; 202 | } 203 | 204 | widget.controller._offset = _endOffset; 205 | 206 | if (animate) { 207 | if (_controller.isCompleted || _controller.isAnimating) { 208 | _controller.reset(); 209 | } 210 | _controller.forward(); 211 | } else { 212 | _startOffset = _endOffset; 213 | } 214 | 215 | setState(() {}); 216 | _handleOnChanged(); 217 | } 218 | 219 | void _onScaleUpdate(ScaleUpdateDetails details) { 220 | widget.controller._offset += details.focalPoint - _previousOffset; 221 | _previousOffset = details.focalPoint; 222 | widget.controller._scale = _previousScale * details.scale; 223 | _startOffset = widget.controller._offset; 224 | _endOffset = widget.controller._offset; 225 | 226 | // In the case where lesser than 2 fingers involved in scaling, we ignore 227 | // the rotation handling. 228 | if (details.pointerCount > 1) { 229 | // In the first touch, we reset all the values. 230 | if (_previousPointerCount != details.pointerCount) { 231 | _previousPointerCount = details.pointerCount; 232 | _previousGestureRotation = 0.0; 233 | } 234 | 235 | // Instead of directly embracing the details.rotation, we need to 236 | // perform calculation to ensure that each round of rotation is smooth. 237 | // A user rotate the image using finger and release is considered as a 238 | // round. Without this calculation, the rotation degree of the image will 239 | // be reset. 240 | final gestureRotation = vm.degrees(details.rotation); 241 | 242 | // Within a round of rotation, the details.rotation is provided with 243 | // incremented value when user rotates. We don't need this, all we 244 | // want is the offset. 245 | final gestureRotationOffset = _previousGestureRotation - gestureRotation; 246 | 247 | // Remove the offset and constraint the degree scope to 0° <= degree <= 248 | // 360°. Constraint the scope is unnecessary, however, by doing this, 249 | // it would make our life easier when debugging. 250 | final rotationAfterCalculation = 251 | (widget.controller.rotation - gestureRotationOffset) % 360; 252 | 253 | /* details.rotation is in radians, convert this to degrees and set 254 | our rotation */ 255 | widget.controller._rotation = rotationAfterCalculation; 256 | _previousGestureRotation = gestureRotation; 257 | } 258 | 259 | setState(() {}); 260 | _handleOnChanged(); 261 | } 262 | 263 | void _handleOnChanged() { 264 | widget.onChanged?.call(MatrixDecomposition( 265 | scale: widget.controller.scale, 266 | rotation: widget.controller.rotation, 267 | translation: widget.controller._offset)); 268 | } 269 | 270 | @override 271 | Widget build(BuildContext context) { 272 | final r = vm.radians(widget.controller._rotation); 273 | final s = widget.controller._scale * widget.controller._getMinScale(); 274 | final o = Offset.lerp(_startOffset, _endOffset, _animation.value)!; 275 | 276 | Widget buildInnerCanvas() { 277 | final ip = IgnorePointer( 278 | key: _key, 279 | child: Transform( 280 | alignment: Alignment.center, 281 | transform: Matrix4.identity() 282 | ..translate(o.dx, o.dy, 0) 283 | ..rotateZ(r) 284 | ..scale(s, s, 1), 285 | child: FittedBox( 286 | fit: BoxFit.cover, 287 | child: widget.child, 288 | ), 289 | ), 290 | ); 291 | 292 | List widgets = []; 293 | 294 | if (widget.background != null) { 295 | widgets.add(widget.background!); 296 | } 297 | 298 | widgets.add(ip); 299 | 300 | if (widget.foreground != null) { 301 | widgets.add(widget.foreground!); 302 | } 303 | 304 | if (widgets.length == 1) { 305 | return ip; 306 | } else { 307 | return Stack( 308 | fit: StackFit.expand, 309 | children: widgets, 310 | ); 311 | } 312 | } 313 | 314 | Widget buildRepaintBoundary() { 315 | final repaint = RepaintBoundary( 316 | key: _repaintBoundaryKey, 317 | child: buildInnerCanvas(), 318 | ); 319 | 320 | final helper = widget.helper; 321 | 322 | if (helper == null) { 323 | return repaint; 324 | } 325 | 326 | return Stack( 327 | fit: StackFit.expand, 328 | children: [repaint, helper], 329 | ); 330 | } 331 | 332 | final gd = GestureDetector( 333 | onScaleStart: (details) { 334 | _previousOffset = details.focalPoint; 335 | _previousScale = max(widget.controller._scale, 1); 336 | }, 337 | onScaleUpdate: _onScaleUpdate, 338 | onScaleEnd: (details) { 339 | widget.controller._scale = max(widget.controller._scale, 1); 340 | _previousPointerCount = 0; 341 | _reCenterImage(); 342 | }, 343 | ); 344 | 345 | List over = [ 346 | CropRenderObjectWidget( 347 | aspectRatio: widget.controller._aspectRatio, 348 | backgroundColor: widget.backgroundColor, 349 | shape: widget.shape, 350 | dimColor: widget.dimColor, 351 | padding: widget.padding, 352 | radius: widget.radius, 353 | child: buildRepaintBoundary(), 354 | ), 355 | ]; 356 | 357 | if (widget.overlay != null) { 358 | over.add(widget.overlay!); 359 | } 360 | 361 | if (widget.interactive) { 362 | over.add(gd); 363 | } 364 | 365 | return ClipRect( 366 | key: _parent, 367 | child: Stack( 368 | fit: StackFit.expand, 369 | children: over, 370 | ), 371 | ); 372 | } 373 | 374 | @override 375 | void dispose() { 376 | _controller.dispose(); 377 | widget.controller.removeListener(_reCenterImage); 378 | super.dispose(); 379 | } 380 | } 381 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 54; 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 = 1300; 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 | alwaysOutOfDate = 1; 175 | buildActionMask = 2147483647; 176 | files = ( 177 | ); 178 | inputPaths = ( 179 | ); 180 | name = "Thin Binary"; 181 | outputPaths = ( 182 | ); 183 | runOnlyForDeploymentPostprocessing = 0; 184 | shellPath = /bin/sh; 185 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 186 | }; 187 | 9740EEB61CF901F6004384FC /* Run Script */ = { 188 | isa = PBXShellScriptBuildPhase; 189 | alwaysOutOfDate = 1; 190 | buildActionMask = 2147483647; 191 | files = ( 192 | ); 193 | inputPaths = ( 194 | ); 195 | name = "Run Script"; 196 | outputPaths = ( 197 | ); 198 | runOnlyForDeploymentPostprocessing = 0; 199 | shellPath = /bin/sh; 200 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 201 | }; 202 | /* End PBXShellScriptBuildPhase section */ 203 | 204 | /* Begin PBXSourcesBuildPhase section */ 205 | 97C146EA1CF9000F007C117D /* Sources */ = { 206 | isa = PBXSourcesBuildPhase; 207 | buildActionMask = 2147483647; 208 | files = ( 209 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 210 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 211 | ); 212 | runOnlyForDeploymentPostprocessing = 0; 213 | }; 214 | /* End PBXSourcesBuildPhase section */ 215 | 216 | /* Begin PBXVariantGroup section */ 217 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 218 | isa = PBXVariantGroup; 219 | children = ( 220 | 97C146FB1CF9000F007C117D /* Base */, 221 | ); 222 | name = Main.storyboard; 223 | sourceTree = ""; 224 | }; 225 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 226 | isa = PBXVariantGroup; 227 | children = ( 228 | 97C147001CF9000F007C117D /* Base */, 229 | ); 230 | name = LaunchScreen.storyboard; 231 | sourceTree = ""; 232 | }; 233 | /* End PBXVariantGroup section */ 234 | 235 | /* Begin XCBuildConfiguration section */ 236 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 237 | isa = XCBuildConfiguration; 238 | buildSettings = { 239 | ALWAYS_SEARCH_USER_PATHS = NO; 240 | CLANG_ANALYZER_NONNULL = YES; 241 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 242 | CLANG_CXX_LIBRARY = "libc++"; 243 | CLANG_ENABLE_MODULES = YES; 244 | CLANG_ENABLE_OBJC_ARC = YES; 245 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 246 | CLANG_WARN_BOOL_CONVERSION = YES; 247 | CLANG_WARN_COMMA = YES; 248 | CLANG_WARN_CONSTANT_CONVERSION = YES; 249 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 250 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 251 | CLANG_WARN_EMPTY_BODY = YES; 252 | CLANG_WARN_ENUM_CONVERSION = YES; 253 | CLANG_WARN_INFINITE_RECURSION = YES; 254 | CLANG_WARN_INT_CONVERSION = YES; 255 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 256 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 257 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 258 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 259 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 260 | CLANG_WARN_STRICT_PROTOTYPES = YES; 261 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 262 | CLANG_WARN_UNREACHABLE_CODE = YES; 263 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 264 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 265 | COPY_PHASE_STRIP = NO; 266 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 267 | ENABLE_NS_ASSERTIONS = NO; 268 | ENABLE_STRICT_OBJC_MSGSEND = YES; 269 | GCC_C_LANGUAGE_STANDARD = gnu99; 270 | GCC_NO_COMMON_BLOCKS = YES; 271 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 272 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 273 | GCC_WARN_UNDECLARED_SELECTOR = YES; 274 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 275 | GCC_WARN_UNUSED_FUNCTION = YES; 276 | GCC_WARN_UNUSED_VARIABLE = YES; 277 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 278 | MTL_ENABLE_DEBUG_INFO = NO; 279 | SDKROOT = iphoneos; 280 | SUPPORTED_PLATFORMS = iphoneos; 281 | TARGETED_DEVICE_FAMILY = "1,2"; 282 | VALIDATE_PRODUCT = YES; 283 | }; 284 | name = Profile; 285 | }; 286 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 287 | isa = XCBuildConfiguration; 288 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 289 | buildSettings = { 290 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 291 | CLANG_ENABLE_MODULES = YES; 292 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 293 | ENABLE_BITCODE = NO; 294 | INFOPLIST_FILE = Runner/Info.plist; 295 | LD_RUNPATH_SEARCH_PATHS = ( 296 | "$(inherited)", 297 | "@executable_path/Frameworks", 298 | ); 299 | PRODUCT_BUNDLE_IDENTIFIER = dev.pub.crop.app; 300 | PRODUCT_NAME = "$(TARGET_NAME)"; 301 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 302 | SWIFT_VERSION = 5.0; 303 | VERSIONING_SYSTEM = "apple-generic"; 304 | }; 305 | name = Profile; 306 | }; 307 | 97C147031CF9000F007C117D /* Debug */ = { 308 | isa = XCBuildConfiguration; 309 | buildSettings = { 310 | ALWAYS_SEARCH_USER_PATHS = NO; 311 | CLANG_ANALYZER_NONNULL = YES; 312 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 313 | CLANG_CXX_LIBRARY = "libc++"; 314 | CLANG_ENABLE_MODULES = YES; 315 | CLANG_ENABLE_OBJC_ARC = YES; 316 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 317 | CLANG_WARN_BOOL_CONVERSION = YES; 318 | CLANG_WARN_COMMA = YES; 319 | CLANG_WARN_CONSTANT_CONVERSION = YES; 320 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 321 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 322 | CLANG_WARN_EMPTY_BODY = YES; 323 | CLANG_WARN_ENUM_CONVERSION = YES; 324 | CLANG_WARN_INFINITE_RECURSION = YES; 325 | CLANG_WARN_INT_CONVERSION = YES; 326 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 327 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 328 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 329 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 330 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 331 | CLANG_WARN_STRICT_PROTOTYPES = YES; 332 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 333 | CLANG_WARN_UNREACHABLE_CODE = YES; 334 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 335 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 336 | COPY_PHASE_STRIP = NO; 337 | DEBUG_INFORMATION_FORMAT = dwarf; 338 | ENABLE_STRICT_OBJC_MSGSEND = YES; 339 | ENABLE_TESTABILITY = YES; 340 | GCC_C_LANGUAGE_STANDARD = gnu99; 341 | GCC_DYNAMIC_NO_PIC = NO; 342 | GCC_NO_COMMON_BLOCKS = YES; 343 | GCC_OPTIMIZATION_LEVEL = 0; 344 | GCC_PREPROCESSOR_DEFINITIONS = ( 345 | "DEBUG=1", 346 | "$(inherited)", 347 | ); 348 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 349 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 350 | GCC_WARN_UNDECLARED_SELECTOR = YES; 351 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 352 | GCC_WARN_UNUSED_FUNCTION = YES; 353 | GCC_WARN_UNUSED_VARIABLE = YES; 354 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 355 | MTL_ENABLE_DEBUG_INFO = YES; 356 | ONLY_ACTIVE_ARCH = YES; 357 | SDKROOT = iphoneos; 358 | TARGETED_DEVICE_FAMILY = "1,2"; 359 | }; 360 | name = Debug; 361 | }; 362 | 97C147041CF9000F007C117D /* Release */ = { 363 | isa = XCBuildConfiguration; 364 | buildSettings = { 365 | ALWAYS_SEARCH_USER_PATHS = NO; 366 | CLANG_ANALYZER_NONNULL = YES; 367 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 368 | CLANG_CXX_LIBRARY = "libc++"; 369 | CLANG_ENABLE_MODULES = YES; 370 | CLANG_ENABLE_OBJC_ARC = YES; 371 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 372 | CLANG_WARN_BOOL_CONVERSION = YES; 373 | CLANG_WARN_COMMA = YES; 374 | CLANG_WARN_CONSTANT_CONVERSION = YES; 375 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 376 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 377 | CLANG_WARN_EMPTY_BODY = YES; 378 | CLANG_WARN_ENUM_CONVERSION = YES; 379 | CLANG_WARN_INFINITE_RECURSION = YES; 380 | CLANG_WARN_INT_CONVERSION = YES; 381 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 382 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 383 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 384 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 385 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 386 | CLANG_WARN_STRICT_PROTOTYPES = YES; 387 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 388 | CLANG_WARN_UNREACHABLE_CODE = YES; 389 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 390 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 391 | COPY_PHASE_STRIP = NO; 392 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 393 | ENABLE_NS_ASSERTIONS = NO; 394 | ENABLE_STRICT_OBJC_MSGSEND = YES; 395 | GCC_C_LANGUAGE_STANDARD = gnu99; 396 | GCC_NO_COMMON_BLOCKS = YES; 397 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 398 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 399 | GCC_WARN_UNDECLARED_SELECTOR = YES; 400 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 401 | GCC_WARN_UNUSED_FUNCTION = YES; 402 | GCC_WARN_UNUSED_VARIABLE = YES; 403 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 404 | MTL_ENABLE_DEBUG_INFO = NO; 405 | SDKROOT = iphoneos; 406 | SUPPORTED_PLATFORMS = iphoneos; 407 | SWIFT_COMPILATION_MODE = wholemodule; 408 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 409 | TARGETED_DEVICE_FAMILY = "1,2"; 410 | VALIDATE_PRODUCT = YES; 411 | }; 412 | name = Release; 413 | }; 414 | 97C147061CF9000F007C117D /* Debug */ = { 415 | isa = XCBuildConfiguration; 416 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 417 | buildSettings = { 418 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 419 | CLANG_ENABLE_MODULES = YES; 420 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 421 | ENABLE_BITCODE = NO; 422 | INFOPLIST_FILE = Runner/Info.plist; 423 | LD_RUNPATH_SEARCH_PATHS = ( 424 | "$(inherited)", 425 | "@executable_path/Frameworks", 426 | ); 427 | PRODUCT_BUNDLE_IDENTIFIER = dev.pub.crop.app; 428 | PRODUCT_NAME = "$(TARGET_NAME)"; 429 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 430 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 431 | SWIFT_VERSION = 5.0; 432 | VERSIONING_SYSTEM = "apple-generic"; 433 | }; 434 | name = Debug; 435 | }; 436 | 97C147071CF9000F007C117D /* Release */ = { 437 | isa = XCBuildConfiguration; 438 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 439 | buildSettings = { 440 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 441 | CLANG_ENABLE_MODULES = YES; 442 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 443 | ENABLE_BITCODE = NO; 444 | INFOPLIST_FILE = Runner/Info.plist; 445 | LD_RUNPATH_SEARCH_PATHS = ( 446 | "$(inherited)", 447 | "@executable_path/Frameworks", 448 | ); 449 | PRODUCT_BUNDLE_IDENTIFIER = dev.pub.crop.app; 450 | PRODUCT_NAME = "$(TARGET_NAME)"; 451 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 452 | SWIFT_VERSION = 5.0; 453 | VERSIONING_SYSTEM = "apple-generic"; 454 | }; 455 | name = Release; 456 | }; 457 | /* End XCBuildConfiguration section */ 458 | 459 | /* Begin XCConfigurationList section */ 460 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 461 | isa = XCConfigurationList; 462 | buildConfigurations = ( 463 | 97C147031CF9000F007C117D /* Debug */, 464 | 97C147041CF9000F007C117D /* Release */, 465 | 249021D3217E4FDB00AE95B9 /* Profile */, 466 | ); 467 | defaultConfigurationIsVisible = 0; 468 | defaultConfigurationName = Release; 469 | }; 470 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 471 | isa = XCConfigurationList; 472 | buildConfigurations = ( 473 | 97C147061CF9000F007C117D /* Debug */, 474 | 97C147071CF9000F007C117D /* Release */, 475 | 249021D4217E4FDB00AE95B9 /* Profile */, 476 | ); 477 | defaultConfigurationIsVisible = 0; 478 | defaultConfigurationName = Release; 479 | }; 480 | /* End XCConfigurationList section */ 481 | }; 482 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 483 | } 484 | --------------------------------------------------------------------------------