feat: Add document scanner package

This commit is contained in:
Anton Stubenbord
2023-02-22 18:17:50 +01:00
parent a8a41b38a8
commit 9c5a45f329
105 changed files with 3998 additions and 0 deletions

View File

@@ -0,0 +1,40 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
.packages
build/
android/src/main/jniLibs
android/CMakeFiles
android/Makefile
android/.cxx
CMakeCache.txt
cmake_install.cmake
ios/opencv2.framework
include

View File

@@ -0,0 +1,33 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled.
version:
revision: 9944297138845a94256f1cf37beb88ff9a8e811a
channel: stable
project_type: plugin
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 9944297138845a94256f1cf37beb88ff9a8e811a
base_revision: 9944297138845a94256f1cf37beb88ff9a8e811a
- platform: android
create_revision: 9944297138845a94256f1cf37beb88ff9a8e811a
base_revision: 9944297138845a94256f1cf37beb88ff9a8e811a
- platform: ios
create_revision: 9944297138845a94256f1cf37beb88ff9a8e811a
base_revision: 9944297138845a94256f1cf37beb88ff9a8e811a
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

View File

@@ -0,0 +1,3 @@
## 0.0.1
* TODO: Describe initial release.

View File

@@ -0,0 +1 @@
TODO: Add your license here.

View File

@@ -0,0 +1,15 @@
# paperless_document_scanner
A new Flutter plugin project.
## Getting Started
This project is a starting point for a Flutter
[plug-in package](https://flutter.dev/developing-packages/),
a specialized package that includes platform-specific implementation code for
Android and/or iOS.
For help getting started with Flutter development, view the
[online documentation](https://flutter.dev/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

View File

@@ -0,0 +1,4 @@
include: package:flutter_lints/flutter.yaml
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View File

@@ -0,0 +1,9 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.cxx

View File

@@ -0,0 +1,13 @@
cmake_minimum_required(VERSION 3.6.0)
include_directories(../include)
add_library(lib_opencv SHARED IMPORTED)
set_target_properties(lib_opencv PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libopencv_java4.so)
set(EDGE_DETECTION_DIR "../ios/Classes")
set(SOURCES
${EDGE_DETECTION_DIR}/native_edge_detection.cpp
${EDGE_DETECTION_DIR}/edge_detector.cpp
${EDGE_DETECTION_DIR}/image_processor.cpp
${EDGE_DETECTION_DIR}/conversion_utils.cpp
)
add_library(native_edge_detection SHARED ${SOURCES})
target_link_libraries(native_edge_detection lib_opencv)

View File

@@ -0,0 +1,68 @@
group 'de.astubenbord.paperless_document_scanner'
version '1.0-SNAPSHOT'
buildscript {
ext.kotlin_version = '1.7.10'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.2.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
compileSdkVersion 31
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
main {
jniLibs.srcDirs = ['jniLibs']
}
}
defaultConfig {
minSdkVersion 21
}
lintOptions {
disable 'InvalidPackage'
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
defaultConfig {
externalNativeBuild {
cmake {
cppFlags '-frtti -fexceptions -std=c++11'
arguments "-DANDROID_STL=c++_shared"
}
}
}
}

View File

@@ -0,0 +1 @@
rootProject.name = 'paperless_document_scanner'

View File

@@ -0,0 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="de.astubenbord.paperless_document_scanner">
</manifest>

View File

@@ -0,0 +1,35 @@
package de.astubenbord.paperless_document_scanner
import androidx.annotation.NonNull
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
/** PaperlessDocumentScannerPlugin */
class PaperlessDocumentScannerPlugin: FlutterPlugin, MethodCallHandler {
/// The MethodChannel that will the communication between Flutter and native Android
///
/// This local reference serves to register the plugin with the Flutter Engine and unregister it
/// when the Flutter Engine is detached from the Activity
private lateinit var channel : MethodChannel
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "paperless_document_scanner")
channel.setMethodCallHandler(this)
}
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
if (call.method == "getPlatformVersion") {
result.success("Android ${android.os.Build.VERSION.RELEASE}")
} else {
result.notImplemented()
}
}
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}
}

View File

@@ -0,0 +1,44 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release

View File

@@ -0,0 +1,16 @@
# paperless_document_scanner_example
Demonstrates how to use the paperless_document_scanner plugin.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

View File

@@ -0,0 +1,29 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at
# https://dart-lang.github.io/linter/lints/index.html.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View File

@@ -0,0 +1,13 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
# Remember to never publicly share your keystore.
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
key.properties
**/*.keystore
**/*.jks

View File

@@ -0,0 +1,71 @@
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion flutter.compileSdkVersion
ndkVersion flutter.ndkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "de.astubenbord.paperless_document_scanner_example"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdkVersion 21
targetSdkVersion 31
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}
flutter {
source '../..'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}

View File

@@ -0,0 +1,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="de.astubenbord.paperless_document_scanner_example">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@@ -0,0 +1,34 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="de.astubenbord.paperless_document_scanner_example">
<application
android:label="paperless_document_scanner_example"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>

View File

@@ -0,0 +1,6 @@
package de.astubenbord.paperless_document_scanner_example
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -0,0 +1,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="de.astubenbord.paperless_document_scanner_example">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@@ -0,0 +1,31 @@
buildscript {
ext.kotlin_version = '1.7.10'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.2.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}

View File

@@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true

View File

@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip

View File

@@ -0,0 +1,11 @@
include ':app'
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
def properties = new Properties()
assert localPropertiesFile.exists()
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"

View File

@@ -0,0 +1,34 @@
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>11.0</string>
</dict>
</plist>

View File

@@ -0,0 +1 @@
#include "Generated.xcconfig"

View File

@@ -0,0 +1 @@
#include "Generated.xcconfig"

View File

@@ -0,0 +1,483 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1300;
ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = de.astubenbord.paperlessDocumentScannerExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = de.astubenbord.paperlessDocumentScannerExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = de.astubenbord.paperlessDocumentScannerExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1300"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@@ -0,0 +1,13 @@
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

View File

@@ -0,0 +1,122 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

View File

@@ -0,0 +1,5 @@
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
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.

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Paperless Document Scanner</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>paperless_document_scanner_example</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1 @@
#import "GeneratedPluginRegistrant.h"

View File

@@ -0,0 +1,19 @@
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
class CameraView extends StatelessWidget {
const CameraView({super.key, required this.controller});
final CameraController controller;
@override
Widget build(BuildContext context) {
if (!controller.value.isInitialized) {
return Container();
}
return Center(
child: CameraPreview(controller),
);
}
}

View File

