├── ios ├── Assets │ └── .gitkeep ├── Classes │ ├── DocumentScannerPlugin.h │ ├── DocumentScannerFactory.h │ ├── DocumentScannerPlugin.m │ ├── IPDFCameraViewController.h │ ├── DocumentScannerView.h │ ├── DocumentScannerFactory.m │ ├── DocumentScannerView.m │ └── IPDFCameraViewController.m ├── .gitignore └── document_scanner.podspec ├── android ├── .idea │ ├── .name │ ├── .gitignore │ ├── compiler.xml │ ├── modules.xml │ ├── misc.xml │ ├── gradle.xml │ └── jarRepositories.xml ├── settings.gradle ├── .gitignore ├── gradle.properties ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── document_scanner │ │ │ ├── helpers │ │ │ ├── AppConstant.java │ │ │ ├── Quadrilateral.java │ │ │ ├── OpenNoteMessage.java │ │ │ ├── PreviewFrame.java │ │ │ ├── ScannedDocument.java │ │ │ ├── Utils.java │ │ │ └── CustomOpenCVLoader.java │ │ │ ├── views │ │ │ ├── MainView.java │ │ │ └── HUDCanvasView.java │ │ │ ├── DocumentScannerFactory.java │ │ │ ├── DocumentScannerViewManager.java │ │ │ ├── DocumentScannerPlugin.java │ │ │ └── ImageProcessor.java │ │ └── res │ │ ├── anim │ │ └── blink.xml │ │ └── layout │ │ └── activity_open_note_scanner.xml ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── build.gradle ├── .idea ├── .gitignore ├── runConfigurations │ └── example_lib_main_dart.xml └── runConfigurations.xml ├── example ├── ios │ ├── Flutter │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── AppFrameworkInfo.plist │ ├── Runner │ │ ├── AppDelegate.h │ │ ├── Assets.xcassets │ │ │ ├── LaunchImage.imageset │ │ │ │ ├── LaunchImage.png │ │ │ │ ├── LaunchImage@2x.png │ │ │ │ ├── LaunchImage@3x.png │ │ │ │ ├── README.md │ │ │ │ └── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ │ ├── Icon-App-20x20@1x.png │ │ │ │ ├── Icon-App-20x20@2x.png │ │ │ │ ├── Icon-App-20x20@3x.png │ │ │ │ ├── Icon-App-29x29@1x.png │ │ │ │ ├── Icon-App-29x29@2x.png │ │ │ │ ├── Icon-App-29x29@3x.png │ │ │ │ ├── Icon-App-40x40@1x.png │ │ │ │ ├── Icon-App-40x40@2x.png │ │ │ │ ├── Icon-App-40x40@3x.png │ │ │ │ ├── Icon-App-60x60@2x.png │ │ │ │ ├── Icon-App-60x60@3x.png │ │ │ │ ├── Icon-App-76x76@1x.png │ │ │ │ ├── Icon-App-76x76@2x.png │ │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ │ └── Contents.json │ │ ├── main.m │ │ ├── AppDelegate.m │ │ ├── Base.lproj │ │ │ ├── Main.storyboard │ │ │ └── LaunchScreen.storyboard │ │ └── Info.plist │ ├── Runner.xcodeproj │ │ ├── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ ├── .gitignore │ ├── Podfile.lock │ └── Podfile ├── android │ ├── gradle.properties │ ├── .gitignore │ ├── app │ │ ├── src │ │ │ ├── main │ │ │ │ ├── res │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── values │ │ │ │ │ │ └── styles.xml │ │ │ │ │ └── drawable │ │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── java │ │ │ │ │ └── com │ │ │ │ │ │ └── example │ │ │ │ │ │ └── document_scanner_example │ │ │ │ │ │ └── MainActivity.java │ │ │ │ └── AndroidManifest.xml │ │ │ ├── debug │ │ │ │ └── AndroidManifest.xml │ │ │ └── profile │ │ │ │ └── AndroidManifest.xml │ │ └── build.gradle │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ ├── settings.gradle │ └── build.gradle ├── .metadata ├── README.md ├── .gitignore ├── test │ └── widget_test.dart ├── pubspec.yaml └── lib │ └── main.dart ├── .metadata ├── test └── document_scanner_test.dart ├── pubspec.yaml ├── LICENSE ├── CHANGELOG.md ├── README.md ├── lib ├── scannedImage.dart └── document_scanner.dart └── .gitignore /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /android/.idea/.name: -------------------------------------------------------------------------------- 1 | document_scanner -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /android/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | //include ':openCVLibrary310'//include ':openCVLibrary310' 2 | rootProject.name = 'document_scanner' 3 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Classes/DocumentScannerPlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface DocumentScannerPlugin : NSObject 4 | @end 5 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eliasteeny/flutter_document_scanner/HEAD/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eliasteeny/flutter_document_scanner/HEAD/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eliasteeny/flutter_document_scanner/HEAD/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eliasteeny/flutter_document_scanner/HEAD/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eliasteeny/flutter_document_scanner/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eliasteeny/flutter_document_scanner/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /android/.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eliasteeny/flutter_document_scanner/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eliasteeny/flutter_document_scanner/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eliasteeny/flutter_document_scanner/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eliasteeny/flutter_document_scanner/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eliasteeny/flutter_document_scanner/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eliasteeny/flutter_document_scanner/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eliasteeny/flutter_document_scanner/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eliasteeny/flutter_document_scanner/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eliasteeny/flutter_document_scanner/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eliasteeny/flutter_document_scanner/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eliasteeny/flutter_document_scanner/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eliasteeny/flutter_document_scanner/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eliasteeny/flutter_document_scanner/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eliasteeny/flutter_document_scanner/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eliasteeny/flutter_document_scanner/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eliasteeny/flutter_document_scanner/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eliasteeny/flutter_document_scanner/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip 6 | -------------------------------------------------------------------------------- /example/ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip 7 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/runConfigurations/example_lib_main_dart.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: f139b11009aeb8ed2a3a3aa8b0066e482709dde3 8 | channel: stable 9 | 10 | project_type: plugin 11 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: f139b11009aeb8ed2a3a3aa8b0066e482709dde3 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /android/src/main/java/com/example/document_scanner/helpers/AppConstant.java: -------------------------------------------------------------------------------- 1 | package com.example.document_scanner.helpers; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | 6 | public class AppConstant { 7 | 8 | // supported file formats 9 | public static final List FILE_EXTN = Arrays.asList("jpg", "jpeg", 10 | "png"); 11 | } 12 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #import "AppDelegate.h" 2 | #import "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /android/src/main/java/com/example/document_scanner/helpers/Quadrilateral.java: -------------------------------------------------------------------------------- 1 | package com.example.document_scanner.helpers; 2 | 3 | import org.opencv.core.MatOfPoint; 4 | import org.opencv.core.Point; 5 | 6 | /** 7 | * Created by allgood on 05/03/16. 8 | */ 9 | public class Quadrilateral { 10 | public MatOfPoint contour; 11 | public Point[] points; 12 | 13 | public Quadrilateral(MatOfPoint contour, Point[] points) { 14 | this.contour = contour; 15 | this.points = points; 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/Generated.xcconfig 37 | /Flutter/flutter_export_environment.sh -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/example/document_scanner_example/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.document_scanner_example; 2 | 3 | import androidx.annotation.NonNull; 4 | import io.flutter.embedding.android.FlutterActivity; 5 | import io.flutter.embedding.engine.FlutterEngine; 6 | import io.flutter.plugins.GeneratedPluginRegistrant; 7 | 8 | public class MainActivity extends FlutterActivity { 9 | @Override 10 | public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { 11 | GeneratedPluginRegistrant.registerWith(flutterEngine); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /android/src/main/res/anim/blink.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 14 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.5.0' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | jcenter() 16 | 17 | } 18 | } 19 | 20 | rootProject.buildDir = '../build' 21 | subprojects { 22 | project.buildDir = "${rootProject.buildDir}/${project.name}" 23 | } 24 | subprojects { 25 | project.evaluationDependsOn(':app') 26 | } 27 | 28 | task clean(type: Delete) { 29 | delete rootProject.buildDir 30 | } 31 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # document_scanner_example 2 | 3 | Demonstrates how to use the document_scanner plugin. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /ios/Classes/DocumentScannerFactory.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "DocumentScannerView.h" 3 | 4 | @interface DocumentScannerFactory : NSObject 5 | - (instancetype)initWithRegistrar:(NSObject *)registrar; 6 | @end 7 | 8 | 9 | @interface ScannerController : NSObject < FlutterPlatformView> 10 | @property (strong, nonatomic) DocumentScannerView *scannerView; 11 | -(instancetype)initWithFrame:(CGRect)frame 12 | viewIdentifier:(int64_t)viewId 13 | arguments:(id _Nullable)args 14 | registrar:(NSObject*)registrar ; 15 | @end 16 | -------------------------------------------------------------------------------- /test/document_scanner_test.dart: -------------------------------------------------------------------------------- 1 | // import 'package:flutter/services.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | 4 | void main() { 5 | // const MethodChannel channel = MethodChannel('document_scanner'); 6 | 7 | TestWidgetsFlutterBinding.ensureInitialized(); 8 | 9 | // setUp(() { 10 | // channel.setMockMethodCallHandler((MethodCall methodCall) async { 11 | // return '42'; 12 | // }); 13 | // }); 14 | 15 | // tearDown(() { 16 | // channel.setMockMethodCallHandler(null); 17 | // }); 18 | 19 | // test('getPlatformVersion', () async { 20 | // expect(await DocumentScanner.platformVersion, '42'); 21 | // }); 22 | } 23 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /android/src/main/java/com/example/document_scanner/helpers/OpenNoteMessage.java: -------------------------------------------------------------------------------- 1 | package com.example.document_scanner.helpers; 2 | 3 | /** 4 | * Created by allgood on 05/03/16. 5 | */ 6 | public class OpenNoteMessage { 7 | 8 | private String command; 9 | private Object obj; 10 | 11 | public OpenNoteMessage(String command , Object obj ) { 12 | setObj(obj); 13 | setCommand(command); 14 | } 15 | 16 | 17 | public String getCommand() { 18 | return command; 19 | } 20 | 21 | public void setCommand(String command) { 22 | this.command = command; 23 | } 24 | 25 | public Object getObj() { 26 | return obj; 27 | } 28 | 29 | public void setObj(Object obj) { 30 | this.obj = obj; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | -------------------------------------------------------------------------------- /android/.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 20 | 21 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - document_scanner (0.0.1): 3 | - Flutter 4 | - Flutter (1.0.0) 5 | - flutter_plugin_android_lifecycle (0.0.1): 6 | - Flutter 7 | 8 | DEPENDENCIES: 9 | - document_scanner (from `.symlinks/plugins/document_scanner/ios`) 10 | - Flutter (from `Flutter`) 11 | - flutter_plugin_android_lifecycle (from `.symlinks/plugins/flutter_plugin_android_lifecycle/ios`) 12 | 13 | EXTERNAL SOURCES: 14 | document_scanner: 15 | :path: ".symlinks/plugins/document_scanner/ios" 16 | Flutter: 17 | :path: Flutter 18 | flutter_plugin_android_lifecycle: 19 | :path: ".symlinks/plugins/flutter_plugin_android_lifecycle/ios" 20 | 21 | SPEC CHECKSUMS: 22 | document_scanner: 3b9b9257ec9fe5d7e1f2e1e249c0dd101246d790 23 | Flutter: 0e3d915762c693b495b44d77113d4970485de6ec 24 | flutter_plugin_android_lifecycle: 47de533a02850f070f5696a623995e93eddcdb9b 25 | 26 | PODFILE CHECKSUM: 3dbe063e9c90a5d7c9e4e76e70a821b9e2c1d271 27 | 28 | COCOAPODS: 1.9.3 29 | -------------------------------------------------------------------------------- /android/src/main/java/com/example/document_scanner/helpers/PreviewFrame.java: -------------------------------------------------------------------------------- 1 | package com.example.document_scanner.helpers; 2 | 3 | import org.opencv.core.Mat; 4 | 5 | /** 6 | * Created by allgood on 06/03/16. 7 | */ 8 | public class PreviewFrame { 9 | 10 | private final boolean previewOnly; 11 | private Mat frame; 12 | private boolean autoMode; 13 | 14 | public PreviewFrame(Mat frame , boolean autoMode , boolean previewOnly ) { 15 | this.frame = frame; 16 | this.autoMode = autoMode; 17 | this.previewOnly = previewOnly; 18 | } 19 | 20 | public Mat getFrame() { 21 | return frame; 22 | } 23 | 24 | public void setFrame(Mat frame) { 25 | this.frame = frame; 26 | } 27 | 28 | public boolean isAutoMode() { 29 | return autoMode; 30 | } 31 | 32 | public boolean isPreviewOnly() { 33 | return previewOnly; 34 | } 35 | 36 | public void setAutoMode(boolean autoMode) { 37 | this.autoMode = autoMode; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ios/document_scanner.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. 3 | # Run `pod lib lint document_scanner.podspec' to validate before publishing. 4 | # 5 | Pod::Spec.new do |s| 6 | s.name = 'document_scanner' 7 | s.version = '0.0.1' 8 | s.summary = 'A new flutter plugin project.' 9 | s.description = <<-DESC 10 | A new flutter plugin project. 11 | DESC 12 | s.homepage = 'http://example.com' 13 | s.license = { :file => '../LICENSE' } 14 | s.author = { 'Your Company' => 'email@example.com' } 15 | s.source = { :path => '.' } 16 | s.source_files = 'Classes/**/*' 17 | s.public_header_files = 'Classes/**/*.h' 18 | s.dependency 'Flutter' 19 | s.platform = :ios, '8.0' 20 | 21 | # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported. 22 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } 23 | end 24 | -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:document_scanner_example/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Verify Platform version', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that platform version is retrieved. 19 | expect( 20 | find.byWidgetPredicate( 21 | (Widget widget) => widget is Text && 22 | widget.data!.startsWith('Running on:'), 23 | ), 24 | findsOneWidget, 25 | ); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: document_scanner 2 | description: A plugin that creates a native platform view that scans documents, as a flutter widget. 3 | version: 0.2.1 4 | homepage: https://github.com/eliasteeny/flutter_document_scanner 5 | 6 | environment: 7 | sdk: '>=2.12.0 <3.0.0' 8 | flutter: ">=1.12.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | flutter_plugin_android_lifecycle: ^2.0.2 14 | 15 | dev_dependencies: 16 | flutter_test: 17 | sdk: flutter 18 | 19 | 20 | flutter: 21 | plugin: 22 | platforms: 23 | android: 24 | package: com.example.document_scanner 25 | pluginClass: DocumentScannerPlugin 26 | ios: 27 | pluginClass: DocumentScannerPlugin 28 | # androidPackage: com.example.document_scanner 29 | # pluginClass: DocumentScannerPlugin 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | group 'com.example.document_scanner' 2 | version '1.0' 3 | 4 | buildscript { 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.5.0' 12 | 13 | } 14 | } 15 | 16 | rootProject.allprojects { 17 | repositories { 18 | google() 19 | jcenter() 20 | maven { url 'https://jitpack.io' } 21 | } 22 | } 23 | 24 | apply plugin: 'com.android.library' 25 | 26 | android { 27 | compileSdkVersion 28 28 | 29 | defaultConfig { 30 | minSdkVersion 19 31 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 32 | } 33 | lintOptions { 34 | disable 'InvalidPackage' 35 | } 36 | compileOptions { 37 | sourceCompatibility = 1.8 38 | targetCompatibility = 1.8 39 | } 40 | } 41 | 42 | dependencies { 43 | implementation 'com.google.zxing:core:3.0.1' 44 | implementation 'com.github.eliasteeny:opencv_android_module:3.1.0-fix3' 45 | implementation project(path: ':flutter_plugin_android_lifecycle') 46 | 47 | } 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [year] [fullname] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /ios/Classes/DocumentScannerPlugin.m: -------------------------------------------------------------------------------- 1 | #import "DocumentScannerPlugin.h" 2 | #import "DocumentScannerView.h" 3 | #import "DocumentScannerFactory.h" 4 | 5 | @implementation DocumentScannerPlugin { 6 | NSObject* _registrar; 7 | FlutterMethodChannel* _channel; 8 | NSMutableDictionary* _mapControllers; 9 | } 10 | + (void)registerWithRegistrar:(NSObject*)registrar { 11 | FlutterMethodChannel* channel = [FlutterMethodChannel 12 | methodChannelWithName:@"document_scanner" 13 | binaryMessenger:[registrar messenger]]; 14 | DocumentScannerPlugin* instance = [[DocumentScannerPlugin alloc] init]; 15 | DocumentScannerFactory* factory = [[DocumentScannerFactory alloc] initWithRegistrar:registrar]; 16 | [registrar registerViewFactory:factory withId:@"document_scanner"]; 17 | // [registrar addMethodCallDelegate:instance channel:channel]; 18 | 19 | } 20 | 21 | - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { 22 | if ([@"getPlatformVersion" isEqualToString:call.method]) { 23 | result([@"iOS " stringByAppendingString:[[UIDevice currentDevice] systemVersion]]); 24 | } else { 25 | result(FlutterMethodNotImplemented); 26 | } 27 | } 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.2.1 2 | * Android : added noGrayScale option. 3 | * Android : added opencv jnilibs through jitpack for reduced package size 4 | 5 | ## 0.2.0 6 | * Migrated to null safety. 7 | * Added a bit of documentation. 8 | * Upgraded dependencies to the latest version. 9 | 10 | ## 0.1.2 11 | 12 | * Android : fixed a bug that was sometimes crashing the app after scanning the document. 13 | 14 | ## 0.1.1 15 | 16 | * Android : fixed a bug that caused **onDocumentScanned** getting called with a null document. 17 | * Android : fixed a bug that was sometimes crashing the app. 18 | * Android : changed scanned document image save location from external storage to internal cache directory. 19 | 20 | ## 0.1.0 21 | 22 | * Document Scanner property : **onPictureTaken** name changed to **onDocumentScanned** 23 | * Document Scannner **onDocumentScanned** argument changed from **String** (scanned document location) to **ScannedImage** object that holds more information about the scanned document 24 | * Added **getScannedDocumentAsFile()** method on class **ScannedImage** 25 | 26 | ## 0.0.4 27 | 28 | * made the initial setup for android much easier 29 | 30 | ## 0.0.3 31 | 32 | * added android instructions 33 | 34 | ## 0.0.2 35 | 36 | * added IOS support 37 | 38 | ## 0.0.1 39 | 40 | * inital alpha release 41 | * needs testing for android 42 | -------------------------------------------------------------------------------- /android/.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Classes/IPDFCameraViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // IPDFCameraViewController.h 3 | // InstaPDF 4 | // 5 | // Created by Maximilian Mackh on 06/01/15. 6 | // Copyright (c) 2015 mackh ag. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef NS_ENUM(NSInteger,IPDFCameraViewType) 12 | { 13 | IPDFCameraViewTypeBlackAndWhite, 14 | IPDFCameraViewTypeNormal 15 | }; 16 | 17 | typedef NS_ENUM(NSInteger, IPDFRectangeType) 18 | { 19 | IPDFRectangeTypeGood, 20 | IPDFRectangeTypeBadAngle, 21 | IPDFRectangeTypeTooFar 22 | }; 23 | 24 | @protocol IPDFCameraViewControllerDelegate 25 | 26 | - (void) didDetectRectangle: (CIRectangleFeature*) rectangle withType: (IPDFRectangeType) type; 27 | 28 | @end 29 | 30 | @interface IPDFCameraViewController : UIView 31 | 32 | - (void)setupCameraView; 33 | 34 | - (void)start; 35 | - (void)stop; 36 | 37 | @property (nonatomic,assign,getter=isBorderDetectionEnabled) BOOL enableBorderDetection; 38 | @property (nonatomic,assign,getter=isTorchEnabled) BOOL enableTorch; 39 | @property (nonatomic,assign,getter=isFrontCam) BOOL useFrontCam; 40 | 41 | @property (weak, nonatomic) id delegate; 42 | 43 | @property (nonatomic,assign) IPDFCameraViewType cameraViewType; 44 | 45 | - (void)focusAtPoint:(CGPoint)point completionHandler:(void(^)(void))completionHandler; 46 | 47 | - (void)captureImageWithCompletionHander:(void(^)(UIImage *data, UIImage *initialData, CIRectangleFeature *rectangleFeature))completionHandler; 48 | 49 | @property (nonatomic, strong) UIColor* overlayColor; 50 | @property (nonatomic, assign) float saturation; 51 | @property (nonatomic, assign) float contrast; 52 | @property (nonatomic, assign) float brightness; 53 | @property (nonatomic, assign) NSInteger detectionRefreshRateInMS; 54 | 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 9 | 10 | 15 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSCameraUsageDescription 6 | Camera Permission Description 7 | io.flutter.embedded_views_preview 8 | 9 | CFBundleDevelopmentRegion 10 | $(DEVELOPMENT_LANGUAGE) 11 | CFBundleExecutable 12 | $(EXECUTABLE_NAME) 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | document_scanner_example 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | $(FLUTTER_BUILD_NAME) 23 | CFBundleSignature 24 | ???? 25 | CFBundleVersion 26 | $(FLUTTER_BUILD_NUMBER) 27 | LSRequiresIPhoneOS 28 | 29 | UILaunchStoryboardName 30 | LaunchScreen 31 | UIMainStoryboardFile 32 | Main 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | UIViewControllerBasedStatusBarAppearance 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # document_scanner 2 | 3 | A platform view plugin for Flutter apps that adds document scanning functionality on Android/IOS. 4 | 5 | ## Warning 6 | Document Scanner customization options aren't working for now 7 | 8 | 9 | ## Setup 10 | 11 | Handle camera access permission 12 | 13 | ### **IOS** 14 | 15 | Add a boolean property to the app's Info.plist file with the key io.flutter.embedded_views_preview and the value true to enable embedded views preview. 16 | 17 | io.flutter.embedded_views_preview 18 | 19 | 20 | Add a String property to the app's Info.plist file with the key NSCameraUsageDescription and the value as the description for why your app needs camera access. 21 | 22 | NSCameraUsageDescription 23 | Camera Permission Description 24 | 25 | ### **Android** 26 | 27 | minSdkVersion should be at least 19 28 | 29 | 30 | 31 | ## How to use ? 32 | 33 | first add as a dependency in pubspec.yaml 34 | 35 | import: 36 | 37 | ``` 38 | import 'package:document_scanner/document_scanner.dart'; 39 | ``` 40 | 41 | then use it as a widget: 42 | ``` 43 | File scannedDocument; 44 | 45 | DocumentScanner( 46 | onDocumentScanned: (ScannedImage scannedImage) { 47 | print("document : " + scannedImage.croppedImage); 48 | scannedDocument = scannedImage.getScannedDocumentAsFile(); 49 | }, 50 | 51 | ) 52 | ``` 53 | 54 | ## Credits 55 | 56 | This is a React Native package implemented on Flutter. 57 | 58 | Reference to the React Native package : https://github.com/Michaelvilleneuve/react-native-document-scanner 59 | 60 | ## Contributing 61 | 62 | ### Step 1 63 | 64 | - Fork this project's repo : https://github.com/eliasteeny/flutter_document_scanner 65 | 66 | ### Step 2 67 | 68 | - Create a new pull request. 69 | 70 | 71 | 72 | ## License 73 | This project is licensed under the MIT License - see the LICENSE.md file for details 74 | 75 | -------------------------------------------------------------------------------- /ios/Classes/DocumentScannerView.h: -------------------------------------------------------------------------------- 1 | #import "IPDFCameraViewController.h" 2 | #import 3 | 4 | 5 | //@interface DocumentScannerView : IPDFCameraViewController 6 | // 7 | ////@property (nonatomic, copy) RCTDirectEventBlock onPictureTaken; 8 | ////@property (nonatomic, copy) RCTDirectEventBlock onRectangleDetect; 9 | //@property (nonatomic, assign) NSInteger detectionCountBeforeCapture; 10 | //@property (assign, nonatomic) NSInteger stableCounter; 11 | //@property (nonatomic, assign) float quality; 12 | //@property (nonatomic, assign) BOOL useBase64; 13 | //@property (nonatomic, assign) BOOL captureMultiple; 14 | //@property (nonatomic, assign) BOOL saveInAppDocument; 15 | //-(void)onPictureTaken; 16 | //-(void) onRectangleDetect; 17 | //-(void) capture; 18 | //@end 19 | // 20 | // 21 | // 22 | //#import "IPDFCameraViewController.h" 23 | 24 | 25 | @interface DocumentScannerView : IPDFCameraViewController 26 | 27 | //@property (nonatomic, copy) RCTBubblingEventBlock onPictureTaken; 28 | //@property (nonatomic, copy) RCTBubblingEventBlock onRectangleDetect; 29 | @property (nonatomic, assign) NSInteger detectionCountBeforeCapture; 30 | @property (nonatomic, assign) NSInteger stableCounter; 31 | @property (nonatomic, assign) double durationBetweenCaptures; 32 | @property (nonatomic, assign) double lastCaptureTime; 33 | @property (nonatomic, assign) float quality; 34 | @property (nonatomic, assign) BOOL useBase64; 35 | @property (nonatomic, assign) BOOL captureMultiple; 36 | @property (nonatomic, assign) BOOL saveInAppDocument; 37 | @property (nonatomic, assign) FlutterMethodChannel* flutterChannel; 38 | 39 | -(instancetype) init : (float)channelBrightness contrast: (float)channelContrast; 40 | 41 | - (instancetype)initWithChannel : (FlutterMethodChannel*) channel; 42 | 43 | //- (instancetype)initWithChannelAndArgs : (FlutterMethodChannel*) channel brightness:(float)channelBrightness contrast: (float)channelContrast; 44 | 45 | - (void) capture ; 46 | - (void) onPictureTaken: (NSDictionary*) result; 47 | - (void) onRectangleDetect; 48 | //- (void) setChannelBrightness:(float)brightness; 49 | //- (void) setChannelContrast:(float)contrast; 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: document_scanner_example 2 | description: Demonstrates how to use the document_scanner plugin. 3 | publish_to: 'none' 4 | 5 | environment: 6 | sdk: '>=2.12.0 <3.0.0' 7 | 8 | dependencies: 9 | flutter: 10 | sdk: flutter 11 | permission_handler: ^8.0.0+2 12 | 13 | # The following adds the Cupertino Icons font to your application. 14 | # Use with the CupertinoIcons class for iOS style icons. 15 | cupertino_icons: ^1.0.3 16 | 17 | dev_dependencies: 18 | flutter_test: 19 | sdk: flutter 20 | 21 | document_scanner: 22 | path: ../ 23 | 24 | # For information on the generic Dart part of this file, see the 25 | # following page: https://dart.dev/tools/pub/pubspec 26 | 27 | # The following section is specific to Flutter. 28 | flutter: 29 | 30 | # The following line ensures that the Material Icons font is 31 | # included with your application, so that you can use the icons in 32 | # the material Icons class. 33 | uses-material-design: true 34 | 35 | # To add assets to your application, add an assets section, like this: 36 | # assets: 37 | # - images/a_dot_burr.jpeg 38 | # - images/a_dot_ham.jpeg 39 | 40 | # An image asset can refer to one or more resolution-specific "variants", see 41 | # https://flutter.dev/assets-and-images/#resolution-aware. 42 | 43 | # For details regarding adding assets from package dependencies, see 44 | # https://flutter.dev/assets-and-images/#from-packages 45 | 46 | # To add custom fonts to your application, add a fonts section here, 47 | # in this "flutter" section. Each entry in this list should have a 48 | # "family" key with the font family name, and a "fonts" key with a 49 | # list giving the asset and other descriptors for the font. For 50 | # example: 51 | # fonts: 52 | # - family: Schyler 53 | # fonts: 54 | # - asset: fonts/Schyler-Regular.ttf 55 | # - asset: fonts/Schyler-Italic.ttf 56 | # style: italic 57 | # - family: Trajan Pro 58 | # fonts: 59 | # - asset: fonts/TrajanPro.ttf 60 | # - asset: fonts/TrajanPro_Bold.ttf 61 | # weight: 700 62 | # 63 | # For details regarding fonts from package dependencies, 64 | # see https://flutter.dev/custom-fonts/#from-packages 65 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | 27 | android { 28 | compileSdkVersion 28 29 | 30 | lintOptions { 31 | disable 'InvalidPackage' 32 | } 33 | 34 | defaultConfig { 35 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 36 | applicationId "com.example.document_scanner_example" 37 | minSdkVersion 19 38 | targetSdkVersion 28 39 | versionCode flutterVersionCode.toInteger() 40 | versionName flutterVersionName 41 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 42 | } 43 | 44 | buildTypes { 45 | release { 46 | // TODO: Add your own signing config for the release build. 47 | // Signing with the debug keys for now, so `flutter run --release` works. 48 | signingConfig signingConfigs.debug 49 | } 50 | } 51 | } 52 | 53 | flutter { 54 | source '../..' 55 | } 56 | 57 | dependencies { 58 | testImplementation 'junit:junit:4.12' 59 | androidTestImplementation 'androidx.test:runner:1.1.1' 60 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 61 | implementation 'androidx.appcompat:appcompat:1.1.0' 62 | implementation 'com.google.zxing:core:3.0.1' 63 | implementation 'org.piwik.sdk:piwik-sdk:0.0.4' 64 | implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.5' 65 | implementation 'us.feras.mdv:markdownview:1.1.0' 66 | 67 | } 68 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /android/src/main/java/com/example/document_scanner/helpers/ScannedDocument.java: -------------------------------------------------------------------------------- 1 | package com.example.document_scanner.helpers; 2 | 3 | 4 | 5 | import org.opencv.core.Mat; 6 | import org.opencv.core.Point; 7 | import org.opencv.core.Size; 8 | 9 | import java.util.HashMap; 10 | 11 | /** 12 | * Created by allgood on 05/03/16. 13 | */ 14 | public class ScannedDocument { 15 | 16 | public Mat original; 17 | public Mat processed; 18 | public Quadrilateral quadrilateral; 19 | public Point[] previewPoints; 20 | public Size previewSize; 21 | public Size originalSize; 22 | 23 | public Point[] originalPoints; 24 | 25 | public int heightWithRatio; 26 | public int widthWithRatio; 27 | 28 | public ScannedDocument(Mat original) { 29 | this.original = original; 30 | } 31 | 32 | public Mat getProcessed() { 33 | return processed; 34 | } 35 | 36 | public ScannedDocument setProcessed(Mat processed) { 37 | this.processed = processed; 38 | return this; 39 | } 40 | 41 | public HashMap previewPointsAsHash() { 42 | if (this.previewPoints == null) return null; 43 | HashMap rectangleCoordinates = new HashMap(); 44 | 45 | HashMap topLeft = new HashMap(); 46 | topLeft.put("x", this.originalPoints[0].x); 47 | topLeft.put("y", this.originalPoints[0].y); 48 | 49 | HashMap topRight = new HashMap(); 50 | topRight.put("x", this.originalPoints[1].x); 51 | topRight.put("y", this.originalPoints[1].y); 52 | 53 | HashMap bottomRight = new HashMap(); 54 | bottomRight.put("x", this.originalPoints[2].x); 55 | bottomRight.put("y", this.originalPoints[2].y); 56 | 57 | HashMap bottomLeft = new HashMap(); 58 | bottomLeft.put("x", this.originalPoints[3].x); 59 | bottomLeft.put("y", this.originalPoints[3].y); 60 | 61 | 62 | 63 | rectangleCoordinates.put("topLeft", topLeft); 64 | rectangleCoordinates.put("topRight", topRight); 65 | rectangleCoordinates.put("bottomRight", bottomRight); 66 | rectangleCoordinates.put("bottomLeft", bottomLeft); 67 | 68 | return rectangleCoordinates; 69 | } 70 | 71 | public void release() { 72 | if (processed != null) { 73 | processed.release(); 74 | } 75 | if (original != null) { 76 | original.release(); 77 | } 78 | 79 | if (quadrilateral != null && quadrilateral.contour != null) { 80 | quadrilateral.contour.release(); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /android/src/main/java/com/example/document_scanner/views/MainView.java: -------------------------------------------------------------------------------- 1 | package com.example.document_scanner.views; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.view.LayoutInflater; 6 | import android.widget.FrameLayout; 7 | 8 | import com.example.document_scanner.R; 9 | 10 | /** 11 | * Created by andre on 09/01/2018. 12 | */ 13 | 14 | public class MainView extends FrameLayout { 15 | private OpenNoteCameraView view = null; 16 | private FrameLayout frameLayout = null; 17 | 18 | public static MainView instance = null; 19 | 20 | public static MainView getInstance() { 21 | return instance; 22 | } 23 | 24 | public static void createInstance(Context context, Activity activity) { 25 | instance = new MainView(context, activity); 26 | } 27 | 28 | private MainView(Context context, Activity activity) { 29 | super(context); 30 | 31 | LayoutInflater lf = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 32 | this.frameLayout = (FrameLayout) lf.inflate(R.layout.activity_open_note_scanner, null); 33 | // OpenNoteCameraView.createInstance(context, -1, activity, frameLayout); 34 | 35 | view = new OpenNoteCameraView(context, -1, activity, frameLayout); 36 | addViewInLayout(view, 0, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); 37 | addViewInLayout(frameLayout, 1, view.getLayoutParams()); 38 | } 39 | 40 | @Override 41 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 42 | for (int i = 0; i < getChildCount(); i++) { 43 | getChildAt(i).layout(l, t, r, b); 44 | } 45 | } 46 | 47 | public void setDocumentAnimation(boolean animate) { 48 | view.setDocumentAnimation(animate); 49 | } 50 | 51 | public void setDetectionCountBeforeCapture(int numberOfRectangles) { 52 | view.setDetectionCountBeforeCapture(numberOfRectangles); 53 | } 54 | 55 | public void setEnableTorch(boolean enable) { 56 | view.setEnableTorch(enable); 57 | } 58 | 59 | public void setOnScannerListener(OpenNoteCameraView.OnScannerListener listener) { 60 | view.setOnScannerListener(listener); 61 | } 62 | 63 | public void removeOnScannerListener() { 64 | view.removeOnScannerListener(); 65 | } 66 | 67 | public void setOnProcessingListener(OpenNoteCameraView.OnProcessingListener listener) { 68 | view.setOnProcessingListener(listener); 69 | } 70 | 71 | public void removeOnProcessingListener() { 72 | view.removeOnProcessingListener(); 73 | } 74 | 75 | public void setOverlayColor(String rgbaColor) { 76 | view.setOverlayColor(rgbaColor); 77 | } 78 | 79 | public void setBrightness(double brightness) { 80 | view.setBrightness(brightness); 81 | } 82 | 83 | public void setContrast(double contrast) { 84 | view.setContrast(contrast); 85 | } 86 | 87 | public void setManualOnly(boolean manualOnly) { 88 | view.setManualOnly(manualOnly); 89 | } 90 | 91 | public void setRemoveGrayScale(boolean grayscale) { 92 | view.setRemoveGrayScale(grayscale); 93 | } 94 | 95 | public void capture() { 96 | view.capture(); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /android/src/main/java/com/example/document_scanner/views/HUDCanvasView.java: -------------------------------------------------------------------------------- 1 | package com.example.document_scanner.views; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Paint; 6 | import android.graphics.drawable.shapes.Shape; 7 | import android.util.AttributeSet; 8 | import android.view.View; 9 | 10 | import java.util.ArrayList; 11 | 12 | /** 13 | * Draw an array of shapes on a canvas 14 | * 15 | * @author http://todobom.com 16 | */ 17 | public class HUDCanvasView extends View { 18 | 19 | private ArrayList shapes = new ArrayList<>(); 20 | 21 | public HUDCanvasView(Context context) { 22 | super(context); 23 | } 24 | 25 | public HUDCanvasView(Context context, AttributeSet attrs) { 26 | super(context, attrs); 27 | } 28 | 29 | public HUDCanvasView(Context context, AttributeSet attrs, int defStyle) { 30 | super(context, attrs, defStyle); 31 | } 32 | 33 | 34 | public class HUDShape { 35 | private final Shape mShape; 36 | private final Paint mPaint; 37 | private final Paint mBorder; 38 | 39 | public HUDShape(Shape shape , Paint paint ) { 40 | mShape = shape; 41 | mPaint = paint; 42 | mBorder = null; 43 | } 44 | 45 | public HUDShape(Shape shape , Paint paint , Paint border ) { 46 | mShape = shape; 47 | mPaint = paint; 48 | mBorder = border; 49 | mBorder.setStyle(Paint.Style.STROKE); 50 | } 51 | 52 | public void draw ( Canvas canvas ) { 53 | mShape.draw(canvas,mPaint); 54 | 55 | if (mBorder != null) { 56 | mShape.draw(canvas,mBorder); 57 | } 58 | } 59 | 60 | public Shape getShape() { 61 | return mShape; 62 | } 63 | } 64 | 65 | @Override 66 | protected void onDraw(Canvas canvas) { 67 | super.onDraw(canvas); 68 | 69 | // TODO: consider storing these as member variables to reduce 70 | // allocations per draw cycle. 71 | int paddingLeft = getPaddingLeft(); 72 | int paddingTop = getPaddingTop(); 73 | int paddingRight = getPaddingRight(); 74 | int paddingBottom = getPaddingBottom(); 75 | 76 | int contentWidth = getWidth() - paddingLeft - paddingRight; 77 | int contentHeight = getHeight() - paddingTop - paddingBottom; 78 | 79 | for ( HUDShape s: shapes ) { 80 | s.getShape().resize(contentWidth, contentHeight); 81 | s.draw(canvas); 82 | } 83 | 84 | } 85 | 86 | public HUDShape addShape(Shape shape , Paint paint ) { 87 | HUDShape hudShape = new HUDShape(shape, paint); 88 | shapes.add( hudShape ); 89 | return hudShape; 90 | } 91 | 92 | public HUDShape addShape(Shape shape , Paint paint , Paint border ) { 93 | HUDShape hudShape = new HUDShape(shape, paint , border ); 94 | shapes.add(hudShape); 95 | return hudShape; 96 | } 97 | 98 | public void removeShape(HUDShape shape) { 99 | shapes.remove(shape); 100 | } 101 | 102 | public void removeShape(int index) { 103 | shapes.remove(index); 104 | } 105 | 106 | public void clear() { 107 | shapes.clear(); 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | import 'package:document_scanner/document_scanner.dart'; 6 | import 'package:permission_handler/permission_handler.dart'; 7 | 8 | void main() => runApp(MyApp()); 9 | 10 | class MyApp extends StatefulWidget { 11 | @override 12 | _MyAppState createState() => _MyAppState(); 13 | } 14 | 15 | class _MyAppState extends State { 16 | File? scannedDocument; 17 | Future? cameraPermissionFuture; 18 | 19 | @override 20 | void initState() { 21 | cameraPermissionFuture = Permission.camera.request(); 22 | super.initState(); 23 | } 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return MaterialApp( 28 | home: Scaffold( 29 | appBar: AppBar( 30 | title: const Text('Plugin example app'), 31 | ), 32 | body: FutureBuilder( 33 | future: cameraPermissionFuture, 34 | builder: (BuildContext context, 35 | AsyncSnapshot snapshot) { 36 | if (snapshot.connectionState == ConnectionState.done) { 37 | if (snapshot.data!.isGranted) 38 | return Stack( 39 | children: [ 40 | Column( 41 | children: [ 42 | Expanded( 43 | child: scannedDocument != null 44 | ? Image( 45 | image: FileImage(scannedDocument!), 46 | ) 47 | : DocumentScanner( 48 | // documentAnimation: false, 49 | noGrayScale: true, 50 | onDocumentScanned: 51 | (ScannedImage scannedImage) { 52 | print("document : " + 53 | scannedImage.croppedImage!); 54 | 55 | setState(() { 56 | scannedDocument = scannedImage 57 | .getScannedDocumentAsFile(); 58 | // imageLocation = image; 59 | }); 60 | }, 61 | ), 62 | ), 63 | ], 64 | ), 65 | scannedDocument != null 66 | ? Positioned( 67 | bottom: 20, 68 | left: 0, 69 | right: 0, 70 | child: RaisedButton( 71 | child: Text("retry"), 72 | onPressed: () { 73 | setState(() { 74 | scannedDocument = null; 75 | }); 76 | }), 77 | ) 78 | : Container(), 79 | ], 80 | ); 81 | else 82 | return Center( 83 | child: Text("camera permission denied"), 84 | ); 85 | } else { 86 | return Center( 87 | child: CircularProgressIndicator(), 88 | ); 89 | } 90 | }, 91 | )), 92 | ); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /android/src/main/java/com/example/document_scanner/DocumentScannerFactory.java: -------------------------------------------------------------------------------- 1 | package com.example.document_scanner; 2 | 3 | import android.app.Activity; 4 | import android.app.Application; 5 | import android.content.Context; 6 | import android.content.ContextWrapper; 7 | import android.util.Log; 8 | 9 | import androidx.lifecycle.Lifecycle; 10 | 11 | import java.util.Map; 12 | import java.util.concurrent.atomic.AtomicInteger; 13 | 14 | import io.flutter.plugin.common.BinaryMessenger; 15 | import io.flutter.plugin.common.MethodChannel; 16 | import io.flutter.plugin.common.PluginRegistry; 17 | import io.flutter.plugin.common.StandardMessageCodec; 18 | import io.flutter.plugin.platform.PlatformView; 19 | import io.flutter.plugin.platform.PlatformViewFactory; 20 | 21 | public class DocumentScannerFactory extends PlatformViewFactory { 22 | private final AtomicInteger mActivityState; 23 | private final BinaryMessenger binaryMessenger; 24 | private final Application application; 25 | private final int activityHashCode; 26 | private final Lifecycle lifecycle; 27 | private final PluginRegistry.Registrar registrar; // V1 embedding only. 28 | private final Activity activity; 29 | private final MethodChannel channel; 30 | DocumentScannerFactory( 31 | AtomicInteger state, 32 | BinaryMessenger binaryMessenger, 33 | Application application, 34 | Lifecycle lifecycle, 35 | PluginRegistry.Registrar registrar, 36 | int activityHashCode,Activity activity, 37 | MethodChannel channel) { 38 | super(StandardMessageCodec.INSTANCE); 39 | mActivityState = state; 40 | this.binaryMessenger = binaryMessenger; 41 | this.application = application; 42 | this.activityHashCode = activityHashCode; 43 | this.lifecycle = lifecycle; 44 | this.registrar = registrar; 45 | this.activity = activity; 46 | this.channel = channel; 47 | } 48 | 49 | @SuppressWarnings("unchecked") 50 | @Override 51 | public PlatformView create(Context context, int id, Object args) { 52 | Map params = (Map) args; 53 | // final GoogleMapBuilder builder = new GoogleMapBuilder(); 54 | 55 | // Convert.interpretGoogleMapOptions(params.get("options"), builder); 56 | // if (params.containsKey("initialCameraPosition")) { 57 | // 58 | //// builder.setInitialCameraPosition(position); 59 | // } 60 | // if (params.containsKey("markersToAdd")) { 61 | //// builder.setInitialMarkers(params.get("markersToAdd")); 62 | // } 63 | // if (params.containsKey("polygonsToAdd")) { 64 | //// builder.setInitialPolygons(params.get("polygonsToAdd")); 65 | // } 66 | // if (params.containsKey("polylinesToAdd")) { 67 | //// builder.setInitialPolylines(params.get("polylinesToAdd")); 68 | // } 69 | // if (params.containsKey("circlesToAdd")) { 70 | //// builder.setInitialCircles(params.get("circlesToAdd")); 71 | // } 72 | 73 | return new DocumentScannerViewManager(activity.getApplicationContext(),activity ,params,channel); 74 | } 75 | 76 | public Activity getActivity(Context context) 77 | { 78 | if (context == null) 79 | { 80 | Log.d("debug", "context null"); 81 | return null; 82 | } 83 | else if (context instanceof ContextWrapper) 84 | { 85 | if (context instanceof Activity) 86 | { 87 | Log.d("debug", "context found 1"); 88 | return (Activity) context; 89 | } 90 | else 91 | { 92 | Log.d("debug", "context found 2"); 93 | return getActivity(((ContextWrapper) context).getBaseContext()); 94 | } 95 | } 96 | Log.d("debug", "returned null"); 97 | return null; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /lib/scannedImage.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'dart:io'; 4 | 5 | class ScannedImage { 6 | ScannedImage({ 7 | // this.rectangleCoordinates, 8 | this.croppedImage, 9 | this.width, 10 | this.initialImage, 11 | this.height, 12 | }); 13 | 14 | // RectangleCoordinates rectangleCoordinates; 15 | /// Cropped image (scanned document) file location. 16 | String? croppedImage; 17 | 18 | /// Cropped image (scanned document) width. 19 | int? width; 20 | 21 | /// The initial image before cropping the document. 22 | String? initialImage; 23 | 24 | /// Cropped image (scanned document) height. 25 | int? height; 26 | 27 | /// Parse a JSON message that is returned from platform channel. 28 | factory ScannedImage.fromJson(String str) => 29 | ScannedImage.fromMap(json.decode(str)); 30 | 31 | // String toJson() => json.encode(toMap()); 32 | 33 | /// Convert a map (json) to a ScannedImage instance. 34 | factory ScannedImage.fromMap(Map json) => ScannedImage( 35 | // rectangleCoordinates: json["rectangleCoordinates"] == null 36 | // ? null 37 | // : RectangleCoordinates.fromMap(json["rectangleCoordinates"]), 38 | croppedImage: 39 | json["croppedImage"] == null ? null : json["croppedImage"], 40 | width: json["width"] == null ? null : json["width"], 41 | initialImage: 42 | json["initialImage"] == null ? null : json["initialImage"], 43 | height: json["height"] == null ? null : json["height"], 44 | ); 45 | 46 | // Map toMap() => { 47 | // // "rectangleCoordinates": rectangleCoordinates == null ? null : rectangleCoordinates.toMap(), 48 | // "croppedImage": croppedImage == null ? null : croppedImage, 49 | // "width": width == null ? null : width, 50 | // "initialImage": initialImage == null ? null : initialImage, 51 | // "height": height == null ? null : height, 52 | // }; 53 | 54 | /// Get croppedImage (scanned document) as File. 55 | File getScannedDocumentAsFile() => File.fromUri( 56 | Uri.parse(croppedImage!), 57 | ); 58 | } 59 | 60 | // class RectangleCoordinates { 61 | // RectangleCoordinates({ 62 | // this.bottomLeft, 63 | // this.bottomRight, 64 | // this.topLeft, 65 | // this.topRight, 66 | // }); 67 | 68 | // Corner bottomLeft; 69 | // Corner bottomRight; 70 | // Corner topLeft; 71 | // Corner topRight; 72 | 73 | // factory RectangleCoordinates.fromJson(String str) => 74 | // RectangleCoordinates.fromMap(json.decode(str)); 75 | 76 | // // String toJson() => json.encode(toMap()); 77 | 78 | // factory RectangleCoordinates.fromMap(Map json) => 79 | // RectangleCoordinates( 80 | // bottomLeft: json["bottomLeft"] == null 81 | // ? null 82 | // : Corner.fromMap(json["bottomLeft"]), 83 | // bottomRight: json["bottomRight"] == null 84 | // ? null 85 | // : Corner.fromMap(json["bottomRight"]), 86 | // topLeft: 87 | // json["topLeft"] == null ? null : Corner.fromMap(json["topLeft"]), 88 | // topRight: 89 | // json["topRight"] == null ? null : Corner.fromMap(json["topRight"]), 90 | // ); 91 | 92 | // // Map toMap() => { 93 | // // "bottomLeft": bottomLeft == null ? null : bottomLeft.toMap(), 94 | // // "bottomRight": bottomRight == null ? null : bottomRight.toMap(), 95 | // // "topLeft": topLeft == null ? null : topLeft.toMap(), 96 | // // "topRight": topRight == null ? null : topRight.toMap(), 97 | // // }; 98 | // } 99 | 100 | // class Corner { 101 | // Corner({ 102 | // this.x, 103 | // this.y, 104 | // }); 105 | 106 | // double x; 107 | // double y; 108 | 109 | // factory Corner.fromJson(String str) => Corner.fromMap(json.decode(str)); 110 | 111 | // // String toJson() => json.encode(toMap()); 112 | 113 | // factory Corner.fromMap(Map json) => Corner( 114 | // x: json["x"] == null ? null : json["x"], 115 | // y: json["y"] == null ? null : json["y"], 116 | // ); 117 | 118 | // // Map toMap() => { 119 | // // "x": x == null ? null : x, 120 | // // "y": y == null ? null : y, 121 | // // }; 122 | // } 123 | -------------------------------------------------------------------------------- /android/src/main/res/layout/activity_open_note_scanner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 15 | 16 | 19 | 20 | 31 | 32 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 65 | 66 | 77 | 78 | 87 | 88 | 89 | 90 | 96 | 97 | 101 | 102 | 103 | 104 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def parse_KV_file(file, separator='=') 14 | file_abs_path = File.expand_path(file) 15 | if !File.exists? file_abs_path 16 | return []; 17 | end 18 | generated_key_values = {} 19 | skip_line_start_symbols = ["#", "/"] 20 | File.foreach(file_abs_path) do |line| 21 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 22 | plugin = line.split(pattern=separator) 23 | if plugin.length == 2 24 | podname = plugin[0].strip() 25 | path = plugin[1].strip() 26 | podpath = File.expand_path("#{path}", file_abs_path) 27 | generated_key_values[podname] = podpath 28 | else 29 | puts "Invalid plugin specification: #{line}" 30 | end 31 | end 32 | generated_key_values 33 | end 34 | 35 | target 'Runner' do 36 | # Flutter Pod 37 | 38 | copied_flutter_dir = File.join(__dir__, 'Flutter') 39 | copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') 40 | copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') 41 | unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) 42 | # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. 43 | # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. 44 | # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. 45 | 46 | generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') 47 | unless File.exist?(generated_xcode_build_settings_path) 48 | raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" 49 | end 50 | generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) 51 | cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; 52 | 53 | unless File.exist?(copied_framework_path) 54 | FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) 55 | end 56 | unless File.exist?(copied_podspec_path) 57 | FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) 58 | end 59 | end 60 | 61 | # Keep pod path relative so it can be checked into Podfile.lock. 62 | pod 'Flutter', :path => 'Flutter' 63 | 64 | # Plugin Pods 65 | 66 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 67 | # referring to absolute paths on developers' machines. 68 | system('rm -rf .symlinks') 69 | system('mkdir -p .symlinks/plugins') 70 | plugin_pods = parse_KV_file('../.flutter-plugins') 71 | plugin_pods.each do |name, path| 72 | symlink = File.join('.symlinks', 'plugins', name) 73 | File.symlink(path, symlink) 74 | pod name, :path => File.join(symlink, 'ios') 75 | end 76 | end 77 | 78 | # Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. 79 | install! 'cocoapods', :disable_input_output_paths => true 80 | 81 | post_install do |installer| 82 | installer.pods_project.targets.each do |target| 83 | target.build_configurations.each do |config| 84 | config.build_settings['ENABLE_BITCODE'] = 'NO' 85 | 86 | config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ 87 | '$(inherited)', 88 | 89 | ## dart: PermissionGroup.calendar 90 | 'PERMISSION_EVENTS=0', 91 | 92 | ## dart: PermissionGroup.reminders 93 | 'PERMISSION_REMINDERS=0', 94 | 95 | ## dart: PermissionGroup.contacts 96 | 'PERMISSION_CONTACTS=0', 97 | 98 | ## dart: PermissionGroup.camera 99 | # 'PERMISSION_CAMERA=0', 100 | 101 | ## dart: PermissionGroup.microphone 102 | 'PERMISSION_MICROPHONE=0', 103 | 104 | ## dart: PermissionGroup.speech 105 | 'PERMISSION_SPEECH_RECOGNIZER=0', 106 | 107 | ## dart: PermissionGroup.photos 108 | 'PERMISSION_PHOTOS=0', 109 | 110 | ## dart: [PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse] 111 | 'PERMISSION_LOCATION=0', 112 | 113 | ## dart: PermissionGroup.notification 114 | 'PERMISSION_NOTIFICATIONS=0', 115 | 116 | ## dart: PermissionGroup.mediaLibrary 117 | 'PERMISSION_MEDIA_LIBRARY=0', 118 | 119 | ## dart: PermissionGroup.sensors 120 | 'PERMISSION_SENSORS=0' 121 | ] 122 | end 123 | end 124 | end 125 | -------------------------------------------------------------------------------- /lib/document_scanner.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io' show Platform; 3 | 4 | import 'package:document_scanner/scannedImage.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter/services.dart'; 7 | 8 | export 'package:document_scanner/scannedImage.dart'; 9 | 10 | const String _methodChannelIdentifier = 'document_scanner'; 11 | 12 | /// Document scanner Platform view. 13 | /// 14 | /// Creates a platform specific (only Android and iOS) UI view that displays the device's camera and attempts to detect documents. 15 | /// When a document is detected, [onDocumentScanned] is called with an instance of [ScannedImage]. 16 | /// The whole image is saved and it's url is returned as [scannedDocument.initialImage]. 17 | /// The document is cropped and saved and it's url is returned as [scannedDocument.croppedImage]. 18 | /// ```dart 19 | /// DocumentScanner( 20 | /// onDocumentScanned: (ScannedImage scannedImage) { 21 | /// print("document : " + scannedImage.croppedImage!); 22 | /// }, 23 | ///) 24 | /// ``` 25 | class DocumentScanner extends StatefulWidget { 26 | /// onDocumentScanned gets called when the scanner successfully scans a rectangle (document) 27 | final Function(ScannedImage) onDocumentScanned; 28 | 29 | // final bool documentAnimation; 30 | // final String overlayColor; 31 | // final int detectionCountBeforeCapture; 32 | // final int detectionRefreshRateInMS; 33 | // final bool enableTorch; 34 | // final bool useFrontCam; 35 | // final double brightness; 36 | // final double saturation; 37 | // final double contrast; 38 | // final double quality; 39 | // final bool useBase64; 40 | // final bool saveInAppDocument; 41 | // final bool captureMultiple; 42 | // final bool manualOnly; 43 | final bool noGrayScale; 44 | 45 | DocumentScanner({ 46 | required this.onDocumentScanned, 47 | // this.documentAnimation = true, 48 | // this.overlayColor, 49 | // this.detectionCountBeforeCapture, 50 | // this.detectionRefreshRateInMS, 51 | // this.enableTorch, 52 | // this.useFrontCam, 53 | // this.brightness, 54 | // this.saturation, 55 | // this.contrast, 56 | // this.quality, 57 | // this.useBase64, 58 | // this.saveInAppDocument, 59 | // this.captureMultiple, 60 | // this.manualOnly, 61 | this.noGrayScale = true, 62 | }); 63 | 64 | final MethodChannel _channel = const MethodChannel(_methodChannelIdentifier); 65 | 66 | @override 67 | _DocState createState() => _DocState(); 68 | } 69 | 70 | class _DocState extends State { 71 | @override 72 | void initState() { 73 | print("initializing document scanner state"); 74 | widget._channel.setMethodCallHandler(_onDocumentScanned); 75 | super.initState(); 76 | } 77 | 78 | Future _onDocumentScanned(MethodCall call) async { 79 | if (call.method == "onPictureTaken") { 80 | Map argsAsMap = 81 | Map.from(call.arguments); 82 | 83 | ScannedImage scannedImage = ScannedImage.fromMap(argsAsMap); 84 | 85 | // ScannedImage scannedImage = ScannedImage( 86 | // croppedImage: argsAsMap["croppedImage"], 87 | // initialImage: argsAsMap["initialImage"]); 88 | 89 | // print("scanned image decoded"); 90 | // print(scannedImage.toJson()); 91 | 92 | if (scannedImage.croppedImage != null) { 93 | // print("scanned image not null"); 94 | widget.onDocumentScanned(scannedImage); 95 | } 96 | } 97 | 98 | return; 99 | } 100 | 101 | @override 102 | Widget build(BuildContext context) { 103 | if (Platform.isAndroid) { 104 | return AndroidView( 105 | viewType: _methodChannelIdentifier, 106 | creationParamsCodec: const StandardMessageCodec(), 107 | creationParams: _getParams(), 108 | ); 109 | } else if (Platform.isIOS) { 110 | print("platform ios"); 111 | return UiKitView( 112 | viewType: _methodChannelIdentifier, 113 | creationParams: _getParams(), 114 | creationParamsCodec: const StandardMessageCodec(), 115 | ); 116 | } else { 117 | throw ("Current Platform is not supported"); 118 | } 119 | } 120 | 121 | Map _getParams() { 122 | Map allParams = { 123 | // "documentAnimation": widget.documentAnimation, 124 | // "overlayColor": widget.overlayColor, 125 | // "detectionCountBeforeCapture": widget.detectionCountBeforeCapture, 126 | // "enableTorch": widget.enableTorch, 127 | // "manualOnly": widget.manualOnly, 128 | "noGrayScale": widget.noGrayScale, 129 | // "brightness": widget.brightness, 130 | // "contrast": widget.contrast, 131 | // "saturation": widget.saturation, 132 | }; 133 | 134 | Map nonNullParams = {}; 135 | allParams.forEach((key, value) { 136 | if (value != null) { 137 | nonNullParams.addAll({key: value}); 138 | } 139 | }); 140 | 141 | return nonNullParams; 142 | //hamed touch (all above lines commented but below) 143 | //return {}; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /android/src/main/java/com/example/document_scanner/DocumentScannerViewManager.java: -------------------------------------------------------------------------------- 1 | package com.example.document_scanner; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.os.Handler; 6 | import android.os.Looper; 7 | import android.util.Log; 8 | import android.view.View; 9 | 10 | import com.example.document_scanner.views.MainView; 11 | import com.example.document_scanner.views.OpenNoteCameraView; 12 | 13 | import java.lang.reflect.Method; 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | 17 | import io.flutter.plugin.common.MethodChannel; 18 | import io.flutter.plugin.common.PluginRegistry; 19 | import io.flutter.plugin.platform.PlatformView; 20 | 21 | 22 | /** 23 | * Created by Andre on 29/11/2017. 24 | */ 25 | 26 | public class DocumentScannerViewManager implements PlatformView { 27 | 28 | 29 | private MainView view = null; 30 | Context context; 31 | Activity activity; 32 | 33 | 34 | 35 | 36 | final MethodChannel channel; 37 | 38 | Map params; 39 | 40 | DocumentScannerViewManager(Context context,Activity activity, Map params,MethodChannel channel){ 41 | this.context = context; 42 | this.activity = activity; 43 | this.params = params; 44 | this.channel = channel; 45 | } 46 | 47 | 48 | 49 | @Override 50 | public View getView() { 51 | MainView.createInstance(context,activity); 52 | view = MainView.getInstance(); 53 | setParams(); 54 | return view; 55 | } 56 | 57 | @Override 58 | public void dispose() { 59 | 60 | } 61 | 62 | void setParams(){ 63 | view.setOnProcessingListener(new OpenNoteCameraView.OnProcessingListener() { 64 | 65 | @Override 66 | public void onProcessingChange(Map data) { 67 | //Log.d("debug",path.toString()); 68 | Handler uiThreadHandler = new Handler(context.getMainLooper()); 69 | Runnable runnable = new Runnable() { 70 | @Override 71 | public void run() { 72 | 73 | channel.invokeMethod("onPictureTaken",data); 74 | } 75 | }; 76 | uiThreadHandler.postAtFrontOfQueue(runnable ); 77 | } 78 | }); 79 | 80 | view.setOnScannerListener(new OpenNoteCameraView.OnScannerListener() { 81 | 82 | @Override 83 | public void onPictureTaken(Map data) { 84 | 85 | Handler uiThreadHandler = new Handler(context.getMainLooper()); 86 | Runnable runnable = new Runnable() { 87 | @Override 88 | public void run() { 89 | 90 | channel.invokeMethod("onPictureTaken",data); 91 | } 92 | }; 93 | uiThreadHandler.postAtFrontOfQueue(runnable ); 94 | 95 | 96 | } 97 | }); 98 | 99 | boolean documentAnimation; 100 | if(params.containsKey("documentAnimation")){ 101 | documentAnimation =(boolean) params.get("documentAnimation"); 102 | }else{ 103 | documentAnimation = false; 104 | } 105 | 106 | view.setDocumentAnimation(documentAnimation); 107 | 108 | String overlayColor; 109 | if(params.containsKey("overlayColor")){ 110 | overlayColor = (String ) params.get("overlayColor"); 111 | view.setOverlayColor(overlayColor); 112 | } 113 | 114 | int detectionCountBeforeCapture; 115 | if(params.containsKey("detectionCountBeforeCapture")){ 116 | detectionCountBeforeCapture =(int) params.get("detectionCountBeforeCapture"); 117 | }else{ 118 | detectionCountBeforeCapture = 15; 119 | } 120 | view.setDetectionCountBeforeCapture(detectionCountBeforeCapture); 121 | 122 | 123 | boolean enableTorch; 124 | if(params.containsKey("enableTorch")){ 125 | enableTorch =(boolean) params.get("enableTorch"); 126 | }else{ 127 | enableTorch = false; 128 | } 129 | 130 | view.setEnableTorch(enableTorch); 131 | 132 | 133 | boolean manualOnly; 134 | if(params.containsKey("manualOnly")){ 135 | manualOnly =(boolean) params.get("manualOnly"); 136 | }else{ 137 | manualOnly = false; 138 | } 139 | 140 | view.setManualOnly(manualOnly); 141 | 142 | boolean noGrayScale; 143 | if(params.containsKey("noGrayScale")){ 144 | noGrayScale =(boolean) params.get("noGrayScale"); 145 | }else{ 146 | noGrayScale = false; 147 | } 148 | 149 | view.setRemoveGrayScale(noGrayScale); 150 | 151 | double brightness; 152 | if(params.containsKey("brightness")){ 153 | brightness =(double) params.get("brightness"); 154 | }else{ 155 | brightness = 10; 156 | } 157 | view.setBrightness(brightness); 158 | 159 | 160 | double contrast; 161 | if(params.containsKey("contrast")){ 162 | contrast =(double) params.get("contrast"); 163 | }else{ 164 | contrast = 1; 165 | } 166 | view.setContrast(contrast); 167 | } 168 | 169 | 170 | 171 | 172 | } 173 | -------------------------------------------------------------------------------- /ios/Classes/DocumentScannerFactory.m: -------------------------------------------------------------------------------- 1 | #import "DocumentScannerFactory.h" 2 | #import "DocumentScannerView.h" 3 | 4 | #import 5 | 6 | @implementation DocumentScannerFactory { 7 | NSObject* _registrar; 8 | } 9 | 10 | - (instancetype)initWithRegistrar:(NSObject*)registrar { 11 | self = [super init]; 12 | if (self) { 13 | _registrar = registrar; 14 | } 15 | return self; 16 | } 17 | 18 | - (NSObject*)createArgsCodec { 19 | return [FlutterStandardMessageCodec sharedInstance]; 20 | } 21 | 22 | - (NSObject*)createWithFrame:(CGRect)frame 23 | viewIdentifier:(int64_t)viewId 24 | arguments:(id _Nullable)args { 25 | 26 | [self requestCameraPermissionsIfNeeded]; 27 | return [[ScannerController alloc] initWithFrame:frame 28 | viewIdentifier:viewId 29 | arguments:args 30 | registrar:_registrar]; 31 | // ScannerController *controller = [ScannerController alloc]; 32 | // return controller; 33 | 34 | // return [[FLTGoogleMapController alloc] initWithFrame:frame 35 | // viewIdentifier:viewId 36 | // arguments:args 37 | // registrar:_registrar]; 38 | } 39 | - (void)requestCameraPermissionsIfNeeded { 40 | 41 | // check camera authorization status 42 | AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]; 43 | switch (authStatus) { 44 | case AVAuthorizationStatusAuthorized: { // camera authorized 45 | // do camera intensive stuff 46 | } 47 | break; 48 | case AVAuthorizationStatusNotDetermined: { // request authorization 49 | 50 | [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) { 51 | dispatch_async(dispatch_get_main_queue(), ^{ 52 | 53 | if(granted) { 54 | // do camera intensive stuff 55 | } else { 56 | [self notifyUserOfCameraAccessDenial]; 57 | } 58 | }); 59 | }]; 60 | } 61 | break; 62 | case AVAuthorizationStatusRestricted: 63 | case AVAuthorizationStatusDenied: { 64 | dispatch_async(dispatch_get_main_queue(), ^{ 65 | [self notifyUserOfCameraAccessDenial]; 66 | }); 67 | } 68 | break; 69 | default: 70 | break; 71 | } 72 | } 73 | 74 | 75 | 76 | - (void)notifyUserOfCameraAccessDenial { 77 | // display a useful message asking the user to grant permissions from within Settings > Privacy > Camera 78 | } 79 | 80 | @end 81 | 82 | @implementation ScannerController{ 83 | 84 | int64_t _viewId; 85 | FlutterMethodChannel* _channel; 86 | BOOL _trackCameraPosition; 87 | NSObject* _registrar; 88 | BOOL _cameraDidInitialSetup; 89 | 90 | } 91 | - (instancetype)initWithFrame:(CGRect)frame 92 | viewIdentifier:(int64_t)viewId 93 | arguments:(id _Nullable)args 94 | registrar:(NSObject*)registrar { 95 | if (self = [super init]) { 96 | _viewId = viewId; 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | _trackCameraPosition = NO; 109 | // InterpretMapOptions(args[@"options"], self); 110 | NSString* channelName = 111 | [NSString stringWithFormat:@"document_scanner"]; 112 | _channel = [FlutterMethodChannel methodChannelWithName:channelName 113 | binaryMessenger:registrar.messenger]; 114 | 115 | 116 | 117 | // _scannerView = [DocumentScannerView new]; 118 | _scannerView = [[DocumentScannerView alloc] initWithChannel:_channel]; 119 | 120 | __weak __typeof__(self) weakSelf = self; 121 | [_channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) { 122 | if (weakSelf) { 123 | // [weakSelf onMethodCall:call result:result]; 124 | } 125 | }]; 126 | // _mapView.delegate = weakSelf; 127 | _registrar = registrar; 128 | _cameraDidInitialSetup = NO; 129 | 130 | float channelBrightness = 0; 131 | float channelContrast = 0; 132 | 133 | id brightness = args[@"brightness"]; 134 | if ([brightness isKindOfClass:[NSNumber class]]){ 135 | channelBrightness = [brightness floatValue]; 136 | 137 | }else{ 138 | channelBrightness = 5; 139 | } 140 | id contrast = args[@"contrast"]; 141 | if ([contrast isKindOfClass:[NSNumber class]]){ 142 | channelContrast = [contrast floatValue]; 143 | }else{ 144 | channelContrast = 1.3; 145 | } 146 | 147 | 148 | // _scannerView = [[DocumentScannerView alloc] initWithChannel:_channel brightness:channelBrightness contrast:channelContrast]; 149 | 150 | // id polygonsToAdd = args[@"polygonsToAdd"]; 151 | // if ([polygonsToAdd isKindOfClass:[NSArray class]]) { 152 | //// [_polygonsController addPolygons:polygonsToAdd]; 153 | // } 154 | // id polylinesToAdd = args[@"polylinesToAdd"]; 155 | // if ([polylinesToAdd isKindOfClass:[NSArray class]]) { 156 | //// [_polylinesController addPolylines:polylinesToAdd]; 157 | // } 158 | // id circlesToAdd = args[@"circlesToAdd"]; 159 | // if ([circlesToAdd isKindOfClass:[NSArray class]]) { 160 | //// [_circlesController addCircles:circlesToAdd]; 161 | // } 162 | } 163 | 164 | return self; 165 | } 166 | 167 | - (dispatch_queue_t)methodQueue 168 | { 169 | return dispatch_get_main_queue(); 170 | } 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | // 179 | //UIButton *createButton(CGFloat x, CGFloat y, CGFloat width, CGFloat height, NSString *caption, NSTextAlignment textPosition, UIColor *textColor, UIColor *backColor) { 180 | // UIButton *control = [[UIButton alloc] initWithFrame: CGRectMake(x, y, width, height)]; 181 | // [control setTitle:caption forState:UIControlStateNormal]; 182 | // control.titleLabel.textAlignment = textPosition; 183 | // control.backgroundColor = backColor; 184 | // [control setTitleColor: textColor forState:UIControlStateNormal]; 185 | // return control; 186 | //} 187 | 188 | 189 | 190 | 191 | 192 | 193 | - (nonnull UIView *)view { 194 | 195 | return _scannerView; 196 | } 197 | 198 | @end 199 | 200 | 201 | 202 | 203 | -------------------------------------------------------------------------------- /android/src/main/java/com/example/document_scanner/helpers/Utils.java: -------------------------------------------------------------------------------- 1 | package com.example.document_scanner.helpers; 2 | 3 | import android.content.ContentValues; 4 | import android.content.Context; 5 | import android.content.SharedPreferences; 6 | import android.content.pm.PackageInfo; 7 | import android.content.pm.PackageManager; 8 | import android.graphics.Bitmap; 9 | import android.graphics.BitmapFactory; 10 | import android.graphics.Point; 11 | import android.preference.PreferenceManager; 12 | import android.provider.MediaStore; 13 | import android.view.Display; 14 | import android.view.WindowManager; 15 | 16 | import java.io.File; 17 | import java.util.ArrayList; 18 | import java.util.Arrays; 19 | import java.util.Comparator; 20 | import java.util.Locale; 21 | import java.util.regex.Matcher; 22 | import java.util.regex.Pattern; 23 | 24 | import javax.microedition.khronos.egl.EGL10; 25 | import javax.microedition.khronos.egl.EGLConfig; 26 | import javax.microedition.khronos.egl.EGLContext; 27 | import javax.microedition.khronos.egl.EGLDisplay; 28 | 29 | public class Utils { 30 | 31 | private final SharedPreferences mSharedPref; 32 | private Context _context; 33 | 34 | // constructor 35 | public Utils(Context context) { 36 | this._context = context; 37 | mSharedPref = PreferenceManager.getDefaultSharedPreferences(context); 38 | } 39 | 40 | /* 41 | * Reading file paths from SDCard 42 | */ 43 | public ArrayList getFilePaths() { 44 | ArrayList filePaths = new ArrayList(); 45 | 46 | File directory = new File( 47 | android.os.Environment.getExternalStorageDirectory() 48 | + File.separator + mSharedPref.getString("storage_folder","OpenNoteScanner")); 49 | 50 | // check for directory 51 | if (directory.isDirectory()) { 52 | // getting list of file paths 53 | File[] listFiles = directory.listFiles(); 54 | 55 | Arrays.sort(listFiles, new Comparator() { 56 | public int compare(File f1, File f2) { 57 | return f2.getName().compareTo(f1.getName()); 58 | } 59 | }); 60 | 61 | // Check for count 62 | if (listFiles.length > 0) { 63 | 64 | // loop through all files 65 | for (int i = 0; i < listFiles.length; i++) { 66 | 67 | // get file path 68 | String filePath = listFiles[i].getAbsolutePath(); 69 | 70 | // check for supported file extension 71 | if (IsSupportedFile(filePath)) { 72 | // Add image path to array list 73 | filePaths.add(filePath); 74 | } 75 | } 76 | } 77 | } 78 | 79 | return filePaths; 80 | } 81 | 82 | /* 83 | * Check supported file extensions 84 | * 85 | * @returns boolean 86 | */ 87 | private boolean IsSupportedFile(String filePath) { 88 | String ext = filePath.substring((filePath.lastIndexOf(".") + 1), 89 | filePath.length()); 90 | 91 | if (AppConstant.FILE_EXTN 92 | .contains(ext.toLowerCase(Locale.getDefault()))) 93 | return true; 94 | else 95 | return false; 96 | 97 | } 98 | 99 | /* 100 | * getting screen width 101 | */ 102 | public int getScreenWidth() { 103 | int columnWidth; 104 | WindowManager wm = (WindowManager) _context 105 | .getSystemService(Context.WINDOW_SERVICE); 106 | Display display = wm.getDefaultDisplay(); 107 | 108 | final Point point = new Point(); 109 | try { 110 | display.getSize(point); 111 | } catch (NoSuchMethodError ignore) { // Older device 112 | point.x = display.getWidth(); 113 | point.y = display.getHeight(); 114 | } 115 | columnWidth = point.x; 116 | return columnWidth; 117 | } 118 | 119 | 120 | public static int getMaxTextureSize() { 121 | // Safe minimum default size 122 | final int IMAGE_MAX_BITMAP_DIMENSION = 2048; 123 | 124 | // Get EGL Display 125 | EGL10 egl = (EGL10) EGLContext.getEGL(); 126 | EGLDisplay display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); 127 | 128 | // Initialise 129 | int[] version = new int[2]; 130 | egl.eglInitialize(display, version); 131 | 132 | // Query total number of configurations 133 | int[] totalConfigurations = new int[1]; 134 | egl.eglGetConfigs(display, null, 0, totalConfigurations); 135 | 136 | // Query actual list configurations 137 | EGLConfig[] configurationsList = new EGLConfig[totalConfigurations[0]]; 138 | egl.eglGetConfigs(display, configurationsList, totalConfigurations[0], totalConfigurations); 139 | 140 | int[] textureSize = new int[1]; 141 | int maximumTextureSize = 0; 142 | 143 | // Iterate through all the configurations to located the maximum texture size 144 | for (int i = 0; i < totalConfigurations[0]; i++) { 145 | // Only need to check for width since opengl textures are always squared 146 | egl.eglGetConfigAttrib(display, configurationsList[i], EGL10.EGL_MAX_PBUFFER_WIDTH, textureSize); 147 | 148 | // Keep track of the maximum texture size 149 | if (maximumTextureSize < textureSize[0]) 150 | maximumTextureSize = textureSize[0]; 151 | } 152 | 153 | // Release 154 | egl.eglTerminate(display); 155 | 156 | // Return largest texture size found, or default 157 | return Math.max(maximumTextureSize, IMAGE_MAX_BITMAP_DIMENSION); 158 | } 159 | 160 | public static boolean isMatch(String s, String pattern) { 161 | try { 162 | Pattern patt = Pattern.compile(pattern); 163 | Matcher matcher = patt.matcher(s); 164 | return matcher.matches(); 165 | } catch (RuntimeException e) { 166 | return false; 167 | } 168 | } 169 | 170 | public static Bitmap decodeSampledBitmapFromUri(String path, int reqWidth, int reqHeight) { 171 | 172 | Bitmap bm = null; 173 | // First decode with inJustDecodeBounds=true to check dimensions 174 | final BitmapFactory.Options options = new BitmapFactory.Options(); 175 | options.inJustDecodeBounds = true; 176 | BitmapFactory.decodeFile(path, options); 177 | 178 | // Calculate inSampleSize 179 | options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); 180 | 181 | // Decode bitmap with inSampleSize set 182 | options.inJustDecodeBounds = false; 183 | bm = BitmapFactory.decodeFile(path, options); 184 | 185 | return bm; 186 | } 187 | 188 | public static int calculateInSampleSize( 189 | BitmapFactory.Options options, int reqWidth, int reqHeight) { 190 | 191 | // Raw height and width of image 192 | final int height = options.outHeight; 193 | final int width = options.outWidth; 194 | int inSampleSize = 1; 195 | 196 | if (height > reqHeight || width > reqWidth) { 197 | if (width > height) { 198 | inSampleSize = Math.round((float)height / (float)reqHeight); 199 | } else { 200 | inSampleSize = Math.round((float)width / (float)reqWidth); 201 | } 202 | } 203 | 204 | return inSampleSize; 205 | } 206 | 207 | public static void addImageToGallery(final String filePath, final Context context) { 208 | 209 | ContentValues values = new ContentValues(); 210 | 211 | values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis()); 212 | values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); 213 | values.put(MediaStore.MediaColumns.DATA, filePath); 214 | 215 | context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); 216 | } 217 | 218 | public static void removeImageFromGallery(String filePath, Context context) { 219 | context.getContentResolver().delete(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 220 | MediaStore.Images.Media.DATA 221 | + "='" 222 | + filePath 223 | + "'", null); 224 | } 225 | 226 | public static boolean isPackageInstalled(Context context , String packagename) { 227 | PackageManager pm = context.getPackageManager(); 228 | boolean app_installed = false; 229 | try 230 | { 231 | PackageInfo info = pm.getPackageInfo(packagename, PackageManager.GET_ACTIVITIES); 232 | String label = (String) info.applicationInfo.loadLabel(pm); 233 | app_installed = (label != null); 234 | } 235 | catch (PackageManager.NameNotFoundException e) 236 | { 237 | app_installed = false; 238 | } 239 | return app_installed; 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /ios/Classes/DocumentScannerView.m: -------------------------------------------------------------------------------- 1 | #import "DocumentScannerView.h" 2 | #import "IPDFCameraViewController.h" 3 | 4 | @implementation DocumentScannerView 5 | 6 | 7 | 8 | //- (instancetype)init{ 9 | // self = [super init]; 10 | // 11 | // NSLog(@"setting args ..."); 12 | // self.detectionRefreshRateInMS = 55; 13 | // self.overlayColor = [UIColor colorWithRed: 1.00 green: 0.00 blue: 0.00 alpha: 1.00]; 14 | // self.enableTorch = true; 15 | // self.useFrontCam = false; 16 | // self.useBase64 = false; 17 | // self.saveInAppDocument = true; 18 | // self.captureMultiple = false; 19 | // self.detectionCountBeforeCapture = 5; 20 | // self.detectionRefreshRateInMS = 50; 21 | // self.saturation = 1; 22 | // self.quality = 1; 23 | //// self.brightness = channelBrightness; 24 | //// self.contrast = channelContrast; 25 | // self.brightness = 0; 26 | // self.contrast = 1; 27 | // self.durationBetweenCaptures = 0; 28 | // 29 | // 30 | // if (self) { 31 | // 32 | // [self setEnableBorderDetection:YES]; 33 | // [self setDelegate: self]; 34 | // } 35 | // 36 | // return self; 37 | //} 38 | - (instancetype)init{ 39 | self = [super init]; 40 | 41 | //RCT_EXPORT_VIEW_PROPERTY(onPictureTaken, RCTBubblingEventBlock) 42 | //RCT_EXPORT_VIEW_PROPERTY(onRectangleDetect, RCTBubblingEventBlock) 43 | 44 | 45 | 46 | 47 | 48 | 49 | //RCT_EXPORT_VIEW_PROPERTY(durationBetweenCaptures, double) 50 | 51 | 52 | // 53 | //RCT_EXPORT_METHOD(capture:(nonnull NSNumber *)reactTag) 54 | 55 | // NSLog(@"brightness : %f",channelBrightness); 56 | // NSLog(@"contrast : %f",channelContrast); 57 | self.detectionRefreshRateInMS = 50; 58 | self.overlayColor = [UIColor colorWithRed: 1.00 green: 0.00 blue: 0.00 alpha: 0.50]; 59 | self.enableTorch = false; 60 | self.useFrontCam = false; 61 | self.useBase64 = false; 62 | self.saveInAppDocument = true; 63 | self.captureMultiple = false; 64 | self.detectionCountBeforeCapture = 8; 65 | self.detectionRefreshRateInMS = 50; 66 | self.saturation = 1; 67 | self.quality = 1; 68 | // self.brightness = channelBrightness; 69 | // self.contrast = channelContrast; 70 | self.brightness = 0.2; 71 | self.contrast = 1.4; 72 | self.durationBetweenCaptures = 0; 73 | 74 | 75 | if (self) { 76 | 77 | [self setEnableBorderDetection:YES]; 78 | [self setDelegate: self]; 79 | } 80 | 81 | return self; 82 | } 83 | 84 | -(instancetype) initWithChannel:(FlutterMethodChannel *)channel { 85 | DocumentScannerView* instance = [DocumentScannerView new]; 86 | instance.flutterChannel = channel; 87 | 88 | 89 | return instance; 90 | } 91 | //-(instancetype)initWithChannelAndArgs:(FlutterMethodChannel *)channel brightness:(float)channelBrightness contrast:(float)channelContrast { 92 | // DocumentScannerView* instance = [[DocumentScannerView alloc] init:channelBrightness contrast:channelContrast]; 93 | // instance.flutterChannel = channel; 94 | // 95 | // 96 | // return instance; 97 | //} 98 | 99 | -(void) onPictureTaken { 100 | printf("on picture taken"); 101 | } 102 | 103 | -(void) onRectangleDetect { 104 | printf("on rect detect"); 105 | } 106 | 107 | //- (void)setChannelBrightness:(float)brightness { 108 | // self.brightness = brightness; 109 | //} 110 | // 111 | //- (void)setChannelContrast:(float)contrast { 112 | // self.contrast = contrast; 113 | //} 114 | 115 | 116 | - (void) didDetectRectangle:(CIRectangleFeature *)rectangle withType:(IPDFRectangeType)type { 117 | switch (type) { 118 | case IPDFRectangeTypeGood: 119 | self.stableCounter ++; 120 | break; 121 | default: 122 | self.stableCounter = 0; 123 | break; 124 | } 125 | // if (self.onRectangleDetect) { 126 | // self.onRectangleDetect(@{@"stableCounter": @(self.stableCounter), @"lastDetectionType": @(type)}); 127 | // } 128 | // 129 | if (self.stableCounter > self.detectionCountBeforeCapture && 130 | [NSDate timeIntervalSinceReferenceDate] > self.lastCaptureTime + self.durationBetweenCaptures) { 131 | self.lastCaptureTime = [NSDate timeIntervalSinceReferenceDate]; 132 | self.stableCounter = 0; 133 | [self capture]; 134 | } 135 | } 136 | - (void) onPictureTaken: (NSDictionary*) result { 137 | printf("on picture taken"); 138 | } 139 | 140 | - (void) capture { 141 | 142 | [self captureImageWithCompletionHander:^(UIImage *croppedImage, UIImage *initialImage, CIRectangleFeature *rectangleFeature) { 143 | // if (self.onPictureTaken) { 144 | NSData *croppedImageData = UIImageJPEGRepresentation(croppedImage, self.quality); 145 | 146 | if (initialImage.imageOrientation != UIImageOrientationUp) { 147 | UIGraphicsBeginImageContextWithOptions(initialImage.size, false, initialImage.scale); 148 | [initialImage drawInRect:CGRectMake(0, 0, initialImage.size.width 149 | , initialImage.size.height)]; 150 | initialImage = UIGraphicsGetImageFromCurrentImageContext(); 151 | UIGraphicsEndImageContext(); 152 | } 153 | NSData *initialImageData = UIImageJPEGRepresentation(initialImage, self.quality); 154 | 155 | /* 156 | RectangleCoordinates expects a rectanle viewed from portrait, 157 | while rectangleFeature returns a rectangle viewed from landscape, which explains the nonsense of the mapping below. 158 | Sorry about that. 159 | */ 160 | id rectangleCoordinates = rectangleFeature ? @{ 161 | @"topLeft": @{ @"y": @(rectangleFeature.bottomLeft.x + 30), @"x": @(rectangleFeature.bottomLeft.y)}, 162 | @"topRight": @{ @"y": @(rectangleFeature.topLeft.x + 30), @"x": @(rectangleFeature.topLeft.y)}, 163 | @"bottomLeft": @{ @"y": @(rectangleFeature.bottomRight.x), @"x": @(rectangleFeature.bottomRight.y)}, 164 | @"bottomRight": @{ @"y": @(rectangleFeature.topRight.x), @"x": @(rectangleFeature.topRight.y)}, 165 | } : [NSNull null]; 166 | if (self.useBase64) { 167 | 168 | dispatch_async(dispatch_get_main_queue(), ^{ 169 | printf("calling flutter onPictureTaken base64"); 170 | [self->_flutterChannel invokeMethod:@"onPictureTaken" arguments:@{ 171 | @"croppedImage": [croppedImageData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength], 172 | @"initialImage": [initialImageData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength], 173 | @"rectangleCoordinates": rectangleCoordinates }]; 174 | }); 175 | 176 | // onPictureTaken(@{ 177 | // @"croppedImage": [croppedImageData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength], 178 | // @"initialImage": [initialImageData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength], 179 | // @"rectangleCoordinates": rectangleCoordinates }); 180 | } 181 | else { 182 | NSString *dir = NSTemporaryDirectory(); 183 | if (self.saveInAppDocument) { 184 | dir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; 185 | } 186 | NSString *croppedFilePath = [dir stringByAppendingPathComponent:[NSString stringWithFormat:@"cropped_img_%i.jpeg",(int)[NSDate date].timeIntervalSince1970]]; 187 | NSString *initialFilePath = [dir stringByAppendingPathComponent:[NSString stringWithFormat:@"initial_img_%i.jpeg",(int)[NSDate date].timeIntervalSince1970]]; 188 | 189 | [croppedImageData writeToFile:croppedFilePath atomically:YES]; 190 | [initialImageData writeToFile:initialFilePath atomically:YES]; 191 | 192 | 193 | 194 | dispatch_async(dispatch_get_main_queue(), ^{ 195 | 196 | printf("calling flutter onPictureTaken file"); 197 | // NSLog(croppedFilePath); 198 | // NSLog(initialFilePath); 199 | // NSLog(rectangleCoordinates); 200 | 201 | [self->_flutterChannel invokeMethod:@"onPictureTaken" arguments:@{ 202 | @"croppedImage": croppedFilePath, 203 | @"initialImage": initialFilePath, 204 | @"rectangleCoordinates": rectangleCoordinates 205 | 206 | }]; 207 | 208 | }); 209 | 210 | 211 | // self.onPictureTaken(@{ 212 | // @"croppedImage": croppedFilePath, 213 | // @"initialImage": initialFilePath, 214 | // @"rectangleCoordinates": rectangleCoordinates }); 215 | } 216 | // } 217 | 218 | if (!self.captureMultiple) { 219 | [self stop]; 220 | } 221 | }]; 222 | 223 | } 224 | 225 | 226 | @end 227 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/flutter,android,androidstudio,xcode,objective-c,dart 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=flutter,android,androidstudio,xcode,objective-c,dart 3 | 4 | ### Android ### 5 | # Built application files 6 | *.apk 7 | *.aar 8 | *.ap_ 9 | *.aab 10 | 11 | # Files for the ART/Dalvik VM 12 | *.dex 13 | 14 | # Java class files 15 | *.class 16 | 17 | # Generated files 18 | bin/ 19 | gen/ 20 | out/ 21 | # Uncomment the following line in case you need and you don't have the release build type files in your app 22 | # release/ 23 | 24 | # Gradle files 25 | .gradle/ 26 | build/ 27 | 28 | # Local configuration file (sdk path, etc) 29 | local.properties 30 | 31 | # Proguard folder generated by Eclipse 32 | proguard/ 33 | 34 | # Log Files 35 | *.log 36 | 37 | # Android Studio Navigation editor temp files 38 | .navigation/ 39 | 40 | # Android Studio captures folder 41 | captures/ 42 | 43 | # IntelliJ 44 | *.iml 45 | .idea/workspace.xml 46 | .idea/tasks.xml 47 | .idea/gradle.xml 48 | .idea/assetWizardSettings.xml 49 | .idea/dictionaries 50 | .idea/libraries 51 | .idea/jarRepositories.xml 52 | # Android Studio 3 in .gitignore file. 53 | .idea/caches 54 | .idea/modules.xml 55 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 56 | .idea/navEditor.xml 57 | 58 | # Keystore files 59 | # Uncomment the following lines if you do not want to check your keystore files in. 60 | #*.jks 61 | #*.keystore 62 | 63 | # External native build folder generated in Android Studio 2.2 and later 64 | .externalNativeBuild 65 | .cxx/ 66 | 67 | # Google Services (e.g. APIs or Firebase) 68 | # google-services.json 69 | 70 | # Freeline 71 | freeline.py 72 | freeline/ 73 | freeline_project_description.json 74 | 75 | # fastlane 76 | fastlane/report.xml 77 | fastlane/Preview.html 78 | fastlane/screenshots 79 | fastlane/test_output 80 | fastlane/readme.md 81 | 82 | # Version control 83 | vcs.xml 84 | 85 | # lint 86 | lint/intermediates/ 87 | lint/generated/ 88 | lint/outputs/ 89 | lint/tmp/ 90 | # lint/reports/ 91 | 92 | # Android Profiling 93 | *.hprof 94 | 95 | ### Android Patch ### 96 | gen-external-apklibs 97 | output.json 98 | 99 | # Replacement of .externalNativeBuild directories introduced 100 | # with Android Studio 3.5. 101 | 102 | ### Dart ### 103 | # See https://www.dartlang.org/guides/libraries/private-files 104 | 105 | # Files and directories created by pub 106 | .dart_tool/ 107 | .packages 108 | # If you're building an application, you may want to check-in your pubspec.lock 109 | pubspec.lock 110 | 111 | # Directory created by dartdoc 112 | # If you don't generate documentation locally you can remove this line. 113 | doc/api/ 114 | 115 | # dotenv environment variables file 116 | .env* 117 | 118 | # Avoid committing generated Javascript files: 119 | *.dart.js 120 | *.info.json # Produced by the --dump-info flag. 121 | *.js # When generated by dart2js. Don't specify *.js if your 122 | # project includes source files written in JavaScript. 123 | *.js_ 124 | *.js.deps 125 | *.js.map 126 | 127 | .flutter-plugins 128 | .flutter-plugins-dependencies 129 | 130 | ### Flutter ### 131 | # Flutter/Dart/Pub related 132 | **/doc/api/ 133 | .fvm/ 134 | .pub-cache/ 135 | .pub/ 136 | coverage/ 137 | lib/generated_plugin_registrant.dart 138 | # For library packages, don’t commit the pubspec.lock file. 139 | # Regenerating the pubspec.lock file lets you test your package against the latest compatible versions of its dependencies. 140 | # See https://dart.dev/guides/libraries/private-files#pubspeclock 141 | #pubspec.lock 142 | 143 | # Android related 144 | **/android/**/gradle-wrapper.jar 145 | **/android/.gradle 146 | **/android/captures/ 147 | **/android/gradlew 148 | **/android/gradlew.bat 149 | **/android/key.properties 150 | **/android/local.properties 151 | **/android/**/GeneratedPluginRegistrant.java 152 | 153 | # iOS/XCode related 154 | **/ios/**/*.mode1v3 155 | **/ios/**/*.mode2v3 156 | **/ios/**/*.moved-aside 157 | **/ios/**/*.pbxuser 158 | **/ios/**/*.perspectivev3 159 | **/ios/**/*sync/ 160 | **/ios/**/.sconsign.dblite 161 | **/ios/**/.tags* 162 | **/ios/**/.vagrant/ 163 | **/ios/**/DerivedData/ 164 | **/ios/**/Icon? 165 | **/ios/**/Pods/ 166 | **/ios/**/.symlinks/ 167 | **/ios/**/profile 168 | **/ios/**/xcuserdata 169 | **/ios/.generated/ 170 | **/ios/Flutter/.last_build_id 171 | **/ios/Flutter/App.framework 172 | **/ios/Flutter/Flutter.framework 173 | **/ios/Flutter/Flutter.podspec 174 | **/ios/Flutter/Generated.xcconfig 175 | **/ios/Flutter/app.flx 176 | **/ios/Flutter/app.zip 177 | **/ios/Flutter/flutter_assets/ 178 | **/ios/Flutter/flutter_export_environment.sh 179 | **/ios/ServiceDefinitions.json 180 | **/ios/Runner/GeneratedPluginRegistrant.* 181 | 182 | # Exceptions to above rules. 183 | !**/ios/**/default.mode1v3 184 | !**/ios/**/default.mode2v3 185 | !**/ios/**/default.pbxuser 186 | !**/ios/**/default.perspectivev3 187 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 188 | 189 | ### Objective-C ### 190 | # Xcode 191 | # 192 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 193 | 194 | ## User settings 195 | xcuserdata/ 196 | 197 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 198 | *.xcscmblueprint 199 | *.xccheckout 200 | 201 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 202 | DerivedData/ 203 | *.moved-aside 204 | *.pbxuser 205 | !default.pbxuser 206 | *.mode1v3 207 | !default.mode1v3 208 | *.mode2v3 209 | !default.mode2v3 210 | *.perspectivev3 211 | !default.perspectivev3 212 | 213 | ## Obj-C/Swift specific 214 | *.hmap 215 | 216 | ## App packaging 217 | *.ipa 218 | *.dSYM.zip 219 | *.dSYM 220 | 221 | # CocoaPods 222 | # We recommend against adding the Pods directory to your .gitignore. However 223 | # you should judge for yourself, the pros and cons are mentioned at: 224 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 225 | # Pods/ 226 | # Add this line if you want to avoid checking in source code from the Xcode workspace 227 | # *.xcworkspace 228 | 229 | # Carthage 230 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 231 | # Carthage/Checkouts 232 | 233 | Carthage/Build/ 234 | 235 | # fastlane 236 | # It is recommended to not store the screenshots in the git repo. 237 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 238 | # For more information about the recommended setup visit: 239 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 240 | 241 | fastlane/screenshots/**/*.png 242 | 243 | # Code Injection 244 | # After new code Injection tools there's a generated folder /iOSInjectionProject 245 | # https://github.com/johnno1962/injectionforxcode 246 | 247 | iOSInjectionProject/ 248 | 249 | ### Objective-C Patch ### 250 | 251 | ### Xcode ### 252 | # Xcode 253 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 254 | 255 | 256 | 257 | 258 | ## Gcc Patch 259 | /*.gcno 260 | 261 | ### Xcode Patch ### 262 | *.xcodeproj/* 263 | !*.xcodeproj/project.pbxproj 264 | !*.xcodeproj/xcshareddata/ 265 | !*.xcworkspace/contents.xcworkspacedata 266 | **/xcshareddata/WorkspaceSettings.xcsettings 267 | 268 | ### AndroidStudio ### 269 | # Covers files to be ignored for android development using Android Studio. 270 | 271 | # Built application files 272 | 273 | # Files for the ART/Dalvik VM 274 | 275 | # Java class files 276 | 277 | # Generated files 278 | 279 | # Gradle files 280 | .gradle 281 | 282 | # Signing files 283 | .signing/ 284 | 285 | # Local configuration file (sdk path, etc) 286 | 287 | # Proguard folder generated by Eclipse 288 | 289 | # Log Files 290 | 291 | # Android Studio 292 | /*/build/ 293 | /*/local.properties 294 | /*/out 295 | /*/*/build 296 | /*/*/production 297 | *.ipr 298 | *~ 299 | *.swp 300 | 301 | # Keystore files 302 | *.jks 303 | *.keystore 304 | 305 | # Google Services (e.g. APIs or Firebase) 306 | # google-services.json 307 | 308 | # Android Patch 309 | 310 | # External native build folder generated in Android Studio 2.2 and later 311 | 312 | # NDK 313 | obj/ 314 | 315 | # IntelliJ IDEA 316 | *.iws 317 | /out/ 318 | 319 | # User-specific configurations 320 | .idea/caches/ 321 | .idea/libraries/ 322 | .idea/shelf/ 323 | .idea/.name 324 | .idea/compiler.xml 325 | .idea/copyright/profiles_settings.xml 326 | .idea/encodings.xml 327 | .idea/misc.xml 328 | .idea/scopes/scope_settings.xml 329 | .idea/vcs.xml 330 | .idea/jsLibraryMappings.xml 331 | .idea/datasources.xml 332 | .idea/dataSources.ids 333 | .idea/sqlDataSources.xml 334 | .idea/dynamic.xml 335 | .idea/uiDesigner.xml 336 | 337 | # OS-specific files 338 | .DS_Store 339 | .DS_Store? 340 | ._* 341 | .Spotlight-V100 342 | .Trashes 343 | ehthumbs.db 344 | Thumbs.db 345 | 346 | # Legacy Eclipse project files 347 | .classpath 348 | .project 349 | .cproject 350 | .settings/ 351 | 352 | # Mobile Tools for Java (J2ME) 353 | .mtj.tmp/ 354 | 355 | # Package Files # 356 | *.war 357 | *.ear 358 | 359 | # virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml) 360 | hs_err_pid* 361 | 362 | ## Plugin-specific files: 363 | 364 | # mpeltonen/sbt-idea plugin 365 | .idea_modules/ 366 | 367 | # JIRA plugin 368 | atlassian-ide-plugin.xml 369 | 370 | # Mongo Explorer plugin 371 | .idea/mongoSettings.xml 372 | 373 | # Crashlytics plugin (for Android Studio and IntelliJ) 374 | com_crashlytics_export_strings.xml 375 | crashlytics.properties 376 | crashlytics-build.properties 377 | fabric.properties 378 | 379 | ### AndroidStudio Patch ### 380 | 381 | !/gradle/wrapper/gradle-wrapper.jar 382 | 383 | # End of https://www.toptal.com/developers/gitignore/api/flutter,android,androidstudio,xcode,objective-c,dart -------------------------------------------------------------------------------- /android/src/main/java/com/example/document_scanner/DocumentScannerPlugin.java: -------------------------------------------------------------------------------- 1 | package com.example.document_scanner; 2 | 3 | import android.app.Activity; 4 | import android.app.Application; 5 | import android.os.Bundle; 6 | import android.util.Log; 7 | 8 | import androidx.annotation.NonNull; 9 | import androidx.lifecycle.DefaultLifecycleObserver; 10 | import androidx.lifecycle.Lifecycle; 11 | import androidx.lifecycle.LifecycleOwner; 12 | 13 | import io.flutter.app.FlutterActivity; 14 | import io.flutter.embedding.engine.plugins.FlutterPlugin; 15 | import io.flutter.embedding.engine.plugins.activity.ActivityAware; 16 | import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; 17 | import io.flutter.embedding.engine.plugins.lifecycle.FlutterLifecycleAdapter; 18 | import io.flutter.plugin.common.MethodCall; 19 | import io.flutter.plugin.common.MethodChannel; 20 | import io.flutter.plugin.common.PluginRegistry.Registrar; 21 | import java.util.concurrent.atomic.AtomicInteger; 22 | 23 | 24 | public class DocumentScannerPlugin 25 | implements Application.ActivityLifecycleCallbacks, 26 | FlutterPlugin, 27 | ActivityAware, 28 | DefaultLifecycleObserver, MethodChannel.MethodCallHandler { 29 | static final int CREATED = 1; 30 | static final int STARTED = 2; 31 | static final int RESUMED = 3; 32 | static final int PAUSED = 4; 33 | static final int STOPPED = 5; 34 | static final int DESTROYED = 6; 35 | private final AtomicInteger state = new AtomicInteger(0); 36 | private int registrarActivityHashCode; 37 | private FlutterPluginBinding pluginBinding; 38 | private Lifecycle lifecycle; 39 | static MethodChannel methodChannel; 40 | 41 | private static final String VIEW_TYPE = "document_scanner"; 42 | 43 | // @Override 44 | // public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) { 45 | //// final MethodChannel channel = new MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "document_scanner"); 46 | //// channel.setMethodCallHandler(new DocumentScannerPlugin()); 47 | // flutterPluginBinding.getPlatformViewRegistry().registerViewFactory("document_scanner",new DocumentScannerViewManager(flutterPluginBinding.)); 48 | // 49 | // 50 | // } 51 | 52 | 53 | 54 | public static void registerWith(Registrar registrar) { 55 | 56 | final MethodChannel channel = new MethodChannel(registrar.messenger(), "document_scanner"); 57 | channel.setMethodCallHandler(new DocumentScannerPlugin()); 58 | methodChannel = channel; 59 | if (registrar.activity() == null) { 60 | // When a background flutter view tries to register the plugin, the registrar has no activity. 61 | // We stop the registration process as this plugin is foreground only. 62 | return; 63 | } 64 | final DocumentScannerPlugin plugin = new DocumentScannerPlugin(registrar.activity()); 65 | registrar.activity().getApplication().registerActivityLifecycleCallbacks(plugin); 66 | registrar 67 | .platformViewRegistry() 68 | .registerViewFactory( 69 | VIEW_TYPE, 70 | new DocumentScannerFactory(plugin.state, registrar.messenger(), null, null, registrar, -1,registrar.activity(),channel)); 71 | } 72 | 73 | public DocumentScannerPlugin() {} 74 | 75 | // FlutterPlugin 76 | 77 | 78 | @Override 79 | public void onAttachedToEngine(FlutterPluginBinding binding) { 80 | 81 | final MethodChannel channel = new MethodChannel(binding.getFlutterEngine().getDartExecutor(), "document_scanner"); 82 | channel.setMethodCallHandler(new DocumentScannerPlugin()); 83 | 84 | methodChannel= channel; 85 | pluginBinding = binding; 86 | } 87 | 88 | @Override 89 | public void onDetachedFromEngine(FlutterPluginBinding binding) { 90 | pluginBinding = null; 91 | } 92 | 93 | // ActivityAware 94 | 95 | @Override 96 | public void onAttachedToActivity(ActivityPluginBinding binding) { 97 | lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding); 98 | 99 | 100 | 101 | 102 | lifecycle.addObserver(this); 103 | pluginBinding 104 | .getPlatformViewRegistry() 105 | .registerViewFactory( 106 | VIEW_TYPE, 107 | new DocumentScannerFactory( 108 | state, 109 | pluginBinding.getBinaryMessenger(), 110 | binding.getActivity().getApplication(), 111 | lifecycle, 112 | null, 113 | binding.getActivity().hashCode(),binding.getActivity(),methodChannel)); 114 | } 115 | 116 | @Override 117 | public void onDetachedFromActivity() { 118 | lifecycle.removeObserver(this); 119 | } 120 | 121 | @Override 122 | public void onDetachedFromActivityForConfigChanges() { 123 | this.onDetachedFromActivity(); 124 | } 125 | 126 | @Override 127 | public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) { 128 | lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding); 129 | lifecycle.addObserver(this); 130 | } 131 | 132 | // DefaultLifecycleObserver methods 133 | 134 | @Override 135 | public void onCreate(@NonNull LifecycleOwner owner) { 136 | state.set(CREATED); 137 | } 138 | 139 | @Override 140 | public void onStart(@NonNull LifecycleOwner owner) { 141 | state.set(STARTED); 142 | } 143 | 144 | @Override 145 | public void onResume(@NonNull LifecycleOwner owner) { 146 | state.set(RESUMED); 147 | } 148 | 149 | @Override 150 | public void onPause(@NonNull LifecycleOwner owner) { 151 | state.set(PAUSED); 152 | } 153 | 154 | @Override 155 | public void onStop(@NonNull LifecycleOwner owner) { 156 | state.set(STOPPED); 157 | } 158 | 159 | @Override 160 | public void onDestroy(@NonNull LifecycleOwner owner) { 161 | state.set(DESTROYED); 162 | } 163 | 164 | // Application.ActivityLifecycleCallbacks methods 165 | 166 | @Override 167 | public void onActivityCreated(Activity activity, Bundle savedInstanceState) { 168 | 169 | if (activity.hashCode() != registrarActivityHashCode) { 170 | return; 171 | } 172 | state.set(CREATED); 173 | } 174 | 175 | @Override 176 | public void onActivityStarted(Activity activity) { 177 | 178 | if (activity.hashCode() != registrarActivityHashCode) { 179 | return; 180 | } 181 | state.set(STARTED); 182 | } 183 | 184 | @Override 185 | public void onActivityResumed(Activity activity) { 186 | if (activity.hashCode() != registrarActivityHashCode) { 187 | return; 188 | } 189 | state.set(RESUMED); 190 | } 191 | 192 | @Override 193 | public void onActivityPaused(Activity activity) { 194 | if (activity.hashCode() != registrarActivityHashCode) { 195 | return; 196 | } 197 | state.set(PAUSED); 198 | } 199 | 200 | @Override 201 | public void onActivityStopped(Activity activity) { 202 | if (activity.hashCode() != registrarActivityHashCode) { 203 | return; 204 | } 205 | state.set(STOPPED); 206 | } 207 | 208 | @Override 209 | public void onActivitySaveInstanceState(Activity activity, Bundle outState) {} 210 | 211 | @Override 212 | public void onActivityDestroyed(Activity activity) { 213 | if (activity.hashCode() != registrarActivityHashCode) { 214 | return; 215 | } 216 | activity.getApplication().unregisterActivityLifecycleCallbacks(this); 217 | state.set(DESTROYED); 218 | } 219 | 220 | private DocumentScannerPlugin(Activity activity) { 221 | 222 | this.registrarActivityHashCode = activity.hashCode(); 223 | } 224 | 225 | @Override 226 | public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) { 227 | if (methodCall.method.equals("getPlatformVersion")) { 228 | Log.d("debug","sucess"); 229 | Log.d("debug","success"); 230 | Log.d("debug","sucess"); 231 | Log.d("debug","success"); 232 | Log.d("debug","sucess"); 233 | Log.d("debug","success"); 234 | Log.d("debug","sucess"); 235 | result.success("Androiddd " + android.os.Build.VERSION.RELEASE); 236 | } else { 237 | result.notImplemented(); 238 | } 239 | } 240 | } 241 | ////package com.example.document_scanner; 242 | //// 243 | ////import androidx.annotation.NonNull; 244 | ////import io.flutter.embedding.engine.plugins.FlutterPlugin; 245 | ////import io.flutter.plugin.common.MethodCall; 246 | ////import io.flutter.plugin.common.MethodChannel; 247 | ////import io.flutter.plugin.common.MethodChannel.MethodCallHandler; 248 | ////import io.flutter.plugin.common.MethodChannel.Result; 249 | ////import io.flutter.plugin.common.PluginRegistry.Registrar; 250 | //// 251 | ////import com.example.document_scanner.views.MainView; 252 | ////import com.example.document_scanner.views.OpenNoteCameraView; 253 | //// 254 | ////import android.app.Activity; 255 | ////import android.content.Context; 256 | //// 257 | ////import java.util.HashMap; 258 | //// 259 | /////** DocumentScannerPlugin */ 260 | ////public class DocumentScannerPlugin implements FlutterPlugin, MethodCallHandler { 261 | //// private MainView view = null; 262 | //// 263 | //// @Override 264 | //// public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) { 265 | ////// final MethodChannel channel = new MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "document_scanner"); 266 | ////// channel.setMethodCallHandler(new DocumentScannerPlugin()); 267 | //// flutterPluginBinding.getPlatformViewRegistry().registerViewFactory("document_scanner",new DocumentScannerViewManager(flutterPluginBinding.)); 268 | //// 269 | //// 270 | //// } 271 | //// 272 | //// // This static function is optional and equivalent to onAttachedToEngine. It supports the old 273 | //// // pre-Flutter-1.12 Android projects. You are encouraged to continue supporting 274 | //// // plugin registration via this function while apps migrate to use the new Android APIs 275 | //// // post-flutter-1.12 via https://flutter.dev/go/android-project-migration. 276 | //// // 277 | //// // It is encouraged to share logic between onAttachedToEngine and registerWith to keep 278 | //// // them functionally equivalent. Only one of onAttachedToEngine or registerWith will be called 279 | //// // depending on the user's project. onAttachedToEngine or registerWith must both be defined 280 | //// // in the same class. 281 | //// public static void registerWith(Registrar registrar) { 282 | ////// final MethodChannel channel = new MethodChannel(registrar.messenger(), "document_scanner"); 283 | ////// channel.setMethodCallHandler(new DocumentScannerPlugin()); 284 | //// 285 | ////registrar.platformViewRegistry().registerViewFactory("document_scanner",new DocumentScannerViewManager(registrar.)); 286 | //// } 287 | //// 288 | //// @Override 289 | //// public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { 290 | //// if (call.method.equals("getPlatformVersion")) { 291 | //// 292 | //// result.success("Androiddd " + android.os.Build.VERSION.RELEASE); 293 | //// } else { 294 | //// result.notImplemented(); 295 | //// } 296 | //// } 297 | //// 298 | //// @Override 299 | //// public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { 300 | //// } 301 | //// 302 | //// public void openScanner(Context context){ 303 | ////// MainView.createInstance(context, ); 304 | //// 305 | //// view = MainView.getInstance(); 306 | ////// view.setOnProcessingListener(new OpenNoteCameraView.OnProcessingListener() { 307 | ////// @Override 308 | ////// public void onProcessingChange(HashMap data) { 309 | ////// dispatchEvent(reactContext, "onProcessingChange", data); 310 | ////// } 311 | ////// }); 312 | //// 313 | ////// view.setOnScannerListener(new OpenNoteCameraView.OnScannerListener() { 314 | ////// @Override 315 | ////// public void onPictureTaken(WritableMap data) { 316 | ////// dispatchEvent(reactContext, "onPictureTaken", data); 317 | ////// } 318 | ////// }); 319 | //// 320 | ////// return view; 321 | //// } 322 | ////} 323 | -------------------------------------------------------------------------------- /android/src/main/java/com/example/document_scanner/helpers/CustomOpenCVLoader.java: -------------------------------------------------------------------------------- 1 | package com.example.document_scanner.helpers; 2 | 3 | import android.app.AlertDialog; 4 | import android.app.Dialog; 5 | import android.app.DownloadManager; 6 | import android.content.BroadcastReceiver; 7 | import android.content.ComponentName; 8 | import android.content.Context; 9 | import android.content.DialogInterface; 10 | import android.content.Intent; 11 | import android.content.IntentFilter; 12 | import android.content.ServiceConnection; 13 | import android.content.pm.PackageInfo; 14 | import android.content.pm.PackageManager; 15 | import android.database.Cursor; 16 | import android.net.Uri; 17 | import android.os.Build; 18 | import android.os.IBinder; 19 | import android.provider.Settings; 20 | import android.util.Log; 21 | import android.widget.Toast; 22 | 23 | import com.example.document_scanner.R; 24 | 25 | import org.opencv.android.LoaderCallbackInterface; 26 | import org.opencv.android.OpenCVLoader; 27 | 28 | /** 29 | * Created by allgood on 22/02/16. 30 | */ 31 | public class CustomOpenCVLoader extends OpenCVLoader { 32 | 33 | private static ServiceConnection dummyServiceConnection = new ServiceConnection() { 34 | @Override 35 | public void onServiceConnected(ComponentName name, IBinder service) { 36 | 37 | } 38 | 39 | @Override 40 | public void onServiceDisconnected(ComponentName name) { 41 | 42 | } 43 | }; 44 | private static long myDownloadReference; 45 | private static LoaderCallbackInterface Callback; 46 | private static String Version; 47 | private static AlertDialog mAskInstallDialog; 48 | 49 | public static boolean isGooglePlayInstalled(Context context) { 50 | PackageManager pm = context.getPackageManager(); 51 | boolean app_installed = false; 52 | try 53 | { 54 | PackageInfo info = pm.getPackageInfo("com.android.vending", PackageManager.GET_ACTIVITIES); 55 | String label = (String) info.applicationInfo.loadLabel(pm); 56 | app_installed = (label != null && label.equals("Google Play Store")); 57 | } 58 | catch (PackageManager.NameNotFoundException e) 59 | { 60 | app_installed = false; 61 | } 62 | return app_installed; 63 | } 64 | 65 | public static boolean isOpenCVInstalled(String Version, Context AppContext) { 66 | Intent intent = new Intent("org.opencv.engine.BIND"); 67 | intent.setPackage("org.opencv.engine"); 68 | boolean result = AppContext.bindService(intent, dummyServiceConnection, Context.BIND_AUTO_CREATE); 69 | AppContext.unbindService(dummyServiceConnection); 70 | return result; 71 | }; 72 | 73 | static MyBroadcastReceiver onComplete; 74 | 75 | private static class MyBroadcastReceiver extends BroadcastReceiver { 76 | 77 | private static final String TAG = "CustomOpenCVLoader"; 78 | private Context AppContext; 79 | 80 | public MyBroadcastReceiver(Context appContext) { 81 | AppContext = appContext; 82 | } 83 | 84 | public void onReceive(Context ctxt, Intent intent) { 85 | 86 | long id = intent.getExtras().getLong(DownloadManager.EXTRA_DOWNLOAD_ID); 87 | 88 | if ( id == myDownloadReference ) { 89 | DownloadManager dm = (DownloadManager) AppContext.getSystemService(AppContext.DOWNLOAD_SERVICE); 90 | 91 | DownloadManager.Query query = new DownloadManager.Query(); 92 | query.setFilterById(id); 93 | Cursor cursor = dm.query(query); 94 | 95 | if (cursor.moveToFirst()) { 96 | // get the status of the download 97 | int columnIndex = cursor.getColumnIndex(DownloadManager 98 | .COLUMN_STATUS); 99 | int status = cursor.getInt(columnIndex); 100 | 101 | int fileNameIndex = cursor.getColumnIndex(DownloadManager 102 | .COLUMN_LOCAL_FILENAME); 103 | String savedFilePath = cursor.getString(fileNameIndex); 104 | 105 | // get the reason - more detail on the status 106 | int columnReason = cursor.getColumnIndex(DownloadManager 107 | .COLUMN_REASON); 108 | int reason = cursor.getInt(columnReason); 109 | 110 | switch (status) { 111 | case DownloadManager.STATUS_SUCCESSFUL: 112 | 113 | waitOpenCVDialog.dismiss(); 114 | AppContext.unregisterReceiver(onComplete); 115 | 116 | String path = "file://" + savedFilePath; 117 | Uri uri = Uri.parse(path); 118 | Log.d(TAG,"dm query: " + path ); 119 | intent = new Intent(Intent.ACTION_VIEW); 120 | intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 121 | intent.setDataAndType(uri, dm.getMimeTypeForDownloadedFile(id)); 122 | 123 | AppContext.startActivity(intent); 124 | break; 125 | case DownloadManager.STATUS_FAILED: 126 | Toast.makeText(AppContext, 127 | "FAILED: " + reason, 128 | Toast.LENGTH_LONG).show(); 129 | AppContext.unregisterReceiver(onComplete); 130 | break; 131 | default: 132 | Log.d("CustomOpenCVLoader", "Received download manager status: " + status); 133 | } 134 | } else { 135 | Log.d("CustomOpenCVLoader","missing download"); 136 | AppContext.unregisterReceiver(onComplete); 137 | } 138 | cursor.close(); 139 | 140 | } 141 | } 142 | } 143 | 144 | static AlertDialog.Builder waitInstallOpenCV; 145 | static Dialog waitOpenCVDialog; 146 | 147 | 148 | public static boolean initAsync(String version, final Context AppContext, LoaderCallbackInterface callback) { 149 | 150 | Version = version; 151 | Callback = callback; 152 | 153 | // if dialog is showing, remove 154 | if (mAskInstallDialog != null) { 155 | mAskInstallDialog.dismiss(); 156 | mAskInstallDialog = null; 157 | } 158 | 159 | // if don't have google play, check for OpenCV before trying to init 160 | if (!isOpenCVInstalled(Version,AppContext)) { 161 | 162 | boolean isNonPlayAppAllowed = false; 163 | try { 164 | isNonPlayAppAllowed = Settings.Secure.getInt(AppContext.getContentResolver(), Settings.Secure.INSTALL_NON_MARKET_APPS) == 1; 165 | } catch (Settings.SettingNotFoundException e) { 166 | e.printStackTrace(); 167 | } 168 | 169 | // AlertDialog.Builder askInstallOpenCV = new AlertDialog.Builder(AppContext); 170 | // 171 | // askInstallOpenCV.setTitle(R.string.install_opencv); 172 | // askInstallOpenCV.setMessage(R.string.ask_install_opencv); 173 | // askInstallOpenCV.setCancelable(false); 174 | 175 | // if (isNonPlayAppAllowed) { 176 | // askInstallOpenCV.setNeutralButton(R.string.githubdownload, new DialogInterface.OnClickListener() { 177 | // 178 | // String arch = Build.SUPPORTED_ABIS[0]; 179 | // 180 | // public void onClick(DialogInterface dialog, int which) { 181 | // 182 | // String sAndroidUrl = "https://github.com/ctodobom/opencv/releases/download/3.1.0/OpenCV_3.1.0_Manager_3.10_" + arch + ".apk"; 183 | // 184 | // onComplete = new MyBroadcastReceiver(AppContext); 185 | // AppContext.registerReceiver(onComplete, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)); 186 | // 187 | // final DownloadManager dm = (DownloadManager) AppContext.getSystemService(AppContext.DOWNLOAD_SERVICE); 188 | // DownloadManager.Request request = new DownloadManager.Request(Uri.parse(sAndroidUrl)); 189 | // String sDest = "file://" + android.os.Environment.getExternalStorageDirectory().toString() + "/Download/OpenCV_3.1.0_Manager_3.10_" + arch + ".apk"; 190 | // request.setDestinationUri(Uri.parse(sDest)); 191 | // myDownloadReference = dm.enqueue(request); 192 | // 193 | // dialog.dismiss(); 194 | // 195 | // waitInstallOpenCV = new AlertDialog.Builder(AppContext); 196 | // 197 | // waitInstallOpenCV.setTitle(R.string.downloading); 198 | // waitInstallOpenCV.setMessage(R.string.downloading_opencv); 199 | // 200 | // waitInstallOpenCV.setCancelable(false); 201 | // waitInstallOpenCV.setOnCancelListener(new DialogInterface.OnCancelListener() { 202 | // 203 | // @Override 204 | // public void onCancel(DialogInterface dialog) { 205 | // dm.remove(myDownloadReference); 206 | // AppContext.unregisterReceiver(onComplete); 207 | // dialog.dismiss(); 208 | // mAskInstallDialog = null; 209 | // } 210 | // }); 211 | // 212 | // waitInstallOpenCV.setNegativeButton(R.string.answer_cancel, new DialogInterface.OnClickListener() { 213 | // @Override 214 | // public void onClick(DialogInterface dialog, int which) { 215 | // dm.remove(myDownloadReference); 216 | // AppContext.unregisterReceiver(onComplete); 217 | // dialog.dismiss(); 218 | // mAskInstallDialog = null; 219 | // } 220 | // }); 221 | // 222 | // waitOpenCVDialog = waitInstallOpenCV.create(); 223 | // waitOpenCVDialog.show(); 224 | // 225 | // } 226 | // 227 | // }); 228 | // } 229 | 230 | // if (isGooglePlayInstalled(AppContext)) { 231 | // askInstallOpenCV.setPositiveButton(R.string.googleplay, new DialogInterface.OnClickListener() { 232 | // 233 | // @Override 234 | // public void onClick(DialogInterface dialog, int which) { 235 | // dialog.dismiss(); 236 | // AppContext.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=org.opencv.engine"))); 237 | // } 238 | // }); 239 | // } 240 | // 241 | // if (!isNonPlayAppAllowed && !isGooglePlayInstalled(AppContext)) { 242 | // askInstallOpenCV.setMessage( AppContext.getString(R.string.ask_install_opencv) 243 | // + "\n\n" + AppContext.getString(R.string.messageactivateunknown) 244 | // ); 245 | // 246 | // askInstallOpenCV.setNeutralButton(R.string.activateunknown , new DialogInterface.OnClickListener() { 247 | // 248 | // @Override 249 | // public void onClick(DialogInterface dialog, int which) { 250 | // dialog.dismiss(); 251 | // AppContext.startActivity(new Intent(Settings.ACTION_SECURITY_SETTINGS)); 252 | // } 253 | // }); 254 | // } 255 | // 256 | // mAskInstallDialog = askInstallOpenCV.create(); 257 | // 258 | // mAskInstallDialog.show(); 259 | // 260 | // } else { 261 | // // initialize opencv 262 | // return OpenCVLoader.initAsync(Version, AppContext, Callback); 263 | } 264 | 265 | return false; 266 | 267 | } 268 | 269 | } 270 | -------------------------------------------------------------------------------- /ios/Classes/IPDFCameraViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // IPDFCameraViewController.m 3 | // InstaPDF 4 | // 5 | // Created by Maximilian Mackh on 06/01/15. 6 | // Copyright (c) 2015 mackh ag. All rights reserved. 7 | // 8 | 9 | #import "IPDFCameraViewController.h" 10 | 11 | //#import 12 | #import 13 | #import 14 | #import 15 | #import 16 | #import 17 | #import 18 | 19 | @interface IPDFCameraViewController () 20 | 21 | @property (nonatomic,strong) AVCaptureSession *captureSession; 22 | @property (nonatomic,strong) AVCaptureDevice *captureDevice; 23 | @property (nonatomic,strong) EAGLContext *context; 24 | 25 | @property (nonatomic, strong) AVCaptureStillImageOutput* stillImageOutput; 26 | 27 | @property (nonatomic, assign) BOOL forceStop; 28 | @property (nonatomic, assign) float lastDetectionRate; 29 | 30 | @property (atomic, assign) BOOL isCapturing; 31 | @property (atomic, assign) CGFloat imageDetectionConfidence; 32 | 33 | @end 34 | 35 | @implementation IPDFCameraViewController 36 | { 37 | CIContext *_coreImageContext; 38 | GLuint _renderBuffer; 39 | GLKView *_glkView; 40 | 41 | BOOL _isStopped; 42 | 43 | NSTimer *_borderDetectTimeKeeper; 44 | BOOL _borderDetectFrame; 45 | CIRectangleFeature *_borderDetectLastRectangleFeature; 46 | } 47 | 48 | - (void)awakeFromNib 49 | { 50 | [super awakeFromNib]; 51 | 52 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_backgroundMode) name:UIApplicationWillResignActiveNotification object:nil]; 53 | 54 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_foregroundMode) name:UIApplicationDidBecomeActiveNotification object:nil]; 55 | } 56 | 57 | - (void)_backgroundMode 58 | { 59 | self.forceStop = YES; 60 | } 61 | 62 | - (void)_foregroundMode 63 | { 64 | self.forceStop = NO; 65 | } 66 | 67 | - (void)invalidate 68 | { 69 | [self stop]; 70 | } 71 | 72 | - (void)dealloc 73 | { 74 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 75 | } 76 | 77 | - (void)createGLKView 78 | { 79 | if (self.context) return; 80 | 81 | self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; 82 | GLKView *view = [[GLKView alloc] initWithFrame:self.bounds]; 83 | view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 84 | view.translatesAutoresizingMaskIntoConstraints = YES; 85 | view.context = self.context; 86 | view.contentScaleFactor = 1.0f; 87 | view.drawableDepthFormat = GLKViewDrawableDepthFormat24; 88 | [self insertSubview:view atIndex:0]; 89 | _glkView = view; 90 | glGenRenderbuffers(1, &_renderBuffer); 91 | glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer); 92 | _coreImageContext = [CIContext contextWithEAGLContext:self.context]; 93 | [EAGLContext setCurrentContext:self.context]; 94 | } 95 | 96 | - (void)setupCameraView 97 | { 98 | 99 | 100 | // NSLog(@"contrast in camera view : %f",self.); 101 | [self createGLKView]; 102 | 103 | AVCaptureDevice *device = nil; 104 | NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; 105 | for (AVCaptureDevice *possibleDevice in devices) { 106 | if (self.useFrontCam) { 107 | if ([possibleDevice position] == AVCaptureDevicePositionFront) { 108 | device = possibleDevice; 109 | } 110 | } else { 111 | if ([possibleDevice position] != AVCaptureDevicePositionFront) { 112 | device = possibleDevice; 113 | } 114 | } 115 | } 116 | if (!device) return; 117 | 118 | self.imageDetectionConfidence = 0.0; 119 | 120 | AVCaptureSession *session = [[AVCaptureSession alloc] init]; 121 | self.captureSession = session; 122 | [session beginConfiguration]; 123 | self.captureDevice = device; 124 | 125 | NSError *error = nil; 126 | AVCaptureDeviceInput* input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error]; 127 | session.sessionPreset = AVCaptureSessionPresetPhoto; 128 | [session addInput:input]; 129 | 130 | AVCaptureVideoDataOutput *dataOutput = [[AVCaptureVideoDataOutput alloc] init]; 131 | [dataOutput setAlwaysDiscardsLateVideoFrames:YES]; 132 | [dataOutput setVideoSettings:@{(id)kCVPixelBufferPixelFormatTypeKey:@(kCVPixelFormatType_32BGRA)}]; 133 | [dataOutput setSampleBufferDelegate:self queue:dispatch_get_main_queue()]; 134 | [session addOutput:dataOutput]; 135 | 136 | self.stillImageOutput = [[AVCaptureStillImageOutput alloc] init]; 137 | [session addOutput:self.stillImageOutput]; 138 | 139 | AVCaptureConnection *connection = [dataOutput.connections firstObject]; 140 | [connection setVideoOrientation:AVCaptureVideoOrientationPortrait]; 141 | 142 | if (device.isFlashAvailable) 143 | { 144 | [device lockForConfiguration:nil]; 145 | [device setFlashMode:AVCaptureFlashModeOff]; 146 | [device unlockForConfiguration]; 147 | 148 | if ([device isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) 149 | { 150 | [device lockForConfiguration:nil]; 151 | [device setFocusMode:AVCaptureFocusModeContinuousAutoFocus]; 152 | [device unlockForConfiguration]; 153 | } 154 | } 155 | 156 | [session commitConfiguration]; 157 | } 158 | 159 | - (void)setCameraViewType:(IPDFCameraViewType)cameraViewType 160 | { 161 | UIBlurEffect * effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark]; 162 | UIVisualEffectView *viewWithBlurredBackground =[[UIVisualEffectView alloc] initWithEffect:effect]; 163 | viewWithBlurredBackground.frame = self.bounds; 164 | [self insertSubview:viewWithBlurredBackground aboveSubview:_glkView]; 165 | 166 | _cameraViewType = cameraViewType; 167 | 168 | 169 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^ 170 | { 171 | [viewWithBlurredBackground removeFromSuperview]; 172 | }); 173 | } 174 | 175 | -(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection 176 | { 177 | if (self.forceStop) return; 178 | if (_isStopped || self.isCapturing || !CMSampleBufferIsValid(sampleBuffer)) return; 179 | 180 | CVPixelBufferRef pixelBuffer = (CVPixelBufferRef)CMSampleBufferGetImageBuffer(sampleBuffer); 181 | 182 | CIImage *image = [CIImage imageWithCVPixelBuffer:pixelBuffer]; 183 | 184 | if (self.cameraViewType != IPDFCameraViewTypeNormal) 185 | { 186 | image = [self filteredImageUsingEnhanceFilterOnImage:image]; 187 | } 188 | else 189 | { 190 | image = [self filteredImageUsingContrastFilterOnImage:image]; 191 | } 192 | 193 | if (self.isBorderDetectionEnabled) 194 | { 195 | if (_borderDetectFrame) 196 | { 197 | _borderDetectLastRectangleFeature = [self biggestRectangleInRectangles:[[self highAccuracyRectangleDetector] featuresInImage:image]]; 198 | _borderDetectFrame = NO; 199 | } 200 | 201 | if (_borderDetectLastRectangleFeature) 202 | { 203 | self.imageDetectionConfidence += .5; 204 | 205 | image = [self drawHighlightOverlayForPoints:image topLeft:_borderDetectLastRectangleFeature.topLeft topRight:_borderDetectLastRectangleFeature.topRight bottomLeft:_borderDetectLastRectangleFeature.bottomLeft bottomRight:_borderDetectLastRectangleFeature.bottomRight]; 206 | } 207 | else 208 | { 209 | self.imageDetectionConfidence = 0.0f; 210 | } 211 | } 212 | 213 | if (self.context && _coreImageContext) 214 | { 215 | [_coreImageContext drawImage:image inRect:self.bounds fromRect:image.extent]; 216 | [self.context presentRenderbuffer:GL_RENDERBUFFER]; 217 | 218 | [_glkView setNeedsDisplay]; 219 | } 220 | } 221 | 222 | - (void)enableBorderDetectFrame 223 | { 224 | _borderDetectFrame = YES; 225 | } 226 | 227 | - (CIImage *)drawHighlightOverlayForPoints:(CIImage *)image topLeft:(CGPoint)topLeft topRight:(CGPoint)topRight bottomLeft:(CGPoint)bottomLeft bottomRight:(CGPoint)bottomRight 228 | { 229 | CIImage *overlay = [CIImage imageWithColor:[[CIColor alloc] initWithColor:self.overlayColor]]; 230 | overlay = [overlay imageByCroppingToRect:image.extent]; 231 | overlay = [overlay imageByApplyingFilter:@"CIPerspectiveTransformWithExtent" withInputParameters:@{@"inputExtent":[CIVector vectorWithCGRect:image.extent],@"inputTopLeft":[CIVector vectorWithCGPoint:topLeft],@"inputTopRight":[CIVector vectorWithCGPoint:topRight],@"inputBottomLeft":[CIVector vectorWithCGPoint:bottomLeft],@"inputBottomRight":[CIVector vectorWithCGPoint:bottomRight]}]; 232 | 233 | return [overlay imageByCompositingOverImage:image]; 234 | } 235 | 236 | - (void)start 237 | { 238 | 239 | 240 | _isStopped = NO; 241 | 242 | [self.captureSession startRunning]; 243 | 244 | float detectionRefreshRate = _detectionRefreshRateInMS; 245 | CGFloat detectionRefreshRateInSec = detectionRefreshRate/100; 246 | 247 | if (_lastDetectionRate != _detectionRefreshRateInMS) { 248 | if (_borderDetectTimeKeeper) { 249 | [_borderDetectTimeKeeper invalidate]; 250 | } 251 | _borderDetectTimeKeeper = [NSTimer scheduledTimerWithTimeInterval:detectionRefreshRateInSec target:self selector:@selector(enableBorderDetectFrame) userInfo:nil repeats:YES]; 252 | } 253 | 254 | [self hideGLKView:NO completion:nil]; 255 | 256 | _lastDetectionRate = _detectionRefreshRateInMS; 257 | } 258 | 259 | - (void)stop 260 | { 261 | _isStopped = YES; 262 | 263 | [self.captureSession stopRunning]; 264 | 265 | [_borderDetectTimeKeeper invalidate]; 266 | 267 | [self hideGLKView:YES completion:nil]; 268 | } 269 | 270 | - (void)setEnableTorch:(BOOL)enableTorch 271 | { 272 | _enableTorch = enableTorch; 273 | 274 | AVCaptureDevice *device = self.captureDevice; 275 | if ([device hasTorch] && [device hasFlash]) 276 | { 277 | [device lockForConfiguration:nil]; 278 | if (enableTorch) 279 | { 280 | [device setTorchMode:AVCaptureTorchModeOn]; 281 | } 282 | else 283 | { 284 | [device setTorchMode:AVCaptureTorchModeOff]; 285 | } 286 | [device unlockForConfiguration]; 287 | } 288 | } 289 | 290 | - (void)setUseFrontCam:(BOOL)useFrontCam 291 | { 292 | _useFrontCam = useFrontCam; 293 | [self stop]; 294 | [self setupCameraView]; 295 | [self start]; 296 | } 297 | 298 | 299 | - (void)setContrast:(float)contrast 300 | { 301 | 302 | _contrast = contrast; 303 | } 304 | 305 | - (void)setSaturation:(float)saturation 306 | { 307 | _saturation = saturation; 308 | } 309 | 310 | - (void)setBrightness:(float)brightness 311 | { 312 | _brightness = brightness; 313 | } 314 | 315 | - (void)setDetectionRefreshRateInMS:(NSInteger)detectionRefreshRateInMS 316 | { 317 | _detectionRefreshRateInMS = detectionRefreshRateInMS; 318 | } 319 | 320 | 321 | - (void)focusAtPoint:(CGPoint)point completionHandler:(void(^)(void))completionHandler 322 | { 323 | AVCaptureDevice *device = self.captureDevice; 324 | CGPoint pointOfInterest = CGPointZero; 325 | CGSize frameSize = self.bounds.size; 326 | pointOfInterest = CGPointMake(point.y / frameSize.height, 1.f - (point.x / frameSize.width)); 327 | 328 | if ([device isFocusPointOfInterestSupported] && [device isFocusModeSupported:AVCaptureFocusModeAutoFocus]) 329 | { 330 | NSError *error; 331 | if ([device lockForConfiguration:&error]) 332 | { 333 | if ([device isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) 334 | { 335 | [device setFocusMode:AVCaptureFocusModeContinuousAutoFocus]; 336 | [device setFocusPointOfInterest:pointOfInterest]; 337 | } 338 | 339 | if([device isExposurePointOfInterestSupported] && [device isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]) 340 | { 341 | [device setExposurePointOfInterest:pointOfInterest]; 342 | [device setExposureMode:AVCaptureExposureModeContinuousAutoExposure]; 343 | completionHandler(); 344 | } 345 | 346 | [device unlockForConfiguration]; 347 | } 348 | } 349 | else 350 | { 351 | completionHandler(); 352 | } 353 | } 354 | 355 | - (void)captureImageWithCompletionHander:(void(^)(UIImage *data, UIImage *initialData, CIRectangleFeature *rectangleFeature))completionHandler; 356 | { 357 | 358 | if (self.isCapturing) return; 359 | self.isCapturing = true; 360 | 361 | __weak typeof(self) weakSelf = self; 362 | 363 | [weakSelf hideGLKView:YES completion:^ 364 | { 365 | [weakSelf hideGLKView:NO completion:^ 366 | { 367 | [weakSelf hideGLKView:YES completion:nil]; 368 | }]; 369 | }]; 370 | 371 | AVCaptureConnection *videoConnection = nil; 372 | for (AVCaptureConnection *connection in self.stillImageOutput.connections) 373 | { 374 | for (AVCaptureInputPort *port in [connection inputPorts]) 375 | { 376 | if ([[port mediaType] isEqual:AVMediaTypeVideo] ) 377 | { 378 | videoConnection = connection; 379 | break; 380 | } 381 | } 382 | if (videoConnection) break; 383 | } 384 | 385 | [self.stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler: ^(CMSampleBufferRef imageSampleBuffer, NSError *error) 386 | { 387 | NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer]; 388 | 389 | if (weakSelf.cameraViewType == IPDFCameraViewTypeBlackAndWhite || weakSelf.isBorderDetectionEnabled) 390 | { 391 | CIImage *enhancedImage = [CIImage imageWithData:imageData]; 392 | 393 | if (weakSelf.cameraViewType == IPDFCameraViewTypeBlackAndWhite) 394 | { 395 | enhancedImage = [self filteredImageUsingEnhanceFilterOnImage:enhancedImage]; 396 | } 397 | else 398 | { 399 | enhancedImage = [self filteredImageUsingContrastFilterOnImage:enhancedImage]; 400 | } 401 | 402 | if (weakSelf.isBorderDetectionEnabled && rectangleDetectionConfidenceHighEnough(weakSelf.imageDetectionConfidence)) 403 | { 404 | CIRectangleFeature *rectangleFeature = [self biggestRectangleInRectangles:[[self highAccuracyRectangleDetector] featuresInImage:enhancedImage]]; 405 | 406 | if (rectangleFeature) 407 | { 408 | enhancedImage = [self correctPerspectiveForImage:enhancedImage withFeatures:rectangleFeature]; 409 | 410 | UIGraphicsBeginImageContext(CGSizeMake(enhancedImage.extent.size.height, enhancedImage.extent.size.width)); 411 | [[UIImage imageWithCIImage:enhancedImage scale:1.0 orientation:UIImageOrientationRight] drawInRect:CGRectMake(0,0, enhancedImage.extent.size.height, enhancedImage.extent.size.width)]; 412 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 413 | UIImage *initialImage = [UIImage imageWithData:imageData]; 414 | UIGraphicsEndImageContext(); 415 | 416 | [weakSelf hideGLKView:NO completion:nil]; 417 | completionHandler(image, initialImage, rectangleFeature); 418 | } 419 | } else { 420 | [weakSelf hideGLKView:NO completion:nil]; 421 | UIImage *initialImage = [UIImage imageWithData:imageData]; 422 | completionHandler(initialImage, initialImage, nil); 423 | } 424 | 425 | } 426 | else 427 | { 428 | [weakSelf hideGLKView:NO completion:nil]; 429 | UIImage *initialImage = [UIImage imageWithData:imageData]; 430 | completionHandler(initialImage, initialImage, nil); 431 | } 432 | 433 | weakSelf.isCapturing = NO; 434 | }]; 435 | } 436 | 437 | - (void)hideGLKView:(BOOL)hidden completion:(void(^)(void))completion 438 | { 439 | [UIView animateWithDuration:0.1 animations:^ 440 | { 441 | self->_glkView.alpha = (hidden) ? 0.0 : 1.0; 442 | } 443 | completion:^(BOOL finished) 444 | { 445 | if (!completion) return; 446 | completion(); 447 | }]; 448 | } 449 | 450 | - (CIImage *)filteredImageUsingEnhanceFilterOnImage:(CIImage *)image 451 | { 452 | [self start]; 453 | return [CIFilter filterWithName:@"CIColorControls" keysAndValues:kCIInputImageKey, image, @"inputBrightness", @(self.brightness), @"inputContrast", @(self.contrast), @"inputSaturation", @(self.saturation), nil].outputImage; 454 | } 455 | 456 | - (CIImage *)filteredImageUsingContrastFilterOnImage:(CIImage *)image 457 | { 458 | return [CIFilter filterWithName:@"CIColorControls" withInputParameters:@{@"inputContrast":@(1.0),kCIInputImageKey:image}].outputImage; 459 | } 460 | 461 | - (CIImage *)correctPerspectiveForImage:(CIImage *)image withFeatures:(CIRectangleFeature *)rectangleFeature 462 | { 463 | NSMutableDictionary *rectangleCoordinates = [NSMutableDictionary new]; 464 | CGPoint newLeft = CGPointMake(rectangleFeature.topLeft.x + 30, rectangleFeature.topLeft.y); 465 | CGPoint newRight = CGPointMake(rectangleFeature.topRight.x, rectangleFeature.topRight.y); 466 | CGPoint newBottomLeft = CGPointMake(rectangleFeature.bottomLeft.x + 30, rectangleFeature.bottomLeft.y); 467 | CGPoint newBottomRight = CGPointMake(rectangleFeature.bottomRight.x, rectangleFeature.bottomRight.y); 468 | 469 | 470 | rectangleCoordinates[@"inputTopLeft"] = [CIVector vectorWithCGPoint:newLeft]; 471 | rectangleCoordinates[@"inputTopRight"] = [CIVector vectorWithCGPoint:newRight]; 472 | rectangleCoordinates[@"inputBottomLeft"] = [CIVector vectorWithCGPoint:newBottomLeft]; 473 | rectangleCoordinates[@"inputBottomRight"] = [CIVector vectorWithCGPoint:newBottomRight]; 474 | return [image imageByApplyingFilter:@"CIPerspectiveCorrection" withInputParameters:rectangleCoordinates]; 475 | } 476 | 477 | - (CIDetector *)rectangleDetetor 478 | { 479 | static CIDetector *detector = nil; 480 | static dispatch_once_t onceToken; 481 | dispatch_once(&onceToken, ^ 482 | { 483 | detector = [CIDetector detectorOfType:CIDetectorTypeRectangle context:nil options:@{CIDetectorAccuracy : CIDetectorAccuracyLow,CIDetectorTracking : @(YES)}]; 484 | }); 485 | return detector; 486 | } 487 | 488 | - (CIDetector *)highAccuracyRectangleDetector 489 | { 490 | static CIDetector *detector = nil; 491 | static dispatch_once_t onceToken; 492 | dispatch_once(&onceToken, ^ 493 | { 494 | detector = [CIDetector detectorOfType:CIDetectorTypeRectangle context:nil options:@{CIDetectorAccuracy : CIDetectorAccuracyHigh, CIDetectorReturnSubFeatures: @(YES) }]; 495 | }); 496 | return detector; 497 | } 498 | 499 | - (CIRectangleFeature *)biggestRectangleInRectangles:(NSArray *)rectangles 500 | { 501 | if (![rectangles count]) return nil; 502 | 503 | float halfPerimiterValue = 0; 504 | 505 | CIRectangleFeature *biggestRectangle = [rectangles firstObject]; 506 | 507 | for (CIRectangleFeature *rect in rectangles) 508 | { 509 | CGPoint p1 = rect.topLeft; 510 | CGPoint p2 = rect.topRight; 511 | CGFloat width = hypotf(p1.x - p2.x, p1.y - p2.y); 512 | 513 | CGPoint p3 = rect.topLeft; 514 | CGPoint p4 = rect.bottomLeft; 515 | CGFloat height = hypotf(p3.x - p4.x, p3.y - p4.y); 516 | 517 | CGFloat currentHalfPerimiterValue = height + width; 518 | 519 | if (halfPerimiterValue < currentHalfPerimiterValue) 520 | { 521 | halfPerimiterValue = currentHalfPerimiterValue; 522 | biggestRectangle = rect; 523 | } 524 | } 525 | 526 | if (self.delegate) { 527 | [self.delegate didDetectRectangle:biggestRectangle withType:[self typeForRectangle:biggestRectangle]]; 528 | } 529 | 530 | return biggestRectangle; 531 | } 532 | 533 | - (IPDFRectangeType) typeForRectangle: (CIRectangleFeature*) rectangle { 534 | if (fabs(rectangle.topRight.y - rectangle.topLeft.y) > 100 || 535 | fabs(rectangle.topRight.x - rectangle.bottomRight.x) > 100 || 536 | fabs(rectangle.topLeft.x - rectangle.bottomLeft.x) > 100 || 537 | fabs(rectangle.bottomLeft.y - rectangle.bottomRight.y) > 100) { 538 | return IPDFRectangeTypeBadAngle; 539 | } else if ((_glkView.frame.origin.y + _glkView.frame.size.height) - rectangle.topLeft.y > 150 || 540 | (_glkView.frame.origin.y + _glkView.frame.size.height) - rectangle.topRight.y > 150 || 541 | _glkView.frame.origin.y - rectangle.bottomLeft.y > 150 || 542 | _glkView.frame.origin.y - rectangle.bottomRight.y > 150) { 543 | return IPDFRectangeTypeTooFar; 544 | } 545 | return IPDFRectangeTypeGood; 546 | } 547 | 548 | BOOL rectangleDetectionConfidenceHighEnough(float confidence) 549 | { 550 | return (confidence > 1.0); 551 | } 552 | 553 | @end 554 | -------------------------------------------------------------------------------- /android/src/main/java/com/example/document_scanner/ImageProcessor.java: -------------------------------------------------------------------------------- 1 | package com.example.document_scanner; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.SharedPreferences; 6 | import android.graphics.Bitmap; 7 | import android.graphics.Color; 8 | import android.graphics.Paint; 9 | import android.graphics.Path; 10 | import android.graphics.drawable.shapes.PathShape; 11 | import android.os.Handler; 12 | import android.os.Looper; 13 | import android.os.Message; 14 | import android.preference.PreferenceManager; 15 | import android.util.Log; 16 | 17 | import com.example.document_scanner.views.OpenNoteCameraView; 18 | import com.google.zxing.BinaryBitmap; 19 | import com.google.zxing.ChecksumException; 20 | import com.google.zxing.FormatException; 21 | import com.google.zxing.LuminanceSource; 22 | import com.google.zxing.NotFoundException; 23 | import com.google.zxing.RGBLuminanceSource; 24 | 25 | import com.google.zxing.Result; 26 | import com.google.zxing.ResultPoint; 27 | import com.google.zxing.common.HybridBinarizer; 28 | import com.google.zxing.multi.qrcode.QRCodeMultiReader; 29 | import com.example.document_scanner.helpers.OpenNoteMessage; 30 | import com.example.document_scanner.helpers.PreviewFrame; 31 | import com.example.document_scanner.helpers.Quadrilateral; 32 | import com.example.document_scanner.helpers.ScannedDocument; 33 | import com.example.document_scanner.helpers.Utils; 34 | import com.example.document_scanner.views.HUDCanvasView; 35 | 36 | import org.opencv.core.Core; 37 | import org.opencv.core.CvType; 38 | import org.opencv.core.Mat; 39 | import org.opencv.core.MatOfInt; 40 | import org.opencv.core.MatOfPoint; 41 | import org.opencv.core.MatOfPoint2f; 42 | import org.opencv.core.Point; 43 | import org.opencv.core.Rect; 44 | import org.opencv.core.Scalar; 45 | import org.opencv.core.Size; 46 | import org.opencv.imgcodecs.Imgcodecs; 47 | import org.opencv.imgproc.Imgproc; 48 | 49 | import org.opencv.calib3d.Calib3d; 50 | import java.util.ArrayList; 51 | import java.util.Arrays; 52 | import java.util.Collections; 53 | import java.util.Comparator; 54 | import java.util.Date; 55 | import java.util.HashMap; 56 | import java.util.List; 57 | 58 | /** 59 | * Created by allgood on 05/03/16. 60 | */ 61 | public class ImageProcessor extends Handler { 62 | 63 | private static final String TAG = "ImageProcessor"; 64 | private final Handler mUiHandler; 65 | private final OpenNoteCameraView mMainActivity; 66 | private boolean mBugRotate; 67 | private boolean colorMode = false; 68 | private boolean filterMode = true; 69 | private double colorGain = 1; // contrast 70 | private double colorBias = 10; // bright 71 | private int colorThresh = 115; // threshold 72 | private Size mPreviewSize; 73 | private Point[] mPreviewPoints; 74 | private ResultPoint[] qrResultPoints; 75 | private int numOfSquares = 0; 76 | private int numOfRectangles = 10; 77 | private boolean noGrayscale; 78 | 79 | public ImageProcessor(Looper looper, Handler uiHandler, OpenNoteCameraView mainActivity, Context context) { 80 | super(looper); 81 | mUiHandler = uiHandler; 82 | this.mMainActivity = mainActivity; 83 | SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context); 84 | mBugRotate = sharedPref.getBoolean("bug_rotate", false); 85 | } 86 | 87 | public void setNumOfRectangles(int numOfRectangles) { 88 | this.numOfRectangles = numOfRectangles; 89 | } 90 | 91 | public void setBrightness(double brightness) { 92 | this.colorBias = brightness; 93 | } 94 | 95 | public void setContrast(double contrast) { 96 | this.colorGain = contrast; 97 | } 98 | 99 | public void setRemoveGrayScale(boolean grayscale) { 100 | this.noGrayscale = grayscale; 101 | } 102 | 103 | public void handleMessage(Message msg) { 104 | 105 | if (msg.obj.getClass() == OpenNoteMessage.class) { 106 | 107 | OpenNoteMessage obj = (OpenNoteMessage) msg.obj; 108 | 109 | String command = obj.getCommand(); 110 | 111 | // Log.d(TAG, "Message Received: " + command + " - " + obj.getObj().toString()); 112 | 113 | if (command.equals("previewFrame")) { 114 | processPreviewFrame((PreviewFrame) obj.getObj()); 115 | } else if (command.equals("pictureTaken")) { 116 | processPicture((Mat) obj.getObj()); 117 | } else if (command.equals("colorMode")) { 118 | colorMode = (Boolean) obj.getObj(); 119 | } else if (command.equals("filterMode")) { 120 | filterMode = (Boolean) obj.getObj(); 121 | } 122 | } 123 | } 124 | 125 | private void processPreviewFrame(PreviewFrame previewFrame) { 126 | 127 | Result[] results = {}; 128 | 129 | Mat frame = previewFrame.getFrame(); 130 | 131 | try { 132 | results = zxing(frame); 133 | } catch (ChecksumException | FormatException e) { 134 | // TODO Auto-generated catch block 135 | e.printStackTrace(); 136 | } 137 | 138 | boolean qrOk = false; 139 | String currentQR = null; 140 | 141 | for (Result result : results) { 142 | String qrText = result.getText(); 143 | if (Utils.isMatch(qrText, "^P.. V.. S[0-9]+") && checkQR(qrText)) { 144 | Log.d(TAG, "QR Code valid: " + result.getText()); 145 | qrOk = true; 146 | currentQR = qrText; 147 | qrResultPoints = result.getResultPoints(); 148 | break; 149 | } else { 150 | Log.d(TAG, "QR Code ignored: " + result.getText()); 151 | } 152 | } 153 | 154 | boolean autoMode = previewFrame.isAutoMode(); 155 | boolean previewOnly = previewFrame.isPreviewOnly(); 156 | boolean focused = mMainActivity.isFocused(); 157 | 158 | if (detectPreviewDocument(frame) && focused) { 159 | numOfSquares++; 160 | if (numOfSquares == numOfRectangles) { 161 | mMainActivity.requestPicture(); 162 | mMainActivity.waitSpinnerVisible(); 163 | numOfSquares = 0; 164 | } 165 | } else { 166 | numOfSquares = 0; 167 | } 168 | 169 | frame.release(); 170 | mMainActivity.setImageProcessorBusy(false); 171 | 172 | } 173 | 174 | public void processPicture(Mat picture) { 175 | 176 | Mat img = Imgcodecs.imdecode(picture, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); 177 | picture.release(); 178 | 179 | Log.d(TAG, "processPicture - imported image " + img.size().width + "x" + img.size().height); 180 | 181 | if (mBugRotate) { 182 | Core.flip(img, img, 1); 183 | Core.flip(img, img, 0); 184 | } 185 | 186 | ScannedDocument doc = detectDocument(img); 187 | 188 | mMainActivity.getHUD().clear(); 189 | mMainActivity.invalidateHUD(); 190 | mMainActivity.saveDocument(doc); 191 | doc.release(); 192 | picture.release(); 193 | 194 | mMainActivity.setImageProcessorBusy(false); 195 | mMainActivity.setAttemptToFocus(false); 196 | mMainActivity.waitSpinnerInvisible(); 197 | 198 | } 199 | 200 | private ScannedDocument detectDocument(Mat inputRgba) { 201 | ArrayList contours = findContours(inputRgba); 202 | 203 | ScannedDocument sd = new ScannedDocument(inputRgba); 204 | 205 | sd.originalSize = inputRgba.size(); 206 | Quadrilateral quad = getQuadrilateral(contours, sd.originalSize); 207 | 208 | double ratio = sd.originalSize.height / 500; 209 | sd.heightWithRatio = Double.valueOf(sd.originalSize.width / ratio).intValue(); 210 | sd.widthWithRatio = Double.valueOf(sd.originalSize.height / ratio).intValue(); 211 | 212 | Mat doc; 213 | if (quad != null) { 214 | 215 | sd.originalPoints = new Point[4]; 216 | 217 | sd.originalPoints[0] = new Point(sd.widthWithRatio - quad.points[3].y, quad.points[3].x); // Topleft 218 | sd.originalPoints[1] = new Point(sd.widthWithRatio - quad.points[0].y, quad.points[0].x); // TopRight 219 | sd.originalPoints[2] = new Point(sd.widthWithRatio - quad.points[1].y, quad.points[1].x); // BottomRight 220 | sd.originalPoints[3] = new Point(sd.widthWithRatio - quad.points[2].y, quad.points[2].x); // BottomLeft 221 | 222 | sd.previewPoints = mPreviewPoints; 223 | 224 | MatOfPoint c = quad.contour; 225 | 226 | sd.quadrilateral = quad; 227 | sd.previewPoints = mPreviewPoints; 228 | sd.previewSize = mPreviewSize; 229 | 230 | doc = fourPointTransform(inputRgba, quad.points); 231 | } else { 232 | doc = new Mat(inputRgba.size(), CvType.CV_8UC4); 233 | inputRgba.copyTo(doc); 234 | } 235 | enhanceDocument(doc); 236 | return sd.setProcessed(doc); 237 | } 238 | 239 | private HashMap pageHistory = new HashMap<>(); 240 | 241 | private boolean checkQR(String qrCode) { 242 | 243 | return !(pageHistory.containsKey(qrCode) && pageHistory.get(qrCode) > new Date().getTime() / 1000 - 15); 244 | 245 | } 246 | 247 | private boolean detectPreviewDocument(Mat inputRgba) { 248 | 249 | ArrayList contours = findContours(inputRgba); 250 | 251 | Quadrilateral quad = getQuadrilateral(contours, inputRgba.size()); 252 | 253 | // Log.i("DESENHAR", "Quad----->" + quad); 254 | 255 | mPreviewPoints = null; 256 | mPreviewSize = inputRgba.size(); 257 | 258 | if (quad != null) { 259 | 260 | Point[] rescaledPoints = new Point[4]; 261 | 262 | double ratio = inputRgba.size().height / 500; 263 | 264 | for (int i = 0; i < 4; i++) { 265 | int x = Double.valueOf(quad.points[i].x * ratio).intValue(); 266 | int y = Double.valueOf(quad.points[i].y * ratio).intValue(); 267 | if (mBugRotate) { 268 | rescaledPoints[(i + 2) % 4] = new Point(Math.abs(x - mPreviewSize.width), 269 | Math.abs(y - mPreviewSize.height)); 270 | } else { 271 | rescaledPoints[i] = new Point(x, y); 272 | } 273 | } 274 | 275 | mPreviewPoints = rescaledPoints; 276 | 277 | drawDocumentBox(mPreviewPoints, mPreviewSize); 278 | 279 | return true; 280 | 281 | } 282 | 283 | mMainActivity.getHUD().clear(); 284 | mMainActivity.invalidateHUD(); 285 | 286 | return false; 287 | 288 | } 289 | 290 | private void drawDocumentBox(Point[] points, Size stdSize) { 291 | 292 | Path path = new Path(); 293 | 294 | HUDCanvasView hud = mMainActivity.getHUD(); 295 | 296 | // ATTENTION: axis are swapped 297 | 298 | float previewWidth = (float) stdSize.height; 299 | float previewHeight = (float) stdSize.width; 300 | 301 | path.moveTo(previewWidth - (float) points[0].y, (float) points[0].x); 302 | path.lineTo(previewWidth - (float) points[1].y, (float) points[1].x); 303 | path.lineTo(previewWidth - (float) points[2].y, (float) points[2].x); 304 | path.lineTo(previewWidth - (float) points[3].y, (float) points[3].x); 305 | path.close(); 306 | 307 | PathShape newBox = new PathShape(path, previewWidth, previewHeight); 308 | 309 | Paint paint = new Paint(); 310 | paint.setColor(mMainActivity.parsedOverlayColor()); 311 | 312 | Paint border = new Paint(); 313 | border.setColor(mMainActivity.parsedOverlayColor()); 314 | border.setStrokeWidth(5); 315 | 316 | hud.clear(); 317 | hud.addShape(newBox, paint, border); 318 | mMainActivity.invalidateHUD(); 319 | 320 | } 321 | 322 | private Quadrilateral getQuadrilateral(ArrayList contours, Size srcSize) { 323 | 324 | double ratio = srcSize.height / 500; 325 | int height = Double.valueOf(srcSize.height / ratio).intValue(); 326 | int width = Double.valueOf(srcSize.width / ratio).intValue(); 327 | Size size = new Size(width, height); 328 | 329 | // Log.i("COUCOU", "Size----->" + size); 330 | for (MatOfPoint c : contours) { 331 | MatOfPoint2f c2f = new MatOfPoint2f(c.toArray()); 332 | double peri = Imgproc.arcLength(c2f, true); 333 | MatOfPoint2f approx = new MatOfPoint2f(); 334 | Imgproc.approxPolyDP(c2f, approx, 0.02 * peri, true); 335 | 336 | Point[] points = approx.toArray(); 337 | 338 | // select biggest 4 angles polygon 339 | // if (points.length == 4) { 340 | Point[] foundPoints = sortPoints(points); 341 | 342 | if (insideArea(foundPoints, size)) { 343 | 344 | return new Quadrilateral(c, foundPoints); 345 | } 346 | // } 347 | } 348 | 349 | return null; 350 | } 351 | 352 | private Point[] sortPoints(Point[] src) { 353 | 354 | ArrayList srcPoints = new ArrayList<>(Arrays.asList(src)); 355 | 356 | Point[] result = { null, null, null, null }; 357 | 358 | Comparator sumComparator = new Comparator() { 359 | @Override 360 | public int compare(Point lhs, Point rhs) { 361 | return Double.valueOf(lhs.y + lhs.x).compareTo(rhs.y + rhs.x); 362 | } 363 | }; 364 | 365 | Comparator diffComparator = new Comparator() { 366 | 367 | @Override 368 | public int compare(Point lhs, Point rhs) { 369 | return Double.valueOf(lhs.y - lhs.x).compareTo(rhs.y - rhs.x); 370 | } 371 | }; 372 | 373 | // top-left corner = minimal sum 374 | result[0] = Collections.min(srcPoints, sumComparator); 375 | 376 | // bottom-right corner = maximal sum 377 | result[2] = Collections.max(srcPoints, sumComparator); 378 | 379 | // top-right corner = minimal diference 380 | result[1] = Collections.min(srcPoints, diffComparator); 381 | 382 | // bottom-left corner = maximal diference 383 | result[3] = Collections.max(srcPoints, diffComparator); 384 | 385 | return result; 386 | } 387 | 388 | private boolean insideArea(Point[] rp, Size size) { 389 | 390 | int width = Double.valueOf(size.width).intValue(); 391 | int height = Double.valueOf(size.height).intValue(); 392 | 393 | int minimumSize = width / 10; 394 | 395 | boolean isANormalShape = rp[0].x != rp[1].x && rp[1].y != rp[0].y && rp[2].y != rp[3].y && rp[3].x != rp[2].x; 396 | boolean isBigEnough = ((rp[1].x - rp[0].x >= minimumSize) && (rp[2].x - rp[3].x >= minimumSize) 397 | && (rp[3].y - rp[0].y >= minimumSize) && (rp[2].y - rp[1].y >= minimumSize)); 398 | 399 | double leftOffset = rp[0].x - rp[3].x; 400 | double rightOffset = rp[1].x - rp[2].x; 401 | double bottomOffset = rp[0].y - rp[1].y; 402 | double topOffset = rp[2].y - rp[3].y; 403 | 404 | boolean isAnActualRectangle = ((leftOffset <= minimumSize && leftOffset >= -minimumSize) 405 | && (rightOffset <= minimumSize && rightOffset >= -minimumSize) 406 | && (bottomOffset <= minimumSize && bottomOffset >= -minimumSize) 407 | && (topOffset <= minimumSize && topOffset >= -minimumSize)); 408 | 409 | return isANormalShape && isAnActualRectangle && isBigEnough; 410 | } 411 | 412 | private void enhanceDocument(Mat src) { 413 | 414 | 415 | // Imgproc.cvtColor(src, src, Imgproc.COLOR_RGBA2GRAY); 416 | //src.convertTo(src, CvType.CV_8UC1, colorGain, colorBias); 417 | 418 | if (!noGrayscale) { 419 | Imgproc.cvtColor(src, src, Imgproc.COLOR_RGBA2GRAY); 420 | } 421 | src.convertTo(src, CvType.CV_8UC1, colorGain, colorBias); 422 | 423 | 424 | } 425 | 426 | /** 427 | * When a pixel have any of its three elements above the threshold value and the 428 | * average of the three values are less than 80% of the higher one, brings all 429 | * three values to the max possible keeping the relation between them, any 430 | * absolute white keeps the value, all others go to absolute black. 431 | * 432 | * src must be a 3 channel image with 8 bits per channel 433 | * 434 | * @param src 435 | * @param threshold 436 | */ 437 | private void colorThresh(Mat src, int threshold) { 438 | Size srcSize = src.size(); 439 | int size = (int) (srcSize.height * srcSize.width) * 3; 440 | byte[] d = new byte[size]; 441 | src.get(0, 0, d); 442 | 443 | for (int i = 0; i < size; i += 3) { 444 | 445 | // the "& 0xff" operations are needed to convert the signed byte to double 446 | 447 | // avoid unneeded work 448 | if ((double) (d[i] & 0xff) == 255) { 449 | continue; 450 | } 451 | 452 | double max = Math.max(Math.max((double) (d[i] & 0xff), (double) (d[i + 1] & 0xff)), 453 | (double) (d[i + 2] & 0xff)); 454 | double mean = ((double) (d[i] & 0xff) + (double) (d[i + 1] & 0xff) + (double) (d[i + 2] & 0xff)) / 3; 455 | 456 | if (max > threshold && mean < max * 0.8) { 457 | d[i] = (byte) ((double) (d[i] & 0xff) * 255 / max); 458 | d[i + 1] = (byte) ((double) (d[i + 1] & 0xff) * 255 / max); 459 | d[i + 2] = (byte) ((double) (d[i + 2] & 0xff) * 255 / max); 460 | } else { 461 | d[i] = d[i + 1] = d[i + 2] = 0; 462 | } 463 | } 464 | src.put(0, 0, d); 465 | } 466 | 467 | private Mat fourPointTransform(Mat src, Point[] pts) { 468 | 469 | double ratio = src.size().height / 500; 470 | int height = Double.valueOf(src.size().height / ratio).intValue(); 471 | int width = Double.valueOf(src.size().width / ratio).intValue(); 472 | 473 | Point tl = pts[0]; 474 | Point tr = pts[1]; 475 | Point br = pts[2]; 476 | Point bl = pts[3]; 477 | 478 | double widthA = Math.sqrt(Math.pow(br.x - bl.x, 2) + Math.pow(br.y - bl.y, 2)); 479 | double widthB = Math.sqrt(Math.pow(tr.x - tl.x, 2) + Math.pow(tr.y - tl.y, 2)); 480 | 481 | double dw = Math.max(widthA, widthB) * ratio; 482 | int maxWidth = Double.valueOf(dw).intValue(); 483 | 484 | double heightA = Math.sqrt(Math.pow(tr.x - br.x, 2) + Math.pow(tr.y - br.y, 2)); 485 | double heightB = Math.sqrt(Math.pow(tl.x - bl.x, 2) + Math.pow(tl.y - bl.y, 2)); 486 | 487 | double dh = Math.max(heightA, heightB) * ratio; 488 | int maxHeight = Double.valueOf(dh).intValue(); 489 | 490 | Mat doc = new Mat(maxHeight, maxWidth, CvType.CV_8UC4); 491 | 492 | Mat src_mat = new Mat(4, 1, CvType.CV_32FC2); 493 | Mat dst_mat = new Mat(4, 1, CvType.CV_32FC2); 494 | 495 | src_mat.put(0, 0, tl.x * ratio, tl.y * ratio, tr.x * ratio, tr.y * ratio, br.x * ratio, br.y * ratio, 496 | bl.x * ratio, bl.y * ratio); 497 | dst_mat.put(0, 0, 0.0, 0.0, dw, 0.0, dw, dh, 0.0, dh); 498 | 499 | Mat m = Imgproc.getPerspectiveTransform(src_mat, dst_mat); 500 | 501 | Imgproc.warpPerspective(src, doc, m, doc.size()); 502 | 503 | return doc; 504 | } 505 | 506 | private ArrayList findContours(Mat src) { 507 | 508 | Mat grayImage = null; 509 | Mat cannedImage = null; 510 | Mat resizedImage = null; 511 | 512 | double ratio = src.size().height / 500; 513 | int height = Double.valueOf(src.size().height / ratio).intValue(); 514 | int width = Double.valueOf(src.size().width / ratio).intValue(); 515 | Size size = new Size(width, height); 516 | 517 | resizedImage = new Mat(size, CvType.CV_8UC4); 518 | grayImage = new Mat(size, CvType.CV_8UC4); 519 | cannedImage = new Mat(size, CvType.CV_8UC1); 520 | 521 | Imgproc.resize(src, resizedImage, size); 522 | Imgproc.cvtColor(resizedImage, grayImage, Imgproc.COLOR_RGBA2GRAY, 4); 523 | Imgproc.GaussianBlur(grayImage, grayImage, new Size(5, 5), 0); 524 | Imgproc.Canny(grayImage, cannedImage, 80, 100, 3, false); 525 | 526 | ArrayList contours = new ArrayList(); 527 | Mat hierarchy = new Mat(); 528 | 529 | Imgproc.findContours(cannedImage, contours, hierarchy, Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_SIMPLE); 530 | 531 | hierarchy.release(); 532 | 533 | Collections.sort(contours, new Comparator() { 534 | 535 | @Override 536 | public int compare(MatOfPoint lhs, MatOfPoint rhs) { 537 | return Double.valueOf(Imgproc.contourArea(rhs)).compareTo(Imgproc.contourArea(lhs)); 538 | } 539 | }); 540 | 541 | resizedImage.release(); 542 | grayImage.release(); 543 | cannedImage.release(); 544 | 545 | return contours; 546 | } 547 | 548 | private QRCodeMultiReader qrCodeMultiReader = new QRCodeMultiReader(); 549 | 550 | public Result[] zxing(Mat inputImage) throws ChecksumException, FormatException { 551 | 552 | int w = inputImage.width(); 553 | int h = inputImage.height(); 554 | 555 | Mat southEast; 556 | 557 | if (mBugRotate) { 558 | southEast = inputImage.submat(h - h / 4, h, 0, w / 2 - h / 4); 559 | } else { 560 | southEast = inputImage.submat(0, h / 4, w / 2 + h / 4, w); 561 | } 562 | 563 | Bitmap bMap = Bitmap.createBitmap(southEast.width(), southEast.height(), Bitmap.Config.ARGB_8888); 564 | org.opencv.android.Utils.matToBitmap(southEast, bMap); 565 | southEast.release(); 566 | int[] intArray = new int[bMap.getWidth() * bMap.getHeight()]; 567 | // copy pixel data from the Bitmap into the 'intArray' array 568 | bMap.getPixels(intArray, 0, bMap.getWidth(), 0, 0, bMap.getWidth(), bMap.getHeight()); 569 | 570 | LuminanceSource source = new RGBLuminanceSource(bMap.getWidth(), bMap.getHeight(), intArray); 571 | 572 | BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); 573 | 574 | Result[] results = {}; 575 | try { 576 | results = qrCodeMultiReader.decodeMultiple(bitmap); 577 | } catch (NotFoundException e) { 578 | } 579 | 580 | return results; 581 | 582 | } 583 | 584 | public void setBugRotate(boolean bugRotate) { 585 | mBugRotate = bugRotate; 586 | } 587 | 588 | } --------------------------------------------------------------------------------