@@ -0,0 +1,85 @@
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:paperless_document_scanner/types/edge_detection_result.dart';
import 'edge_detection_shape/edge_detection_shape.dart';
class ImagePreview extends StatefulWidget {
const ImagePreview({
super.key,
required this.imagePath,
required this.edgeDetectionResult,
});
final String imagePath;
final EdgeDetectionResult? edgeDetectionResult;
@override
State<ImagePreview> createState() => _ImagePreviewState();
}
class _ImagePreviewState extends State<ImagePreview> {
GlobalKey imageWidgetKey = GlobalKey();
@override
Widget build(BuildContext mainContext) {
return Center(
child: Stack(
fit: StackFit.expand,
children: <Widget>[
const Center(child: Text('Loading ...')),
Image.file(File(widget.imagePath),
fit: BoxFit.contain, key: imageWidgetKey),
FutureBuilder<ui.Image>(
future: loadUiImage(widget.imagePath),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(
child: Text("Loading..."),
);
} else {
return _getEdgePaint(snapshot.data!, context);
}
}),
],
),
);
}
Widget _getEdgePaint(
ui.Image image,
BuildContext context,
) {
if (widget.edgeDetectionResult == null) return Container();
final keyContext = imageWidgetKey.currentContext;
if (keyContext == null) {
return Container();
}
final box = keyContext.findRenderObject() as RenderBox;
return EdgeDetectionShape(
originalImageSize: Size(
image.width.toDouble(),
image.height.toDouble(),
),
renderedImageSize: Size(box.size.width, box.size.height),
edgeDetectionResult: widget.edgeDetectionResult!,
);
}
Future<ui.Image> loadUiImage(String imageAssetPath) async {
final Uint8List data = await File(imageAssetPath).readAsBytes();
final Completer<ui.Image> completer = Completer();
ui.decodeImageFromList(Uint8List.view(data.buffer), (ui.Image image) {
return completer.complete(image);
});
return completer.future;
}
}

View File

@@ -0,0 +1,89 @@
import 'package:flutter/material.dart';
class AnimatedTouchBubblePart extends StatefulWidget {
AnimatedTouchBubblePart({
required this.dragging,
required this.size,
});
final bool dragging;
final double size;
@override
_AnimatedTouchBubblePartState createState() =>
_AnimatedTouchBubblePartState();
}
class _AnimatedTouchBubblePartState extends State<AnimatedTouchBubblePart>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<Color?> _colorAnimation;
late Animation<double> _sizeAnimation;
@override
void didChangeDependencies() {
_controller = AnimationController(
duration: const Duration(milliseconds: 1000),
vsync: this,
);
_sizeAnimation = Tween<double>(begin: 0.5, end: 1.0).animate(_controller);
_colorAnimation = ColorTween(
begin: Theme.of(context).colorScheme.primary,
end: Theme.of(context).colorScheme.primary,
).animate(
CurvedAnimation(
parent: _controller,
curve: Interval(0.5, 1.0),
),
);
_controller.repeat();
super.didChangeDependencies();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
Center(
child: Container(
width: widget.dragging ? 0 : widget.size / 2,
height: widget.dragging ? 0 : widget.size / 2,
decoration: BoxDecoration(
color: Theme.of(context).accentColor.withOpacity(0.5),
borderRadius: widget.dragging
? BorderRadius.circular(widget.size)
: BorderRadius.circular(widget.size / 4)))),
AnimatedBuilder(
builder: (context, child) {
return Center(
child: Container(
width: widget.dragging
? 0
: widget.size * _sizeAnimation.value,
height: widget.dragging
? 0
: widget.size * _sizeAnimation.value,
decoration: BoxDecoration(
border: Border.all(
color: _colorAnimation.value ?? Colors.transparent,
width: widget.size / 20),
borderRadius: widget.dragging
? BorderRadius.zero
: BorderRadius.circular(
widget.size * _sizeAnimation.value / 2))));
},
animation: _controller,
)
],
);
}
}

View File

@@ -0,0 +1,213 @@
import 'dart:math';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:paperless_document_scanner/paperless_document_scanner.dart';
import 'package:paperless_document_scanner/types/edge_detection_result.dart';
import 'edge_painter.dart';
import 'magnifier.dart' as m;
import 'touch_bubble.dart';
class EdgeDetectionShape extends StatefulWidget {
const EdgeDetectionShape({
super.key,
required this.renderedImageSize,
required this.originalImageSize,
required this.edgeDetectionResult,
});
final Size renderedImageSize;
final Size originalImageSize;
final EdgeDetectionResult edgeDetectionResult;
@override
State<EdgeDetectionShape> createState() => _EdgeDetectionShapeState();
}
class _EdgeDetectionShapeState extends State<EdgeDetectionShape> {
late double edgeDraggerSize;
List<Offset> points = [];
late Offset _topLeft;
late Offset _topRight;
late Offset _bottomLeft;
late Offset _bottomRight;
late double renderedImageWidth;
late double renderedImageHeight;
late double top;
late double left;
Offset? currentDragPosition;
@override
void didChangeDependencies() {
double shortestSide = min(
MediaQuery.of(context).size.width, MediaQuery.of(context).size.height);
edgeDraggerSize = shortestSide / 12;
super.didChangeDependencies();
}
@override
void initState() {
top = 0.0;
left = 0.0;
_topLeft = widget.edgeDetectionResult.topLeft;
_topRight = widget.edgeDetectionResult.topRight;
_bottomLeft = widget.edgeDetectionResult.bottomLeft;
_bottomRight = widget.edgeDetectionResult.bottomRight;
double widthFactor =
widget.renderedImageSize.width / widget.originalImageSize.width;
double heightFactor =
widget.renderedImageSize.height / widget.originalImageSize.height;
double sizeFactor = min(widthFactor, heightFactor);
renderedImageHeight = widget.originalImageSize.height * sizeFactor;
top = ((widget.renderedImageSize.height - renderedImageHeight) / 2);
renderedImageWidth = widget.originalImageSize.width * sizeFactor;
left = ((widget.renderedImageSize.width - renderedImageWidth) / 2);
super.initState();
}
@override
Widget build(BuildContext context) {
return m.Magnifier(
visible: currentDragPosition != null,
position: currentDragPosition ?? Offset.zero,
child: Stack(
children: [
_buildTouchBubbles(),
CustomPaint(
painter: EdgePainter(
points: points,
color: Theme.of(context).colorScheme.primary.withOpacity(0.5),
),
)
],
),
);
}
Offset _getNewPositionAfterDrag(Offset position) {
return Offset(
position.dx / renderedImageWidth,
position.dy / renderedImageHeight,
);
}
Offset _clampOffset(Offset givenOffset) {
double absoluteX = givenOffset.dx * renderedImageWidth;
double absoluteY = givenOffset.dy * renderedImageHeight;
return Offset(absoluteX.clamp(0.0, renderedImageWidth) / renderedImageWidth,
absoluteY.clamp(0.0, renderedImageHeight) / renderedImageHeight);
}
Widget _buildTouchBubbles() {
points = [
Offset(
left + _topLeft.dx * renderedImageWidth,
top + _topLeft.dy * renderedImageHeight,
),
Offset(
left + _topRight.dx * renderedImageWidth,
top + _topRight.dy * renderedImageHeight,
),
Offset(
left + _bottomRight.dx * renderedImageWidth,
top + _bottomRight.dy * renderedImageHeight,
),
Offset(
left + _bottomLeft.dx * renderedImageWidth,
top + _bottomLeft.dy * renderedImageHeight,
),
Offset(
left + _topLeft.dx * renderedImageWidth,
top + _topLeft.dy * renderedImageHeight,
),
];
return SizedBox(
width: widget.renderedImageSize.width,
height: widget.renderedImageSize.height,
child: Stack(
children: [
Positioned(
left: points[0].dx - (edgeDraggerSize / 2),
top: points[0].dy - (edgeDraggerSize / 2),
child: TouchBubble(
size: edgeDraggerSize,
onDragFinished: () => setState(() => currentDragPosition = null),
onDrag: (position) {
setState(
() {
currentDragPosition = Offset(points[0].dx, points[0].dy);
_topLeft = _clampOffset(
widget.edgeDetectionResult.topLeft +
_getNewPositionAfterDrag(position),
);
},
);
},
),
),
Positioned(
left: points[1].dx - (edgeDraggerSize / 2),
top: points[1].dy - (edgeDraggerSize / 2),
child: TouchBubble(
size: edgeDraggerSize,
onDrag: (position) {
setState(() {
currentDragPosition = Offset(points[1].dx, points[1].dy);
_topRight = _clampOffset(
widget.edgeDetectionResult.topRight +
_getNewPositionAfterDrag(position),
);
});
},
onDragFinished: () => setState(() => currentDragPosition = null),
),
),
Positioned(
left: points[2].dx - (edgeDraggerSize / 2),
top: points[2].dy - (edgeDraggerSize / 2),
child: TouchBubble(
size: edgeDraggerSize,
onDrag: (position) {
setState(() {
currentDragPosition = Offset(points[2].dx, points[2].dy);
_bottomRight = _clampOffset(
widget.edgeDetectionResult.bottomRight +
_getNewPositionAfterDrag(position),
);
});
},
onDragFinished: () => setState(() => currentDragPosition = null),
),
),
Positioned(
left: points[3].dx - (edgeDraggerSize / 2),
top: points[3].dy - (edgeDraggerSize / 2),
child: TouchBubble(
size: edgeDraggerSize,
onDrag: (position) {
setState(() {
_bottomLeft = _clampOffset(
widget.edgeDetectionResult.bottomLeft +
_getNewPositionAfterDrag(position),
);
currentDragPosition = Offset(points[3].dx, points[3].dy);
});
},
onDragFinished: () => setState(() => currentDragPosition = null),
),
),
],
),
);
}
}

View File

@@ -0,0 +1,25 @@
import 'dart:ui';
import 'package:flutter/material.dart';
class EdgePainter extends CustomPainter {
EdgePainter({required this.points, required this.color});
final List<Offset> points;
final Color color;
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = color.withOpacity(0.5)
..strokeWidth = 2
..strokeCap = StrokeCap.round;
canvas.drawPoints(PointMode.polygon, points, paint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}

View File

@@ -0,0 +1,100 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'magnifier_painter.dart';
class Magnifier extends StatefulWidget {
const Magnifier({
super.key,
required this.child,
required this.position,
this.visible = true,
this.scale = 1.5,
this.size = const Size(160, 160),
});
final Widget child;
final Offset position;
final bool visible;
final double scale;
final Size size;
@override
_MagnifierState createState() => _MagnifierState();
}
class _MagnifierState extends State<Magnifier> {
late Size _magnifierSize;
late double _scale;
late Matrix4 _matrix;
@override
void initState() {
_magnifierSize = widget.size;
_scale = widget.scale;
_calculateMatrix();
super.initState();
}
@override
void didUpdateWidget(Magnifier oldWidget) {
super.didUpdateWidget(oldWidget);
_calculateMatrix();
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
widget.child,
if (widget.visible && widget.position != null) _getMagnifier(context)
],
);
}
void _calculateMatrix() {
if (widget.position == null) {
return;
}
setState(() {
double newX = widget.position.dx - (_magnifierSize.width / 2 / _scale);
double newY = widget.position.dy - (_magnifierSize.height / 2 / _scale);
final Matrix4 updatedMatrix = Matrix4.identity()
..scale(_scale, _scale)
..translate(-newX, -newY);
_matrix = updatedMatrix;
});
}
Widget _getMagnifier(BuildContext context) {
return Align(
alignment: _getAlignment(),
child: ClipOval(
child: BackdropFilter(
filter: ImageFilter.matrix(_matrix.storage),
child: CustomPaint(
painter: MagnifierPainter(color: Theme.of(context).accentColor),
size: _magnifierSize,
),
),
),
);
}
Alignment _getAlignment() {
if (_bubbleCrossesMagnifier()) {
return Alignment.topRight;
}
return Alignment.topLeft;
}
bool _bubbleCrossesMagnifier() =>
widget.position.dx < widget.size.width &&
widget.position.dy < widget.size.height;
}

View File

@@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
class MagnifierPainter extends CustomPainter {
const MagnifierPainter({required this.color, this.strokeWidth = 5});
final double strokeWidth;
final Color color;
@override
void paint(Canvas canvas, Size size) {
_drawCircle(canvas, size);
_drawCrosshair(canvas, size);
}
void _drawCircle(Canvas canvas, Size size) {
Paint paintObject = Paint()
..style = PaintingStyle.stroke
..strokeWidth = strokeWidth
..color = color;
canvas.drawCircle(
size.center(Offset(0, 0)), size.longestSide / 2, paintObject);
}
void _drawCrosshair(Canvas canvas, Size size) {
Paint crossPaint = Paint()
..strokeWidth = strokeWidth / 2
..color = color;
double crossSize = size.longestSide * 0.04;
canvas.drawLine(size.center(Offset(-crossSize, -crossSize)),
size.center(Offset(crossSize, crossSize)), crossPaint);
canvas.drawLine(size.center(Offset(crossSize, -crossSize)),
size.center(Offset(-crossSize, crossSize)), crossPaint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}

View File

@@ -0,0 +1,66 @@
import 'package:flutter/material.dart';
import 'animated_touch_bubble_part.dart';
class TouchBubble extends StatefulWidget {
const TouchBubble({
super.key,
required this.size,
required this.onDrag,
required this.onDragFinished,
});
final double size;
final Function onDrag;
final Function onDragFinished;
@override
State<TouchBubble> createState() => _TouchBubbleState();
}
class _TouchBubbleState extends State<TouchBubble> {
bool dragging = false;
@override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onPanStart: _startDragging,
onPanUpdate: _drag,
onPanCancel: _cancelDragging,
onPanEnd: (_) => _cancelDragging(),
child: Container(
width: widget.size,
height: widget.size,
decoration: BoxDecoration(
color: Colors.transparent,
borderRadius: BorderRadius.circular(widget.size / 2)),
child: AnimatedTouchBubblePart(
dragging: dragging,
size: widget.size,
)));
}
void _startDragging(DragStartDetails data) {
setState(() {
dragging = true;
});
widget
.onDrag(data.localPosition - Offset(widget.size / 2, widget.size / 2));
}
void _cancelDragging() {
setState(() {
dragging = false;
});
widget.onDragFinished();
}
void _drag(DragUpdateDetails data) {
if (!dragging) {
return;
}
widget.onDrag(data.delta);
}
}

View File

@@ -0,0 +1,130 @@
import 'dart:async';
import 'dart:isolate';
import 'dart:typed_data';
import 'package:paperless_document_scanner/paperless_document_scanner.dart';
import 'package:paperless_document_scanner/types/edge_detection_result.dart';
class EdgeDetector {
static Future<void> startEdgeDetectionFromFileIsolate(
EdgeDetectionFromFileInput edgeDetectionInput,
) async {
EdgeDetectionResult result =
await EdgeDetection.detectEdgesFromFile(edgeDetectionInput.inputPath);
edgeDetectionInput.sendPort.send(result);
}
static Future<void> startEdgeDetectionIsolate(
EdgeDetectionInput edgeDetectionInput,
) async {
EdgeDetectionResult result =
await EdgeDetection.detectEdges(edgeDetectionInput.bytes);
edgeDetectionInput.sendPort.send(result);
}
static Future<void> processImageIsolate(
ProcessImageInput processImageInput) async {
ImageProcessing.processImage(
processImageInput.bytes,
processImageInput.edgeDetectionResult,
);
processImageInput.sendPort.send(true);
}
Future<EdgeDetectionResult> detectEdgesFromFile(String filePath) async {
final port = ReceivePort();
_spawnIsolate<EdgeDetectionInput>(
startEdgeDetectionIsolate,
EdgeDetectionFromFileInput(
inputPath: filePath,
sendPort: port.sendPort,
),
port,
);
return await _subscribeToPort<EdgeDetectionResult>(port);
}
Future<bool> processImageFromFile(
String filePath, EdgeDetectionResult edgeDetectionResult) async {
final port = ReceivePort();
_spawnIsolate<ProcessImageInput>(
processImageIsolate,
ProcessImageFromFileInput(
inputPath: filePath,
edgeDetectionResult: edgeDetectionResult,
sendPort: port.sendPort),
port);
return await _subscribeToPort<bool>(port);
}
void _spawnIsolate<T>(
void Function(T) function,
dynamic input,
ReceivePort port,
) {
Isolate.spawn<T>(function, input,
onError: port.sendPort, onExit: port.sendPort);
}
Future<T> _subscribeToPort<T>(ReceivePort port) async {
late StreamSubscription sub;
var completer = Completer<T>();
sub = port.listen((result) async {
print(result);
await sub.cancel();
completer.complete(await result);
});
return completer.future;
}
}
class EdgeDetectionFromFileInput {
EdgeDetectionFromFileInput({
required this.inputPath,
required this.sendPort,
});
final String inputPath;
final SendPort sendPort;
}
class EdgeDetectionInput {
EdgeDetectionInput({
required this.bytes,
required this.sendPort,
});
final Uint8List bytes;
final SendPort sendPort;
}
class ProcessImageInput {
ProcessImageInput({
required this.bytes,
required this.edgeDetectionResult,
required this.sendPort,
});
final Uint8List bytes;
final EdgeDetectionResult edgeDetectionResult;
final SendPort sendPort;
}
class ProcessImageFromFileInput {
ProcessImageFromFileInput({
required this.inputPath,
required this.edgeDetectionResult,
required this.sendPort,
});
final String inputPath;
final EdgeDetectionResult edgeDetectionResult;
final SendPort sendPort;
}

View File

@@ -0,0 +1,25 @@
import 'dart:io';
import 'package:flutter/material.dart';
class ImageView extends StatefulWidget {
const ImageView({super.key, required this.imagePath});
final String imagePath;
@override
State<ImageView> createState() => _ImageViewState();
}
class _ImageViewState extends State<ImageView> {
GlobalKey imageWidgetKey = GlobalKey();
@override
Widget build(BuildContext mainContext) {
return Center(
child: Image.file(
File(widget.imagePath),
fit: BoxFit.contain,
),
);
}
}

View File

@@ -0,0 +1,113 @@
import 'dart:typed_data';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:image/image.dart' as imglib;
import 'scan.dart';
late final List<CameraDescription> cameras;
void main() async {
WidgetsFlutterBinding.ensureInitialized();
cameras = await availableCameras();
runApp(const EdgeDetectionApp());
}
class EdgeDetectionApp extends StatefulWidget {
const EdgeDetectionApp({super.key});
@override
State<EdgeDetectionApp> createState() => _EdgeDetectionAppState();
}
class _EdgeDetectionAppState extends State<EdgeDetectionApp> {
CameraImage? _image;
late final CameraController _controller;
@override
void initState() {
super.initState();
() async {
_controller = CameraController(
cameras
.where(
(element) => element.lensDirection == CameraLensDirection.back)
.first,
ResolutionPreset.low,
enableAudio: false,
);
await _controller.initialize();
_controller.startImageStream((image) {
setState(() => _image = image);
});
}();
}
Uint8List concatenatePlanes(List<Plane> planes) {
final WriteBuffer allBytes = WriteBuffer();
for (final plane in planes) {
allBytes.putUint8List(plane.bytes);
}
return allBytes.done().buffer.asUint8List();
}
Image convertYUV420toImageColor(CameraImage image) {
final int width = image.width;
final int height = image.height;
final int uvRowStride = image.planes[1].bytesPerRow;
final int uvPixelStride = image.planes[1].bytesPerPixel!;
print("uvRowStride: " + uvRowStride.toString());
print("uvPixelStride: " + uvPixelStride.toString());
// imgLib -> Image package from https://pub.dartlang.org/packages/image
var img = imglib.Image(
width: width,
height: height,
); // Create Image buffer
// Fill image buffer with plane[0] from YUV420_888
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
final int uvIndex =
uvPixelStride * (x / 2).floor() + uvRowStride * (y / 2).floor();
final int index = y * width + x;
final yp = image.planes[0].bytes[index];
final up = image.planes[1].bytes[uvIndex];
final vp = image.planes[2].bytes[uvIndex];
// Calculate pixel color
int r = (yp + vp * 1436 / 1024 - 179).round().clamp(0, 255);
int g = (yp - up * 46549 / 131072 + 44 - vp * 93604 / 131072 + 91)
.round()
.clamp(0, 255);
int b = (yp + up * 1814 / 1024 - 227).round().clamp(0, 255);
// color: 0x FF FF FF FF
// A B G R
img.data?.setPixelRgb(x, y, r, g, b);
}
}
imglib.PngEncoder pngEncoder = new imglib.PngEncoder(level: 0);
final png = pngEncoder.encode(img);
return Image.memory(png);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: Scaffold(
body: Center(
child: _image != null
? convertYUV420toImageColor(_image!)
: Placeholder(),
),
),
);
}
}

View File

@@ -0,0 +1,174 @@
import 'dart:async';
import 'dart:developer';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:paperless_document_scanner/types/edge_detection_result.dart';
import 'camera_view.dart';
import 'cropping_preview.dart';
import 'edge_detector.dart';
import 'image_view.dart';
class Scan extends StatefulWidget {
const Scan({
super.key,
required,
required this.cameras,
});
final List<CameraDescription> cameras;
@override
State<Scan> createState() => _ScanState();
}
class _ScanState extends State<Scan> {
late final CameraController controller;
String? imagePath;
String? croppedImagePath;
EdgeDetectionResult? edgeDetectionResult;
@override
void initState() {
super.initState();
controller = CameraController(
widget.cameras[0],
ResolutionPreset.veryHigh,
imageFormatGroup: ImageFormatGroup.jpeg,
enableAudio: false,
);
() async {
await controller.initialize();
log(controller.value.toString());
}();
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
_getMainWidget(),
_getBottomBar(),
],
),
);
}
Widget _getMainWidget() {
if (croppedImagePath != null) {
return ImageView(imagePath: croppedImagePath!);
}
if (imagePath == null && edgeDetectionResult == null) {
return CameraView(controller: controller);
}
return ImagePreview(
imagePath: imagePath!,
edgeDetectionResult: edgeDetectionResult,
);
}
Widget _getButtonRow() {
if (imagePath != null) {
return Align(
alignment: Alignment.bottomCenter,
child: FloatingActionButton(
child: Icon(Icons.check),
onPressed: () async {
if (croppedImagePath == null) {
return _processImage(imagePath!, edgeDetectionResult!);
}
setState(() {
imagePath = null;
edgeDetectionResult = null;
croppedImagePath = null;
});
},
),
);
}
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FloatingActionButton(
foregroundColor: Colors.white,
child: Icon(Icons.camera_alt),
onPressed: onTakePictureButtonPressed,
),
],
);
}
String timestamp() => DateTime.now().millisecondsSinceEpoch.toString();
Future<String> takePicture() async {
if (!controller.value.isInitialized) {
throw Exception("Select camera first!");
}
final file = await controller.takePicture();
return file.path;
}
Future _detectEdges(String filePath) async {
if (!mounted) {
return;
}
setState(() {
imagePath = filePath;
});
EdgeDetectionResult result =
await EdgeDetector().detectEdgesFromFile(filePath);
setState(() {
edgeDetectionResult = result;
});
}
Future _processImage(
String filePath, EdgeDetectionResult edgeDetectionResult) async {
if (!mounted) {
return;
}
bool result = await EdgeDetector()
.processImageFromFile(filePath, edgeDetectionResult);
if (result == false) {
return;
}
setState(() {
imageCache.clearLiveImages();
imageCache.clear();
croppedImagePath = imagePath;
});
}
void onTakePictureButtonPressed() async {
String filePath = await takePicture();
log('Picture saved to $filePath');
await _detectEdges(filePath);
}
Padding _getBottomBar() {
return Padding(
padding: EdgeInsets.only(bottom: 32),
child: Align(alignment: Alignment.bottomCenter, child: _getButtonRow()),
);
}
}

View File

@@ -0,0 +1,441 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
archive:
dependency: transitive
description:
name: archive
sha256: d6347d54a2d8028e0437e3c099f66fdb8ae02c4720c1e7534c9f24c10351f85d
url: "https://pub.dev"
source: hosted
version: "3.3.6"
async:
dependency: transitive
description:
name: async
sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0
url: "https://pub.dev"
source: hosted
version: "2.10.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
camera:
dependency: "direct main"
description:
name: camera
sha256: e7ac55af24a890d20276821eb3c95857074d03b7de6f9892b99a205ee30bd179
url: "https://pub.dev"
source: hosted
version: "0.10.3"
camera_android:
dependency: transitive
description:
name: camera_android
sha256: e491c836147f60dd8a54cf3895fd2960e13b21b78a9d15b563a1b6c70894f142
url: "https://pub.dev"
source: hosted
version: "0.10.4"
camera_avfoundation:
dependency: transitive
description:
name: camera_avfoundation
sha256: "6a68c20593d4cd58974d555f74a48b244f9db28cc9156de57781122d11b8754b"
url: "https://pub.dev"
source: hosted
version: "0.9.11"
camera_platform_interface:
dependency: transitive
description:
name: camera_platform_interface
sha256: b632be28e61d00a233f67d98ea90fd7041956f27a1c65500188ee459be60e15f
url: "https://pub.dev"
source: hosted
version: "2.4.0"
camera_web:
dependency: transitive
description:
name: camera_web
sha256: "496de93c5d462738ce991dbfe91fb19026f115ed36406700a20a380fb0018299"
url: "https://pub.dev"
source: hosted
version: "0.3.1+1"
characters:
dependency: transitive
description:
name: characters
sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c
url: "https://pub.dev"
source: hosted
version: "1.2.1"
clock:
dependency: transitive
description:
name: clock
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
url: "https://pub.dev"
source: hosted
version: "1.1.1"
collection:
dependency: transitive
description:
name: collection
sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0
url: "https://pub.dev"
source: hosted
version: "1.17.0"
convert:
dependency: transitive
description:
name: convert
sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
url: "https://pub.dev"
source: hosted
version: "3.1.1"
cross_file:
dependency: transitive
description:
name: cross_file
sha256: "0b0036e8cccbfbe0555fd83c1d31a6f30b77a96b598b35a5d36dd41f718695e9"
url: "https://pub.dev"
source: hosted
version: "0.3.3+4"
crypto:
dependency: transitive
description:
name: crypto
sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67
url: "https://pub.dev"
source: hosted
version: "3.0.2"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be
url: "https://pub.dev"
source: hosted
version: "1.0.5"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
url: "https://pub.dev"
source: hosted
version: "1.3.1"
ffi:
dependency: transitive
description:
name: ffi
sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978
url: "https://pub.dev"
source: hosted
version: "2.0.1"
file:
dependency: transitive
description:
name: file
sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
url: "https://pub.dev"
source: hosted
version: "6.1.4"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c
url: "https://pub.dev"
source: hosted
version: "2.0.1"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
sha256: "60fc7b78455b94e6de2333d2f95196d32cf5c22f4b0b0520a628804cb463503b"
url: "https://pub.dev"
source: hosted
version: "2.0.7"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
image:
dependency: "direct main"
description:
name: image
sha256: "483a389d6ccb292b570c31b3a193779b1b0178e7eb571986d9a49904b6861227"
url: "https://pub.dev"
source: hosted
version: "4.0.15"
js:
dependency: transitive
description:
name: js
sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7"
url: "https://pub.dev"
source: hosted
version: "0.6.5"
lints:
dependency: transitive
description:
name: lints
sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
matcher:
dependency: transitive
description:
name: matcher
sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72"
url: "https://pub.dev"
source: hosted
version: "0.12.13"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
url: "https://pub.dev"
source: hosted
version: "0.2.0"
meta:
dependency: transitive
description:
name: meta
sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42"
url: "https://pub.dev"
source: hosted
version: "1.8.0"
paperless_document_scanner:
dependency: "direct main"
description:
path: ".."
relative: true
source: path
version: "0.0.1"
path:
dependency: transitive
description:
name: path
sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b
url: "https://pub.dev"
source: hosted
version: "1.8.2"
path_provider:
dependency: "direct main"
description:
name: path_provider
sha256: dcea5feb97d8abf90cab9e9030b497fb7c3cbf26b7a1fe9e3ef7dcb0a1ddec95
url: "https://pub.dev"
source: hosted
version: "2.0.12"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: a776c088d671b27f6e3aa8881d64b87b3e80201c64e8869b811325de7a76c15e
url: "https://pub.dev"
source: hosted
version: "2.0.22"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "62a68e7e1c6c459f9289859e2fae58290c981ce21d1697faf54910fe1faa4c74"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: "2e32f1640f07caef0d3cb993680f181c79e54a3827b997d5ee221490d131fbd9"
url: "https://pub.dev"
source: hosted
version: "2.1.8"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: f0abc8ebd7253741f05488b4813d936b4d07c6bae3e86148a09e342ee4b08e76
url: "https://pub.dev"
source: hosted
version: "2.0.5"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: bcabbe399d4042b8ee687e17548d5d3f527255253b4a639f5f8d2094a9c2b45c
url: "https://pub.dev"
source: hosted
version: "2.1.3"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: "49392a45ced973e8d94a85fdb21293fbb40ba805fc49f2965101ae748a3683b4"
url: "https://pub.dev"
source: hosted
version: "5.1.0"
platform:
dependency: transitive
description:
name: platform
sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a
url: "https://pub.dev"
source: hosted
version: "2.1.3"
pointycastle:
dependency: transitive
description:
name: pointycastle
sha256: db7306cf0249f838d1a24af52b5a5887c5bf7f31d8bb4e827d071dc0939ad346
url: "https://pub.dev"
source: hosted
version: "3.6.2"
process:
dependency: transitive
description:
name: process
sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09"
url: "https://pub.dev"
source: hosted
version: "4.2.4"
quiver:
dependency: transitive
description:
name: quiver
sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47
url: "https://pub.dev"
source: hosted
version: "3.2.1"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_span:
dependency: transitive
description:
name: source_span
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
url: "https://pub.dev"
source: hosted
version: "1.9.1"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
url: "https://pub.dev"
source: hosted
version: "1.11.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
stream_transform:
dependency: transitive
description:
name: stream_transform
sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
url: "https://pub.dev"
source: hosted
version: "1.2.1"
test_api:
dependency: transitive
description:
name: test_api
sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206
url: "https://pub.dev"
source: hosted
version: "0.4.16"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5"
url: "https://pub.dev"
source: hosted
version: "1.3.1"
vector_math:
dependency: transitive
description:
name: vector_math
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
win32:
dependency: transitive
description:
name: win32
sha256: c9ebe7ee4ab0c2194e65d3a07d8c54c5d00bb001b76081c4a04cdb8448b59e46
url: "https://pub.dev"
source: hosted
version: "3.1.3"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: ee1505df1426458f7f60aac270645098d318a8b4766d85fde75f76f2e21807d1
url: "https://pub.dev"
source: hosted
version: "1.0.0"
xml:
dependency: transitive
description:
name: xml
sha256: "979ee37d622dec6365e2efa4d906c37470995871fe9ae080d967e192d88286b5"
url: "https://pub.dev"
source: hosted
version: "6.2.2"
sdks:
dart: ">=2.19.2 <3.0.0"
flutter: ">=3.0.0"

View File

@@ -0,0 +1,86 @@
name: paperless_document_scanner_example
description: Demonstrates how to use the paperless_document_scanner plugin.
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
environment:
sdk: '>=2.19.2 <3.0.0'
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
flutter:
sdk: flutter
paperless_document_scanner:
# When depending on this package from a real application you should use:
# paperless_document_scanner: ^x.y.z
# See https://dart.dev/tools/pub/dependencies#version-constraints
# The example app is bundled with the plugin so we use a path dependency on
# the parent directory to use the current plugin's version.
path: ../
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
path_provider: ^2.0.12
camera: ^0.10.3
image: ^4.0.15
dev_dependencies:
flutter_test:
sdk: flutter
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^2.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/assets-and-images/#from-packages
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages

View File

@@ -0,0 +1,38 @@
.idea/
.vagrant/
.sconsign.dblite
.svn/
.DS_Store
*.swp
profile
DerivedData/
build/
GeneratedPluginRegistrant.h
GeneratedPluginRegistrant.m
.generated/
*.pbxuser
*.mode1v3
*.mode2v3
*.perspectivev3
!default.pbxuser
!default.mode1v3
!default.mode2v3
!default.perspectivev3
xcuserdata
*.moved-aside
*.pyc
*sync/
Icon?
.tags*
/Flutter/Generated.xcconfig
/Flutter/ephemeral/
/Flutter/flutter_export_environment.sh

View File

View File

@@ -0,0 +1,4 @@
#import <Flutter/Flutter.h>
@interface PaperlessDocumentScannerPlugin : NSObject<FlutterPlugin>
@end

View File

@@ -0,0 +1,15 @@
#import "SimpleEdgeDetectionPlugin.h"
#if __has_include(<paperless_document_scanner/paperless_document_scanner-Swift.h>)
#import <paperless_document_scanner/paperless_document_scanner-Swift.h>
#else
// Support project import fallback if the generated compatibility header
// is not copied when this plugin is created as a library.
// https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816
#import "paperless_document_scanner-Swift.h"
#endif
@implementation SimpleEdgeDetectionPlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
[SwiftSimpleEdgeDetectionPlugin registerWithRegistrar:registrar];
}
@end

View File

@@ -0,0 +1,14 @@
import Flutter
import UIKit
public class PaperlessDocumentScannerPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "paperless_document_scanner", binaryMessenger: registrar.messenger())
let instance = PaperlessDocumentScannerPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
result("iOS " + UIDevice.current.systemVersion)
}
}

View File

@@ -0,0 +1,15 @@
#include "conversion_utils.hpp"
using namespace cv;
uint8_t * ConversionUtils::matrix_to_bytearray(Mat mat)
{
int size = mat.total() * mat.elemSize();
uint8_t *bytes = (uint8_t *)malloc(size);
std::memcpy(bytes, mat.data, size * sizeof(uint8_t));
return bytes;
}
Mat ConversionUtils::bytearray_to_matrix(uint8_t *bytes, int byteCount)
{
std::vector<uint8_t> buf(bytes, bytes + byteCount);
return imdecode(buf, IMREAD_COLOR);
}

View File

@@ -0,0 +1,10 @@
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
class ConversionUtils {
public:
static uint8_t *matrix_to_bytearray(Mat mat);
static Mat bytearray_to_matrix(uint8_t *bytes, int byteCount);
};

View File

@@ -0,0 +1,163 @@
#include "edge_detector.hpp"
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/types_c.h>
using namespace cv;
using namespace std;
// helper function:
// finds a cosine of angle between vectors
// from pt0->pt1 and from pt0->pt2
double EdgeDetector::get_cosine_angle_between_vectors(cv::Point pt1, cv::Point pt2, cv::Point pt0)
{
double dx1 = pt1.x - pt0.x;
double dy1 = pt1.y - pt0.y;
double dx2 = pt2.x - pt0.x;
double dy2 = pt2.y - pt0.y;
return (dx1*dx2 + dy1*dy2)/sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10);
}
vector<cv::Point> image_to_vector(Mat& image)
{
int imageWidth = image.size().width;
int imageHeight = image.size().height;
return {
cv::Point(0, 0),
cv::Point(imageWidth, 0),
cv::Point(0, imageHeight),
cv::Point(imageWidth, imageHeight)
};
}
vector<cv::Point> EdgeDetector::detect_edges(Mat& image)
{
vector<vector<cv::Point>> squares = find_squares(image);
vector<cv::Point>* biggestSquare = NULL;
// Sort so that the points are ordered clockwise
struct sortY {
bool operator() (cv::Point pt1, cv::Point pt2) { return (pt1.y < pt2.y);}
} orderRectangleY;
struct sortX {
bool operator() (cv::Point pt1, cv::Point pt2) { return (pt1.x < pt2.x);}
} orderRectangleX;
for (int i = 0; i < squares.size(); i++) {
vector<cv::Point>* currentSquare = &squares[i];
std::sort(currentSquare->begin(),currentSquare->end(), orderRectangleY);
std::sort(currentSquare->begin(),currentSquare->begin()+2, orderRectangleX);
std::sort(currentSquare->begin()+2,currentSquare->end(), orderRectangleX);
float currentSquareWidth = get_width(*currentSquare);
float currentSquareHeight = get_height(*currentSquare);
if (currentSquareWidth < image.size().width / 5 || currentSquareHeight < image.size().height / 5) {
continue;
}
if (currentSquareWidth > image.size().width * 0.99 || currentSquareHeight > image.size().height * 0.99) {
continue;
}
if (biggestSquare == NULL) {
biggestSquare = currentSquare;
continue;
}
float biggestSquareWidth = get_width(*biggestSquare);
float biggestSquareHeight = get_height(*biggestSquare);
if (currentSquareWidth * currentSquareHeight >= biggestSquareWidth * biggestSquareHeight) {
biggestSquare = currentSquare;
}
}
if (biggestSquare == NULL) {
return image_to_vector(image);
}
std::sort(biggestSquare->begin(),biggestSquare->end(), orderRectangleY);
std::sort(biggestSquare->begin(),biggestSquare->begin()+2, orderRectangleX);
std::sort(biggestSquare->begin()+2,biggestSquare->end(), orderRectangleX);
return *biggestSquare;
}
float EdgeDetector::get_height(vector<cv::Point>& square) {
float upperLeftToLowerRight = square[3].y - square[0].y;
float upperRightToLowerLeft = square[1].y - square[2].y;
return max(upperLeftToLowerRight, upperRightToLowerLeft);
}
float EdgeDetector::get_width(vector<cv::Point>& square) {
float upperLeftToLowerRight = square[3].x - square[0].x;
float upperRightToLowerLeft = square[1].x - square[2].x;
return max(upperLeftToLowerRight, upperRightToLowerLeft);
}
cv::Mat EdgeDetector::debug_squares( cv::Mat image )
{
vector<vector<cv::Point> > squares = find_squares(image);
for (const auto & square : squares) {
// draw rotated rect
cv::RotatedRect minRect = minAreaRect(cv::Mat(square));
cv::Point2f rect_points[4];
minRect.points( rect_points );
for ( int j = 0; j < 4; j++ ) {
cv::line( image, rect_points[j], rect_points[(j+1)%4], cv::Scalar(0,0,255), 1, 8 ); // blue
}
}
return image;
}
vector<vector<cv::Point> > EdgeDetector::find_squares(Mat& image)
{
vector<int> usedThresholdLevel;
vector<vector<Point> > squares;
Mat gray0(image.size(), CV_8U), gray;
cvtColor(image , gray, COLOR_BGR2GRAY);
medianBlur(gray, gray, 3); // blur will enhance edge detection
vector<vector<cv::Point> > contours;
int thresholdLevels[] = {10, 30, 50, 70};
for(int thresholdLevel : thresholdLevels) {
Canny(gray, gray0, thresholdLevel, thresholdLevel*3, 3);
dilate(gray0, gray0, Mat(), Point(-1, -1));
findContours(gray0, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);
vector<Point> approx;
for (const auto & contour : contours) {
approxPolyDP(Mat(contour), approx, arcLength(Mat(contour), true) * 0.02, true);
if (approx.size() == 4 && fabs(contourArea(Mat(approx))) > 1000 &&
isContourConvex(Mat(approx))) {
double maxCosine = 0;
for (int j = 2; j < 5; j++) {
double cosine = fabs(get_cosine_angle_between_vectors(approx[j % 4], approx[j - 2], approx[j - 1]));
maxCosine = MAX(maxCosine, cosine);
}
if (maxCosine < 0.3) {
squares.push_back(approx);
usedThresholdLevel.push_back(thresholdLevel);
}
}
}
}
return squares;
}

View File

@@ -0,0 +1,17 @@
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
class EdgeDetector {
public:
static vector<cv::Point> detect_edges( Mat& image);
static Mat debug_squares( Mat image );
private:
static double get_cosine_angle_between_vectors( cv::Point pt1, cv::Point pt2, cv::Point pt0 );
static vector<vector<cv::Point> > find_squares(Mat& image);
static float get_width(vector<cv::Point>& square);
static float get_height(vector<cv::Point>& square);
};

View File

@@ -0,0 +1,51 @@
#include "image_processor.hpp"
#include <opencv2/opencv.hpp>
using namespace cv;
Point2f computePoint(int p1, int p2)
{
Point2f pt;
pt.x = p1;
pt.y = p2;
return pt;
}
Mat ImageProcessor::process_image(Mat img, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4)
{
cvtColor(img, img, COLOR_BGR2GRAY);
Mat dst = ImageProcessor::crop_and_transform(img, x1, y1, x2, y2, x3, y3, x4, y4);
adaptiveThreshold(dst, dst, 255, cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY, 53, 10);
return dst;
}
Mat ImageProcessor::crop_and_transform(Mat img, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4)
{
float w1 = sqrt(pow(x4 - x3, 2) + pow(x4 - x3, 2));
float w2 = sqrt(pow(x2 - x1, 2) + pow(x2 - x1, 2));
float h1 = sqrt(pow(y2 - y4, 2) + pow(y2 - y4, 2));
float h2 = sqrt(pow(y1 - y3, 2) + pow(y1 - y3, 2));
float maxWidth = (w1 < w2) ? w1 : w2;
float maxHeight = (h1 < h2) ? h1 : h2;
Mat dst = Mat::zeros(maxHeight, maxWidth, CV_8UC1);
vector<Point2f> dst_pts;
vector<Point2f> img_pts;
dst_pts.push_back(Point(0, 0));
dst_pts.push_back(Point(maxWidth - 1, 0));
dst_pts.push_back(Point(0, maxHeight - 1));
dst_pts.push_back(Point(maxWidth - 1, maxHeight - 1));
img_pts.push_back(computePoint(x1, y1));
img_pts.push_back(computePoint(x2, y2));
img_pts.push_back(computePoint(x3, y3));
img_pts.push_back(computePoint(x4, y4));
Mat transformation_matrix = getPerspectiveTransform(img_pts, dst_pts);
warpPerspective(img, dst, transformation_matrix, dst.size());
return dst;
}

View File

@@ -0,0 +1,13 @@
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
class ImageProcessor {
public:
static Mat process_image(Mat img, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4);
private:
static Mat crop_and_transform(Mat img, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4);
};

View File

@@ -0,0 +1,130 @@
#include "native_edge_detection.hpp"
#include "edge_detector.hpp"
#include "image_processor.hpp"
#include "conversion_utils.hpp"
#include <stdlib.h>
#include <opencv2/opencv.hpp>
extern "C" __attribute__((visibility("default"))) __attribute__((used)) struct Coordinate *create_coordinate(double x, double y)
{
struct Coordinate *coordinate = (struct Coordinate *)malloc(sizeof(struct Coordinate));
coordinate->x = x;
coordinate->y = y;
return coordinate;
}
extern "C" __attribute__((visibility("default"))) __attribute__((used)) struct DetectionResult *create_detection_result(
Coordinate *topLeft,
Coordinate *topRight,
Coordinate *bottomLeft,
Coordinate *bottomRight)
{
struct DetectionResult *detectionResult = (struct DetectionResult *)malloc(sizeof(struct DetectionResult));
detectionResult->topLeft = topLeft;
detectionResult->topRight = topRight;
detectionResult->bottomLeft = bottomLeft;
detectionResult->bottomRight = bottomRight;
return detectionResult;
}
extern "C" __attribute__((visibility("default"))) __attribute__((used)) struct DetectionResult *detect_edges_from_file(char *str)
{
struct DetectionResult *coordinate = (struct DetectionResult *)malloc(sizeof(struct DetectionResult));
cv::Mat mat = cv::imread(str);
if (mat.size().width == 0 || mat.size().height == 0)
{
return create_detection_result(
create_coordinate(0, 0),
create_coordinate(1, 0),
create_coordinate(0, 1),
create_coordinate(1, 1));
}
vector<cv::Point> points = EdgeDetector::detect_edges(mat);
return create_detection_result(
create_coordinate((double)points[0].x / mat.size().width, (double)points[0].y / mat.size().height),
create_coordinate((double)points[1].x / mat.size().width, (double)points[1].y / mat.size().height),
create_coordinate((double)points[2].x / mat.size().width, (double)points[2].y / mat.size().height),
create_coordinate((double)points[3].x / mat.size().width, (double)points[3].y / mat.size().height));
}
extern "C" __attribute__((visibility("default"))) __attribute__((used)) struct DetectionResult *detect_edges(uint8_t *bytes, int byteCount)
{
struct DetectionResult *coordinate = (struct DetectionResult *)malloc(sizeof(struct DetectionResult));
Mat mat = ConversionUtils::bytearray_to_matrix(bytes, byteCount);
if (mat.size().width == 0 || mat.size().height == 0)
{
return create_detection_result(
create_coordinate(0, 0),
create_coordinate(1, 0),
create_coordinate(0, 1),
create_coordinate(1, 1));
}
vector<cv::Point> points = EdgeDetector::detect_edges(mat);
return create_detection_result(
create_coordinate((double)points[0].x / mat.size().width, (double)points[0].y / mat.size().height),
create_coordinate((double)points[1].x / mat.size().width, (double)points[1].y / mat.size().height),
create_coordinate((double)points[2].x / mat.size().width, (double)points[2].y / mat.size().height),
create_coordinate((double)points[3].x / mat.size().width, (double)points[3].y / mat.size().height));
}
extern "C" __attribute__((visibility("default"))) __attribute__((used)) bool process_image_from_file(
char *path,
double topLeftX,
double topLeftY,
double topRightX,
double topRightY,
double bottomLeftX,
double bottomLeftY,
double bottomRightX,
double bottomRightY)
{
cv::Mat mat = cv::imread(path);
cv::Mat resizedMat = ImageProcessor::process_image(
mat,
topLeftX * mat.size().width,
topLeftY * mat.size().height,
topRightX * mat.size().width,
topRightY * mat.size().height,
bottomLeftX * mat.size().width,
bottomLeftY * mat.size().height,
bottomRightX * mat.size().width,
bottomRightY * mat.size().height);
return cv::imwrite(path, resizedMat);
}
extern "C" __attribute__((visibility("default"))) __attribute__((used))
uint8_t *
process_image(
uint8_t *bytes,
int byteCount,
double topLeftX,
double topLeftY,
double topRightX,
double topRightY,
double bottomLeftX,
double bottomLeftY,
double bottomRightX,
double bottomRightY)
{
cv::Mat mat = ConversionUtils::bytearray_to_matrix(bytes, byteCount);
cv::Mat resizedMat = ImageProcessor::process_image(
mat,
topLeftX * mat.size().width,
topLeftY * mat.size().height,
topRightX * mat.size().width,
topRightY * mat.size().height,
bottomLeftX * mat.size().width,
bottomLeftY * mat.size().height,
bottomRightX * mat.size().width,
bottomRightY * mat.size().height);
return ConversionUtils::matrix_to_bytearray(resizedMat);
}

View File

@@ -0,0 +1,47 @@
#include <iostream>
struct Coordinate
{
double x;
double y;
};
struct DetectionResult
{
Coordinate *topLeft;
Coordinate *topRight;
Coordinate *bottomLeft;
Coordinate *bottomRight;
};
extern "C" struct ProcessingInput
{
char *path;
DetectionResult detectionResult;
};
extern "C" struct DetectionResult *detect_edges_from_file(char *str);
extern "C" struct DetectionResult *detect_edges(uint8_t *bytes, int byteCount);
extern "C" uint8_t *process_image(
uint8_t *bytes,
int byteCount,
double topLeftX,
double topLeftY,
double topRightX,
double topRightY,
double bottomLeftX,
double bottomLeftY,
double bottomRightX,
double bottomRightY);
extern "C" bool process_image_from_file(
char *path,
double topLeftX,
double topLeftY,
double topRightX,
double topRightY,
double bottomLeftX,
double bottomLeftY,
double bottomRightX,
double bottomRightY);

View File

@@ -0,0 +1,29 @@
#
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
# Run `pod lib lint paperless_document_scanner.podspec` to validate before publishing.
#
Pod::Spec.new do |s|
s.name = 'paperless_document_scanner'
s.version = '0.0.1'
s.summary = 'A new Flutter plugin project.'
s.description = <<-DESC
A new Flutter plugin project.
DESC
s.homepage = 'http://example.com'
s.license = { :file => '../LICENSE' }
s.author = { 'Your Company' => 'email@example.com' }
s.source = { :path => '.' }
s.source_files = 'Classes/**/*.{swift,c,m,h,mm,cpp,plist}'
s.dependency 'Flutter'
s.platform = :ios, '9.0'
# Flutter.framework does not contain a i386 slice.
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
s.swift_version = '5.0'
s.preserve_paths = 'opencv2.framework'
s.xcconfig = { 'OTHER_LDFLAGS' => '-framework opencv2' }
s.vendored_frameworks = 'opencv2.framework'
s.frameworks = 'AVFoundation'
s.library = 'c++'
end

Some files were not shown because too many files have changed in this diff Show More