From 53b514e39d14dcd3ede701615be7a3bbd032ee58 Mon Sep 17 00:00:00 2001 From: Double_V Date: Sun, 13 Dec 2020 17:21:26 +0800 Subject: [PATCH] delete lite,slim, iosdemo and android demo of dyrgaph branch for now (#1403) delete lite,slim, iosdemo and android demo of dyrgaph branch for now --- README_ch.md | 9 +- README_en.md | 12 +- deploy/android_demo/.gitignore | 9 - deploy/android_demo/README.md | 19 - deploy/android_demo/app/.gitignore | 1 - deploy/android_demo/app/build.gradle | 94 - deploy/android_demo/app/proguard-rules.pro | 21 - .../demo/ocr/ExampleInstrumentedTest.java | 26 - .../app/src/main/AndroidManifest.xml | 39 - .../app/src/main/assets/images/5.jpg | Bin 63736 -> 0 bytes .../src/main/assets/labels/ppocr_keys_v1.txt | 6623 ----------------- .../app/src/main/cpp/CMakeLists.txt | 117 - deploy/android_demo/app/src/main/cpp/common.h | 48 - .../android_demo/app/src/main/cpp/native.cpp | 115 - deploy/android_demo/app/src/main/cpp/native.h | 138 - .../app/src/main/cpp/ocr_clipper.cpp | 4629 ------------ .../app/src/main/cpp/ocr_clipper.hpp | 547 -- .../app/src/main/cpp/ocr_crnn_process.cpp | 140 - .../app/src/main/cpp/ocr_crnn_process.h | 19 - .../app/src/main/cpp/ocr_db_post_process.cpp | 336 - .../app/src/main/cpp/ocr_db_post_process.h | 17 - .../app/src/main/cpp/ocr_ppredictor.cpp | 186 - .../app/src/main/cpp/ocr_ppredictor.h | 112 - .../app/src/main/cpp/ppredictor.cpp | 70 - .../app/src/main/cpp/ppredictor.h | 74 - .../app/src/main/cpp/predictor_input.cpp | 29 - .../app/src/main/cpp/predictor_input.h | 28 - .../app/src/main/cpp/predictor_output.cpp | 27 - .../app/src/main/cpp/predictor_output.h | 35 - .../app/src/main/cpp/preprocess.cpp | 84 - .../app/src/main/cpp/preprocess.h | 14 - .../demo/ocr/AppCompatPreferenceActivity.java | 128 - .../paddle/lite/demo/ocr/MainActivity.java | 473 -- .../paddle/lite/demo/ocr/MiniActivity.java | 157 - .../lite/demo/ocr/OCRPredictorNative.java | 100 - .../paddle/lite/demo/ocr/OcrResultModel.java | 52 - .../baidu/paddle/lite/demo/ocr/Predictor.java | 355 - .../lite/demo/ocr/SettingsActivity.java | 201 - .../com/baidu/paddle/lite/demo/ocr/Utils.java | 159 - .../drawable-v24/ic_launcher_foreground.xml | 34 - .../res/drawable/ic_launcher_background.xml | 170 - .../app/src/main/res/layout/activity_main.xml | 99 - .../app/src/main/res/layout/activity_mini.xml | 46 - .../src/main/res/menu/menu_action_options.xml | 21 - .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 - .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 - .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 2963 -> 0 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 4905 -> 0 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 2060 -> 0 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 2783 -> 0 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 4490 -> 0 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 6895 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 6387 -> 0 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 10413 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 9128 -> 0 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 15132 -> 0 bytes .../app/src/main/res/values/arrays.xml | 39 - .../app/src/main/res/values/colors.xml | 6 - .../app/src/main/res/values/strings.xml | 26 - .../app/src/main/res/values/styles.xml | 25 - .../app/src/main/res/xml/file_paths.xml | 4 - .../app/src/main/res/xml/settings.xml | 75 - .../paddle/lite/demo/ocr/ExampleUnitTest.java | 17 - deploy/android_demo/build.gradle | 27 - deploy/android_demo/gradle.properties | 15 - .../gradle/wrapper/gradle-wrapper.jar | Bin 54329 -> 0 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 - deploy/android_demo/gradlew | 172 - deploy/android_demo/gradlew.bat | 84 - deploy/android_demo/settings.gradle | 1 - deploy/ios_demo/download_dependencies.sh | 32 - .../ocr_demo.xcodeproj/project.pbxproj | 462 -- deploy/ios_demo/ocr_demo/AppDelegate.h | 17 - deploy/ios_demo/ocr_demo/AppDelegate.m | 51 - .../AppIcon.appiconset/Contents.json | 98 - .../ocr_demo/Assets.xcassets/Contents.json | 6 - .../Base.lproj/LaunchScreen.storyboard | 25 - .../ocr_demo/Base.lproj/Main.storyboard | 111 - deploy/ios_demo/ocr_demo/BoxLayer.h | 20 - deploy/ios_demo/ocr_demo/BoxLayer.m | 80 - deploy/ios_demo/ocr_demo/Helpers.h | 31 - deploy/ios_demo/ocr_demo/Helpers.m | 17 - deploy/ios_demo/ocr_demo/Info.plist | 49 - deploy/ios_demo/ocr_demo/OcrData.h | 15 - deploy/ios_demo/ocr_demo/OcrData.m | 12 - deploy/ios_demo/ocr_demo/ViewController.h | 16 - deploy/ios_demo/ocr_demo/ViewController.mm | 536 -- deploy/ios_demo/ocr_demo/label_list.txt | 6623 ----------------- deploy/ios_demo/ocr_demo/main.m | 16 - deploy/ios_demo/ocr_demo/ocr.png | Bin 62926 -> 0 bytes .../ios_demo/ocr_demo/pdocr/ocr_clipper.cpp | 4629 ------------ .../ios_demo/ocr_demo/pdocr/ocr_clipper.hpp | 547 -- .../ocr_demo/pdocr/ocr_crnn_process.cpp | 141 - .../ocr_demo/pdocr/ocr_crnn_process.h | 19 - .../ocr_demo/pdocr/ocr_db_post_process.cpp | 372 - .../ocr_demo/pdocr/ocr_db_post_process.h | 10 - deploy/ios_demo/ocr_demo/timer.h | 87 - deploy/lite/Makefile | 77 - deploy/lite/config.txt | 4 - deploy/lite/crnn_process.cc | 115 - deploy/lite/crnn_process.h | 38 - deploy/lite/db_post_process.cc | 301 - deploy/lite/db_post_process.h | 62 - deploy/lite/ocr_db_crnn.cc | 368 - deploy/lite/prepare.sh | 9 - deploy/lite/readme.md | 233 - deploy/lite/readme_en.md | 190 - 107 files changed, 15 insertions(+), 31524 deletions(-) delete mode 100644 deploy/android_demo/.gitignore delete mode 100644 deploy/android_demo/README.md delete mode 100644 deploy/android_demo/app/.gitignore delete mode 100644 deploy/android_demo/app/build.gradle delete mode 100644 deploy/android_demo/app/proguard-rules.pro delete mode 100644 deploy/android_demo/app/src/androidTest/java/com/baidu/paddle/lite/demo/ocr/ExampleInstrumentedTest.java delete mode 100644 deploy/android_demo/app/src/main/AndroidManifest.xml delete mode 100644 deploy/android_demo/app/src/main/assets/images/5.jpg delete mode 100644 deploy/android_demo/app/src/main/assets/labels/ppocr_keys_v1.txt delete mode 100644 deploy/android_demo/app/src/main/cpp/CMakeLists.txt delete mode 100644 deploy/android_demo/app/src/main/cpp/common.h delete mode 100644 deploy/android_demo/app/src/main/cpp/native.cpp delete mode 100644 deploy/android_demo/app/src/main/cpp/native.h delete mode 100644 deploy/android_demo/app/src/main/cpp/ocr_clipper.cpp delete mode 100644 deploy/android_demo/app/src/main/cpp/ocr_clipper.hpp delete mode 100644 deploy/android_demo/app/src/main/cpp/ocr_crnn_process.cpp delete mode 100644 deploy/android_demo/app/src/main/cpp/ocr_crnn_process.h delete mode 100644 deploy/android_demo/app/src/main/cpp/ocr_db_post_process.cpp delete mode 100644 deploy/android_demo/app/src/main/cpp/ocr_db_post_process.h delete mode 100644 deploy/android_demo/app/src/main/cpp/ocr_ppredictor.cpp delete mode 100644 deploy/android_demo/app/src/main/cpp/ocr_ppredictor.h delete mode 100644 deploy/android_demo/app/src/main/cpp/ppredictor.cpp delete mode 100644 deploy/android_demo/app/src/main/cpp/ppredictor.h delete mode 100644 deploy/android_demo/app/src/main/cpp/predictor_input.cpp delete mode 100644 deploy/android_demo/app/src/main/cpp/predictor_input.h delete mode 100644 deploy/android_demo/app/src/main/cpp/predictor_output.cpp delete mode 100644 deploy/android_demo/app/src/main/cpp/predictor_output.h delete mode 100644 deploy/android_demo/app/src/main/cpp/preprocess.cpp delete mode 100644 deploy/android_demo/app/src/main/cpp/preprocess.h delete mode 100644 deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/AppCompatPreferenceActivity.java delete mode 100644 deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/MainActivity.java delete mode 100644 deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/MiniActivity.java delete mode 100644 deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/OCRPredictorNative.java delete mode 100644 deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/OcrResultModel.java delete mode 100644 deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/Predictor.java delete mode 100644 deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/SettingsActivity.java delete mode 100644 deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/Utils.java delete mode 100644 deploy/android_demo/app/src/main/res/drawable-v24/ic_launcher_foreground.xml delete mode 100644 deploy/android_demo/app/src/main/res/drawable/ic_launcher_background.xml delete mode 100644 deploy/android_demo/app/src/main/res/layout/activity_main.xml delete mode 100644 deploy/android_demo/app/src/main/res/layout/activity_mini.xml delete mode 100644 deploy/android_demo/app/src/main/res/menu/menu_action_options.xml delete mode 100644 deploy/android_demo/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml delete mode 100644 deploy/android_demo/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml delete mode 100644 deploy/android_demo/app/src/main/res/mipmap-hdpi/ic_launcher.png delete mode 100644 deploy/android_demo/app/src/main/res/mipmap-hdpi/ic_launcher_round.png delete mode 100644 deploy/android_demo/app/src/main/res/mipmap-mdpi/ic_launcher.png delete mode 100644 deploy/android_demo/app/src/main/res/mipmap-mdpi/ic_launcher_round.png delete mode 100644 deploy/android_demo/app/src/main/res/mipmap-xhdpi/ic_launcher.png delete mode 100644 deploy/android_demo/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png delete mode 100644 deploy/android_demo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png delete mode 100644 deploy/android_demo/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png delete mode 100644 deploy/android_demo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 deploy/android_demo/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png delete mode 100644 deploy/android_demo/app/src/main/res/values/arrays.xml delete mode 100644 deploy/android_demo/app/src/main/res/values/colors.xml delete mode 100644 deploy/android_demo/app/src/main/res/values/strings.xml delete mode 100644 deploy/android_demo/app/src/main/res/values/styles.xml delete mode 100644 deploy/android_demo/app/src/main/res/xml/file_paths.xml delete mode 100644 deploy/android_demo/app/src/main/res/xml/settings.xml delete mode 100644 deploy/android_demo/app/src/test/java/com/baidu/paddle/lite/demo/ocr/ExampleUnitTest.java delete mode 100644 deploy/android_demo/build.gradle delete mode 100644 deploy/android_demo/gradle.properties delete mode 100644 deploy/android_demo/gradle/wrapper/gradle-wrapper.jar delete mode 100644 deploy/android_demo/gradle/wrapper/gradle-wrapper.properties delete mode 100644 deploy/android_demo/gradlew delete mode 100644 deploy/android_demo/gradlew.bat delete mode 100644 deploy/android_demo/settings.gradle delete mode 100755 deploy/ios_demo/download_dependencies.sh delete mode 100644 deploy/ios_demo/ocr_demo.xcodeproj/project.pbxproj delete mode 100644 deploy/ios_demo/ocr_demo/AppDelegate.h delete mode 100644 deploy/ios_demo/ocr_demo/AppDelegate.m delete mode 100644 deploy/ios_demo/ocr_demo/Assets.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 deploy/ios_demo/ocr_demo/Assets.xcassets/Contents.json delete mode 100644 deploy/ios_demo/ocr_demo/Base.lproj/LaunchScreen.storyboard delete mode 100644 deploy/ios_demo/ocr_demo/Base.lproj/Main.storyboard delete mode 100644 deploy/ios_demo/ocr_demo/BoxLayer.h delete mode 100644 deploy/ios_demo/ocr_demo/BoxLayer.m delete mode 100644 deploy/ios_demo/ocr_demo/Helpers.h delete mode 100644 deploy/ios_demo/ocr_demo/Helpers.m delete mode 100644 deploy/ios_demo/ocr_demo/Info.plist delete mode 100644 deploy/ios_demo/ocr_demo/OcrData.h delete mode 100644 deploy/ios_demo/ocr_demo/OcrData.m delete mode 100644 deploy/ios_demo/ocr_demo/ViewController.h delete mode 100644 deploy/ios_demo/ocr_demo/ViewController.mm delete mode 100644 deploy/ios_demo/ocr_demo/label_list.txt delete mode 100644 deploy/ios_demo/ocr_demo/main.m delete mode 100644 deploy/ios_demo/ocr_demo/ocr.png delete mode 100644 deploy/ios_demo/ocr_demo/pdocr/ocr_clipper.cpp delete mode 100644 deploy/ios_demo/ocr_demo/pdocr/ocr_clipper.hpp delete mode 100644 deploy/ios_demo/ocr_demo/pdocr/ocr_crnn_process.cpp delete mode 100644 deploy/ios_demo/ocr_demo/pdocr/ocr_crnn_process.h delete mode 100644 deploy/ios_demo/ocr_demo/pdocr/ocr_db_post_process.cpp delete mode 100644 deploy/ios_demo/ocr_demo/pdocr/ocr_db_post_process.h delete mode 100644 deploy/ios_demo/ocr_demo/timer.h delete mode 100644 deploy/lite/Makefile delete mode 100644 deploy/lite/config.txt delete mode 100644 deploy/lite/crnn_process.cc delete mode 100644 deploy/lite/crnn_process.h delete mode 100644 deploy/lite/db_post_process.cc delete mode 100644 deploy/lite/db_post_process.h delete mode 100644 deploy/lite/ocr_db_crnn.cc delete mode 100644 deploy/lite/prepare.sh delete mode 100644 deploy/lite/readme.md delete mode 100644 deploy/lite/readme_en.md diff --git a/README_ch.md b/README_ch.md index 588af59c..fa400d31 100644 --- a/README_ch.md +++ b/README_ch.md @@ -77,9 +77,9 @@ PaddleOCR旨在打造一套丰富、领先、且实用的OCR工具库,助力 - [基于Python脚本预测引擎推理](./doc/doc_ch/inference.md) - [基于C++预测引擎推理](./deploy/cpp_infer/readme.md) - [服务化部署](./deploy/hubserving/readme.md) - - [端侧部署](./deploy/lite/readme.md) - - [模型量化](./deploy/slim/quantization/README.md) - - [模型裁剪](./deploy/slim/prune/README.md) + - [端侧部署](https://github.com/PaddlePaddle/PaddleOCR/blob/develop/deploy/lite/readme.md) + - [模型量化](https://github.com/PaddlePaddle/PaddleOCR/blob/develop/deploy/slim/quantization/README.md) + - [模型裁剪](https://github.com/PaddlePaddle/PaddleOCR/blob/develop/deploy/slim/prune/README.md) - [Benchmark](./doc/doc_ch/benchmark.md) - 数据集 - [通用中英文OCR数据集](./doc/doc_ch/datasets.md) @@ -97,6 +97,9 @@ PaddleOCR旨在打造一套丰富、领先、且实用的OCR工具库,助力 - [许可证书](#许可证书) - [贡献代码](#贡献代码) +***注意:动态图端侧部署仍在开发中,目前仅支持动态图训练、python端预测,C++预测, +如果您有需要移动端部署案例或者量化裁剪,请切换到静态图分支;*** + ## PP-OCR Pipline
diff --git a/README_en.md b/README_en.md index 9e839c44..0b5eb1c6 100644 --- a/README_en.md +++ b/README_en.md @@ -88,9 +88,9 @@ For a new language request, please refer to [Guideline for new language_requests - [Python Inference](./doc/doc_en/inference_en.md) - [C++ Inference](./deploy/cpp_infer/readme_en.md) - [Serving](./deploy/hubserving/readme_en.md) - - [Mobile](./deploy/lite/readme_en.md) - - [Model Quantization](./deploy/slim/quantization/README_en.md) - - [Model Compression](./deploy/slim/prune/README_en.md) + - [Mobile](https://github.com/PaddlePaddle/PaddleOCR/blob/develop/deploy/lite/readme_en.md) + - [Model Quantization](https://github.com/PaddlePaddle/PaddleOCR/blob/develop/deploy/slim/quantization/README_en.md) + - [Model Compression](https://github.com/PaddlePaddle/PaddleOCR/blob/develop/deploy/slim/prune/README_en.md) - [Benchmark](./doc/doc_en/benchmark_en.md) - Data Annotation and Synthesis - [Semi-automatic Annotation Tool](./PPOCRLabel/README_en.md) @@ -108,6 +108,12 @@ For a new language request, please refer to [Guideline for new language_requests - [License](#LICENSE) - [Contribution](#CONTRIBUTION) +***Note: The dynamic graphs branch is still under development. +Currently, only dynamic graph training, python-end prediction, and C++ prediction are supported. +If you need mobile-end deployment cases or quantitative demo, +please use the static graph branch.*** + + ## PP-OCR Pipeline diff --git a/deploy/android_demo/.gitignore b/deploy/android_demo/.gitignore deleted file mode 100644 index 93dcb293..00000000 --- a/deploy/android_demo/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -*.iml -.gradle -/local.properties -/.idea/* -.DS_Store -/build -/captures -.externalNativeBuild - diff --git a/deploy/android_demo/README.md b/deploy/android_demo/README.md deleted file mode 100644 index e35e7579..00000000 --- a/deploy/android_demo/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# 如何快速测试 -### 1. 安装最新版本的Android Studio -可以从https://developer.android.com/studio 下载。本Demo使用是4.0版本Android Studio编写。 - -### 2. 按照NDK 20 以上版本 -Demo测试的时候使用的是NDK 20b版本,20版本以上均可以支持编译成功。 - -如果您是初学者,可以用以下方式安装和测试NDK编译环境。 -点击 File -> New ->New Project, 新建 "Native C++" project - -### 3. 导入项目 -点击 File->New->Import Project..., 然后跟着Android Studio的引导导入 - - -# 获得更多支持 -前往[端计算模型生成平台EasyEdge](https://ai.baidu.com/easyedge/app/open_source_demo?referrerUrl=paddlelite),获得更多开发支持: - -- Demo APP:可使用手机扫码安装,方便手机端快速体验文字识别 -- SDK:模型被封装为适配不同芯片硬件和操作系统SDK,包括完善的接口,方便进行二次开发 diff --git a/deploy/android_demo/app/.gitignore b/deploy/android_demo/app/.gitignore deleted file mode 100644 index 796b96d1..00000000 --- a/deploy/android_demo/app/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/deploy/android_demo/app/build.gradle b/deploy/android_demo/app/build.gradle deleted file mode 100644 index 5ecb1169..00000000 --- a/deploy/android_demo/app/build.gradle +++ /dev/null @@ -1,94 +0,0 @@ -import java.security.MessageDigest - -apply plugin: 'com.android.application' - -android { - compileSdkVersion 29 - defaultConfig { - applicationId "com.baidu.paddle.lite.demo.ocr" - minSdkVersion 23 - targetSdkVersion 29 - versionCode 1 - versionName "1.0" - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" - externalNativeBuild { - cmake { - cppFlags "-std=c++11 -frtti -fexceptions -Wno-format" - arguments '-DANDROID_PLATFORM=android-23', '-DANDROID_STL=c++_shared' ,"-DANDROID_ARM_NEON=TRUE" - } - } - ndk { - // abiFilters "arm64-v8a", "armeabi-v7a" - abiFilters "arm64-v8a", "armeabi-v7a" - ldLibs "jnigraphics" - } - } - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - externalNativeBuild { - cmake { - path "src/main/cpp/CMakeLists.txt" - version "3.10.2" - } - } -} - -dependencies { - implementation fileTree(include: ['*.jar'], dir: 'libs') - implementation 'androidx.appcompat:appcompat:1.1.0' - implementation 'androidx.constraintlayout:constraintlayout:1.1.3' - testImplementation 'junit:junit:4.12' - androidTestImplementation 'com.android.support.test:runner:1.0.2' - androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' -} - -def archives = [ - [ - 'src' : 'https://paddlelite-demo.bj.bcebos.com/libs/android/paddle_lite_libs_v2_6_1.tar.gz', - 'dest': 'PaddleLite' - ], - [ - 'src' : 'https://paddlelite-demo.bj.bcebos.com/libs/android/opencv-4.2.0-android-sdk.tar.gz', - 'dest': 'OpenCV' - ], - [ - 'src' : 'https://paddleocr.bj.bcebos.com/deploy/lite/ocr_v1_for_cpu.tar.gz', - 'dest' : 'src/main/assets/models/ocr_v1_for_cpu' - ] -] - -task downloadAndExtractArchives(type: DefaultTask) { - doFirst { - println "Downloading and extracting archives including libs and models" - } - doLast { - // Prepare cache folder for archives - String cachePath = "cache" - if (!file("${cachePath}").exists()) { - mkdir "${cachePath}" - } - archives.eachWithIndex { archive, index -> - MessageDigest messageDigest = MessageDigest.getInstance('MD5') - messageDigest.update(archive.src.bytes) - String cacheName = new BigInteger(1, messageDigest.digest()).toString(32) - // Download the target archive if not exists - boolean copyFiles = !file("${archive.dest}").exists() - if (!file("${cachePath}/${cacheName}.tar.gz").exists()) { - ant.get(src: archive.src, dest: file("${cachePath}/${cacheName}.tar.gz")) - copyFiles = true; // force to copy files from the latest archive files - } - // Extract the target archive if its dest path does not exists - if (copyFiles) { - copy { - from tarTree("${cachePath}/${cacheName}.tar.gz") - into "${archive.dest}" - } - } - } - } -} -preBuild.dependsOn downloadAndExtractArchives \ No newline at end of file diff --git a/deploy/android_demo/app/proguard-rules.pro b/deploy/android_demo/app/proguard-rules.pro deleted file mode 100644 index f1b42451..00000000 --- a/deploy/android_demo/app/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile diff --git a/deploy/android_demo/app/src/androidTest/java/com/baidu/paddle/lite/demo/ocr/ExampleInstrumentedTest.java b/deploy/android_demo/app/src/androidTest/java/com/baidu/paddle/lite/demo/ocr/ExampleInstrumentedTest.java deleted file mode 100644 index 77b179da..00000000 --- a/deploy/android_demo/app/src/androidTest/java/com/baidu/paddle/lite/demo/ocr/ExampleInstrumentedTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.baidu.paddle.lite.demo.ocr; - -import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import static org.junit.Assert.*; - -/** - * Instrumented test, which will execute on an Android device. - * - * @see Testing documentation - */ -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - @Test - public void useAppContext() { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getTargetContext(); - - assertEquals("com.baidu.paddle.lite.demo", appContext.getPackageName()); - } -} diff --git a/deploy/android_demo/app/src/main/AndroidManifest.xml b/deploy/android_demo/app/src/main/AndroidManifest.xml deleted file mode 100644 index 54482b1d..00000000 --- a/deploy/android_demo/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/deploy/android_demo/app/src/main/assets/images/5.jpg b/deploy/android_demo/app/src/main/assets/images/5.jpg deleted file mode 100644 index 8517e125c5e660f1bc603a844f946c68086e4616..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63736 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 @{rU7nFH)Mwr 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

*c& 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 diff --git a/deploy/android_demo/app/src/main/assets/labels/ppocr_keys_v1.txt b/deploy/android_demo/app/src/main/assets/labels/ppocr_keys_v1.txt deleted file mode 100644 index 84b885d8..00000000 --- a/deploy/android_demo/app/src/main/assets/labels/ppocr_keys_v1.txt +++ /dev/null @@ -1,6623 +0,0 @@ -' -疗 -绚 -诚 -娇 -溜 -题 -贿 -者 -廖 -更 -纳 -加 -奉 -公 -一 -就 -汴 -计 -与 -路 -房 -原 -妇 -2 -0 -8 -- -7 -其 -> -: -] -, -, -骑 -刈 -全 -消 -昏 -傈 -安 -久 -钟 -嗅 -不 -影 -处 -驽 -蜿 -资 -关 -椤 -地 -瘸 -专 -问 -忖 -票 -嫉 -炎 -韵 -要 -月 -田 -节 -陂 -鄙 -捌 -备 -拳 -伺 -眼 -网 -盎 -大 -傍 -心 -东 -愉 -汇 -蹿 -科 -每 -业 -里 -航 -晏 -字 -平 -录 -先 -1 -3 -彤 -鲶 -产 -稍 -督 -腴 -有 -象 -岳 -注 -绍 -在 -泺 -文 -定 -核 -名 -水 -过 -理 -让 -偷 -率 -等 -这 -发 -” -为 -含 -肥 -酉 -相 -鄱 -七 -编 -猥 -锛 -日 -镀 -蒂 -掰 -倒 -辆 -栾 -栗 -综 -涩 -州 -雌 -滑 -馀 -了 -机 -块 -司 -宰 -甙 -兴 -矽 -抚 -保 -用 -沧 -秩 -如 -收 -息 -滥 -页 -疑 -埠 -! -! -姥 -异 -橹 -钇 -向 -下 -跄 -的 -椴 -沫 -国 -绥 -獠 -报 -开 -民 -蜇 -何 -分 -凇 -长 -讥 -藏 -掏 -施 -羽 -中 -讲 -派 -嘟 -人 -提 -浼 -间 -世 -而 -古 -多 -倪 -唇 -饯 -控 -庚 -首 -赛 -蜓 -味 -断 -制 -觉 -技 -替 -艰 -溢 -潮 -夕 -钺 -外 -摘 -枋 -动 -双 -单 -啮 -户 -枇 -确 -锦 -曜 -杜 -或 -能 -效 -霜 -盒 -然 -侗 -电 -晁 -放 -步 -鹃 -新 -杖 -蜂 -吒 -濂 -瞬 -评 -总 -隍 -对 -独 -合 -也 -是 -府 -青 -天 -诲 -墙 -组 -滴 -级 -邀 -帘 -示 -已 -时 -骸 -仄 -泅 -和 -遨 -店 -雇 -疫 -持 -巍 -踮 -境 -只 -亨 -目 -鉴 -崤 -闲 -体 -泄 -杂 -作 -般 -轰 -化 -解 -迂 -诿 -蛭 -璀 -腾 -告 -版 -服 -省 -师 -小 -规 -程 -线 -海 -办 -引 -二 -桧 -牌 -砺 -洄 -裴 -修 -图 -痫 -胡 -许 -犊 -事 -郛 -基 -柴 -呼 -食 -研 -奶 -律 -蛋 -因 -葆 -察 -戏 -褒 -戒 -再 -李 -骁 -工 -貂 -油 -鹅 -章 -啄 -休 -场 -给 -睡 -纷 -豆 -器 -捎 -说 -敏 -学 -会 -浒 -设 -诊 -格 -廓 -查 -来 -霓 -室 -溆 -¢ -诡 -寥 -焕 -舜 -柒 -狐 -回 -戟 -砾 -厄 -实 -翩 -尿 -五 -入 -径 -惭 -喹 -股 -宇 -篝 -| -; -美 -期 -云 -九 -祺 -扮 -靠 -锝 -槌 -系 -企 -酰 -阊 -暂 -蚕 -忻 -豁 -本 -羹 -执 -条 -钦 -H -獒 -限 -进 -季 -楦 -于 -芘 -玖 -铋 -茯 -未 -答 -粘 -括 -样 -精 -欠 -矢 -甥 -帷 -嵩 -扣 -令 -仔 -风 -皈 -行 -支 -部 -蓉 -刮 -站 -蜡 -救 -钊 -汗 -松 -嫌 -成 -可 -. -鹤 -院 -从 -交 -政 -怕 -活 -调 -球 -局 -验 -髌 -第 -韫 -谗 -串 -到 -圆 -年 -米 -/ -* -友 -忿 -检 -区 -看 -自 -敢 -刃 -个 -兹 -弄 -流 -留 -同 -没 -齿 -星 -聆 -轼 -湖 -什 -三 -建 -蛔 -儿 -椋 -汕 -震 -颧 -鲤 -跟 -力 -情 -璺 -铨 -陪 -务 -指 -族 -训 -滦 -鄣 -濮 -扒 -商 -箱 -十 -召 -慷 -辗 -所 -莞 -管 -护 -臭 -横 -硒 -嗓 -接 -侦 -六 -露 -党 -馋 -驾 -剖 -高 -侬 -妪 -幂 -猗 -绺 -骐 -央 -酐 -孝 -筝 -课 -徇 -缰 -门 -男 -西 -项 -句 -谙 -瞒 -秃 -篇 -教 -碲 -罚 -声 -呐 -景 -前 -富 -嘴 -鳌 -稀 -免 -朋 -啬 -睐 -去 -赈 -鱼 -住 -肩 -愕 -速 -旁 -波 -厅 -健 -茼 -厥 -鲟 -谅 -投 -攸 -炔 -数 -方 -击 -呋 -谈 -绩 -别 -愫 -僚 -躬 -鹧 -胪 -炳 -招 -喇 -膨 -泵 -蹦 -毛 -结 -5 -4 -谱 -识 -陕 -粽 -婚 -拟 -构 -且 -搜 -任 -潘 -比 -郢 -妨 -醪 -陀 -桔 -碘 -扎 -选 -哈 -骷 -楷 -亿 -明 -缆 -脯 -监 -睫 -逻 -婵 -共 -赴 -淝 -凡 -惦 -及 -达 -揖 -谩 -澹 -减 -焰 -蛹 -番 -祁 -柏 -员 -禄 -怡 -峤 -龙 -白 -叽 -生 -闯 -起 -细 -装 -谕 -竟 -聚 -钙 -上 -导 -渊 -按 -艾 -辘 -挡 -耒 -盹 -饪 -臀 -记 -邮 -蕙 -受 -各 -医 -搂 -普 -滇 -朗 -茸 -带 -翻 -酚 -( -光 -堤 -墟 -蔷 -万 -幻 -〓 -瑙 -辈 -昧 -盏 -亘 -蛀 -吉 -铰 -请 -子 -假 -闻 -税 -井 -诩 -哨 -嫂 -好 -面 -琐 -校 -馊 -鬣 -缂 -营 -访 -炖 -占 -农 -缀 -否 -经 -钚 -棵 -趟 -张 -亟 -吏 -茶 -谨 -捻 -论 -迸 -堂 -玉 -信 -吧 -瞠 -乡 -姬 -寺 -咬 -溏 -苄 -皿 -意 -赉 -宝 -尔 -钰 -艺 -特 -唳 -踉 -都 -荣 -倚 -登 -荐 -丧 -奇 -涵 -批 -炭 -近 -符 -傩 -感 -道 -着 -菊 -虹 -仲 -众 -懈 -濯 -颞 -眺 -南 -释 -北 -缝 -标 -既 -茗 -整 -撼 -迤 -贲 -挎 -耱 -拒 -某 -妍 -卫 -哇 -英 -矶 -藩 -治 -他 -元 -领 -膜 -遮 -穗 -蛾 -飞 -荒 -棺 -劫 -么 -市 -火 -温 -拈 -棚 -洼 -转 -果 -奕 -卸 -迪 -伸 -泳 -斗 -邡 -侄 -涨 -屯 -萋 -胭 -氡 -崮 -枞 -惧 -冒 -彩 -斜 -手 -豚 -随 -旭 -淑 -妞 -形 -菌 -吲 -沱 -争 -驯 -歹 -挟 -兆 -柱 -传 -至 -包 -内 -响 -临 -红 -功 -弩 -衡 -寂 -禁 -老 -棍 -耆 -渍 -织 -害 -氵 -渑 -布 -载 -靥 -嗬 -虽 -苹 -咨 -娄 -库 -雉 -榜 -帜 -嘲 -套 -瑚 -亲 -簸 -欧 -边 -6 -腿 -旮 -抛 -吹 -瞳 -得 -镓 -梗 -厨 -继 -漾 -愣 -憨 -士 -策 -窑 -抑 -躯 -襟 -脏 -参 -贸 -言 -干 -绸 -鳄 -穷 -藜 -音 -折 -详 -) -举 -悍 -甸 -癌 -黎 -谴 -死 -罩 -迁 -寒 -驷 -袖 -媒 -蒋 -掘 -模 -纠 -恣 -观 -祖 -蛆 -碍 -位 -稿 -主 -澧 -跌 -筏 -京 -锏 -帝 -贴 -证 -糠 -才 -黄 -鲸 -略 -炯 -饱 -四 -出 -园 -犀 -牧 -容 -汉 -杆 -浈 -汰 -瑷 -造 -虫 -瘩 -怪 -驴 -济 -应 -花 -沣 -谔 -夙 -旅 -价 -矿 -以 -考 -s -u -呦 -晒 -巡 -茅 -准 -肟 -瓴 -詹 -仟 -褂 -译 -桌 -混 -宁 -怦 -郑 -抿 -些 -余 -鄂 -饴 -攒 -珑 -群 -阖 -岔 -琨 -藓 -预 -环 -洮 -岌 -宀 -杲 -瀵 -最 -常 -囡 -周 -踊 -女 -鼓 -袭 -喉 -简 -范 -薯 -遐 -疏 -粱 -黜 -禧 -法 -箔 -斤 -遥 -汝 -奥 -直 -贞 -撑 -置 -绱 -集 -她 -馅 -逗 -钧 -橱 -魉 -[ -恙 -躁 -唤 -9 -旺 -膘 -待 -脾 -惫 -购 -吗 -依 -盲 -度 -瘿 -蠖 -俾 -之 -镗 -拇 -鲵 -厝 -簧 -续 -款 -展 -啃 -表 -剔 -品 -钻 -腭 -损 -清 -锶 -统 -涌 -寸 -滨 -贪 -链 -吠 -冈 -伎 -迥 -咏 -吁 -览 -防 -迅 -失 -汾 -阔 -逵 -绀 -蔑 -列 -川 -凭 -努 -熨 -揪 -利 -俱 -绉 -抢 -鸨 -我 -即 -责 -膦 -易 -毓 -鹊 -刹 -玷 -岿 -空 -嘞 -绊 -排 -术 -估 -锷 -违 -们 -苟 -铜 -播 -肘 -件 -烫 -审 -鲂 -广 -像 -铌 -惰 -铟 -巳 -胍 -鲍 -康 -憧 -色 -恢 -想 -拷 -尤 -疳 -知 -S -Y -F -D -A -峄 -裕 -帮 -握 -搔 -氐 -氘 -难 -墒 -沮 -雨 -叁 -缥 -悴 -藐 -湫 -娟 -苑 -稠 -颛 -簇 -后 -阕 -闭 -蕤 -缚 -怎 -佞 -码 -嘤 -蔡 -痊 -舱 -螯 -帕 -赫 -昵 -升 -烬 -岫 -、 -疵 -蜻 -髁 -蕨 -隶 -烛 -械 -丑 -盂 -梁 -强 -鲛 -由 -拘 -揉 -劭 -龟 -撤 -钩 -呕 -孛 -费 -妻 -漂 -求 -阑 -崖 -秤 -甘 -通 -深 -补 -赃 -坎 -床 -啪 -承 -吼 -量 -暇 -钼 -烨 -阂 -擎 -脱 -逮 -称 -P -神 -属 -矗 -华 -届 -狍 -葑 -汹 -育 -患 -窒 -蛰 -佼 -静 -槎 -运 -鳗 -庆 -逝 -曼 -疱 -克 -代 -官 -此 -麸 -耧 -蚌 -晟 -例 -础 -榛 -副 -测 -唰 -缢 -迹 -灬 -霁 -身 -岁 -赭 -扛 -又 -菡 -乜 -雾 -板 -读 -陷 -徉 -贯 -郁 -虑 -变 -钓 -菜 -圾 -现 -琢 -式 -乐 -维 -渔 -浜 -左 -吾 -脑 -钡 -警 -T -啵 -拴 -偌 -漱 -湿 -硕 -止 -骼 -魄 -积 -燥 -联 -踢 -玛 -则 -窿 -见 -振 -畿 -送 -班 -钽 -您 -赵 -刨 -印 -讨 -踝 -籍 -谡 -舌 -崧 -汽 -蔽 -沪 -酥 -绒 -怖 -财 -帖 -肱 -私 -莎 -勋 -羔 -霸 -励 -哼 -帐 -将 -帅 -渠 -纪 -婴 -娩 -岭 -厘 -滕 -吻 -伤 -坝 -冠 -戊 -隆 -瘁 -介 -涧 -物 -黍 -并 -姗 -奢 -蹑 -掣 -垸 -锴 -命 -箍 -捉 -病 -辖 -琰 -眭 -迩 -艘 -绌 -繁 -寅 -若 -毋 -思 -诉 -类 -诈 -燮 -轲 -酮 -狂 -重 -反 -职 -筱 -县 -委 -磕 -绣 -奖 -晋 -濉 -志 -徽 -肠 -呈 -獐 -坻 -口 -片 -碰 -几 -村 -柿 -劳 -料 -获 -亩 -惕 -晕 -厌 -号 -罢 -池 -正 -鏖 -煨 -家 -棕 -复 -尝 -懋 -蜥 -锅 -岛 -扰 -队 -坠 -瘾 -钬 -@ -卧 -疣 -镇 -譬 -冰 -彷 -频 -黯 -据 -垄 -采 -八 -缪 -瘫 -型 -熹 -砰 -楠 -襁 -箐 -但 -嘶 -绳 -啤 -拍 -盥 -穆 -傲 -洗 -盯 -塘 -怔 -筛 -丿 -台 -恒 -喂 -葛 -永 -¥ -烟 -酒 -桦 -书 -砂 -蚝 -缉 -态 -瀚 -袄 -圳 -轻 -蛛 -超 -榧 -遛 -姒 -奘 -铮 -右 -荽 -望 -偻 -卡 -丶 -氰 -附 -做 -革 -索 -戚 -坨 -桷 -唁 -垅 -榻 -岐 -偎 -坛 -莨 -山 -殊 -微 -骇 -陈 -爨 -推 -嗝 -驹 -澡 -藁 -呤 -卤 -嘻 -糅 -逛 -侵 -郓 -酌 -德 -摇 -※ -鬃 -被 -慨 -殡 -羸 -昌 -泡 -戛 -鞋 -河 -宪 -沿 -玲 -鲨 -翅 -哽 -源 -铅 -语 -照 -邯 -址 -荃 -佬 -顺 -鸳 -町 -霭 -睾 -瓢 -夸 -椁 -晓 -酿 -痈 -咔 -侏 -券 -噎 -湍 -签 -嚷 -离 -午 -尚 -社 -锤 -背 -孟 -使 -浪 -缦 -潍 -鞅 -军 -姹 -驶 -笑 -鳟 -鲁 -》 -孽 -钜 -绿 -洱 -礴 -焯 -椰 -颖 -囔 -乌 -孔 -巴 -互 -性 -椽 -哞 -聘 -昨 -早 -暮 -胶 -炀 -隧 -低 -彗 -昝 -铁 -呓 -氽 -藉 -喔 -癖 -瑗 -姨 -权 -胱 -韦 -堑 -蜜 -酋 -楝 -砝 -毁 -靓 -歙 -锲 -究 -屋 -喳 -骨 -辨 -碑 -武 -鸠 -宫 -辜 -烊 -适 -坡 -殃 -培 -佩 -供 -走 -蜈 -迟 -翼 -况 -姣 -凛 -浔 -吃 -飘 -债 -犟 -金 -促 -苛 -崇 -坂 -莳 -畔 -绂 -兵 -蠕 -斋 -根 -砍 -亢 -欢 -恬 -崔 -剁 -餐 -榫 -快 -扶 -‖ -濒 -缠 -鳜 -当 -彭 -驭 -浦 -篮 -昀 -锆 -秸 -钳 -弋 -娣 -瞑 -夷 -龛 -苫 -拱 -致 -% -嵊 -障 -隐 -弑 -初 -娓 -抉 -汩 -累 -蓖 -" -唬 -助 -苓 -昙 -押 -毙 -破 -城 -郧 -逢 -嚏 -獭 -瞻 -溱 -婿 -赊 -跨 -恼 -璧 -萃 -姻 -貉 -灵 -炉 -密 -氛 -陶 -砸 -谬 -衔 -点 -琛 -沛 -枳 -层 -岱 -诺 -脍 -榈 -埂 -征 -冷 -裁 -打 -蹴 -素 -瘘 -逞 -蛐 -聊 -激 -腱 -萘 -踵 -飒 -蓟 -吆 -取 -咙 -簋 -涓 -矩 -曝 -挺 -揣 -座 -你 -史 -舵 -焱 -尘 -苏 -笈 -脚 -溉 -榨 -诵 -樊 -邓 -焊 -义 -庶 -儋 -蟋 -蒲 -赦 -呷 -杞 -诠 -豪 -还 -试 -颓 -茉 -太 -除 -紫 -逃 -痴 -草 -充 -鳕 -珉 -祗 -墨 -渭 -烩 -蘸 -慕 -璇 -镶 -穴 -嵘 -恶 -骂 -险 -绋 -幕 -碉 -肺 -戳 -刘 -潞 -秣 -纾 -潜 -銮 -洛 -须 -罘 -销 -瘪 -汞 -兮 -屉 -r -林 -厕 -质 -探 -划 -狸 -殚 -善 -煊 -烹 -〒 -锈 -逯 -宸 -辍 -泱 -柚 -袍 -远 -蹋 -嶙 -绝 -峥 -娥 -缍 -雀 -徵 -认 -镱 -谷 -= -贩 -勉 -撩 -鄯 -斐 -洋 -非 -祚 -泾 -诒 -饿 -撬 -威 -晷 -搭 -芍 -锥 -笺 -蓦 -候 -琊 -档 -礁 -沼 -卵 -荠 -忑 -朝 -凹 -瑞 -头 -仪 -弧 -孵 -畏 -铆 -突 -衲 -车 -浩 -气 -茂 -悖 -厢 -枕 -酝 -戴 -湾 -邹 -飚 -攘 -锂 -写 -宵 -翁 -岷 -无 -喜 -丈 -挑 -嗟 -绛 -殉 -议 -槽 -具 -醇 -淞 -笃 -郴 -阅 -饼 -底 -壕 -砚 -弈 -询 -缕 -庹 -翟 -零 -筷 -暨 -舟 -闺 -甯 -撞 -麂 -茌 -蔼 -很 -珲 -捕 -棠 -角 -阉 -媛 -娲 -诽 -剿 -尉 -爵 -睬 -韩 -诰 -匣 -危 -糍 -镯 -立 -浏 -阳 -少 -盆 -舔 -擘 -匪 -申 -尬 -铣 -旯 -抖 -赘 -瓯 -居 -ˇ -哮 -游 -锭 -茏 -歌 -坏 -甚 -秒 -舞 -沙 -仗 -劲 -潺 -阿 -燧 -郭 -嗖 -霏 -忠 -材 -奂 -耐 -跺 -砀 -输 -岖 -媳 -氟 -极 -摆 -灿 -今 -扔 -腻 -枝 -奎 -药 -熄 -吨 -话 -q -额 -慑 -嘌 -协 -喀 -壳 -埭 -视 -著 -於 -愧 -陲 -翌 -峁 -颅 -佛 -腹 -聋 -侯 -咎 -叟 -秀 -颇 -存 -较 -罪 -哄 -岗 -扫 -栏 -钾 -羌 -己 -璨 -枭 -霉 -煌 -涸 -衿 -键 -镝 -益 -岢 -奏 -连 -夯 -睿 -冥 -均 -糖 -狞 -蹊 -稻 -爸 -刿 -胥 -煜 -丽 -肿 -璃 -掸 -跚 -灾 -垂 -樾 -濑 -乎 -莲 -窄 -犹 -撮 -战 -馄 -软 -络 -显 -鸢 -胸 -宾 -妲 -恕 -埔 -蝌 -份 -遇 -巧 -瞟 -粒 -恰 -剥 -桡 -博 -讯 -凯 -堇 -阶 -滤 -卖 -斌 -骚 -彬 -兑 -磺 -樱 -舷 -两 -娱 -福 -仃 -差 -找 -桁 -÷ -净 -把 -阴 -污 -戬 -雷 -碓 -蕲 -楚 -罡 -焖 -抽 -妫 -咒 -仑 -闱 -尽 -邑 -菁 -爱 -贷 -沥 -鞑 -牡 -嗉 -崴 -骤 -塌 -嗦 -订 -拮 -滓 -捡 -锻 -次 -坪 -杩 -臃 -箬 -融 -珂 -鹗 -宗 -枚 -降 -鸬 -妯 -阄 -堰 -盐 -毅 -必 -杨 -崃 -俺 -甬 -状 -莘 -货 -耸 -菱 -腼 -铸 -唏 -痤 -孚 -澳 -懒 -溅 -翘 -疙 -杷 -淼 -缙 -骰 -喊 -悉 -砻 -坷 -艇 -赁 -界 -谤 -纣 -宴 -晃 -茹 -归 -饭 -梢 -铡 -街 -抄 -肼 -鬟 -苯 -颂 -撷 -戈 -炒 -咆 -茭 -瘙 -负 -仰 -客 -琉 -铢 -封 -卑 -珥 -椿 -镧 -窨 -鬲 -寿 -御 -袤 -铃 -萎 -砖 -餮 -脒 -裳 -肪 -孕 -嫣 -馗 -嵇 -恳 -氯 -江 -石 -褶 -冢 -祸 -阻 -狈 -羞 -银 -靳 -透 -咳 -叼 -敷 -芷 -啥 -它 -瓤 -兰 -痘 -懊 -逑 -肌 -往 -捺 -坊 -甩 -呻 -〃 -沦 -忘 -膻 -祟 -菅 -剧 -崆 -智 -坯 -臧 -霍 -墅 -攻 -眯 -倘 -拢 -骠 -铐 -庭 -岙 -瓠 -′ -缺 -泥 -迢 -捶 -? -? -郏 -喙 -掷 -沌 -纯 -秘 -种 -听 -绘 -固 -螨 -团 -香 -盗 -妒 -埚 -蓝 -拖 -旱 -荞 -铀 -血 -遏 -汲 -辰 -叩 -拽 -幅 -硬 -惶 -桀 -漠 -措 -泼 -唑 -齐 -肾 -念 -酱 -虚 -屁 -耶 -旗 -砦 -闵 -婉 -馆 -拭 -绅 -韧 -忏 -窝 -醋 -葺 -顾 -辞 -倜 -堆 -辋 -逆 -玟 -贱 -疾 -董 -惘 -倌 -锕 -淘 -嘀 -莽 -俭 -笏 -绑 -鲷 -杈 -择 -蟀 -粥 -嗯 -驰 -逾 -案 -谪 -褓 -胫 -哩 -昕 -颚 -鲢 -绠 -躺 -鹄 -崂 -儒 -俨 -丝 -尕 -泌 -啊 -萸 -彰 -幺 -吟 -骄 -苣 -弦 -脊 -瑰 -〈 -诛 -镁 -析 -闪 -剪 -侧 -哟 -框 -螃 -守 -嬗 -燕 -狭 -铈 -缮 -概 -迳 -痧 -鲲 -俯 -售 -笼 -痣 -扉 -挖 -满 -咋 -援 -邱 -扇 -歪 -便 -玑 -绦 -峡 -蛇 -叨 -〖 -泽 -胃 -斓 -喋 -怂 -坟 -猪 -该 -蚬 -炕 -弥 -赞 -棣 -晔 -娠 -挲 -狡 -创 -疖 -铕 -镭 -稷 -挫 -弭 -啾 -翔 -粉 -履 -苘 -哦 -楼 -秕 -铂 -土 -锣 -瘟 -挣 -栉 -习 -享 -桢 -袅 -磨 -桂 -谦 -延 -坚 -蔚 -噗 -署 -谟 -猬 -钎 -恐 -嬉 -雒 -倦 -衅 -亏 -璩 -睹 -刻 -殿 -王 -算 -雕 -麻 -丘 -柯 -骆 -丸 -塍 -谚 -添 -鲈 -垓 -桎 -蚯 -芥 -予 -飕 -镦 -谌 -窗 -醚 -菀 -亮 -搪 -莺 -蒿 -羁 -足 -J -真 -轶 -悬 -衷 -靛 -翊 -掩 -哒 -炅 -掐 -冼 -妮 -l -谐 -稚 -荆 -擒 -犯 -陵 -虏 -浓 -崽 -刍 -陌 -傻 -孜 -千 -靖 -演 -矜 -钕 -煽 -杰 -酗 -渗 -伞 -栋 -俗 -泫 -戍 -罕 -沾 -疽 -灏 -煦 -芬 -磴 -叱 -阱 -榉 -湃 -蜀 -叉 -醒 -彪 -租 -郡 -篷 -屎 -良 -垢 -隗 -弱 -陨 -峪 -砷 -掴 -颁 -胎 -雯 -绵 -贬 -沐 -撵 -隘 -篙 -暖 -曹 -陡 -栓 -填 -臼 -彦 -瓶 -琪 -潼 -哪 -鸡 -摩 -啦 -俟 -锋 -域 -耻 -蔫 -疯 -纹 -撇 -毒 -绶 -痛 -酯 -忍 -爪 -赳 -歆 -嘹 -辕 -烈 -册 -朴 -钱 -吮 -毯 -癜 -娃 -谀 -邵 -厮 -炽 -璞 -邃 -丐 -追 -词 -瓒 -忆 -轧 -芫 -谯 -喷 -弟 -半 -冕 -裙 -掖 -墉 -绮 -寝 -苔 -势 -顷 -褥 -切 -衮 -君 -佳 -嫒 -蚩 -霞 -佚 -洙 -逊 -镖 -暹 -唛 -& -殒 -顶 -碗 -獗 -轭 -铺 -蛊 -废 -恹 -汨 -崩 -珍 -那 -杵 -曲 -纺 -夏 -薰 -傀 -闳 -淬 -姘 -舀 -拧 -卷 -楂 -恍 -讪 -厩 -寮 -篪 -赓 -乘 -灭 -盅 -鞣 -沟 -慎 -挂 -饺 -鼾 -杳 -树 -缨 -丛 -絮 -娌 -臻 -嗳 -篡 -侩 -述 -衰 -矛 -圈 -蚜 -匕 -筹 -匿 -濞 -晨 -叶 -骋 -郝 -挚 -蚴 -滞 -增 -侍 -描 -瓣 -吖 -嫦 -蟒 -匾 -圣 -赌 -毡 -癞 -恺 -百 -曳 -需 -篓 -肮 -庖 -帏 -卿 -驿 -遗 -蹬 -鬓 -骡 -歉 -芎 -胳 -屐 -禽 -烦 -晌 -寄 -媾 -狄 -翡 -苒 -船 -廉 -终 -痞 -殇 -々 -畦 -饶 -改 -拆 -悻 -萄 -£ -瓿 -乃 -訾 -桅 -匮 -溧 -拥 -纱 -铍 -骗 -蕃 -龋 -缬 -父 -佐 -疚 -栎 -醍 -掳 -蓄 -x -惆 -颜 -鲆 -榆 -〔 -猎 -敌 -暴 -谥 -鲫 -贾 -罗 -玻 -缄 -扦 -芪 -癣 -落 -徒 -臾 -恿 -猩 -托 -邴 -肄 -牵 -春 -陛 -耀 -刊 -拓 -蓓 -邳 -堕 -寇 -枉 -淌 -啡 -湄 -兽 -酷 -萼 -碚 -濠 -萤 -夹 -旬 -戮 -梭 -琥 -椭 -昔 -勺 -蜊 -绐 -晚 -孺 -僵 -宣 -摄 -冽 -旨 -萌 -忙 -蚤 -眉 -噼 -蟑 -付 -契 -瓜 -悼 -颡 -壁 -曾 -窕 -颢 -澎 -仿 -俑 -浑 -嵌 -浣 -乍 -碌 -褪 -乱 -蔟 -隙 -玩 -剐 -葫 -箫 -纲 -围 -伐 -决 -伙 -漩 -瑟 -刑 -肓 -镳 -缓 -蹭 -氨 -皓 -典 -畲 -坍 -铑 -檐 -塑 -洞 -倬 -储 -胴 -淳 -戾 -吐 -灼 -惺 -妙 -毕 -珐 -缈 -虱 -盖 -羰 -鸿 -磅 -谓 -髅 -娴 -苴 -唷 -蚣 -霹 -抨 -贤 -唠 -犬 -誓 -逍 -庠 -逼 -麓 -籼 -釉 -呜 -碧 -秧 -氩 -摔 -霄 -穸 -纨 -辟 -妈 -映 -完 -牛 -缴 -嗷 -炊 -恩 -荔 -茆 -掉 -紊 -慌 -莓 -羟 -阙 -萁 -磐 -另 -蕹 -辱 -鳐 -湮 -吡 -吩 -唐 -睦 -垠 -舒 -圜 -冗 -瞿 -溺 -芾 -囱 -匠 -僳 -汐 -菩 -饬 -漓 -黑 -霰 -浸 -濡 -窥 -毂 -蒡 -兢 -驻 -鹉 -芮 -诙 -迫 -雳 -厂 -忐 -臆 -猴 -鸣 -蚪 -栈 -箕 -羡 -渐 -莆 -捍 -眈 -哓 -趴 -蹼 -埕 -嚣 -骛 -宏 -淄 -斑 -噜 -严 -瑛 -垃 -椎 -诱 -压 -庾 -绞 -焘 -廿 -抡 -迄 -棘 -夫 -纬 -锹 -眨 -瞌 -侠 -脐 -竞 -瀑 -孳 -骧 -遁 -姜 -颦 -荪 -滚 -萦 -伪 -逸 -粳 -爬 -锁 -矣 -役 -趣 -洒 -颔 -诏 -逐 -奸 -甭 -惠 -攀 -蹄 -泛 -尼 -拼 -阮 -鹰 -亚 -颈 -惑 -勒 -〉 -际 -肛 -爷 -刚 -钨 -丰 -养 -冶 -鲽 -辉 -蔻 -画 -覆 -皴 -妊 -麦 -返 -醉 -皂 -擀 -〗 -酶 -凑 -粹 -悟 -诀 -硖 -港 -卜 -z -杀 -涕 -± -舍 -铠 -抵 -弛 -段 -敝 -镐 -奠 -拂 -轴 -跛 -袱 -e -t -沉 -菇 -俎 -薪 -峦 -秭 -蟹 -历 -盟 -菠 -寡 -液 -肢 -喻 -染 -裱 -悱 -抱 -氙 -赤 -捅 -猛 -跑 -氮 -谣 -仁 -尺 -辊 -窍 -烙 -衍 -架 -擦 -倏 -璐 -瑁 -币 -楞 -胖 -夔 -趸 -邛 -惴 -饕 -虔 -蝎 -§ -哉 -贝 -宽 -辫 -炮 -扩 -饲 -籽 -魏 -菟 -锰 -伍 -猝 -末 -琳 -哚 -蛎 -邂 -呀 -姿 -鄞 -却 -歧 -仙 -恸 -椐 -森 -牒 -寤 -袒 -婆 -虢 -雅 -钉 -朵 -贼 -欲 -苞 -寰 -故 -龚 -坭 -嘘 -咫 -礼 -硷 -兀 -睢 -汶 -’ -铲 -烧 -绕 -诃 -浃 -钿 -哺 -柜 -讼 -颊 -璁 -腔 -洽 -咐 -脲 -簌 -筠 -镣 -玮 -鞠 -谁 -兼 -姆 -挥 -梯 -蝴 -谘 -漕 -刷 -躏 -宦 -弼 -b -垌 -劈 -麟 -莉 -揭 -笙 -渎 -仕 -嗤 -仓 -配 -怏 -抬 -错 -泯 -镊 -孰 -猿 -邪 -仍 -秋 -鼬 -壹 -歇 -吵 -炼 -< -尧 -射 -柬 -廷 -胧 -霾 -凳 -隋 -肚 -浮 -梦 -祥 -株 -堵 -退 -L -鹫 -跎 -凶 -毽 -荟 -炫 -栩 -玳 -甜 -沂 -鹿 -顽 -伯 -爹 -赔 -蛴 -徐 -匡 -欣 -狰 -缸 -雹 -蟆 -疤 -默 -沤 -啜 -痂 -衣 -禅 -w -i -h -辽 -葳 -黝 -钗 -停 -沽 -棒 -馨 -颌 -肉 -吴 -硫 -悯 -劾 -娈 -马 -啧 -吊 -悌 -镑 -峭 -帆 -瀣 -涉 -咸 -疸 -滋 -泣 -翦 -拙 -癸 -钥 -蜒 -+ -尾 -庄 -凝 -泉 -婢 -渴 -谊 -乞 -陆 -锉 -糊 -鸦 -淮 -I -B -N -晦 -弗 -乔 -庥 -葡 -尻 -席 -橡 -傣 -渣 -拿 -惩 -麋 -斛 -缃 -矮 -蛏 -岘 -鸽 -姐 -膏 -催 -奔 -镒 -喱 -蠡 -摧 -钯 -胤 -柠 -拐 -璋 -鸥 -卢 -荡 -倾 -^ -_ -珀 -逄 -萧 -塾 -掇 -贮 -笆 -聂 -圃 -冲 -嵬 -M -滔 -笕 -值 -炙 -偶 -蜱 -搐 -梆 -汪 -蔬 -腑 -鸯 -蹇 -敞 -绯 -仨 -祯 -谆 -梧 -糗 -鑫 -啸 -豺 -囹 -猾 -巢 -柄 -瀛 -筑 -踌 -沭 -暗 -苁 -鱿 -蹉 -脂 -蘖 -牢 -热 -木 -吸 -溃 -宠 -序 -泞 -偿 -拜 -檩 -厚 -朐 -毗 -螳 -吞 -媚 -朽 -担 -蝗 -橘 -畴 -祈 -糟 -盱 -隼 -郜 -惜 -珠 -裨 -铵 -焙 -琚 -唯 -咚 -噪 -骊 -丫 -滢 -勤 -棉 -呸 -咣 -淀 -隔 -蕾 -窈 -饨 -挨 -煅 -短 -匙 -粕 -镜 -赣 -撕 -墩 -酬 -馁 -豌 -颐 -抗 -酣 -氓 -佑 -搁 -哭 -递 -耷 -涡 -桃 -贻 -碣 -截 -瘦 -昭 -镌 -蔓 -氚 -甲 -猕 -蕴 -蓬 -散 -拾 -纛 -狼 -猷 -铎 -埋 -旖 -矾 -讳 -囊 -糜 -迈 -粟 -蚂 -紧 -鲳 -瘢 -栽 -稼 -羊 -锄 -斟 -睁 -桥 -瓮 -蹙 -祉 -醺 -鼻 -昱 -剃 -跳 -篱 -跷 -蒜 -翎 -宅 -晖 -嗑 -壑 -峻 -癫 -屏 -狠 -陋 -袜 -途 -憎 -祀 -莹 -滟 -佶 -溥 -臣 -约 -盛 -峰 -磁 -慵 -婪 -拦 -莅 -朕 -鹦 -粲 -裤 -哎 -疡 -嫖 -琵 -窟 -堪 -谛 -嘉 -儡 -鳝 -斩 -郾 -驸 -酊 -妄 -胜 -贺 -徙 -傅 -噌 -钢 -栅 -庇 -恋 -匝 -巯 -邈 -尸 -锚 -粗 -佟 -蛟 -薹 -纵 -蚊 -郅 -绢 -锐 -苗 -俞 -篆 -淆 -膀 -鲜 -煎 -诶 -秽 -寻 -涮 -刺 -怀 -噶 -巨 -褰 -魅 -灶 -灌 -桉 -藕 -谜 -舸 -薄 -搀 -恽 -借 -牯 -痉 -渥 -愿 -亓 -耘 -杠 -柩 -锔 -蚶 -钣 -珈 -喘 -蹒 -幽 -赐 -稗 -晤 -莱 -泔 -扯 -肯 -菪 -裆 -腩 -豉 -疆 -骜 -腐 -倭 -珏 -唔 -粮 -亡 -润 -慰 -伽 -橄 -玄 -誉 -醐 -胆 -龊 -粼 -塬 -陇 -彼 -削 -嗣 -绾 -芽 -妗 -垭 -瘴 -爽 -薏 -寨 -龈 -泠 -弹 -赢 -漪 -猫 -嘧 -涂 -恤 -圭 -茧 -烽 -屑 -痕 -巾 -赖 -荸 -凰 -腮 -畈 -亵 -蹲 -偃 -苇 -澜 -艮 -换 -骺 -烘 -苕 -梓 -颉 -肇 -哗 -悄 -氤 -涠 -葬 -屠 -鹭 -植 -竺 -佯 -诣 -鲇 -瘀 -鲅 -邦 -移 -滁 -冯 -耕 -癔 -戌 -茬 -沁 -巩 -悠 -湘 -洪 -痹 -锟 -循 -谋 -腕 -鳃 -钠 -捞 -焉 -迎 -碱 -伫 -急 -榷 -奈 -邝 -卯 -辄 -皲 -卟 -醛 -畹 -忧 -稳 -雄 -昼 -缩 -阈 -睑 -扌 -耗 -曦 -涅 -捏 -瞧 -邕 -淖 -漉 -铝 -耦 -禹 -湛 -喽 -莼 -琅 -诸 -苎 -纂 -硅 -始 -嗨 -傥 -燃 -臂 -赅 -嘈 -呆 -贵 -屹 -壮 -肋 -亍 -蚀 -卅 -豹 -腆 -邬 -迭 -浊 -} -童 -螂 -捐 -圩 -勐 -触 -寞 -汊 -壤 -荫 -膺 -渌 -芳 -懿 -遴 -螈 -泰 -蓼 -蛤 -茜 -舅 -枫 -朔 -膝 -眙 -避 -梅 -判 -鹜 -璜 -牍 -缅 -垫 -藻 -黔 -侥 -惚 -懂 -踩 -腰 -腈 -札 -丞 -唾 -慈 -顿 -摹 -荻 -琬 -~ -斧 -沈 -滂 -胁 -胀 -幄 -莜 -Z -匀 -鄄 -掌 -绰 -茎 -焚 -赋 -萱 -谑 -汁 -铒 -瞎 -夺 -蜗 -野 -娆 -冀 -弯 -篁 -懵 -灞 -隽 -芡 -脘 -俐 -辩 -芯 -掺 -喏 -膈 -蝈 -觐 -悚 -踹 -蔗 -熠 -鼠 -呵 -抓 -橼 -峨 -畜 -缔 -禾 -崭 -弃 -熊 -摒 -凸 -拗 -穹 -蒙 -抒 -祛 -劝 -闫 -扳 -阵 -醌 -踪 -喵 -侣 -搬 -仅 -荧 -赎 -蝾 -琦 -买 -婧 -瞄 -寓 -皎 -冻 -赝 -箩 -莫 -瞰 -郊 -笫 -姝 -筒 -枪 -遣 -煸 -袋 -舆 -痱 -涛 -母 -〇 -启 -践 -耙 -绲 -盘 -遂 -昊 -搞 -槿 -诬 -纰 -泓 -惨 -檬 -亻 -越 -C -o -憩 -熵 -祷 -钒 -暧 -塔 -阗 -胰 -咄 -娶 -魔 -琶 -钞 -邻 -扬 -杉 -殴 -咽 -弓 -〆 -髻 -】 -吭 -揽 -霆 -拄 -殖 -脆 -彻 -岩 -芝 -勃 -辣 -剌 -钝 -嘎 -甄 -佘 -皖 -伦 -授 -徕 -憔 -挪 -皇 -庞 -稔 -芜 -踏 -溴 -兖 -卒 -擢 -饥 -鳞 -煲 -‰ -账 -颗 -叻 -斯 -捧 -鳍 -琮 -讹 -蛙 -纽 -谭 -酸 -兔 -莒 -睇 -伟 -觑 -羲 -嗜 -宜 -褐 -旎 -辛 -卦 -诘 -筋 -鎏 -溪 -挛 -熔 -阜 -晰 -鳅 -丢 -奚 -灸 -呱 -献 -陉 -黛 -鸪 -甾 -萨 -疮 -拯 -洲 -疹 -辑 -叙 -恻 -谒 -允 -柔 -烂 -氏 -逅 -漆 -拎 -惋 -扈 -湟 -纭 -啕 -掬 -擞 -哥 -忽 -涤 -鸵 -靡 -郗 -瓷 -扁 -廊 -怨 -雏 -钮 -敦 -E -懦 -憋 -汀 -拚 -啉 -腌 -岸 -f -痼 -瞅 -尊 -咀 -眩 -飙 -忌 -仝 -迦 -熬 -毫 -胯 -篑 -茄 -腺 -凄 -舛 -碴 -锵 -诧 -羯 -後 -漏 -汤 -宓 -仞 -蚁 -壶 -谰 -皑 -铄 -棰 -罔 -辅 -晶 -苦 -牟 -闽 -\ -烃 -饮 -聿 -丙 -蛳 -朱 -煤 -涔 -鳖 -犁 -罐 -荼 -砒 -淦 -妤 -黏 -戎 -孑 -婕 -瑾 -戢 -钵 -枣 -捋 -砥 -衩 -狙 -桠 -稣 -阎 -肃 -梏 -诫 -孪 -昶 -婊 -衫 -嗔 -侃 -塞 -蜃 -樵 -峒 -貌 -屿 -欺 -缫 -阐 -栖 -诟 -珞 -荭 -吝 -萍 -嗽 -恂 -啻 -蜴 -磬 -峋 -俸 -豫 -谎 -徊 -镍 -韬 -魇 -晴 -U -囟 -猜 -蛮 -坐 -囿 -伴 -亭 -肝 -佗 -蝠 -妃 -胞 -滩 -榴 -氖 -垩 -苋 -砣 -扪 -馏 -姓 -轩 -厉 -夥 -侈 -禀 -垒 -岑 -赏 -钛 -辐 -痔 -披 -纸 -碳 -“ -坞 -蠓 -挤 -荥 -沅 -悔 -铧 -帼 -蒌 -蝇 -a -p -y -n -g -哀 -浆 -瑶 -凿 -桶 -馈 -皮 -奴 -苜 -佤 -伶 -晗 -铱 -炬 -优 -弊 -氢 -恃 -甫 -攥 -端 -锌 -灰 -稹 -炝 -曙 -邋 -亥 -眶 -碾 -拉 -萝 -绔 -捷 -浍 -腋 -姑 -菖 -凌 -涞 -麽 -锢 -桨 -潢 -绎 -镰 -殆 -锑 -渝 -铬 -困 -绽 -觎 -匈 -糙 -暑 -裹 -鸟 -盔 -肽 -迷 -綦 -『 -亳 -佝 -俘 -钴 -觇 -骥 -仆 -疝 -跪 -婶 -郯 -瀹 -唉 -脖 -踞 -针 -晾 -忒 -扼 -瞩 -叛 -椒 -疟 -嗡 -邗 -肆 -跆 -玫 -忡 -捣 -咧 -唆 -艄 -蘑 -潦 -笛 -阚 -沸 -泻 -掊 -菽 -贫 -斥 -髂 -孢 -镂 -赂 -麝 -鸾 -屡 -衬 -苷 -恪 -叠 -希 -粤 -爻 -喝 -茫 -惬 -郸 -绻 -庸 -撅 -碟 -宄 -妹 -膛 -叮 -饵 -崛 -嗲 -椅 -冤 -搅 -咕 -敛 -尹 -垦 -闷 -蝉 -霎 -勰 -败 -蓑 -泸 -肤 -鹌 -幌 -焦 -浠 -鞍 -刁 -舰 -乙 -竿 -裔 -。 -茵 -函 -伊 -兄 -丨 -娜 -匍 -謇 -莪 -宥 -似 -蝽 -翳 -酪 -翠 -粑 -薇 -祢 -骏 -赠 -叫 -Q -噤 -噻 -竖 -芗 -莠 -潭 -俊 -羿 -耜 -O -郫 -趁 -嗪 -囚 -蹶 -芒 -洁 -笋 -鹑 -敲 -硝 -啶 -堡 -渲 -揩 -』 -携 -宿 -遒 -颍 -扭 -棱 -割 -萜 -蔸 -葵 -琴 -捂 -饰 -衙 -耿 -掠 -募 -岂 -窖 -涟 -蔺 -瘤 -柞 -瞪 -怜 -匹 -距 -楔 -炜 -哆 -秦 -缎 -幼 -茁 -绪 -痨 -恨 -楸 -娅 -瓦 -桩 -雪 -嬴 -伏 -榔 -妥 -铿 -拌 -眠 -雍 -缇 -‘ -卓 -搓 -哌 -觞 -噩 -屈 -哧 -髓 -咦 -巅 -娑 -侑 -淫 -膳 -祝 -勾 -姊 -莴 -胄 -疃 -薛 -蜷 -胛 -巷 -芙 -芋 -熙 -闰 -勿 -窃 -狱 -剩 -钏 -幢 -陟 -铛 -慧 -靴 -耍 -k -浙 -浇 -飨 -惟 -绗 -祜 -澈 -啼 -咪 -磷 -摞 -诅 -郦 -抹 -跃 -壬 -吕 -肖 -琏 -颤 -尴 -剡 -抠 -凋 -赚 -泊 -津 -宕 -殷 -倔 -氲 -漫 -邺 -涎 -怠 -$ -垮 -荬 -遵 -俏 -叹 -噢 -饽 -蜘 -孙 -筵 -疼 -鞭 -羧 -牦 -箭 -潴 -c -眸 -祭 -髯 -啖 -坳 -愁 -芩 -驮 -倡 -巽 -穰 -沃 -胚 -怒 -凤 -槛 -剂 -趵 -嫁 -v -邢 -灯 -鄢 -桐 -睽 -檗 -锯 -槟 -婷 -嵋 -圻 -诗 -蕈 -颠 -遭 -痢 -芸 -怯 -馥 -竭 -锗 -徜 -恭 -遍 -籁 -剑 -嘱 -苡 -龄 -僧 -桑 -潸 -弘 -澶 -楹 -悲 -讫 -愤 -腥 -悸 -谍 -椹 -呢 -桓 -葭 -攫 -阀 -翰 -躲 -敖 -柑 -郎 -笨 -橇 -呃 -魁 -燎 -脓 -葩 -磋 -垛 -玺 -狮 -沓 -砜 -蕊 -锺 -罹 -蕉 -翱 -虐 -闾 -巫 -旦 -茱 -嬷 -枯 -鹏 -贡 -芹 -汛 -矫 -绁 -拣 -禺 -佃 -讣 -舫 -惯 -乳 -趋 -疲 -挽 -岚 -虾 -衾 -蠹 -蹂 -飓 -氦 -铖 -孩 -稞 -瑜 -壅 -掀 -勘 -妓 -畅 -髋 -W -庐 -牲 -蓿 -榕 -练 -垣 -唱 -邸 -菲 -昆 -婺 -穿 -绡 -麒 -蚱 -掂 -愚 -泷 -涪 -漳 -妩 -娉 -榄 -讷 -觅 -旧 -藤 -煮 -呛 -柳 -腓 -叭 -庵 -烷 -阡 -罂 -蜕 -擂 -猖 -咿 -媲 -脉 -【 -沏 -貅 -黠 -熏 -哲 -烁 -坦 -酵 -兜 -× -潇 -撒 -剽 -珩 -圹 -乾 -摸 -樟 -帽 -嗒 -襄 -魂 -轿 -憬 -锡 -〕 -喃 -皆 -咖 -隅 -脸 -残 -泮 -袂 -鹂 -珊 -囤 -捆 -咤 -误 -徨 -闹 -淙 -芊 -淋 -怆 -囗 -拨 -梳 -渤 -R -G -绨 -蚓 -婀 -幡 -狩 -麾 -谢 -唢 -裸 -旌 -伉 -纶 -裂 -驳 -砼 -咛 -澄 -樨 -蹈 -宙 -澍 -倍 -貔 -操 -勇 -蟠 -摈 -砧 -虬 -够 -缁 -悦 -藿 -撸 -艹 -摁 -淹 -豇 -虎 -榭 -ˉ -吱 -d -° -喧 -荀 -踱 -侮 -奋 -偕 -饷 -犍 -惮 -坑 -璎 -徘 -宛 -妆 -袈 -倩 -窦 -昂 -荏 -乖 -K -怅 -撰 -鳙 -牙 -袁 -酞 -X -痿 -琼 -闸 -雁 -趾 -荚 -虻 -涝 -《 -杏 -韭 -偈 -烤 -绫 -鞘 -卉 -症 -遢 -蓥 -诋 -杭 -荨 -匆 -竣 -簪 -辙 -敕 -虞 -丹 -缭 -咩 -黟 -m -淤 -瑕 -咂 -铉 -硼 -茨 -嶂 -痒 -畸 -敬 -涿 -粪 -窘 -熟 -叔 -嫔 -盾 -忱 -裘 -憾 -梵 -赡 -珙 -咯 -娘 -庙 -溯 -胺 -葱 -痪 -摊 -荷 -卞 -乒 -髦 -寐 -铭 -坩 -胗 -枷 -爆 -溟 -嚼 -羚 -砬 -轨 -惊 -挠 -罄 -竽 -菏 -氧 -浅 -楣 -盼 -枢 -炸 -阆 -杯 -谏 -噬 -淇 -渺 -俪 -秆 -墓 -泪 -跻 -砌 -痰 -垡 -渡 -耽 -釜 -讶 -鳎 -煞 -呗 -韶 -舶 -绷 -鹳 -缜 -旷 -铊 -皱 -龌 -檀 -霖 -奄 -槐 -艳 -蝶 -旋 -哝 -赶 -骞 -蚧 -腊 -盈 -丁 -` -蜚 -矸 -蝙 -睨 -嚓 -僻 -鬼 -醴 -夜 -彝 -磊 -笔 -拔 -栀 -糕 -厦 -邰 -纫 -逭 -纤 -眦 -膊 -馍 -躇 -烯 -蘼 -冬 -诤 -暄 -骶 -哑 -瘠 -」 -臊 -丕 -愈 -咱 -螺 -擅 -跋 -搏 -硪 -谄 -笠 -淡 -嘿 -骅 -谧 -鼎 -皋 -姚 -歼 -蠢 -驼 -耳 -胬 -挝 -涯 -狗 -蒽 -孓 -犷 -凉 -芦 -箴 -铤 -孤 -嘛 -坤 -V -茴 -朦 -挞 -尖 -橙 -诞 -搴 -碇 -洵 -浚 -帚 -蜍 -漯 -柘 -嚎 -讽 -芭 -荤 -咻 -祠 -秉 -跖 -埃 -吓 -糯 -眷 -馒 -惹 -娼 -鲑 -嫩 -讴 -轮 -瞥 -靶 -褚 -乏 -缤 -宋 -帧 -删 -驱 -碎 -扑 -俩 -俄 -偏 -涣 -竹 -噱 -皙 -佰 -渚 -唧 -斡 -# -镉 -刀 -崎 -筐 -佣 -夭 -贰 -肴 -峙 -哔 -艿 -匐 -牺 -镛 -缘 -仡 -嫡 -劣 -枸 -堀 -梨 -簿 -鸭 -蒸 -亦 -稽 -浴 -{ -衢 -束 -槲 -j -阁 -揍 -疥 -棋 -潋 -聪 -窜 -乓 -睛 -插 -冉 -阪 -苍 -搽 -「 -蟾 -螟 -幸 -仇 -樽 -撂 -慢 -跤 -幔 -俚 -淅 -覃 -觊 -溶 -妖 -帛 -侨 -曰 -妾 -泗 -· -: -瀘 -風 -Ë -( -) -∶ -紅 -紗 -瑭 -雲 -頭 -鶏 -財 -許 -• -¥ -樂 -焗 -麗 -— -; -滙 -東 -榮 -繪 -興 -… -門 -業 -π -楊 -國 -顧 -é -盤 -寳 -Λ -龍 -鳳 -島 -誌 -緣 -結 -銭 -萬 -勝 -祎 -璟 -優 -歡 -臨 -時 -購 -= -★ -藍 -昇 -鐵 -觀 -勅 -農 -聲 -畫 -兿 -術 -發 -劉 -記 -專 -耑 -園 -書 -壴 -種 -Ο -● -褀 -號 -銀 -匯 -敟 -锘 -葉 -橪 -廣 -進 -蒄 -鑽 -阝 -祙 -貢 -鍋 -豊 -夬 -喆 -團 -閣 -開 -燁 -賓 -館 -酡 -沔 -順 -+ -硚 -劵 -饸 -陽 -車 -湓 -復 -萊 -氣 -軒 -華 -堃 -迮 -纟 -戶 -馬 -學 -裡 -電 -嶽 -獨 -マ -シ -サ -ジ -燘 -袪 -環 -❤ -臺 -灣 -専 -賣 -孖 -聖 -攝 -線 -▪ -α -傢 -俬 -夢 -達 -莊 -喬 -貝 -薩 -劍 -羅 -壓 -棛 -饦 -尃 -璈 -囍 -醫 -G -I -A -# -N -鷄 -髙 -嬰 -啓 -約 -隹 -潔 -賴 -藝 -~ -寶 -籣 -麺 -  -嶺 -√ -義 -網 -峩 -長 -∧ -魚 -機 -構 -② -鳯 -偉 -L -B -㙟 -畵 -鴿 -' -詩 -溝 -嚞 -屌 -藔 -佧 -玥 -蘭 -織 -1 -3 -9 -0 -7 -點 -砭 -鴨 -鋪 -銘 -廳 -弍 -‧ -創 -湯 -坶 -℃ -卩 -骝 -& -烜 -荘 -當 -潤 -扞 -係 -懷 -碶 -钅 -蚨 -讠 -☆ -叢 -爲 -埗 -涫 -塗 -→ -楽 -現 -鯨 -愛 -瑪 -鈺 -忄 -悶 -藥 -飾 -樓 -視 -孬 -ㆍ -燚 -苪 -師 -① -丼 -锽 -│ -韓 -標 -è -兒 -閏 -匋 -張 -漢 -Ü -髪 -會 -閑 -檔 -習 -裝 -の -峯 -菘 -輝 -И -雞 -釣 -億 -浐 -K -O -R -8 -H -E -P -T -W -D -S -C -M -F -姌 -饹 -» -晞 -廰 -ä -嵯 -鷹 -負 -飲 -絲 -冚 -楗 -澤 -綫 -區 -❋ -← -質 -靑 -揚 -③ -滬 -統 -産 -協 -﹑ -乸 -畐 -經 -運 -際 -洺 -岽 -為 -粵 -諾 -崋 -豐 -碁 -ɔ -V -2 -6 -齋 -誠 -訂 -´ -勑 -雙 -陳 -無 -í -泩 -媄 -夌 -刂 -i -c -t -o -r -a -嘢 -耄 -燴 -暃 -壽 -媽 -靈 -抻 -體 -唻 -É -冮 -甹 -鎮 -錦 -ʌ -蜛 -蠄 -尓 -駕 -戀 -飬 -逹 -倫 -貴 -極 -Я -Й -寬 -磚 -嶪 -郎 -職 -| -間 -n -d -剎 -伈 -課 -飛 -橋 -瘊 -№ -譜 -骓 -圗 -滘 -縣 -粿 -咅 -養 -濤 -彳 -® -% -Ⅱ -啰 -㴪 -見 -矞 -薬 -糁 -邨 -鲮 -顔 -罱 -З -選 -話 -贏 -氪 -俵 -競 -瑩 -繡 -枱 -β -綉 -á -獅 -爾 -™ -麵 -戋 -淩 -徳 -個 -劇 -場 -務 -簡 -寵 -h -實 -膠 -轱 -圖 -築 -嘣 -樹 -㸃 -營 -耵 -孫 -饃 -鄺 -飯 -麯 -遠 -輸 -坫 -孃 -乚 -閃 -鏢 -㎡ -題 -廠 -關 -↑ -爺 -將 -軍 -連 -篦 -覌 -參 -箸 -- -窠 -棽 -寕 -夀 -爰 -歐 -呙 -閥 -頡 -熱 -雎 -垟 -裟 -凬 -勁 -帑 -馕 -夆 -疌 -枼 -馮 -貨 -蒤 -樸 -彧 -旸 -靜 -龢 -暢 -㐱 -鳥 -珺 -鏡 -灡 -爭 -堷 -廚 -Ó -騰 -診 -┅ -蘇 -褔 -凱 -頂 -豕 -亞 -帥 -嘬 -⊥ -仺 -桖 -複 -饣 -絡 -穂 -顏 -棟 -納 -▏ -濟 -親 -設 -計 -攵 -埌 -烺 -ò -頤 -燦 -蓮 -撻 -節 -講 -濱 -濃 -娽 -洳 -朿 -燈 -鈴 -護 -膚 -铔 -過 -補 -Z -U -5 -4 -坋 -闿 -䖝 -餘 -缐 -铞 -貿 -铪 -桼 -趙 -鍊 -[ -㐂 -垚 -菓 -揸 -捲 -鐘 -滏 -𣇉 -爍 -輪 -燜 -鴻 -鮮 -動 -鹞 -鷗 -丄 -慶 -鉌 -翥 -飮 -腸 -⇋ -漁 -覺 -來 -熘 -昴 -翏 -鲱 -圧 -鄉 -萭 -頔 -爐 -嫚 -г -貭 -類 -聯 -幛 -輕 -訓 -鑒 -夋 -锨 -芃 -珣 -䝉 -扙 -嵐 -銷 -處 -ㄱ -語 -誘 -苝 -歸 -儀 -燒 -楿 -內 -粢 -葒 -奧 -麥 -礻 -滿 -蠔 -穵 -瞭 -態 -鱬 -榞 -硂 -鄭 -黃 -煙 -祐 -奓 -逺 -* -瑄 -獲 -聞 -薦 -讀 -這 -樣 -決 -問 -啟 -們 -執 -説 -轉 -單 -隨 -唘 -帶 -倉 -庫 -還 -贈 -尙 -皺 -■ -餅 -產 -○ -∈ -報 -狀 -楓 -賠 -琯 -嗮 -禮 -` -傳 -> -≤ -嗞 -Φ -≥ -換 -咭 -∣ -↓ -曬 -ε -応 -寫 -″ -終 -様 -純 -費 -療 -聨 -凍 -壐 -郵 -ü -黒 -∫ -製 -塊 -調 -軽 -確 -撃 -級 -馴 -Ⅲ -涇 -繹 -數 -碼 -證 -狒 -処 -劑 -< -晧 -賀 -衆 -] -櫥 -兩 -陰 -絶 -對 -鯉 -憶 -◎ -p -e -Y -蕒 -煖 -頓 -測 -試 -鼽 -僑 -碩 -妝 -帯 -≈ -鐡 -舖 -權 -喫 -倆 -ˋ -該 -悅 -ā -俫 -. -f -s -b -m -k -g -u -j -貼 -淨 -濕 -針 -適 -備 -l -/ -給 -謢 -強 -觸 -衛 -與 -⊙ -$ -緯 -變 -⑴ -⑵ -⑶ -㎏ -殺 -∩ -幚 -─ -價 -▲ -離 -ú -ó -飄 -烏 -関 -閟 -﹝ -﹞ -邏 -輯 -鍵 -驗 -訣 -導 -歷 -屆 -層 -▼ -儱 -錄 -熳 -ē -艦 -吋 -錶 -辧 -飼 -顯 -④ -禦 -販 -気 -対 -枰 -閩 -紀 -幹 -瞓 -貊 -淚 -△ -眞 -墊 -Ω -獻 -褲 -縫 -緑 -亜 -鉅 -餠 -{ -} -◆ -蘆 -薈 -█ -◇ -溫 -彈 -晳 -粧 -犸 -穩 -訊 -崬 -凖 -熥 -П -舊 -條 -紋 -圍 -Ⅳ -筆 -尷 -難 -雜 -錯 -綁 -識 -頰 -鎖 -艶 -□ -殁 -殼 -⑧ -├ -▕ -鵬 -ǐ -ō -ǒ -糝 -綱 -▎ -μ -盜 -饅 -醬 -籤 -蓋 -釀 -鹽 -據 -à -ɡ -辦 -◥ -彐 -┌ -婦 -獸 -鲩 -伱 -ī -蒟 -蒻 -齊 -袆 -腦 -寧 -凈 -妳 -煥 -詢 -偽 -謹 -啫 -鯽 -騷 -鱸 -損 -傷 -鎻 -髮 -買 -冏 -儥 -両 -﹢ -∞ -載 -喰 -z -羙 -悵 -燙 -曉 -員 -組 -徹 -艷 -痠 -鋼 -鼙 -縮 -細 -嚒 -爯 -≠ -維 -" -鱻 -壇 -厍 -帰 -浥 -犇 -薡 -軎 -² -應 -醜 -刪 -緻 -鶴 -賜 -噁 -軌 -尨 -镔 -鷺 -槗 -彌 -葚 -濛 -請 -溇 -緹 -賢 -訪 -獴 -瑅 -資 -縤 -陣 -蕟 -栢 -韻 -祼 -恁 -伢 -謝 -劃 -涑 -總 -衖 -踺 -砋 -凉 -籃 -駿 -苼 -瘋 -昽 -紡 -驊 -腎 -﹗ -響 -杋 -剛 -嚴 -禪 -歓 -槍 -傘 -檸 -檫 -炣 -勢 -鏜 -鎢 -銑 -尐 -減 -奪 -惡 -θ -僮 -婭 -臘 -ū -ì -殻 -鉄 -∑ -蛲 -焼 -緖 -續 -紹 -懮 \ No newline at end of file diff --git a/deploy/android_demo/app/src/main/cpp/CMakeLists.txt b/deploy/android_demo/app/src/main/cpp/CMakeLists.txt deleted file mode 100644 index 742786ad..00000000 --- a/deploy/android_demo/app/src/main/cpp/CMakeLists.txt +++ /dev/null @@ -1,117 +0,0 @@ -# For more information about using CMake with Android Studio, read the -# documentation: https://d.android.com/studio/projects/add-native-code.html - -# Sets the minimum version of CMake required to build the native library. - -cmake_minimum_required(VERSION 3.4.1) - -# Creates and names a library, sets it as either STATIC or SHARED, and provides -# the relative paths to its source code. You can define multiple libraries, and -# CMake builds them for you. Gradle automatically packages shared libraries with -# your APK. - -set(PaddleLite_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../PaddleLite") -include_directories(${PaddleLite_DIR}/cxx/include) - -set(OpenCV_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../OpenCV/sdk/native/jni") -message(STATUS "opencv dir: ${OpenCV_DIR}") -find_package(OpenCV REQUIRED) -message(STATUS "OpenCV libraries: ${OpenCV_LIBS}") -include_directories(${OpenCV_INCLUDE_DIRS}) -aux_source_directory(. SOURCES) -set(CMAKE_CXX_FLAGS - "${CMAKE_CXX_FLAGS} -ffast-math -Ofast -Os" - ) -set(CMAKE_CXX_FLAGS - "${CMAKE_CXX_FLAGS} -fvisibility=hidden -fvisibility-inlines-hidden -fdata-sections -ffunction-sections" - ) -set(CMAKE_SHARED_LINKER_FLAGS - "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--gc-sections -Wl,-z,nocopyreloc") - -add_library( - # Sets the name of the library. - Native - # Sets the library as a shared library. - SHARED - # Provides a relative path to your source file(s). - ${SOURCES}) - -find_library( - # Sets the name of the path variable. - log-lib - # Specifies the name of the NDK library that you want CMake to locate. - log) - -add_library( - # Sets the name of the library. - paddle_light_api_shared - # Sets the library as a shared library. - SHARED - # Provides a relative path to your source file(s). - IMPORTED) - -set_target_properties( - # Specifies the target library. - paddle_light_api_shared - # Specifies the parameter you want to define. - PROPERTIES - IMPORTED_LOCATION - ${PaddleLite_DIR}/cxx/libs/${ANDROID_ABI}/libpaddle_light_api_shared.so - # Provides the path to the library you want to import. -) - - -# Specifies libraries CMake should link to your target library. You can link -# multiple libraries, such as libraries you define in this build script, -# prebuilt third-party libraries, or system libraries. - -target_link_libraries( - # Specifies the target library. - Native - paddle_light_api_shared - ${OpenCV_LIBS} - GLESv2 - EGL - jnigraphics - ${log-lib} -) - -add_custom_command( - TARGET Native - POST_BUILD - COMMAND - ${CMAKE_COMMAND} -E copy - ${PaddleLite_DIR}/cxx/libs/${ANDROID_ABI}/libc++_shared.so - ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libc++_shared.so) - -add_custom_command( - TARGET Native - POST_BUILD - COMMAND - ${CMAKE_COMMAND} -E copy - ${PaddleLite_DIR}/cxx/libs/${ANDROID_ABI}/libpaddle_light_api_shared.so - ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libpaddle_light_api_shared.so) - -add_custom_command( - TARGET Native - POST_BUILD - COMMAND - ${CMAKE_COMMAND} -E copy - ${PaddleLite_DIR}/cxx/libs/${ANDROID_ABI}/libhiai.so - ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libhiai.so) - -add_custom_command( - TARGET Native - POST_BUILD - COMMAND - ${CMAKE_COMMAND} -E copy - ${PaddleLite_DIR}/cxx/libs/${ANDROID_ABI}/libhiai_ir.so - ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libhiai_ir.so) - -add_custom_command( - TARGET Native - POST_BUILD - COMMAND - ${CMAKE_COMMAND} -E copy - ${PaddleLite_DIR}/cxx/libs/${ANDROID_ABI}/libhiai_ir_build.so - ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libhiai_ir_build.so) \ No newline at end of file diff --git a/deploy/android_demo/app/src/main/cpp/common.h b/deploy/android_demo/app/src/main/cpp/common.h deleted file mode 100644 index f3937e91..00000000 --- a/deploy/android_demo/app/src/main/cpp/common.h +++ /dev/null @@ -1,48 +0,0 @@ -// -// Created by fu on 4/25/18. -// - -#pragma once -#import -#import - -#ifdef __ANDROID__ - -#include - -#define LOG_TAG "OCR_NDK" - -#define LOGI(...) \ - __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) -#define LOGW(...) \ - __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) -#define LOGE(...) \ - __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) -#else -#include -#define LOGI(format, ...) \ - fprintf(stdout, "[" LOG_TAG "]" format "\n", ##__VA_ARGS__) -#define LOGW(format, ...) \ - fprintf(stdout, "[" LOG_TAG "]" format "\n", ##__VA_ARGS__) -#define LOGE(format, ...) \ - fprintf(stderr, "[" LOG_TAG "]Error: " format "\n", ##__VA_ARGS__) -#endif - -enum RETURN_CODE { - RETURN_OK = 0 -}; - -enum NET_TYPE{ - NET_OCR = 900100, - NET_OCR_INTERNAL = 991008 -}; - - -template -inline T product(const std::vector &vec) { - if (vec.empty()){ - return 0; - } - return std::accumulate(vec.begin(), vec.end(), 1, std::multiplies()); -} - diff --git a/deploy/android_demo/app/src/main/cpp/native.cpp b/deploy/android_demo/app/src/main/cpp/native.cpp deleted file mode 100644 index 390c594d..00000000 --- a/deploy/android_demo/app/src/main/cpp/native.cpp +++ /dev/null @@ -1,115 +0,0 @@ -// -// Created by fujiayi on 2020/7/5. -// - -#include "native.h" -#include "ocr_ppredictor.h" -#include -#include -#include - -static paddle::lite_api::PowerMode str_to_cpu_mode(const std::string &cpu_mode); - -extern "C" -JNIEXPORT jlong JNICALL -Java_com_baidu_paddle_lite_demo_ocr_OCRPredictorNative_init(JNIEnv *env, jobject thiz, - jstring j_det_model_path, - jstring j_rec_model_path, - jint j_thread_num, - jstring j_cpu_mode) { - std::string det_model_path = jstring_to_cpp_string(env, j_det_model_path); - std::string rec_model_path = jstring_to_cpp_string(env, j_rec_model_path); - int thread_num = j_thread_num; - std::string cpu_mode = jstring_to_cpp_string(env, j_cpu_mode); - ppredictor::OCR_Config conf; - conf.thread_num = thread_num; - conf.mode = str_to_cpu_mode(cpu_mode); - ppredictor::OCR_PPredictor *orc_predictor = new ppredictor::OCR_PPredictor{conf}; - orc_predictor->init_from_file(det_model_path, rec_model_path); - return reinterpret_cast(orc_predictor); -} - -/** - * "LITE_POWER_HIGH" convert to paddle::lite_api::LITE_POWER_HIGH - * @param cpu_mode - * @return - */ -static paddle::lite_api::PowerMode str_to_cpu_mode(const std::string &cpu_mode) { - static std::map cpu_mode_map{ - {"LITE_POWER_HIGH", paddle::lite_api::LITE_POWER_HIGH}, - {"LITE_POWER_LOW", paddle::lite_api::LITE_POWER_HIGH}, - {"LITE_POWER_FULL", paddle::lite_api::LITE_POWER_FULL}, - {"LITE_POWER_NO_BIND", paddle::lite_api::LITE_POWER_NO_BIND}, - {"LITE_POWER_RAND_HIGH", paddle::lite_api::LITE_POWER_RAND_HIGH}, - {"LITE_POWER_RAND_LOW", paddle::lite_api::LITE_POWER_RAND_LOW} - }; - std::string upper_key; - std::transform(cpu_mode.cbegin(), cpu_mode.cend(), upper_key.begin(), ::toupper); - auto index = cpu_mode_map.find(upper_key); - if (index == cpu_mode_map.end()) { - LOGE("cpu_mode not found %s", upper_key.c_str()); - return paddle::lite_api::LITE_POWER_HIGH; - } else { - return index->second; - } - -} - -extern "C" -JNIEXPORT jfloatArray JNICALL -Java_com_baidu_paddle_lite_demo_ocr_OCRPredictorNative_forward(JNIEnv *env, jobject thiz, - jlong java_pointer, jfloatArray buf, - jfloatArray ddims, - jobject original_image) { - LOGI("begin to run native forward"); - if (java_pointer == 0) { - LOGE("JAVA pointer is NULL"); - return cpp_array_to_jfloatarray(env, nullptr, 0); - } - cv::Mat origin = bitmap_to_cv_mat(env, original_image); - if (origin.size == 0) { - LOGE("origin bitmap cannot convert to CV Mat"); - return cpp_array_to_jfloatarray(env, nullptr, 0); - } - ppredictor::OCR_PPredictor *ppredictor = (ppredictor::OCR_PPredictor *) java_pointer; - std::vector dims_float_arr = jfloatarray_to_float_vector(env, ddims); - std::vector dims_arr; - dims_arr.resize(dims_float_arr.size()); - std::copy(dims_float_arr.cbegin(), dims_float_arr.cend(), dims_arr.begin()); - - // 这里值有点大,就不调用jfloatarray_to_float_vector了 - int64_t buf_len = (int64_t) env->GetArrayLength(buf); - jfloat *buf_data = env->GetFloatArrayElements(buf, JNI_FALSE); - float *data = (jfloat *) buf_data; - std::vector results = ppredictor->infer_ocr(dims_arr, data, - buf_len, - NET_OCR, origin); - LOGI("infer_ocr finished with boxes %ld", results.size()); - // 这里将std::vector 序列化成 float数组,传输到java层再反序列化 - std::vector float_arr; - for (const ppredictor::OCRPredictResult &r :results) { - float_arr.push_back(r.points.size()); - float_arr.push_back(r.word_index.size()); - float_arr.push_back(r.score); - for (const std::vector &point : r.points) { - float_arr.push_back(point.at(0)); - float_arr.push_back(point.at(1)); - } - for (int index: r.word_index) { - float_arr.push_back(index); - } - } - return cpp_array_to_jfloatarray(env, float_arr.data(), float_arr.size()); -} - -extern "C" -JNIEXPORT void JNICALL -Java_com_baidu_paddle_lite_demo_ocr_OCRPredictorNative_release(JNIEnv *env, jobject thiz, - jlong java_pointer){ - if (java_pointer == 0) { - LOGE("JAVA pointer is NULL"); - return; - } - ppredictor::OCR_PPredictor *ppredictor = (ppredictor::OCR_PPredictor *) java_pointer; - delete ppredictor; -} \ No newline at end of file diff --git a/deploy/android_demo/app/src/main/cpp/native.h b/deploy/android_demo/app/src/main/cpp/native.h deleted file mode 100644 index 1ef6d949..00000000 --- a/deploy/android_demo/app/src/main/cpp/native.h +++ /dev/null @@ -1,138 +0,0 @@ -// -// Created by fujiayi on 2020/7/5. -// - -#pragma once - - -#include -#include -#include -#include -#include -#include "common.h" - -inline std::string jstring_to_cpp_string(JNIEnv *env, jstring jstr) { - // In java, a unicode char will be encoded using 2 bytes (utf16). - // so jstring will contain characters utf16. std::string in c++ is - // essentially a string of bytes, not characters, so if we want to - // pass jstring from JNI to c++, we have convert utf16 to bytes. - if (!jstr) { - return ""; - } - const jclass stringClass = env->GetObjectClass(jstr); - const jmethodID getBytes = - env->GetMethodID(stringClass, "getBytes", "(Ljava/lang/String;)[B"); - const jbyteArray stringJbytes = (jbyteArray) env->CallObjectMethod( - jstr, getBytes, env->NewStringUTF("UTF-8")); - - size_t length = (size_t) env->GetArrayLength(stringJbytes); - jbyte *pBytes = env->GetByteArrayElements(stringJbytes, NULL); - - std::string ret = std::string(reinterpret_cast(pBytes), length); - env->ReleaseByteArrayElements(stringJbytes, pBytes, JNI_ABORT); - - env->DeleteLocalRef(stringJbytes); - env->DeleteLocalRef(stringClass); - return ret; -} - -inline jstring cpp_string_to_jstring(JNIEnv *env, std::string str) { - auto *data = str.c_str(); - jclass strClass = env->FindClass("java/lang/String"); - jmethodID strClassInitMethodID = - env->GetMethodID(strClass, "", "([BLjava/lang/String;)V"); - - jbyteArray bytes = env->NewByteArray(strlen(data)); - env->SetByteArrayRegion(bytes, 0, strlen(data), - reinterpret_cast(data)); - - jstring encoding = env->NewStringUTF("UTF-8"); - jstring res = (jstring) ( - env->NewObject(strClass, strClassInitMethodID, bytes, encoding)); - - env->DeleteLocalRef(strClass); - env->DeleteLocalRef(encoding); - env->DeleteLocalRef(bytes); - - return res; -} - -inline jfloatArray cpp_array_to_jfloatarray(JNIEnv *env, const float *buf, - int64_t len) { - if (len == 0) { - return env->NewFloatArray(0); - } - jfloatArray result = env->NewFloatArray(len); - env->SetFloatArrayRegion(result, 0, len, buf); - return result; -} - -inline jintArray cpp_array_to_jintarray(JNIEnv *env, const int *buf, - int64_t len) { - jintArray result = env->NewIntArray(len); - env->SetIntArrayRegion(result, 0, len, buf); - return result; -} - -inline jbyteArray cpp_array_to_jbytearray(JNIEnv *env, const int8_t *buf, - int64_t len) { - jbyteArray result = env->NewByteArray(len); - env->SetByteArrayRegion(result, 0, len, buf); - return result; -} - -inline jlongArray int64_vector_to_jlongarray(JNIEnv *env, - const std::vector &vec) { - jlongArray result = env->NewLongArray(vec.size()); - jlong *buf = new jlong[vec.size()]; - for (size_t i = 0; i < vec.size(); ++i) { - buf[i] = (jlong) vec[i]; - } - env->SetLongArrayRegion(result, 0, vec.size(), buf); - delete[] buf; - return result; -} - -inline std::vector jlongarray_to_int64_vector(JNIEnv *env, - jlongArray data) { - int data_size = env->GetArrayLength(data); - jlong *data_ptr = env->GetLongArrayElements(data, nullptr); - std::vector data_vec(data_ptr, data_ptr + data_size); - env->ReleaseLongArrayElements(data, data_ptr, 0); - return data_vec; -} - -inline std::vector jfloatarray_to_float_vector(JNIEnv *env, - jfloatArray data) { - int data_size = env->GetArrayLength(data); - jfloat *data_ptr = env->GetFloatArrayElements(data, nullptr); - std::vector data_vec(data_ptr, data_ptr + data_size); - env->ReleaseFloatArrayElements(data, data_ptr, 0); - return data_vec; -} - -inline cv::Mat bitmap_to_cv_mat(JNIEnv *env, jobject bitmap) { - AndroidBitmapInfo info; - int result = AndroidBitmap_getInfo(env, bitmap, &info); - if (result != ANDROID_BITMAP_RESULT_SUCCESS) { - LOGE("AndroidBitmap_getInfo failed, result: %d", result); - return cv::Mat{}; - } - if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) { - LOGE("Bitmap format is not RGBA_8888 !"); - return cv::Mat{}; - } - unsigned char *srcData = NULL; - AndroidBitmap_lockPixels(env, bitmap, (void **) &srcData); - cv::Mat mat = cv::Mat::zeros(info.height, info.width, CV_8UC4); - memcpy(mat.data, srcData, info.height * info.width * 4); - AndroidBitmap_unlockPixels(env, bitmap); - cv::cvtColor(mat, mat, cv::COLOR_RGBA2BGR); - /** - if (!cv::imwrite("/sdcard/1/copy.jpg", mat)){ - LOGE("Write image failed " ); - } - */ - return mat; -} diff --git a/deploy/android_demo/app/src/main/cpp/ocr_clipper.cpp b/deploy/android_demo/app/src/main/cpp/ocr_clipper.cpp deleted file mode 100644 index d5db3472..00000000 --- a/deploy/android_demo/app/src/main/cpp/ocr_clipper.cpp +++ /dev/null @@ -1,4629 +0,0 @@ -/******************************************************************************* -* * -* Author : Angus Johnson * -* Version : 6.4.2 * -* Date : 27 February 2017 * -* Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2017 * -* * -* License: * -* Use, modification & distribution is subject to Boost Software License Ver 1. * -* http://www.boost.org/LICENSE_1_0.txt * -* * -* Attributions: * -* The code in this library is an extension of Bala Vatti's clipping algorithm: * -* "A generic solution to polygon clipping" * -* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * -* http://portal.acm.org/citation.cfm?id=129906 * -* * -* Computer graphics and geometric modeling: implementation and algorithms * -* By Max K. Agoston * -* Springer; 1 edition (January 4, 2005) * -* http://books.google.com/books?q=vatti+clipping+agoston * -* * -* See also: * -* "Polygon Offsetting by Computing Winding Numbers" * -* Paper no. DETC2005-85513 pp. 565-575 * -* ASME 2005 International Design Engineering Technical Conferences * -* and Computers and Information in Engineering Conference (IDETC/CIE2005) * -* September 24-28, 2005 , Long Beach, California, USA * -* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * -* * -*******************************************************************************/ - -/******************************************************************************* -* * -* This is a translation of the Delphi Clipper library and the naming style * -* used has retained a Delphi flavour. * -* * -*******************************************************************************/ - -#include "ocr_clipper.hpp" -#include -#include -#include -#include -#include -#include -#include -#include - -namespace ClipperLib { - -static double const pi = 3.141592653589793238; -static double const two_pi = pi *2; -static double const def_arc_tolerance = 0.25; - -enum Direction { dRightToLeft, dLeftToRight }; - -static int const Unassigned = -1; //edge not currently 'owning' a solution -static int const Skip = -2; //edge that would otherwise close a path - -#define HORIZONTAL (-1.0E+40) -#define TOLERANCE (1.0e-20) -#define NEAR_ZERO(val) (((val) > -TOLERANCE) && ((val) < TOLERANCE)) - -struct TEdge { - IntPoint Bot; - IntPoint Curr; //current (updated for every new scanbeam) - IntPoint Top; - double Dx; - PolyType PolyTyp; - EdgeSide Side; //side only refers to current side of solution poly - int WindDelta; //1 or -1 depending on winding direction - int WindCnt; - int WindCnt2; //winding count of the opposite polytype - int OutIdx; - TEdge *Next; - TEdge *Prev; - TEdge *NextInLML; - TEdge *NextInAEL; - TEdge *PrevInAEL; - TEdge *NextInSEL; - TEdge *PrevInSEL; -}; - -struct IntersectNode { - TEdge *Edge1; - TEdge *Edge2; - IntPoint Pt; -}; - -struct LocalMinimum { - cInt Y; - TEdge *LeftBound; - TEdge *RightBound; -}; - -struct OutPt; - -//OutRec: contains a path in the clipping solution. Edges in the AEL will -//carry a pointer to an OutRec when they are part of the clipping solution. -struct OutRec { - int Idx; - bool IsHole; - bool IsOpen; - OutRec *FirstLeft; //see comments in clipper.pas - PolyNode *PolyNd; - OutPt *Pts; - OutPt *BottomPt; -}; - -struct OutPt { - int Idx; - IntPoint Pt; - OutPt *Next; - OutPt *Prev; -}; - -struct Join { - OutPt *OutPt1; - OutPt *OutPt2; - IntPoint OffPt; -}; - -struct LocMinSorter -{ - inline bool operator()(const LocalMinimum& locMin1, const LocalMinimum& locMin2) - { - return locMin2.Y < locMin1.Y; - } -}; - -//------------------------------------------------------------------------------ -//------------------------------------------------------------------------------ - -inline cInt Round(double val) -{ - if ((val < 0)) return static_cast(val - 0.5); - else return static_cast(val + 0.5); -} -//------------------------------------------------------------------------------ - -inline cInt Abs(cInt val) -{ - return val < 0 ? -val : val; -} - -//------------------------------------------------------------------------------ -// PolyTree methods ... -//------------------------------------------------------------------------------ - -void PolyTree::Clear() -{ - for (PolyNodes::size_type i = 0; i < AllNodes.size(); ++i) - delete AllNodes[i]; - AllNodes.resize(0); - Childs.resize(0); -} -//------------------------------------------------------------------------------ - -PolyNode* PolyTree::GetFirst() const -{ - if (!Childs.empty()) - return Childs[0]; - else - return 0; -} -//------------------------------------------------------------------------------ - -int PolyTree::Total() const -{ - int result = (int)AllNodes.size(); - //with negative offsets, ignore the hidden outer polygon ... - if (result > 0 && Childs[0] != AllNodes[0]) result--; - return result; -} - -//------------------------------------------------------------------------------ -// PolyNode methods ... -//------------------------------------------------------------------------------ - -PolyNode::PolyNode(): Parent(0), Index(0), m_IsOpen(false) -{ -} -//------------------------------------------------------------------------------ - -int PolyNode::ChildCount() const -{ - return (int)Childs.size(); -} -//------------------------------------------------------------------------------ - -void PolyNode::AddChild(PolyNode& child) -{ - unsigned cnt = (unsigned)Childs.size(); - Childs.push_back(&child); - child.Parent = this; - child.Index = cnt; -} -//------------------------------------------------------------------------------ - -PolyNode* PolyNode::GetNext() const -{ - if (!Childs.empty()) - return Childs[0]; - else - return GetNextSiblingUp(); -} -//------------------------------------------------------------------------------ - -PolyNode* PolyNode::GetNextSiblingUp() const -{ - if (!Parent) //protects against PolyTree.GetNextSiblingUp() - return 0; - else if (Index == Parent->Childs.size() - 1) - return Parent->GetNextSiblingUp(); - else - return Parent->Childs[Index + 1]; -} -//------------------------------------------------------------------------------ - -bool PolyNode::IsHole() const -{ - bool result = true; - PolyNode* node = Parent; - while (node) - { - result = !result; - node = node->Parent; - } - return result; -} -//------------------------------------------------------------------------------ - -bool PolyNode::IsOpen() const -{ - return m_IsOpen; -} -//------------------------------------------------------------------------------ - -#ifndef use_int32 - -//------------------------------------------------------------------------------ -// Int128 class (enables safe math on signed 64bit integers) -// eg Int128 val1((long64)9223372036854775807); //ie 2^63 -1 -// Int128 val2((long64)9223372036854775807); -// Int128 val3 = val1 * val2; -// val3.AsString => "85070591730234615847396907784232501249" (8.5e+37) -//------------------------------------------------------------------------------ - -class Int128 -{ - public: - ulong64 lo; - long64 hi; - - Int128(long64 _lo = 0) - { - lo = (ulong64)_lo; - if (_lo < 0) hi = -1; else hi = 0; - } - - - Int128(const Int128 &val): lo(val.lo), hi(val.hi){} - - Int128(const long64& _hi, const ulong64& _lo): lo(_lo), hi(_hi){} - - Int128& operator = (const long64 &val) - { - lo = (ulong64)val; - if (val < 0) hi = -1; else hi = 0; - return *this; - } - - bool operator == (const Int128 &val) const - {return (hi == val.hi && lo == val.lo);} - - bool operator != (const Int128 &val) const - { return !(*this == val);} - - bool operator > (const Int128 &val) const - { - if (hi != val.hi) - return hi > val.hi; - else - return lo > val.lo; - } - - bool operator < (const Int128 &val) const - { - if (hi != val.hi) - return hi < val.hi; - else - return lo < val.lo; - } - - bool operator >= (const Int128 &val) const - { return !(*this < val);} - - bool operator <= (const Int128 &val) const - { return !(*this > val);} - - Int128& operator += (const Int128 &rhs) - { - hi += rhs.hi; - lo += rhs.lo; - if (lo < rhs.lo) hi++; - return *this; - } - - Int128 operator + (const Int128 &rhs) const - { - Int128 result(*this); - result+= rhs; - return result; - } - - Int128& operator -= (const Int128 &rhs) - { - *this += -rhs; - return *this; - } - - Int128 operator - (const Int128 &rhs) const - { - Int128 result(*this); - result -= rhs; - return result; - } - - Int128 operator-() const //unary negation - { - if (lo == 0) - return Int128(-hi, 0); - else - return Int128(~hi, ~lo + 1); - } - - operator double() const - { - const double shift64 = 18446744073709551616.0; //2^64 - if (hi < 0) - { - if (lo == 0) return (double)hi * shift64; - else return -(double)(~lo + ~hi * shift64); - } - else - return (double)(lo + hi * shift64); - } - -}; -//------------------------------------------------------------------------------ - -Int128 Int128Mul (long64 lhs, long64 rhs) -{ - bool negate = (lhs < 0) != (rhs < 0); - - if (lhs < 0) lhs = -lhs; - ulong64 int1Hi = ulong64(lhs) >> 32; - ulong64 int1Lo = ulong64(lhs & 0xFFFFFFFF); - - if (rhs < 0) rhs = -rhs; - ulong64 int2Hi = ulong64(rhs) >> 32; - ulong64 int2Lo = ulong64(rhs & 0xFFFFFFFF); - - //nb: see comments in clipper.pas - ulong64 a = int1Hi * int2Hi; - ulong64 b = int1Lo * int2Lo; - ulong64 c = int1Hi * int2Lo + int1Lo * int2Hi; - - Int128 tmp; - tmp.hi = long64(a + (c >> 32)); - tmp.lo = long64(c << 32); - tmp.lo += long64(b); - if (tmp.lo < b) tmp.hi++; - if (negate) tmp = -tmp; - return tmp; -}; -#endif - -//------------------------------------------------------------------------------ -// Miscellaneous global functions -//------------------------------------------------------------------------------ - -bool Orientation(const Path &poly) -{ - return Area(poly) >= 0; -} -//------------------------------------------------------------------------------ - -double Area(const Path &poly) -{ - int size = (int)poly.size(); - if (size < 3) return 0; - - double a = 0; - for (int i = 0, j = size -1; i < size; ++i) - { - a += ((double)poly[j].X + poly[i].X) * ((double)poly[j].Y - poly[i].Y); - j = i; - } - return -a * 0.5; -} -//------------------------------------------------------------------------------ - -double Area(const OutPt *op) -{ - const OutPt *startOp = op; - if (!op) return 0; - double a = 0; - do { - a += (double)(op->Prev->Pt.X + op->Pt.X) * (double)(op->Prev->Pt.Y - op->Pt.Y); - op = op->Next; - } while (op != startOp); - return a * 0.5; -} -//------------------------------------------------------------------------------ - -double Area(const OutRec &outRec) -{ - return Area(outRec.Pts); -} -//------------------------------------------------------------------------------ - -bool PointIsVertex(const IntPoint &Pt, OutPt *pp) -{ - OutPt *pp2 = pp; - do - { - if (pp2->Pt == Pt) return true; - pp2 = pp2->Next; - } - while (pp2 != pp); - return false; -} -//------------------------------------------------------------------------------ - -//See "The Point in Polygon Problem for Arbitrary Polygons" by Hormann & Agathos -//http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf -int PointInPolygon(const IntPoint &pt, const Path &path) -{ - //returns 0 if false, +1 if true, -1 if pt ON polygon boundary - int result = 0; - size_t cnt = path.size(); - if (cnt < 3) return 0; - IntPoint ip = path[0]; - for(size_t i = 1; i <= cnt; ++i) - { - IntPoint ipNext = (i == cnt ? path[0] : path[i]); - if (ipNext.Y == pt.Y) - { - if ((ipNext.X == pt.X) || (ip.Y == pt.Y && - ((ipNext.X > pt.X) == (ip.X < pt.X)))) return -1; - } - if ((ip.Y < pt.Y) != (ipNext.Y < pt.Y)) - { - if (ip.X >= pt.X) - { - if (ipNext.X > pt.X) result = 1 - result; - else - { - double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) - - (double)(ipNext.X - pt.X) * (ip.Y - pt.Y); - if (!d) return -1; - if ((d > 0) == (ipNext.Y > ip.Y)) result = 1 - result; - } - } else - { - if (ipNext.X > pt.X) - { - double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) - - (double)(ipNext.X - pt.X) * (ip.Y - pt.Y); - if (!d) return -1; - if ((d > 0) == (ipNext.Y > ip.Y)) result = 1 - result; - } - } - } - ip = ipNext; - } - return result; -} -//------------------------------------------------------------------------------ - -int PointInPolygon (const IntPoint &pt, OutPt *op) -{ - //returns 0 if false, +1 if true, -1 if pt ON polygon boundary - int result = 0; - OutPt* startOp = op; - for(;;) - { - if (op->Next->Pt.Y == pt.Y) - { - if ((op->Next->Pt.X == pt.X) || (op->Pt.Y == pt.Y && - ((op->Next->Pt.X > pt.X) == (op->Pt.X < pt.X)))) return -1; - } - if ((op->Pt.Y < pt.Y) != (op->Next->Pt.Y < pt.Y)) - { - if (op->Pt.X >= pt.X) - { - if (op->Next->Pt.X > pt.X) result = 1 - result; - else - { - double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) - - (double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y); - if (!d) return -1; - if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y)) result = 1 - result; - } - } else - { - if (op->Next->Pt.X > pt.X) - { - double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) - - (double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y); - if (!d) return -1; - if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y)) result = 1 - result; - } - } - } - op = op->Next; - if (startOp == op) break; - } - return result; -} -//------------------------------------------------------------------------------ - -bool Poly2ContainsPoly1(OutPt *OutPt1, OutPt *OutPt2) -{ - OutPt* op = OutPt1; - do - { - //nb: PointInPolygon returns 0 if false, +1 if true, -1 if pt on polygon - int res = PointInPolygon(op->Pt, OutPt2); - if (res >= 0) return res > 0; - op = op->Next; - } - while (op != OutPt1); - return true; -} -//---------------------------------------------------------------------- - -bool SlopesEqual(const TEdge &e1, const TEdge &e2, bool UseFullInt64Range) -{ -#ifndef use_int32 - if (UseFullInt64Range) - return Int128Mul(e1.Top.Y - e1.Bot.Y, e2.Top.X - e2.Bot.X) == - Int128Mul(e1.Top.X - e1.Bot.X, e2.Top.Y - e2.Bot.Y); - else -#endif - return (e1.Top.Y - e1.Bot.Y) * (e2.Top.X - e2.Bot.X) == - (e1.Top.X - e1.Bot.X) * (e2.Top.Y - e2.Bot.Y); -} -//------------------------------------------------------------------------------ - -bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, - const IntPoint pt3, bool UseFullInt64Range) -{ -#ifndef use_int32 - if (UseFullInt64Range) - return Int128Mul(pt1.Y-pt2.Y, pt2.X-pt3.X) == Int128Mul(pt1.X-pt2.X, pt2.Y-pt3.Y); - else -#endif - return (pt1.Y-pt2.Y)*(pt2.X-pt3.X) == (pt1.X-pt2.X)*(pt2.Y-pt3.Y); -} -//------------------------------------------------------------------------------ - -bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, - const IntPoint pt3, const IntPoint pt4, bool UseFullInt64Range) -{ -#ifndef use_int32 - if (UseFullInt64Range) - return Int128Mul(pt1.Y-pt2.Y, pt3.X-pt4.X) == Int128Mul(pt1.X-pt2.X, pt3.Y-pt4.Y); - else -#endif - return (pt1.Y-pt2.Y)*(pt3.X-pt4.X) == (pt1.X-pt2.X)*(pt3.Y-pt4.Y); -} -//------------------------------------------------------------------------------ - -inline bool IsHorizontal(TEdge &e) -{ - return e.Dx == HORIZONTAL; -} -//------------------------------------------------------------------------------ - -inline double GetDx(const IntPoint pt1, const IntPoint pt2) -{ - return (pt1.Y == pt2.Y) ? - HORIZONTAL : (double)(pt2.X - pt1.X) / (pt2.Y - pt1.Y); -} -//--------------------------------------------------------------------------- - -inline void SetDx(TEdge &e) -{ - cInt dy = (e.Top.Y - e.Bot.Y); - if (dy == 0) e.Dx = HORIZONTAL; - else e.Dx = (double)(e.Top.X - e.Bot.X) / dy; -} -//--------------------------------------------------------------------------- - -inline void SwapSides(TEdge &Edge1, TEdge &Edge2) -{ - EdgeSide Side = Edge1.Side; - Edge1.Side = Edge2.Side; - Edge2.Side = Side; -} -//------------------------------------------------------------------------------ - -inline void SwapPolyIndexes(TEdge &Edge1, TEdge &Edge2) -{ - int OutIdx = Edge1.OutIdx; - Edge1.OutIdx = Edge2.OutIdx; - Edge2.OutIdx = OutIdx; -} -//------------------------------------------------------------------------------ - -inline cInt TopX(TEdge &edge, const cInt currentY) -{ - return ( currentY == edge.Top.Y ) ? - edge.Top.X : edge.Bot.X + Round(edge.Dx *(currentY - edge.Bot.Y)); -} -//------------------------------------------------------------------------------ - -void IntersectPoint(TEdge &Edge1, TEdge &Edge2, IntPoint &ip) -{ -#ifdef use_xyz - ip.Z = 0; -#endif - - double b1, b2; - if (Edge1.Dx == Edge2.Dx) - { - ip.Y = Edge1.Curr.Y; - ip.X = TopX(Edge1, ip.Y); - return; - } - else if (Edge1.Dx == 0) - { - ip.X = Edge1.Bot.X; - if (IsHorizontal(Edge2)) - ip.Y = Edge2.Bot.Y; - else - { - b2 = Edge2.Bot.Y - (Edge2.Bot.X / Edge2.Dx); - ip.Y = Round(ip.X / Edge2.Dx + b2); - } - } - else if (Edge2.Dx == 0) - { - ip.X = Edge2.Bot.X; - if (IsHorizontal(Edge1)) - ip.Y = Edge1.Bot.Y; - else - { - b1 = Edge1.Bot.Y - (Edge1.Bot.X / Edge1.Dx); - ip.Y = Round(ip.X / Edge1.Dx + b1); - } - } - else - { - b1 = Edge1.Bot.X - Edge1.Bot.Y * Edge1.Dx; - b2 = Edge2.Bot.X - Edge2.Bot.Y * Edge2.Dx; - double q = (b2-b1) / (Edge1.Dx - Edge2.Dx); - ip.Y = Round(q); - if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx)) - ip.X = Round(Edge1.Dx * q + b1); - else - ip.X = Round(Edge2.Dx * q + b2); - } - - if (ip.Y < Edge1.Top.Y || ip.Y < Edge2.Top.Y) - { - if (Edge1.Top.Y > Edge2.Top.Y) - ip.Y = Edge1.Top.Y; - else - ip.Y = Edge2.Top.Y; - if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx)) - ip.X = TopX(Edge1, ip.Y); - else - ip.X = TopX(Edge2, ip.Y); - } - //finally, don't allow 'ip' to be BELOW curr.Y (ie bottom of scanbeam) ... - if (ip.Y > Edge1.Curr.Y) - { - ip.Y = Edge1.Curr.Y; - //use the more vertical edge to derive X ... - if (std::fabs(Edge1.Dx) > std::fabs(Edge2.Dx)) - ip.X = TopX(Edge2, ip.Y); else - ip.X = TopX(Edge1, ip.Y); - } -} -//------------------------------------------------------------------------------ - -void ReversePolyPtLinks(OutPt *pp) -{ - if (!pp) return; - OutPt *pp1, *pp2; - pp1 = pp; - do { - pp2 = pp1->Next; - pp1->Next = pp1->Prev; - pp1->Prev = pp2; - pp1 = pp2; - } while( pp1 != pp ); -} -//------------------------------------------------------------------------------ - -void DisposeOutPts(OutPt*& pp) -{ - if (pp == 0) return; - pp->Prev->Next = 0; - while( pp ) - { - OutPt *tmpPp = pp; - pp = pp->Next; - delete tmpPp; - } -} -//------------------------------------------------------------------------------ - -inline void InitEdge(TEdge* e, TEdge* eNext, TEdge* ePrev, const IntPoint& Pt) -{ - std::memset(e, 0, sizeof(TEdge)); - e->Next = eNext; - e->Prev = ePrev; - e->Curr = Pt; - e->OutIdx = Unassigned; -} -//------------------------------------------------------------------------------ - -void InitEdge2(TEdge& e, PolyType Pt) -{ - if (e.Curr.Y >= e.Next->Curr.Y) - { - e.Bot = e.Curr; - e.Top = e.Next->Curr; - } else - { - e.Top = e.Curr; - e.Bot = e.Next->Curr; - } - SetDx(e); - e.PolyTyp = Pt; -} -//------------------------------------------------------------------------------ - -TEdge* RemoveEdge(TEdge* e) -{ - //removes e from double_linked_list (but without removing from memory) - e->Prev->Next = e->Next; - e->Next->Prev = e->Prev; - TEdge* result = e->Next; - e->Prev = 0; //flag as removed (see ClipperBase.Clear) - return result; -} -//------------------------------------------------------------------------------ - -inline void ReverseHorizontal(TEdge &e) -{ - //swap horizontal edges' Top and Bottom x's so they follow the natural - //progression of the bounds - ie so their xbots will align with the - //adjoining lower edge. [Helpful in the ProcessHorizontal() method.] - std::swap(e.Top.X, e.Bot.X); -#ifdef use_xyz - std::swap(e.Top.Z, e.Bot.Z); -#endif -} -//------------------------------------------------------------------------------ - -void SwapPoints(IntPoint &pt1, IntPoint &pt2) -{ - IntPoint tmp = pt1; - pt1 = pt2; - pt2 = tmp; -} -//------------------------------------------------------------------------------ - -bool GetOverlapSegment(IntPoint pt1a, IntPoint pt1b, IntPoint pt2a, - IntPoint pt2b, IntPoint &pt1, IntPoint &pt2) -{ - //precondition: segments are Collinear. - if (Abs(pt1a.X - pt1b.X) > Abs(pt1a.Y - pt1b.Y)) - { - if (pt1a.X > pt1b.X) SwapPoints(pt1a, pt1b); - if (pt2a.X > pt2b.X) SwapPoints(pt2a, pt2b); - if (pt1a.X > pt2a.X) pt1 = pt1a; else pt1 = pt2a; - if (pt1b.X < pt2b.X) pt2 = pt1b; else pt2 = pt2b; - return pt1.X < pt2.X; - } else - { - if (pt1a.Y < pt1b.Y) SwapPoints(pt1a, pt1b); - if (pt2a.Y < pt2b.Y) SwapPoints(pt2a, pt2b); - if (pt1a.Y < pt2a.Y) pt1 = pt1a; else pt1 = pt2a; - if (pt1b.Y > pt2b.Y) pt2 = pt1b; else pt2 = pt2b; - return pt1.Y > pt2.Y; - } -} -//------------------------------------------------------------------------------ - -bool FirstIsBottomPt(const OutPt* btmPt1, const OutPt* btmPt2) -{ - OutPt *p = btmPt1->Prev; - while ((p->Pt == btmPt1->Pt) && (p != btmPt1)) p = p->Prev; - double dx1p = std::fabs(GetDx(btmPt1->Pt, p->Pt)); - p = btmPt1->Next; - while ((p->Pt == btmPt1->Pt) && (p != btmPt1)) p = p->Next; - double dx1n = std::fabs(GetDx(btmPt1->Pt, p->Pt)); - - p = btmPt2->Prev; - while ((p->Pt == btmPt2->Pt) && (p != btmPt2)) p = p->Prev; - double dx2p = std::fabs(GetDx(btmPt2->Pt, p->Pt)); - p = btmPt2->Next; - while ((p->Pt == btmPt2->Pt) && (p != btmPt2)) p = p->Next; - double dx2n = std::fabs(GetDx(btmPt2->Pt, p->Pt)); - - if (std::max(dx1p, dx1n) == std::max(dx2p, dx2n) && - std::min(dx1p, dx1n) == std::min(dx2p, dx2n)) - return Area(btmPt1) > 0; //if otherwise identical use orientation - else - return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n); -} -//------------------------------------------------------------------------------ - -OutPt* GetBottomPt(OutPt *pp) -{ - OutPt* dups = 0; - OutPt* p = pp->Next; - while (p != pp) - { - if (p->Pt.Y > pp->Pt.Y) - { - pp = p; - dups = 0; - } - else if (p->Pt.Y == pp->Pt.Y && p->Pt.X <= pp->Pt.X) - { - if (p->Pt.X < pp->Pt.X) - { - dups = 0; - pp = p; - } else - { - if (p->Next != pp && p->Prev != pp) dups = p; - } - } - p = p->Next; - } - if (dups) - { - //there appears to be at least 2 vertices at BottomPt so ... - while (dups != p) - { - if (!FirstIsBottomPt(p, dups)) pp = dups; - dups = dups->Next; - while (dups->Pt != pp->Pt) dups = dups->Next; - } - } - return pp; -} -//------------------------------------------------------------------------------ - -bool Pt2IsBetweenPt1AndPt3(const IntPoint pt1, - const IntPoint pt2, const IntPoint pt3) -{ - if ((pt1 == pt3) || (pt1 == pt2) || (pt3 == pt2)) - return false; - else if (pt1.X != pt3.X) - return (pt2.X > pt1.X) == (pt2.X < pt3.X); - else - return (pt2.Y > pt1.Y) == (pt2.Y < pt3.Y); -} -//------------------------------------------------------------------------------ - -bool HorzSegmentsOverlap(cInt seg1a, cInt seg1b, cInt seg2a, cInt seg2b) -{ - if (seg1a > seg1b) std::swap(seg1a, seg1b); - if (seg2a > seg2b) std::swap(seg2a, seg2b); - return (seg1a < seg2b) && (seg2a < seg1b); -} - -//------------------------------------------------------------------------------ -// ClipperBase class methods ... -//------------------------------------------------------------------------------ - -ClipperBase::ClipperBase() //constructor -{ - m_CurrentLM = m_MinimaList.begin(); //begin() == end() here - m_UseFullRange = false; -} -//------------------------------------------------------------------------------ - -ClipperBase::~ClipperBase() //destructor -{ - Clear(); -} -//------------------------------------------------------------------------------ - -void RangeTest(const IntPoint& Pt, bool& useFullRange) -{ - if (useFullRange) - { - if (Pt.X > hiRange || Pt.Y > hiRange || -Pt.X > hiRange || -Pt.Y > hiRange) - throw clipperException("Coordinate outside allowed range"); - } - else if (Pt.X > loRange|| Pt.Y > loRange || -Pt.X > loRange || -Pt.Y > loRange) - { - useFullRange = true; - RangeTest(Pt, useFullRange); - } -} -//------------------------------------------------------------------------------ - -TEdge* FindNextLocMin(TEdge* E) -{ - for (;;) - { - while (E->Bot != E->Prev->Bot || E->Curr == E->Top) E = E->Next; - if (!IsHorizontal(*E) && !IsHorizontal(*E->Prev)) break; - while (IsHorizontal(*E->Prev)) E = E->Prev; - TEdge* E2 = E; - while (IsHorizontal(*E)) E = E->Next; - if (E->Top.Y == E->Prev->Bot.Y) continue; //ie just an intermediate horz. - if (E2->Prev->Bot.X < E->Bot.X) E = E2; - break; - } - return E; -} -//------------------------------------------------------------------------------ - -TEdge* ClipperBase::ProcessBound(TEdge* E, bool NextIsForward) -{ - TEdge *Result = E; - TEdge *Horz = 0; - - if (E->OutIdx == Skip) - { - //if edges still remain in the current bound beyond the skip edge then - //create another LocMin and call ProcessBound once more - if (NextIsForward) - { - while (E->Top.Y == E->Next->Bot.Y) E = E->Next; - //don't include top horizontals when parsing a bound a second time, - //they will be contained in the opposite bound ... - while (E != Result && IsHorizontal(*E)) E = E->Prev; - } - else - { - while (E->Top.Y == E->Prev->Bot.Y) E = E->Prev; - while (E != Result && IsHorizontal(*E)) E = E->Next; - } - - if (E == Result) - { - if (NextIsForward) Result = E->Next; - else Result = E->Prev; - } - else - { - //there are more edges in the bound beyond result starting with E - if (NextIsForward) - E = Result->Next; - else - E = Result->Prev; - MinimaList::value_type locMin; - locMin.Y = E->Bot.Y; - locMin.LeftBound = 0; - locMin.RightBound = E; - E->WindDelta = 0; - Result = ProcessBound(E, NextIsForward); - m_MinimaList.push_back(locMin); - } - return Result; - } - - TEdge *EStart; - - if (IsHorizontal(*E)) - { - //We need to be careful with open paths because this may not be a - //true local minima (ie E may be following a skip edge). - //Also, consecutive horz. edges may start heading left before going right. - if (NextIsForward) - EStart = E->Prev; - else - EStart = E->Next; - if (IsHorizontal(*EStart)) //ie an adjoining horizontal skip edge - { - if (EStart->Bot.X != E->Bot.X && EStart->Top.X != E->Bot.X) - ReverseHorizontal(*E); - } - else if (EStart->Bot.X != E->Bot.X) - ReverseHorizontal(*E); - } - - EStart = E; - if (NextIsForward) - { - while (Result->Top.Y == Result->Next->Bot.Y && Result->Next->OutIdx != Skip) - Result = Result->Next; - if (IsHorizontal(*Result) && Result->Next->OutIdx != Skip) - { - //nb: at the top of a bound, horizontals are added to the bound - //only when the preceding edge attaches to the horizontal's left vertex - //unless a Skip edge is encountered when that becomes the top divide - Horz = Result; - while (IsHorizontal(*Horz->Prev)) Horz = Horz->Prev; - if (Horz->Prev->Top.X > Result->Next->Top.X) Result = Horz->Prev; - } - while (E != Result) - { - E->NextInLML = E->Next; - if (IsHorizontal(*E) && E != EStart && - E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E); - E = E->Next; - } - if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Prev->Top.X) - ReverseHorizontal(*E); - Result = Result->Next; //move to the edge just beyond current bound - } else - { - while (Result->Top.Y == Result->Prev->Bot.Y && Result->Prev->OutIdx != Skip) - Result = Result->Prev; - if (IsHorizontal(*Result) && Result->Prev->OutIdx != Skip) - { - Horz = Result; - while (IsHorizontal(*Horz->Next)) Horz = Horz->Next; - if (Horz->Next->Top.X == Result->Prev->Top.X || - Horz->Next->Top.X > Result->Prev->Top.X) Result = Horz->Next; - } - - while (E != Result) - { - E->NextInLML = E->Prev; - if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) - ReverseHorizontal(*E); - E = E->Prev; - } - if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) - ReverseHorizontal(*E); - Result = Result->Prev; //move to the edge just beyond current bound - } - - return Result; -} -//------------------------------------------------------------------------------ - -bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) -{ -#ifdef use_lines - if (!Closed && PolyTyp == ptClip) - throw clipperException("AddPath: Open paths must be subject."); -#else - if (!Closed) - throw clipperException("AddPath: Open paths have been disabled."); -#endif - - int highI = (int)pg.size() -1; - if (Closed) while (highI > 0 && (pg[highI] == pg[0])) --highI; - while (highI > 0 && (pg[highI] == pg[highI -1])) --highI; - if ((Closed && highI < 2) || (!Closed && highI < 1)) return false; - - //create a new edge array ... - TEdge *edges = new TEdge [highI +1]; - - bool IsFlat = true; - //1. Basic (first) edge initialization ... - try - { - edges[1].Curr = pg[1]; - RangeTest(pg[0], m_UseFullRange); - RangeTest(pg[highI], m_UseFullRange); - InitEdge(&edges[0], &edges[1], &edges[highI], pg[0]); - InitEdge(&edges[highI], &edges[0], &edges[highI-1], pg[highI]); - for (int i = highI - 1; i >= 1; --i) - { - RangeTest(pg[i], m_UseFullRange); - InitEdge(&edges[i], &edges[i+1], &edges[i-1], pg[i]); - } - } - catch(...) - { - delete [] edges; - throw; //range test fails - } - TEdge *eStart = &edges[0]; - - //2. Remove duplicate vertices, and (when closed) collinear edges ... - TEdge *E = eStart, *eLoopStop = eStart; - for (;;) - { - //nb: allows matching start and end points when not Closed ... - if (E->Curr == E->Next->Curr && (Closed || E->Next != eStart)) - { - if (E == E->Next) break; - if (E == eStart) eStart = E->Next; - E = RemoveEdge(E); - eLoopStop = E; - continue; - } - if (E->Prev == E->Next) - break; //only two vertices - else if (Closed && - SlopesEqual(E->Prev->Curr, E->Curr, E->Next->Curr, m_UseFullRange) && - (!m_PreserveCollinear || - !Pt2IsBetweenPt1AndPt3(E->Prev->Curr, E->Curr, E->Next->Curr))) - { - //Collinear edges are allowed for open paths but in closed paths - //the default is to merge adjacent collinear edges into a single edge. - //However, if the PreserveCollinear property is enabled, only overlapping - //collinear edges (ie spikes) will be removed from closed paths. - if (E == eStart) eStart = E->Next; - E = RemoveEdge(E); - E = E->Prev; - eLoopStop = E; - continue; - } - E = E->Next; - if ((E == eLoopStop) || (!Closed && E->Next == eStart)) break; - } - - if ((!Closed && (E == E->Next)) || (Closed && (E->Prev == E->Next))) - { - delete [] edges; - return false; - } - - if (!Closed) - { - m_HasOpenPaths = true; - eStart->Prev->OutIdx = Skip; - } - - //3. Do second stage of edge initialization ... - E = eStart; - do - { - InitEdge2(*E, PolyTyp); - E = E->Next; - if (IsFlat && E->Curr.Y != eStart->Curr.Y) IsFlat = false; - } - while (E != eStart); - - //4. Finally, add edge bounds to LocalMinima list ... - - //Totally flat paths must be handled differently when adding them - //to LocalMinima list to avoid endless loops etc ... - if (IsFlat) - { - if (Closed) - { - delete [] edges; - return false; - } - E->Prev->OutIdx = Skip; - MinimaList::value_type locMin; - locMin.Y = E->Bot.Y; - locMin.LeftBound = 0; - locMin.RightBound = E; - locMin.RightBound->Side = esRight; - locMin.RightBound->WindDelta = 0; - for (;;) - { - if (E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E); - if (E->Next->OutIdx == Skip) break; - E->NextInLML = E->Next; - E = E->Next; - } - m_MinimaList.push_back(locMin); - m_edges.push_back(edges); - return true; - } - - m_edges.push_back(edges); - bool leftBoundIsForward; - TEdge* EMin = 0; - - //workaround to avoid an endless loop in the while loop below when - //open paths have matching start and end points ... - if (E->Prev->Bot == E->Prev->Top) E = E->Next; - - for (;;) - { - E = FindNextLocMin(E); - if (E == EMin) break; - else if (!EMin) EMin = E; - - //E and E.Prev now share a local minima (left aligned if horizontal). - //Compare their slopes to find which starts which bound ... - MinimaList::value_type locMin; - locMin.Y = E->Bot.Y; - if (E->Dx < E->Prev->Dx) - { - locMin.LeftBound = E->Prev; - locMin.RightBound = E; - leftBoundIsForward = false; //Q.nextInLML = Q.prev - } else - { - locMin.LeftBound = E; - locMin.RightBound = E->Prev; - leftBoundIsForward = true; //Q.nextInLML = Q.next - } - - if (!Closed) locMin.LeftBound->WindDelta = 0; - else if (locMin.LeftBound->Next == locMin.RightBound) - locMin.LeftBound->WindDelta = -1; - else locMin.LeftBound->WindDelta = 1; - locMin.RightBound->WindDelta = -locMin.LeftBound->WindDelta; - - E = ProcessBound(locMin.LeftBound, leftBoundIsForward); - if (E->OutIdx == Skip) E = ProcessBound(E, leftBoundIsForward); - - TEdge* E2 = ProcessBound(locMin.RightBound, !leftBoundIsForward); - if (E2->OutIdx == Skip) E2 = ProcessBound(E2, !leftBoundIsForward); - - if (locMin.LeftBound->OutIdx == Skip) - locMin.LeftBound = 0; - else if (locMin.RightBound->OutIdx == Skip) - locMin.RightBound = 0; - m_MinimaList.push_back(locMin); - if (!leftBoundIsForward) E = E2; - } - return true; -} -//------------------------------------------------------------------------------ - -bool ClipperBase::AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed) -{ - bool result = false; - for (Paths::size_type i = 0; i < ppg.size(); ++i) - if (AddPath(ppg[i], PolyTyp, Closed)) result = true; - return result; -} -//------------------------------------------------------------------------------ - -void ClipperBase::Clear() -{ - DisposeLocalMinimaList(); - for (EdgeList::size_type i = 0; i < m_edges.size(); ++i) - { - TEdge* edges = m_edges[i]; - delete [] edges; - } - m_edges.clear(); - m_UseFullRange = false; - m_HasOpenPaths = false; -} -//------------------------------------------------------------------------------ - -void ClipperBase::Reset() -{ - m_CurrentLM = m_MinimaList.begin(); - if (m_CurrentLM == m_MinimaList.end()) return; //ie nothing to process - std::sort(m_MinimaList.begin(), m_MinimaList.end(), LocMinSorter()); - - m_Scanbeam = ScanbeamList(); //clears/resets priority_queue - //reset all edges ... - for (MinimaList::iterator lm = m_MinimaList.begin(); lm != m_MinimaList.end(); ++lm) - { - InsertScanbeam(lm->Y); - TEdge* e = lm->LeftBound; - if (e) - { - e->Curr = e->Bot; - e->Side = esLeft; - e->OutIdx = Unassigned; - } - - e = lm->RightBound; - if (e) - { - e->Curr = e->Bot; - e->Side = esRight; - e->OutIdx = Unassigned; - } - } - m_ActiveEdges = 0; - m_CurrentLM = m_MinimaList.begin(); -} -//------------------------------------------------------------------------------ - -void ClipperBase::DisposeLocalMinimaList() -{ - m_MinimaList.clear(); - m_CurrentLM = m_MinimaList.begin(); -} -//------------------------------------------------------------------------------ - -bool ClipperBase::PopLocalMinima(cInt Y, const LocalMinimum *&locMin) -{ - if (m_CurrentLM == m_MinimaList.end() || (*m_CurrentLM).Y != Y) return false; - locMin = &(*m_CurrentLM); - ++m_CurrentLM; - return true; -} -//------------------------------------------------------------------------------ - -IntRect ClipperBase::GetBounds() -{ - IntRect result; - MinimaList::iterator lm = m_MinimaList.begin(); - if (lm == m_MinimaList.end()) - { - result.left = result.top = result.right = result.bottom = 0; - return result; - } - result.left = lm->LeftBound->Bot.X; - result.top = lm->LeftBound->Bot.Y; - result.right = lm->LeftBound->Bot.X; - result.bottom = lm->LeftBound->Bot.Y; - while (lm != m_MinimaList.end()) - { - //todo - needs fixing for open paths - result.bottom = std::max(result.bottom, lm->LeftBound->Bot.Y); - TEdge* e = lm->LeftBound; - for (;;) { - TEdge* bottomE = e; - while (e->NextInLML) - { - if (e->Bot.X < result.left) result.left = e->Bot.X; - if (e->Bot.X > result.right) result.right = e->Bot.X; - e = e->NextInLML; - } - result.left = std::min(result.left, e->Bot.X); - result.right = std::max(result.right, e->Bot.X); - result.left = std::min(result.left, e->Top.X); - result.right = std::max(result.right, e->Top.X); - result.top = std::min(result.top, e->Top.Y); - if (bottomE == lm->LeftBound) e = lm->RightBound; - else break; - } - ++lm; - } - return result; -} -//------------------------------------------------------------------------------ - -void ClipperBase::InsertScanbeam(const cInt Y) -{ - m_Scanbeam.push(Y); -} -//------------------------------------------------------------------------------ - -bool ClipperBase::PopScanbeam(cInt &Y) -{ - if (m_Scanbeam.empty()) return false; - Y = m_Scanbeam.top(); - m_Scanbeam.pop(); - while (!m_Scanbeam.empty() && Y == m_Scanbeam.top()) { m_Scanbeam.pop(); } // Pop duplicates. - return true; -} -//------------------------------------------------------------------------------ - -void ClipperBase::DisposeAllOutRecs(){ - for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) - DisposeOutRec(i); - m_PolyOuts.clear(); -} -//------------------------------------------------------------------------------ - -void ClipperBase::DisposeOutRec(PolyOutList::size_type index) -{ - OutRec *outRec = m_PolyOuts[index]; - if (outRec->Pts) DisposeOutPts(outRec->Pts); - delete outRec; - m_PolyOuts[index] = 0; -} -//------------------------------------------------------------------------------ - -void ClipperBase::DeleteFromAEL(TEdge *e) -{ - TEdge* AelPrev = e->PrevInAEL; - TEdge* AelNext = e->NextInAEL; - if (!AelPrev && !AelNext && (e != m_ActiveEdges)) return; //already deleted - if (AelPrev) AelPrev->NextInAEL = AelNext; - else m_ActiveEdges = AelNext; - if (AelNext) AelNext->PrevInAEL = AelPrev; - e->NextInAEL = 0; - e->PrevInAEL = 0; -} -//------------------------------------------------------------------------------ - -OutRec* ClipperBase::CreateOutRec() -{ - OutRec* result = new OutRec; - result->IsHole = false; - result->IsOpen = false; - result->FirstLeft = 0; - result->Pts = 0; - result->BottomPt = 0; - result->PolyNd = 0; - m_PolyOuts.push_back(result); - result->Idx = (int)m_PolyOuts.size() - 1; - return result; -} -//------------------------------------------------------------------------------ - -void ClipperBase::SwapPositionsInAEL(TEdge *Edge1, TEdge *Edge2) -{ - //check that one or other edge hasn't already been removed from AEL ... - if (Edge1->NextInAEL == Edge1->PrevInAEL || - Edge2->NextInAEL == Edge2->PrevInAEL) return; - - if (Edge1->NextInAEL == Edge2) - { - TEdge* Next = Edge2->NextInAEL; - if (Next) Next->PrevInAEL = Edge1; - TEdge* Prev = Edge1->PrevInAEL; - if (Prev) Prev->NextInAEL = Edge2; - Edge2->PrevInAEL = Prev; - Edge2->NextInAEL = Edge1; - Edge1->PrevInAEL = Edge2; - Edge1->NextInAEL = Next; - } - else if (Edge2->NextInAEL == Edge1) - { - TEdge* Next = Edge1->NextInAEL; - if (Next) Next->PrevInAEL = Edge2; - TEdge* Prev = Edge2->PrevInAEL; - if (Prev) Prev->NextInAEL = Edge1; - Edge1->PrevInAEL = Prev; - Edge1->NextInAEL = Edge2; - Edge2->PrevInAEL = Edge1; - Edge2->NextInAEL = Next; - } - else - { - TEdge* Next = Edge1->NextInAEL; - TEdge* Prev = Edge1->PrevInAEL; - Edge1->NextInAEL = Edge2->NextInAEL; - if (Edge1->NextInAEL) Edge1->NextInAEL->PrevInAEL = Edge1; - Edge1->PrevInAEL = Edge2->PrevInAEL; - if (Edge1->PrevInAEL) Edge1->PrevInAEL->NextInAEL = Edge1; - Edge2->NextInAEL = Next; - if (Edge2->NextInAEL) Edge2->NextInAEL->PrevInAEL = Edge2; - Edge2->PrevInAEL = Prev; - if (Edge2->PrevInAEL) Edge2->PrevInAEL->NextInAEL = Edge2; - } - - if (!Edge1->PrevInAEL) m_ActiveEdges = Edge1; - else if (!Edge2->PrevInAEL) m_ActiveEdges = Edge2; -} -//------------------------------------------------------------------------------ - -void ClipperBase::UpdateEdgeIntoAEL(TEdge *&e) -{ - if (!e->NextInLML) - throw clipperException("UpdateEdgeIntoAEL: invalid call"); - - e->NextInLML->OutIdx = e->OutIdx; - TEdge* AelPrev = e->PrevInAEL; - TEdge* AelNext = e->NextInAEL; - if (AelPrev) AelPrev->NextInAEL = e->NextInLML; - else m_ActiveEdges = e->NextInLML; - if (AelNext) AelNext->PrevInAEL = e->NextInLML; - e->NextInLML->Side = e->Side; - e->NextInLML->WindDelta = e->WindDelta; - e->NextInLML->WindCnt = e->WindCnt; - e->NextInLML->WindCnt2 = e->WindCnt2; - e = e->NextInLML; - e->Curr = e->Bot; - e->PrevInAEL = AelPrev; - e->NextInAEL = AelNext; - if (!IsHorizontal(*e)) InsertScanbeam(e->Top.Y); -} -//------------------------------------------------------------------------------ - -bool ClipperBase::LocalMinimaPending() -{ - return (m_CurrentLM != m_MinimaList.end()); -} - -//------------------------------------------------------------------------------ -// TClipper methods ... -//------------------------------------------------------------------------------ - -Clipper::Clipper(int initOptions) : ClipperBase() //constructor -{ - m_ExecuteLocked = false; - m_UseFullRange = false; - m_ReverseOutput = ((initOptions & ioReverseSolution) != 0); - m_StrictSimple = ((initOptions & ioStrictlySimple) != 0); - m_PreserveCollinear = ((initOptions & ioPreserveCollinear) != 0); - m_HasOpenPaths = false; -#ifdef use_xyz - m_ZFill = 0; -#endif -} -//------------------------------------------------------------------------------ - -#ifdef use_xyz -void Clipper::ZFillFunction(ZFillCallback zFillFunc) -{ - m_ZFill = zFillFunc; -} -//------------------------------------------------------------------------------ -#endif - -bool Clipper::Execute(ClipType clipType, Paths &solution, PolyFillType fillType) -{ - return Execute(clipType, solution, fillType, fillType); -} -//------------------------------------------------------------------------------ - -bool Clipper::Execute(ClipType clipType, PolyTree &polytree, PolyFillType fillType) -{ - return Execute(clipType, polytree, fillType, fillType); -} -//------------------------------------------------------------------------------ - -bool Clipper::Execute(ClipType clipType, Paths &solution, - PolyFillType subjFillType, PolyFillType clipFillType) -{ - if( m_ExecuteLocked ) return false; - if (m_HasOpenPaths) - throw clipperException("Error: PolyTree struct is needed for open path clipping."); - m_ExecuteLocked = true; - solution.resize(0); - m_SubjFillType = subjFillType; - m_ClipFillType = clipFillType; - m_ClipType = clipType; - m_UsingPolyTree = false; - bool succeeded = ExecuteInternal(); - if (succeeded) BuildResult(solution); - DisposeAllOutRecs(); - m_ExecuteLocked = false; - return succeeded; -} -//------------------------------------------------------------------------------ - -bool Clipper::Execute(ClipType clipType, PolyTree& polytree, - PolyFillType subjFillType, PolyFillType clipFillType) -{ - if( m_ExecuteLocked ) return false; - m_ExecuteLocked = true; - m_SubjFillType = subjFillType; - m_ClipFillType = clipFillType; - m_ClipType = clipType; - m_UsingPolyTree = true; - bool succeeded = ExecuteInternal(); - if (succeeded) BuildResult2(polytree); - DisposeAllOutRecs(); - m_ExecuteLocked = false; - return succeeded; -} -//------------------------------------------------------------------------------ - -void Clipper::FixHoleLinkage(OutRec &outrec) -{ - //skip OutRecs that (a) contain outermost polygons or - //(b) already have the correct owner/child linkage ... - if (!outrec.FirstLeft || - (outrec.IsHole != outrec.FirstLeft->IsHole && - outrec.FirstLeft->Pts)) return; - - OutRec* orfl = outrec.FirstLeft; - while (orfl && ((orfl->IsHole == outrec.IsHole) || !orfl->Pts)) - orfl = orfl->FirstLeft; - outrec.FirstLeft = orfl; -} -//------------------------------------------------------------------------------ - -bool Clipper::ExecuteInternal() -{ - bool succeeded = true; - try { - Reset(); - m_Maxima = MaximaList(); - m_SortedEdges = 0; - - succeeded = true; - cInt botY, topY; - if (!PopScanbeam(botY)) return false; - InsertLocalMinimaIntoAEL(botY); - while (PopScanbeam(topY) || LocalMinimaPending()) - { - ProcessHorizontals(); - ClearGhostJoins(); - if (!ProcessIntersections(topY)) - { - succeeded = false; - break; - } - ProcessEdgesAtTopOfScanbeam(topY); - botY = topY; - InsertLocalMinimaIntoAEL(botY); - } - } - catch(...) - { - succeeded = false; - } - - if (succeeded) - { - //fix orientations ... - for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) - { - OutRec *outRec = m_PolyOuts[i]; - if (!outRec->Pts || outRec->IsOpen) continue; - if ((outRec->IsHole ^ m_ReverseOutput) == (Area(*outRec) > 0)) - ReversePolyPtLinks(outRec->Pts); - } - - if (!m_Joins.empty()) JoinCommonEdges(); - - //unfortunately FixupOutPolygon() must be done after JoinCommonEdges() - for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) - { - OutRec *outRec = m_PolyOuts[i]; - if (!outRec->Pts) continue; - if (outRec->IsOpen) - FixupOutPolyline(*outRec); - else - FixupOutPolygon(*outRec); - } - - if (m_StrictSimple) DoSimplePolygons(); - } - - ClearJoins(); - ClearGhostJoins(); - return succeeded; -} -//------------------------------------------------------------------------------ - -void Clipper::SetWindingCount(TEdge &edge) -{ - TEdge *e = edge.PrevInAEL; - //find the edge of the same polytype that immediately preceeds 'edge' in AEL - while (e && ((e->PolyTyp != edge.PolyTyp) || (e->WindDelta == 0))) e = e->PrevInAEL; - if (!e) - { - if (edge.WindDelta == 0) - { - PolyFillType pft = (edge.PolyTyp == ptSubject ? m_SubjFillType : m_ClipFillType); - edge.WindCnt = (pft == pftNegative ? -1 : 1); - } - else - edge.WindCnt = edge.WindDelta; - edge.WindCnt2 = 0; - e = m_ActiveEdges; //ie get ready to calc WindCnt2 - } - else if (edge.WindDelta == 0 && m_ClipType != ctUnion) - { - edge.WindCnt = 1; - edge.WindCnt2 = e->WindCnt2; - e = e->NextInAEL; //ie get ready to calc WindCnt2 - } - else if (IsEvenOddFillType(edge)) - { - //EvenOdd filling ... - if (edge.WindDelta == 0) - { - //are we inside a subj polygon ... - bool Inside = true; - TEdge *e2 = e->PrevInAEL; - while (e2) - { - if (e2->PolyTyp == e->PolyTyp && e2->WindDelta != 0) - Inside = !Inside; - e2 = e2->PrevInAEL; - } - edge.WindCnt = (Inside ? 0 : 1); - } - else - { - edge.WindCnt = edge.WindDelta; - } - edge.WindCnt2 = e->WindCnt2; - e = e->NextInAEL; //ie get ready to calc WindCnt2 - } - else - { - //nonZero, Positive or Negative filling ... - if (e->WindCnt * e->WindDelta < 0) - { - //prev edge is 'decreasing' WindCount (WC) toward zero - //so we're outside the previous polygon ... - if (Abs(e->WindCnt) > 1) - { - //outside prev poly but still inside another. - //when reversing direction of prev poly use the same WC - if (e->WindDelta * edge.WindDelta < 0) edge.WindCnt = e->WindCnt; - //otherwise continue to 'decrease' WC ... - else edge.WindCnt = e->WindCnt + edge.WindDelta; - } - else - //now outside all polys of same polytype so set own WC ... - edge.WindCnt = (edge.WindDelta == 0 ? 1 : edge.WindDelta); - } else - { - //prev edge is 'increasing' WindCount (WC) away from zero - //so we're inside the previous polygon ... - if (edge.WindDelta == 0) - edge.WindCnt = (e->WindCnt < 0 ? e->WindCnt - 1 : e->WindCnt + 1); - //if wind direction is reversing prev then use same WC - else if (e->WindDelta * edge.WindDelta < 0) edge.WindCnt = e->WindCnt; - //otherwise add to WC ... - else edge.WindCnt = e->WindCnt + edge.WindDelta; - } - edge.WindCnt2 = e->WindCnt2; - e = e->NextInAEL; //ie get ready to calc WindCnt2 - } - - //update WindCnt2 ... - if (IsEvenOddAltFillType(edge)) - { - //EvenOdd filling ... - while (e != &edge) - { - if (e->WindDelta != 0) - edge.WindCnt2 = (edge.WindCnt2 == 0 ? 1 : 0); - e = e->NextInAEL; - } - } else - { - //nonZero, Positive or Negative filling ... - while ( e != &edge ) - { - edge.WindCnt2 += e->WindDelta; - e = e->NextInAEL; - } - } -} -//------------------------------------------------------------------------------ - -bool Clipper::IsEvenOddFillType(const TEdge& edge) const -{ - if (edge.PolyTyp == ptSubject) - return m_SubjFillType == pftEvenOdd; else - return m_ClipFillType == pftEvenOdd; -} -//------------------------------------------------------------------------------ - -bool Clipper::IsEvenOddAltFillType(const TEdge& edge) const -{ - if (edge.PolyTyp == ptSubject) - return m_ClipFillType == pftEvenOdd; else - return m_SubjFillType == pftEvenOdd; -} -//------------------------------------------------------------------------------ - -bool Clipper::IsContributing(const TEdge& edge) const -{ - PolyFillType pft, pft2; - if (edge.PolyTyp == ptSubject) - { - pft = m_SubjFillType; - pft2 = m_ClipFillType; - } else - { - pft = m_ClipFillType; - pft2 = m_SubjFillType; - } - - switch(pft) - { - case pftEvenOdd: - //return false if a subj line has been flagged as inside a subj polygon - if (edge.WindDelta == 0 && edge.WindCnt != 1) return false; - break; - case pftNonZero: - if (Abs(edge.WindCnt) != 1) return false; - break; - case pftPositive: - if (edge.WindCnt != 1) return false; - break; - default: //pftNegative - if (edge.WindCnt != -1) return false; - } - - switch(m_ClipType) - { - case ctIntersection: - switch(pft2) - { - case pftEvenOdd: - case pftNonZero: - return (edge.WindCnt2 != 0); - case pftPositive: - return (edge.WindCnt2 > 0); - default: - return (edge.WindCnt2 < 0); - } - break; - case ctUnion: - switch(pft2) - { - case pftEvenOdd: - case pftNonZero: - return (edge.WindCnt2 == 0); - case pftPositive: - return (edge.WindCnt2 <= 0); - default: - return (edge.WindCnt2 >= 0); - } - break; - case ctDifference: - if (edge.PolyTyp == ptSubject) - switch(pft2) - { - case pftEvenOdd: - case pftNonZero: - return (edge.WindCnt2 == 0); - case pftPositive: - return (edge.WindCnt2 <= 0); - default: - return (edge.WindCnt2 >= 0); - } - else - switch(pft2) - { - case pftEvenOdd: - case pftNonZero: - return (edge.WindCnt2 != 0); - case pftPositive: - return (edge.WindCnt2 > 0); - default: - return (edge.WindCnt2 < 0); - } - break; - case ctXor: - if (edge.WindDelta == 0) //XOr always contributing unless open - switch(pft2) - { - case pftEvenOdd: - case pftNonZero: - return (edge.WindCnt2 == 0); - case pftPositive: - return (edge.WindCnt2 <= 0); - default: - return (edge.WindCnt2 >= 0); - } - else - return true; - break; - default: - return true; - } -} -//------------------------------------------------------------------------------ - -OutPt* Clipper::AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) -{ - OutPt* result; - TEdge *e, *prevE; - if (IsHorizontal(*e2) || ( e1->Dx > e2->Dx )) - { - result = AddOutPt(e1, Pt); - e2->OutIdx = e1->OutIdx; - e1->Side = esLeft; - e2->Side = esRight; - e = e1; - if (e->PrevInAEL == e2) - prevE = e2->PrevInAEL; - else - prevE = e->PrevInAEL; - } else - { - result = AddOutPt(e2, Pt); - e1->OutIdx = e2->OutIdx; - e1->Side = esRight; - e2->Side = esLeft; - e = e2; - if (e->PrevInAEL == e1) - prevE = e1->PrevInAEL; - else - prevE = e->PrevInAEL; - } - - if (prevE && prevE->OutIdx >= 0 && prevE->Top.Y < Pt.Y && e->Top.Y < Pt.Y) - { - cInt xPrev = TopX(*prevE, Pt.Y); - cInt xE = TopX(*e, Pt.Y); - if (xPrev == xE && (e->WindDelta != 0) && (prevE->WindDelta != 0) && - SlopesEqual(IntPoint(xPrev, Pt.Y), prevE->Top, IntPoint(xE, Pt.Y), e->Top, m_UseFullRange)) - { - OutPt* outPt = AddOutPt(prevE, Pt); - AddJoin(result, outPt, e->Top); - } - } - return result; -} -//------------------------------------------------------------------------------ - -void Clipper::AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) -{ - AddOutPt( e1, Pt ); - if (e2->WindDelta == 0) AddOutPt(e2, Pt); - if( e1->OutIdx == e2->OutIdx ) - { - e1->OutIdx = Unassigned; - e2->OutIdx = Unassigned; - } - else if (e1->OutIdx < e2->OutIdx) - AppendPolygon(e1, e2); - else - AppendPolygon(e2, e1); -} -//------------------------------------------------------------------------------ - -void Clipper::AddEdgeToSEL(TEdge *edge) -{ - //SEL pointers in PEdge are reused to build a list of horizontal edges. - //However, we don't need to worry about order with horizontal edge processing. - if( !m_SortedEdges ) - { - m_SortedEdges = edge; - edge->PrevInSEL = 0; - edge->NextInSEL = 0; - } - else - { - edge->NextInSEL = m_SortedEdges; - edge->PrevInSEL = 0; - m_SortedEdges->PrevInSEL = edge; - m_SortedEdges = edge; - } -} -//------------------------------------------------------------------------------ - -bool Clipper::PopEdgeFromSEL(TEdge *&edge) -{ - if (!m_SortedEdges) return false; - edge = m_SortedEdges; - DeleteFromSEL(m_SortedEdges); - return true; -} -//------------------------------------------------------------------------------ - -void Clipper::CopyAELToSEL() -{ - TEdge* e = m_ActiveEdges; - m_SortedEdges = e; - while ( e ) - { - e->PrevInSEL = e->PrevInAEL; - e->NextInSEL = e->NextInAEL; - e = e->NextInAEL; - } -} -//------------------------------------------------------------------------------ - -void Clipper::AddJoin(OutPt *op1, OutPt *op2, const IntPoint OffPt) -{ - Join* j = new Join; - j->OutPt1 = op1; - j->OutPt2 = op2; - j->OffPt = OffPt; - m_Joins.push_back(j); -} -//------------------------------------------------------------------------------ - -void Clipper::ClearJoins() -{ - for (JoinList::size_type i = 0; i < m_Joins.size(); i++) - delete m_Joins[i]; - m_Joins.resize(0); -} -//------------------------------------------------------------------------------ - -void Clipper::ClearGhostJoins() -{ - for (JoinList::size_type i = 0; i < m_GhostJoins.size(); i++) - delete m_GhostJoins[i]; - m_GhostJoins.resize(0); -} -//------------------------------------------------------------------------------ - -void Clipper::AddGhostJoin(OutPt *op, const IntPoint OffPt) -{ - Join* j = new Join; - j->OutPt1 = op; - j->OutPt2 = 0; - j->OffPt = OffPt; - m_GhostJoins.push_back(j); -} -//------------------------------------------------------------------------------ - -void Clipper::InsertLocalMinimaIntoAEL(const cInt botY) -{ - const LocalMinimum *lm; - while (PopLocalMinima(botY, lm)) - { - TEdge* lb = lm->LeftBound; - TEdge* rb = lm->RightBound; - - OutPt *Op1 = 0; - if (!lb) - { - //nb: don't insert LB into either AEL or SEL - InsertEdgeIntoAEL(rb, 0); - SetWindingCount(*rb); - if (IsContributing(*rb)) - Op1 = AddOutPt(rb, rb->Bot); - } - else if (!rb) - { - InsertEdgeIntoAEL(lb, 0); - SetWindingCount(*lb); - if (IsContributing(*lb)) - Op1 = AddOutPt(lb, lb->Bot); - InsertScanbeam(lb->Top.Y); - } - else - { - InsertEdgeIntoAEL(lb, 0); - InsertEdgeIntoAEL(rb, lb); - SetWindingCount( *lb ); - rb->WindCnt = lb->WindCnt; - rb->WindCnt2 = lb->WindCnt2; - if (IsContributing(*lb)) - Op1 = AddLocalMinPoly(lb, rb, lb->Bot); - InsertScanbeam(lb->Top.Y); - } - - if (rb) - { - if (IsHorizontal(*rb)) - { - AddEdgeToSEL(rb); - if (rb->NextInLML) - InsertScanbeam(rb->NextInLML->Top.Y); - } - else InsertScanbeam( rb->Top.Y ); - } - - if (!lb || !rb) continue; - - //if any output polygons share an edge, they'll need joining later ... - if (Op1 && IsHorizontal(*rb) && - m_GhostJoins.size() > 0 && (rb->WindDelta != 0)) - { - for (JoinList::size_type i = 0; i < m_GhostJoins.size(); ++i) - { - Join* jr = m_GhostJoins[i]; - //if the horizontal Rb and a 'ghost' horizontal overlap, then convert - //the 'ghost' join to a real join ready for later ... - if (HorzSegmentsOverlap(jr->OutPt1->Pt.X, jr->OffPt.X, rb->Bot.X, rb->Top.X)) - AddJoin(jr->OutPt1, Op1, jr->OffPt); - } - } - - if (lb->OutIdx >= 0 && lb->PrevInAEL && - lb->PrevInAEL->Curr.X == lb->Bot.X && - lb->PrevInAEL->OutIdx >= 0 && - SlopesEqual(lb->PrevInAEL->Bot, lb->PrevInAEL->Top, lb->Curr, lb->Top, m_UseFullRange) && - (lb->WindDelta != 0) && (lb->PrevInAEL->WindDelta != 0)) - { - OutPt *Op2 = AddOutPt(lb->PrevInAEL, lb->Bot); - AddJoin(Op1, Op2, lb->Top); - } - - if(lb->NextInAEL != rb) - { - - if (rb->OutIdx >= 0 && rb->PrevInAEL->OutIdx >= 0 && - SlopesEqual(rb->PrevInAEL->Curr, rb->PrevInAEL->Top, rb->Curr, rb->Top, m_UseFullRange) && - (rb->WindDelta != 0) && (rb->PrevInAEL->WindDelta != 0)) - { - OutPt *Op2 = AddOutPt(rb->PrevInAEL, rb->Bot); - AddJoin(Op1, Op2, rb->Top); - } - - TEdge* e = lb->NextInAEL; - if (e) - { - while( e != rb ) - { - //nb: For calculating winding counts etc, IntersectEdges() assumes - //that param1 will be to the Right of param2 ABOVE the intersection ... - IntersectEdges(rb , e , lb->Curr); //order important here - e = e->NextInAEL; - } - } - } - - } -} -//------------------------------------------------------------------------------ - -void Clipper::DeleteFromSEL(TEdge *e) -{ - TEdge* SelPrev = e->PrevInSEL; - TEdge* SelNext = e->NextInSEL; - if( !SelPrev && !SelNext && (e != m_SortedEdges) ) return; //already deleted - if( SelPrev ) SelPrev->NextInSEL = SelNext; - else m_SortedEdges = SelNext; - if( SelNext ) SelNext->PrevInSEL = SelPrev; - e->NextInSEL = 0; - e->PrevInSEL = 0; -} -//------------------------------------------------------------------------------ - -#ifdef use_xyz -void Clipper::SetZ(IntPoint& pt, TEdge& e1, TEdge& e2) -{ - if (pt.Z != 0 || !m_ZFill) return; - else if (pt == e1.Bot) pt.Z = e1.Bot.Z; - else if (pt == e1.Top) pt.Z = e1.Top.Z; - else if (pt == e2.Bot) pt.Z = e2.Bot.Z; - else if (pt == e2.Top) pt.Z = e2.Top.Z; - else (*m_ZFill)(e1.Bot, e1.Top, e2.Bot, e2.Top, pt); -} -//------------------------------------------------------------------------------ -#endif - -void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &Pt) -{ - bool e1Contributing = ( e1->OutIdx >= 0 ); - bool e2Contributing = ( e2->OutIdx >= 0 ); - -#ifdef use_xyz - SetZ(Pt, *e1, *e2); -#endif - -#ifdef use_lines - //if either edge is on an OPEN path ... - if (e1->WindDelta == 0 || e2->WindDelta == 0) - { - //ignore subject-subject open path intersections UNLESS they - //are both open paths, AND they are both 'contributing maximas' ... - if (e1->WindDelta == 0 && e2->WindDelta == 0) return; - - //if intersecting a subj line with a subj poly ... - else if (e1->PolyTyp == e2->PolyTyp && - e1->WindDelta != e2->WindDelta && m_ClipType == ctUnion) - { - if (e1->WindDelta == 0) - { - if (e2Contributing) - { - AddOutPt(e1, Pt); - if (e1Contributing) e1->OutIdx = Unassigned; - } - } - else - { - if (e1Contributing) - { - AddOutPt(e2, Pt); - if (e2Contributing) e2->OutIdx = Unassigned; - } - } - } - else if (e1->PolyTyp != e2->PolyTyp) - { - //toggle subj open path OutIdx on/off when Abs(clip.WndCnt) == 1 ... - if ((e1->WindDelta == 0) && abs(e2->WindCnt) == 1 && - (m_ClipType != ctUnion || e2->WindCnt2 == 0)) - { - AddOutPt(e1, Pt); - if (e1Contributing) e1->OutIdx = Unassigned; - } - else if ((e2->WindDelta == 0) && (abs(e1->WindCnt) == 1) && - (m_ClipType != ctUnion || e1->WindCnt2 == 0)) - { - AddOutPt(e2, Pt); - if (e2Contributing) e2->OutIdx = Unassigned; - } - } - return; - } -#endif - - //update winding counts... - //assumes that e1 will be to the Right of e2 ABOVE the intersection - if ( e1->PolyTyp == e2->PolyTyp ) - { - if ( IsEvenOddFillType( *e1) ) - { - int oldE1WindCnt = e1->WindCnt; - e1->WindCnt = e2->WindCnt; - e2->WindCnt = oldE1WindCnt; - } else - { - if (e1->WindCnt + e2->WindDelta == 0 ) e1->WindCnt = -e1->WindCnt; - else e1->WindCnt += e2->WindDelta; - if ( e2->WindCnt - e1->WindDelta == 0 ) e2->WindCnt = -e2->WindCnt; - else e2->WindCnt -= e1->WindDelta; - } - } else - { - if (!IsEvenOddFillType(*e2)) e1->WindCnt2 += e2->WindDelta; - else e1->WindCnt2 = ( e1->WindCnt2 == 0 ) ? 1 : 0; - if (!IsEvenOddFillType(*e1)) e2->WindCnt2 -= e1->WindDelta; - else e2->WindCnt2 = ( e2->WindCnt2 == 0 ) ? 1 : 0; - } - - PolyFillType e1FillType, e2FillType, e1FillType2, e2FillType2; - if (e1->PolyTyp == ptSubject) - { - e1FillType = m_SubjFillType; - e1FillType2 = m_ClipFillType; - } else - { - e1FillType = m_ClipFillType; - e1FillType2 = m_SubjFillType; - } - if (e2->PolyTyp == ptSubject) - { - e2FillType = m_SubjFillType; - e2FillType2 = m_ClipFillType; - } else - { - e2FillType = m_ClipFillType; - e2FillType2 = m_SubjFillType; - } - - cInt e1Wc, e2Wc; - switch (e1FillType) - { - case pftPositive: e1Wc = e1->WindCnt; break; - case pftNegative: e1Wc = -e1->WindCnt; break; - default: e1Wc = Abs(e1->WindCnt); - } - switch(e2FillType) - { - case pftPositive: e2Wc = e2->WindCnt; break; - case pftNegative: e2Wc = -e2->WindCnt; break; - default: e2Wc = Abs(e2->WindCnt); - } - - if ( e1Contributing && e2Contributing ) - { - if ((e1Wc != 0 && e1Wc != 1) || (e2Wc != 0 && e2Wc != 1) || - (e1->PolyTyp != e2->PolyTyp && m_ClipType != ctXor) ) - { - AddLocalMaxPoly(e1, e2, Pt); - } - else - { - AddOutPt(e1, Pt); - AddOutPt(e2, Pt); - SwapSides( *e1 , *e2 ); - SwapPolyIndexes( *e1 , *e2 ); - } - } - else if ( e1Contributing ) - { - if (e2Wc == 0 || e2Wc == 1) - { - AddOutPt(e1, Pt); - SwapSides(*e1, *e2); - SwapPolyIndexes(*e1, *e2); - } - } - else if ( e2Contributing ) - { - if (e1Wc == 0 || e1Wc == 1) - { - AddOutPt(e2, Pt); - SwapSides(*e1, *e2); - SwapPolyIndexes(*e1, *e2); - } - } - else if ( (e1Wc == 0 || e1Wc == 1) && (e2Wc == 0 || e2Wc == 1)) - { - //neither edge is currently contributing ... - - cInt e1Wc2, e2Wc2; - switch (e1FillType2) - { - case pftPositive: e1Wc2 = e1->WindCnt2; break; - case pftNegative : e1Wc2 = -e1->WindCnt2; break; - default: e1Wc2 = Abs(e1->WindCnt2); - } - switch (e2FillType2) - { - case pftPositive: e2Wc2 = e2->WindCnt2; break; - case pftNegative: e2Wc2 = -e2->WindCnt2; break; - default: e2Wc2 = Abs(e2->WindCnt2); - } - - if (e1->PolyTyp != e2->PolyTyp) - { - AddLocalMinPoly(e1, e2, Pt); - } - else if (e1Wc == 1 && e2Wc == 1) - switch( m_ClipType ) { - case ctIntersection: - if (e1Wc2 > 0 && e2Wc2 > 0) - AddLocalMinPoly(e1, e2, Pt); - break; - case ctUnion: - if ( e1Wc2 <= 0 && e2Wc2 <= 0 ) - AddLocalMinPoly(e1, e2, Pt); - break; - case ctDifference: - if (((e1->PolyTyp == ptClip) && (e1Wc2 > 0) && (e2Wc2 > 0)) || - ((e1->PolyTyp == ptSubject) && (e1Wc2 <= 0) && (e2Wc2 <= 0))) - AddLocalMinPoly(e1, e2, Pt); - break; - case ctXor: - AddLocalMinPoly(e1, e2, Pt); - } - else - SwapSides( *e1, *e2 ); - } -} -//------------------------------------------------------------------------------ - -void Clipper::SetHoleState(TEdge *e, OutRec *outrec) -{ - TEdge *e2 = e->PrevInAEL; - TEdge *eTmp = 0; - while (e2) - { - if (e2->OutIdx >= 0 && e2->WindDelta != 0) - { - if (!eTmp) eTmp = e2; - else if (eTmp->OutIdx == e2->OutIdx) eTmp = 0; - } - e2 = e2->PrevInAEL; - } - if (!eTmp) - { - outrec->FirstLeft = 0; - outrec->IsHole = false; - } - else - { - outrec->FirstLeft = m_PolyOuts[eTmp->OutIdx]; - outrec->IsHole = !outrec->FirstLeft->IsHole; - } -} -//------------------------------------------------------------------------------ - -OutRec* GetLowermostRec(OutRec *outRec1, OutRec *outRec2) -{ - //work out which polygon fragment has the correct hole state ... - if (!outRec1->BottomPt) - outRec1->BottomPt = GetBottomPt(outRec1->Pts); - if (!outRec2->BottomPt) - outRec2->BottomPt = GetBottomPt(outRec2->Pts); - OutPt *OutPt1 = outRec1->BottomPt; - OutPt *OutPt2 = outRec2->BottomPt; - if (OutPt1->Pt.Y > OutPt2->Pt.Y) return outRec1; - else if (OutPt1->Pt.Y < OutPt2->Pt.Y) return outRec2; - else if (OutPt1->Pt.X < OutPt2->Pt.X) return outRec1; - else if (OutPt1->Pt.X > OutPt2->Pt.X) return outRec2; - else if (OutPt1->Next == OutPt1) return outRec2; - else if (OutPt2->Next == OutPt2) return outRec1; - else if (FirstIsBottomPt(OutPt1, OutPt2)) return outRec1; - else return outRec2; -} -//------------------------------------------------------------------------------ - -bool OutRec1RightOfOutRec2(OutRec* outRec1, OutRec* outRec2) -{ - do - { - outRec1 = outRec1->FirstLeft; - if (outRec1 == outRec2) return true; - } while (outRec1); - return false; -} -//------------------------------------------------------------------------------ - -OutRec* Clipper::GetOutRec(int Idx) -{ - OutRec* outrec = m_PolyOuts[Idx]; - while (outrec != m_PolyOuts[outrec->Idx]) - outrec = m_PolyOuts[outrec->Idx]; - return outrec; -} -//------------------------------------------------------------------------------ - -void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) -{ - //get the start and ends of both output polygons ... - OutRec *outRec1 = m_PolyOuts[e1->OutIdx]; - OutRec *outRec2 = m_PolyOuts[e2->OutIdx]; - - OutRec *holeStateRec; - if (OutRec1RightOfOutRec2(outRec1, outRec2)) - holeStateRec = outRec2; - else if (OutRec1RightOfOutRec2(outRec2, outRec1)) - holeStateRec = outRec1; - else - holeStateRec = GetLowermostRec(outRec1, outRec2); - - //get the start and ends of both output polygons and - //join e2 poly onto e1 poly and delete pointers to e2 ... - - OutPt* p1_lft = outRec1->Pts; - OutPt* p1_rt = p1_lft->Prev; - OutPt* p2_lft = outRec2->Pts; - OutPt* p2_rt = p2_lft->Prev; - - //join e2 poly onto e1 poly and delete pointers to e2 ... - if( e1->Side == esLeft ) - { - if( e2->Side == esLeft ) - { - //z y x a b c - ReversePolyPtLinks(p2_lft); - p2_lft->Next = p1_lft; - p1_lft->Prev = p2_lft; - p1_rt->Next = p2_rt; - p2_rt->Prev = p1_rt; - outRec1->Pts = p2_rt; - } else - { - //x y z a b c - p2_rt->Next = p1_lft; - p1_lft->Prev = p2_rt; - p2_lft->Prev = p1_rt; - p1_rt->Next = p2_lft; - outRec1->Pts = p2_lft; - } - } else - { - if( e2->Side == esRight ) - { - //a b c z y x - ReversePolyPtLinks(p2_lft); - p1_rt->Next = p2_rt; - p2_rt->Prev = p1_rt; - p2_lft->Next = p1_lft; - p1_lft->Prev = p2_lft; - } else - { - //a b c x y z - p1_rt->Next = p2_lft; - p2_lft->Prev = p1_rt; - p1_lft->Prev = p2_rt; - p2_rt->Next = p1_lft; - } - } - - outRec1->BottomPt = 0; - if (holeStateRec == outRec2) - { - if (outRec2->FirstLeft != outRec1) - outRec1->FirstLeft = outRec2->FirstLeft; - outRec1->IsHole = outRec2->IsHole; - } - outRec2->Pts = 0; - outRec2->BottomPt = 0; - outRec2->FirstLeft = outRec1; - - int OKIdx = e1->OutIdx; - int ObsoleteIdx = e2->OutIdx; - - e1->OutIdx = Unassigned; //nb: safe because we only get here via AddLocalMaxPoly - e2->OutIdx = Unassigned; - - TEdge* e = m_ActiveEdges; - while( e ) - { - if( e->OutIdx == ObsoleteIdx ) - { - e->OutIdx = OKIdx; - e->Side = e1->Side; - break; - } - e = e->NextInAEL; - } - - outRec2->Idx = outRec1->Idx; -} -//------------------------------------------------------------------------------ - -OutPt* Clipper::AddOutPt(TEdge *e, const IntPoint &pt) -{ - if( e->OutIdx < 0 ) - { - OutRec *outRec = CreateOutRec(); - outRec->IsOpen = (e->WindDelta == 0); - OutPt* newOp = new OutPt; - outRec->Pts = newOp; - newOp->Idx = outRec->Idx; - newOp->Pt = pt; - newOp->Next = newOp; - newOp->Prev = newOp; - if (!outRec->IsOpen) - SetHoleState(e, outRec); - e->OutIdx = outRec->Idx; - return newOp; - } else - { - OutRec *outRec = m_PolyOuts[e->OutIdx]; - //OutRec.Pts is the 'Left-most' point & OutRec.Pts.Prev is the 'Right-most' - OutPt* op = outRec->Pts; - - bool ToFront = (e->Side == esLeft); - if (ToFront && (pt == op->Pt)) return op; - else if (!ToFront && (pt == op->Prev->Pt)) return op->Prev; - - OutPt* newOp = new OutPt; - newOp->Idx = outRec->Idx; - newOp->Pt = pt; - newOp->Next = op; - newOp->Prev = op->Prev; - newOp->Prev->Next = newOp; - op->Prev = newOp; - if (ToFront) outRec->Pts = newOp; - return newOp; - } -} -//------------------------------------------------------------------------------ - -OutPt* Clipper::GetLastOutPt(TEdge *e) -{ - OutRec *outRec = m_PolyOuts[e->OutIdx]; - if (e->Side == esLeft) - return outRec->Pts; - else - return outRec->Pts->Prev; -} -//------------------------------------------------------------------------------ - -void Clipper::ProcessHorizontals() -{ - TEdge* horzEdge; - while (PopEdgeFromSEL(horzEdge)) - ProcessHorizontal(horzEdge); -} -//------------------------------------------------------------------------------ - -inline bool IsMinima(TEdge *e) -{ - return e && (e->Prev->NextInLML != e) && (e->Next->NextInLML != e); -} -//------------------------------------------------------------------------------ - -inline bool IsMaxima(TEdge *e, const cInt Y) -{ - return e && e->Top.Y == Y && !e->NextInLML; -} -//------------------------------------------------------------------------------ - -inline bool IsIntermediate(TEdge *e, const cInt Y) -{ - return e->Top.Y == Y && e->NextInLML; -} -//------------------------------------------------------------------------------ - -TEdge *GetMaximaPair(TEdge *e) -{ - if ((e->Next->Top == e->Top) && !e->Next->NextInLML) - return e->Next; - else if ((e->Prev->Top == e->Top) && !e->Prev->NextInLML) - return e->Prev; - else return 0; -} -//------------------------------------------------------------------------------ - -TEdge *GetMaximaPairEx(TEdge *e) -{ - //as GetMaximaPair() but returns 0 if MaxPair isn't in AEL (unless it's horizontal) - TEdge* result = GetMaximaPair(e); - if (result && (result->OutIdx == Skip || - (result->NextInAEL == result->PrevInAEL && !IsHorizontal(*result)))) return 0; - return result; -} -//------------------------------------------------------------------------------ - -void Clipper::SwapPositionsInSEL(TEdge *Edge1, TEdge *Edge2) -{ - if( !( Edge1->NextInSEL ) && !( Edge1->PrevInSEL ) ) return; - if( !( Edge2->NextInSEL ) && !( Edge2->PrevInSEL ) ) return; - - if( Edge1->NextInSEL == Edge2 ) - { - TEdge* Next = Edge2->NextInSEL; - if( Next ) Next->PrevInSEL = Edge1; - TEdge* Prev = Edge1->PrevInSEL; - if( Prev ) Prev->NextInSEL = Edge2; - Edge2->PrevInSEL = Prev; - Edge2->NextInSEL = Edge1; - Edge1->PrevInSEL = Edge2; - Edge1->NextInSEL = Next; - } - else if( Edge2->NextInSEL == Edge1 ) - { - TEdge* Next = Edge1->NextInSEL; - if( Next ) Next->PrevInSEL = Edge2; - TEdge* Prev = Edge2->PrevInSEL; - if( Prev ) Prev->NextInSEL = Edge1; - Edge1->PrevInSEL = Prev; - Edge1->NextInSEL = Edge2; - Edge2->PrevInSEL = Edge1; - Edge2->NextInSEL = Next; - } - else - { - TEdge* Next = Edge1->NextInSEL; - TEdge* Prev = Edge1->PrevInSEL; - Edge1->NextInSEL = Edge2->NextInSEL; - if( Edge1->NextInSEL ) Edge1->NextInSEL->PrevInSEL = Edge1; - Edge1->PrevInSEL = Edge2->PrevInSEL; - if( Edge1->PrevInSEL ) Edge1->PrevInSEL->NextInSEL = Edge1; - Edge2->NextInSEL = Next; - if( Edge2->NextInSEL ) Edge2->NextInSEL->PrevInSEL = Edge2; - Edge2->PrevInSEL = Prev; - if( Edge2->PrevInSEL ) Edge2->PrevInSEL->NextInSEL = Edge2; - } - - if( !Edge1->PrevInSEL ) m_SortedEdges = Edge1; - else if( !Edge2->PrevInSEL ) m_SortedEdges = Edge2; -} -//------------------------------------------------------------------------------ - -TEdge* GetNextInAEL(TEdge *e, Direction dir) -{ - return dir == dLeftToRight ? e->NextInAEL : e->PrevInAEL; -} -//------------------------------------------------------------------------------ - -void GetHorzDirection(TEdge& HorzEdge, Direction& Dir, cInt& Left, cInt& Right) -{ - if (HorzEdge.Bot.X < HorzEdge.Top.X) - { - Left = HorzEdge.Bot.X; - Right = HorzEdge.Top.X; - Dir = dLeftToRight; - } else - { - Left = HorzEdge.Top.X; - Right = HorzEdge.Bot.X; - Dir = dRightToLeft; - } -} -//------------------------------------------------------------------------ - -/******************************************************************************* -* Notes: Horizontal edges (HEs) at scanline intersections (ie at the Top or * -* Bottom of a scanbeam) are processed as if layered. The order in which HEs * -* are processed doesn't matter. HEs intersect with other HE Bot.Xs only [#] * -* (or they could intersect with Top.Xs only, ie EITHER Bot.Xs OR Top.Xs), * -* and with other non-horizontal edges [*]. Once these intersections are * -* processed, intermediate HEs then 'promote' the Edge above (NextInLML) into * -* the AEL. These 'promoted' edges may in turn intersect [%] with other HEs. * -*******************************************************************************/ - -void Clipper::ProcessHorizontal(TEdge *horzEdge) -{ - Direction dir; - cInt horzLeft, horzRight; - bool IsOpen = (horzEdge->WindDelta == 0); - - GetHorzDirection(*horzEdge, dir, horzLeft, horzRight); - - TEdge* eLastHorz = horzEdge, *eMaxPair = 0; - while (eLastHorz->NextInLML && IsHorizontal(*eLastHorz->NextInLML)) - eLastHorz = eLastHorz->NextInLML; - if (!eLastHorz->NextInLML) - eMaxPair = GetMaximaPair(eLastHorz); - - MaximaList::const_iterator maxIt; - MaximaList::const_reverse_iterator maxRit; - if (m_Maxima.size() > 0) - { - //get the first maxima in range (X) ... - if (dir == dLeftToRight) - { - maxIt = m_Maxima.begin(); - while (maxIt != m_Maxima.end() && *maxIt <= horzEdge->Bot.X) maxIt++; - if (maxIt != m_Maxima.end() && *maxIt >= eLastHorz->Top.X) - maxIt = m_Maxima.end(); - } - else - { - maxRit = m_Maxima.rbegin(); - while (maxRit != m_Maxima.rend() && *maxRit > horzEdge->Bot.X) maxRit++; - if (maxRit != m_Maxima.rend() && *maxRit <= eLastHorz->Top.X) - maxRit = m_Maxima.rend(); - } - } - - OutPt* op1 = 0; - - for (;;) //loop through consec. horizontal edges - { - - bool IsLastHorz = (horzEdge == eLastHorz); - TEdge* e = GetNextInAEL(horzEdge, dir); - while(e) - { - - //this code block inserts extra coords into horizontal edges (in output - //polygons) whereever maxima touch these horizontal edges. This helps - //'simplifying' polygons (ie if the Simplify property is set). - if (m_Maxima.size() > 0) - { - if (dir == dLeftToRight) - { - while (maxIt != m_Maxima.end() && *maxIt < e->Curr.X) - { - if (horzEdge->OutIdx >= 0 && !IsOpen) - AddOutPt(horzEdge, IntPoint(*maxIt, horzEdge->Bot.Y)); - maxIt++; - } - } - else - { - while (maxRit != m_Maxima.rend() && *maxRit > e->Curr.X) - { - if (horzEdge->OutIdx >= 0 && !IsOpen) - AddOutPt(horzEdge, IntPoint(*maxRit, horzEdge->Bot.Y)); - maxRit++; - } - } - }; - - if ((dir == dLeftToRight && e->Curr.X > horzRight) || - (dir == dRightToLeft && e->Curr.X < horzLeft)) break; - - //Also break if we've got to the end of an intermediate horizontal edge ... - //nb: Smaller Dx's are to the right of larger Dx's ABOVE the horizontal. - if (e->Curr.X == horzEdge->Top.X && horzEdge->NextInLML && - e->Dx < horzEdge->NextInLML->Dx) break; - - if (horzEdge->OutIdx >= 0 && !IsOpen) //note: may be done multiple times - { -#ifdef use_xyz - if (dir == dLeftToRight) SetZ(e->Curr, *horzEdge, *e); - else SetZ(e->Curr, *e, *horzEdge); -#endif - op1 = AddOutPt(horzEdge, e->Curr); - TEdge* eNextHorz = m_SortedEdges; - while (eNextHorz) - { - if (eNextHorz->OutIdx >= 0 && - HorzSegmentsOverlap(horzEdge->Bot.X, - horzEdge->Top.X, eNextHorz->Bot.X, eNextHorz->Top.X)) - { - OutPt* op2 = GetLastOutPt(eNextHorz); - AddJoin(op2, op1, eNextHorz->Top); - } - eNextHorz = eNextHorz->NextInSEL; - } - AddGhostJoin(op1, horzEdge->Bot); - } - - //OK, so far we're still in range of the horizontal Edge but make sure - //we're at the last of consec. horizontals when matching with eMaxPair - if(e == eMaxPair && IsLastHorz) - { - if (horzEdge->OutIdx >= 0) - AddLocalMaxPoly(horzEdge, eMaxPair, horzEdge->Top); - DeleteFromAEL(horzEdge); - DeleteFromAEL(eMaxPair); - return; - } - - if(dir == dLeftToRight) - { - IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y); - IntersectEdges(horzEdge, e, Pt); - } - else - { - IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y); - IntersectEdges( e, horzEdge, Pt); - } - TEdge* eNext = GetNextInAEL(e, dir); - SwapPositionsInAEL( horzEdge, e ); - e = eNext; - } //end while(e) - - //Break out of loop if HorzEdge.NextInLML is not also horizontal ... - if (!horzEdge->NextInLML || !IsHorizontal(*horzEdge->NextInLML)) break; - - UpdateEdgeIntoAEL(horzEdge); - if (horzEdge->OutIdx >= 0) AddOutPt(horzEdge, horzEdge->Bot); - GetHorzDirection(*horzEdge, dir, horzLeft, horzRight); - - } //end for (;;) - - if (horzEdge->OutIdx >= 0 && !op1) - { - op1 = GetLastOutPt(horzEdge); - TEdge* eNextHorz = m_SortedEdges; - while (eNextHorz) - { - if (eNextHorz->OutIdx >= 0 && - HorzSegmentsOverlap(horzEdge->Bot.X, - horzEdge->Top.X, eNextHorz->Bot.X, eNextHorz->Top.X)) - { - OutPt* op2 = GetLastOutPt(eNextHorz); - AddJoin(op2, op1, eNextHorz->Top); - } - eNextHorz = eNextHorz->NextInSEL; - } - AddGhostJoin(op1, horzEdge->Top); - } - - if (horzEdge->NextInLML) - { - if(horzEdge->OutIdx >= 0) - { - op1 = AddOutPt( horzEdge, horzEdge->Top); - UpdateEdgeIntoAEL(horzEdge); - if (horzEdge->WindDelta == 0) return; - //nb: HorzEdge is no longer horizontal here - TEdge* ePrev = horzEdge->PrevInAEL; - TEdge* eNext = horzEdge->NextInAEL; - if (ePrev && ePrev->Curr.X == horzEdge->Bot.X && - ePrev->Curr.Y == horzEdge->Bot.Y && ePrev->WindDelta != 0 && - (ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y && - SlopesEqual(*horzEdge, *ePrev, m_UseFullRange))) - { - OutPt* op2 = AddOutPt(ePrev, horzEdge->Bot); - AddJoin(op1, op2, horzEdge->Top); - } - else if (eNext && eNext->Curr.X == horzEdge->Bot.X && - eNext->Curr.Y == horzEdge->Bot.Y && eNext->WindDelta != 0 && - eNext->OutIdx >= 0 && eNext->Curr.Y > eNext->Top.Y && - SlopesEqual(*horzEdge, *eNext, m_UseFullRange)) - { - OutPt* op2 = AddOutPt(eNext, horzEdge->Bot); - AddJoin(op1, op2, horzEdge->Top); - } - } - else - UpdateEdgeIntoAEL(horzEdge); - } - else - { - if (horzEdge->OutIdx >= 0) AddOutPt(horzEdge, horzEdge->Top); - DeleteFromAEL(horzEdge); - } -} -//------------------------------------------------------------------------------ - -bool Clipper::ProcessIntersections(const cInt topY) -{ - if( !m_ActiveEdges ) return true; - try { - BuildIntersectList(topY); - size_t IlSize = m_IntersectList.size(); - if (IlSize == 0) return true; - if (IlSize == 1 || FixupIntersectionOrder()) ProcessIntersectList(); - else return false; - } - catch(...) - { - m_SortedEdges = 0; - DisposeIntersectNodes(); - throw clipperException("ProcessIntersections error"); - } - m_SortedEdges = 0; - return true; -} -//------------------------------------------------------------------------------ - -void Clipper::DisposeIntersectNodes() -{ - for (size_t i = 0; i < m_IntersectList.size(); ++i ) - delete m_IntersectList[i]; - m_IntersectList.clear(); -} -//------------------------------------------------------------------------------ - -void Clipper::BuildIntersectList(const cInt topY) -{ - if ( !m_ActiveEdges ) return; - - //prepare for sorting ... - TEdge* e = m_ActiveEdges; - m_SortedEdges = e; - while( e ) - { - e->PrevInSEL = e->PrevInAEL; - e->NextInSEL = e->NextInAEL; - e->Curr.X = TopX( *e, topY ); - e = e->NextInAEL; - } - - //bubblesort ... - bool isModified; - do - { - isModified = false; - e = m_SortedEdges; - while( e->NextInSEL ) - { - TEdge *eNext = e->NextInSEL; - IntPoint Pt; - if(e->Curr.X > eNext->Curr.X) - { - IntersectPoint(*e, *eNext, Pt); - if (Pt.Y < topY) Pt = IntPoint(TopX(*e, topY), topY); - IntersectNode * newNode = new IntersectNode; - newNode->Edge1 = e; - newNode->Edge2 = eNext; - newNode->Pt = Pt; - m_IntersectList.push_back(newNode); - - SwapPositionsInSEL(e, eNext); - isModified = true; - } - else - e = eNext; - } - if( e->PrevInSEL ) e->PrevInSEL->NextInSEL = 0; - else break; - } - while ( isModified ); - m_SortedEdges = 0; //important -} -//------------------------------------------------------------------------------ - - -void Clipper::ProcessIntersectList() -{ - for (size_t i = 0; i < m_IntersectList.size(); ++i) - { - IntersectNode* iNode = m_IntersectList[i]; - { - IntersectEdges( iNode->Edge1, iNode->Edge2, iNode->Pt); - SwapPositionsInAEL( iNode->Edge1 , iNode->Edge2 ); - } - delete iNode; - } - m_IntersectList.clear(); -} -//------------------------------------------------------------------------------ - -bool IntersectListSort(IntersectNode* node1, IntersectNode* node2) -{ - return node2->Pt.Y < node1->Pt.Y; -} -//------------------------------------------------------------------------------ - -inline bool EdgesAdjacent(const IntersectNode &inode) -{ - return (inode.Edge1->NextInSEL == inode.Edge2) || - (inode.Edge1->PrevInSEL == inode.Edge2); -} -//------------------------------------------------------------------------------ - -bool Clipper::FixupIntersectionOrder() -{ - //pre-condition: intersections are sorted Bottom-most first. - //Now it's crucial that intersections are made only between adjacent edges, - //so to ensure this the order of intersections may need adjusting ... - CopyAELToSEL(); - std::sort(m_IntersectList.begin(), m_IntersectList.end(), IntersectListSort); - size_t cnt = m_IntersectList.size(); - for (size_t i = 0; i < cnt; ++i) - { - if (!EdgesAdjacent(*m_IntersectList[i])) - { - size_t j = i + 1; - while (j < cnt && !EdgesAdjacent(*m_IntersectList[j])) j++; - if (j == cnt) return false; - std::swap(m_IntersectList[i], m_IntersectList[j]); - } - SwapPositionsInSEL(m_IntersectList[i]->Edge1, m_IntersectList[i]->Edge2); - } - return true; -} -//------------------------------------------------------------------------------ - -void Clipper::DoMaxima(TEdge *e) -{ - TEdge* eMaxPair = GetMaximaPairEx(e); - if (!eMaxPair) - { - if (e->OutIdx >= 0) - AddOutPt(e, e->Top); - DeleteFromAEL(e); - return; - } - - TEdge* eNext = e->NextInAEL; - while(eNext && eNext != eMaxPair) - { - IntersectEdges(e, eNext, e->Top); - SwapPositionsInAEL(e, eNext); - eNext = e->NextInAEL; - } - - if(e->OutIdx == Unassigned && eMaxPair->OutIdx == Unassigned) - { - DeleteFromAEL(e); - DeleteFromAEL(eMaxPair); - } - else if( e->OutIdx >= 0 && eMaxPair->OutIdx >= 0 ) - { - if (e->OutIdx >= 0) AddLocalMaxPoly(e, eMaxPair, e->Top); - DeleteFromAEL(e); - DeleteFromAEL(eMaxPair); - } -#ifdef use_lines - else if (e->WindDelta == 0) - { - if (e->OutIdx >= 0) - { - AddOutPt(e, e->Top); - e->OutIdx = Unassigned; - } - DeleteFromAEL(e); - - if (eMaxPair->OutIdx >= 0) - { - AddOutPt(eMaxPair, e->Top); - eMaxPair->OutIdx = Unassigned; - } - DeleteFromAEL(eMaxPair); - } -#endif - else throw clipperException("DoMaxima error"); -} -//------------------------------------------------------------------------------ - -void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY) -{ - TEdge* e = m_ActiveEdges; - while( e ) - { - //1. process maxima, treating them as if they're 'bent' horizontal edges, - // but exclude maxima with horizontal edges. nb: e can't be a horizontal. - bool IsMaximaEdge = IsMaxima(e, topY); - - if(IsMaximaEdge) - { - TEdge* eMaxPair = GetMaximaPairEx(e); - IsMaximaEdge = (!eMaxPair || !IsHorizontal(*eMaxPair)); - } - - if(IsMaximaEdge) - { - if (m_StrictSimple) m_Maxima.push_back(e->Top.X); - TEdge* ePrev = e->PrevInAEL; - DoMaxima(e); - if( !ePrev ) e = m_ActiveEdges; - else e = ePrev->NextInAEL; - } - else - { - //2. promote horizontal edges, otherwise update Curr.X and Curr.Y ... - if (IsIntermediate(e, topY) && IsHorizontal(*e->NextInLML)) - { - UpdateEdgeIntoAEL(e); - if (e->OutIdx >= 0) - AddOutPt(e, e->Bot); - AddEdgeToSEL(e); - } - else - { - e->Curr.X = TopX( *e, topY ); - e->Curr.Y = topY; -#ifdef use_xyz - e->Curr.Z = topY == e->Top.Y ? e->Top.Z : (topY == e->Bot.Y ? e->Bot.Z : 0); -#endif - } - - //When StrictlySimple and 'e' is being touched by another edge, then - //make sure both edges have a vertex here ... - if (m_StrictSimple) - { - TEdge* ePrev = e->PrevInAEL; - if ((e->OutIdx >= 0) && (e->WindDelta != 0) && ePrev && (ePrev->OutIdx >= 0) && - (ePrev->Curr.X == e->Curr.X) && (ePrev->WindDelta != 0)) - { - IntPoint pt = e->Curr; -#ifdef use_xyz - SetZ(pt, *ePrev, *e); -#endif - OutPt* op = AddOutPt(ePrev, pt); - OutPt* op2 = AddOutPt(e, pt); - AddJoin(op, op2, pt); //StrictlySimple (type-3) join - } - } - - e = e->NextInAEL; - } - } - - //3. Process horizontals at the Top of the scanbeam ... - m_Maxima.sort(); - ProcessHorizontals(); - m_Maxima.clear(); - - //4. Promote intermediate vertices ... - e = m_ActiveEdges; - while(e) - { - if(IsIntermediate(e, topY)) - { - OutPt* op = 0; - if( e->OutIdx >= 0 ) - op = AddOutPt(e, e->Top); - UpdateEdgeIntoAEL(e); - - //if output polygons share an edge, they'll need joining later ... - TEdge* ePrev = e->PrevInAEL; - TEdge* eNext = e->NextInAEL; - if (ePrev && ePrev->Curr.X == e->Bot.X && - ePrev->Curr.Y == e->Bot.Y && op && - ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y && - SlopesEqual(e->Curr, e->Top, ePrev->Curr, ePrev->Top, m_UseFullRange) && - (e->WindDelta != 0) && (ePrev->WindDelta != 0)) - { - OutPt* op2 = AddOutPt(ePrev, e->Bot); - AddJoin(op, op2, e->Top); - } - else if (eNext && eNext->Curr.X == e->Bot.X && - eNext->Curr.Y == e->Bot.Y && op && - eNext->OutIdx >= 0 && eNext->Curr.Y > eNext->Top.Y && - SlopesEqual(e->Curr, e->Top, eNext->Curr, eNext->Top, m_UseFullRange) && - (e->WindDelta != 0) && (eNext->WindDelta != 0)) - { - OutPt* op2 = AddOutPt(eNext, e->Bot); - AddJoin(op, op2, e->Top); - } - } - e = e->NextInAEL; - } -} -//------------------------------------------------------------------------------ - -void Clipper::FixupOutPolyline(OutRec &outrec) -{ - OutPt *pp = outrec.Pts; - OutPt *lastPP = pp->Prev; - while (pp != lastPP) - { - pp = pp->Next; - if (pp->Pt == pp->Prev->Pt) - { - if (pp == lastPP) lastPP = pp->Prev; - OutPt *tmpPP = pp->Prev; - tmpPP->Next = pp->Next; - pp->Next->Prev = tmpPP; - delete pp; - pp = tmpPP; - } - } - - if (pp == pp->Prev) - { - DisposeOutPts(pp); - outrec.Pts = 0; - return; - } -} -//------------------------------------------------------------------------------ - -void Clipper::FixupOutPolygon(OutRec &outrec) -{ - //FixupOutPolygon() - removes duplicate points and simplifies consecutive - //parallel edges by removing the middle vertex. - OutPt *lastOK = 0; - outrec.BottomPt = 0; - OutPt *pp = outrec.Pts; - bool preserveCol = m_PreserveCollinear || m_StrictSimple; - - for (;;) - { - if (pp->Prev == pp || pp->Prev == pp->Next) - { - DisposeOutPts(pp); - outrec.Pts = 0; - return; - } - - //test for duplicate points and collinear edges ... - if ((pp->Pt == pp->Next->Pt) || (pp->Pt == pp->Prev->Pt) || - (SlopesEqual(pp->Prev->Pt, pp->Pt, pp->Next->Pt, m_UseFullRange) && - (!preserveCol || !Pt2IsBetweenPt1AndPt3(pp->Prev->Pt, pp->Pt, pp->Next->Pt)))) - { - lastOK = 0; - OutPt *tmp = pp; - pp->Prev->Next = pp->Next; - pp->Next->Prev = pp->Prev; - pp = pp->Prev; - delete tmp; - } - else if (pp == lastOK) break; - else - { - if (!lastOK) lastOK = pp; - pp = pp->Next; - } - } - outrec.Pts = pp; -} -//------------------------------------------------------------------------------ - -int PointCount(OutPt *Pts) -{ - if (!Pts) return 0; - int result = 0; - OutPt* p = Pts; - do - { - result++; - p = p->Next; - } - while (p != Pts); - return result; -} -//------------------------------------------------------------------------------ - -void Clipper::BuildResult(Paths &polys) -{ - polys.reserve(m_PolyOuts.size()); - for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) - { - if (!m_PolyOuts[i]->Pts) continue; - Path pg; - OutPt* p = m_PolyOuts[i]->Pts->Prev; - int cnt = PointCount(p); - if (cnt < 2) continue; - pg.reserve(cnt); - for (int i = 0; i < cnt; ++i) - { - pg.push_back(p->Pt); - p = p->Prev; - } - polys.push_back(pg); - } -} -//------------------------------------------------------------------------------ - -void Clipper::BuildResult2(PolyTree& polytree) -{ - polytree.Clear(); - polytree.AllNodes.reserve(m_PolyOuts.size()); - //add each output polygon/contour to polytree ... - for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) - { - OutRec* outRec = m_PolyOuts[i]; - int cnt = PointCount(outRec->Pts); - if ((outRec->IsOpen && cnt < 2) || (!outRec->IsOpen && cnt < 3)) continue; - FixHoleLinkage(*outRec); - PolyNode* pn = new PolyNode(); - //nb: polytree takes ownership of all the PolyNodes - polytree.AllNodes.push_back(pn); - outRec->PolyNd = pn; - pn->Parent = 0; - pn->Index = 0; - pn->Contour.reserve(cnt); - OutPt *op = outRec->Pts->Prev; - for (int j = 0; j < cnt; j++) - { - pn->Contour.push_back(op->Pt); - op = op->Prev; - } - } - - //fixup PolyNode links etc ... - polytree.Childs.reserve(m_PolyOuts.size()); - for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) - { - OutRec* outRec = m_PolyOuts[i]; - if (!outRec->PolyNd) continue; - if (outRec->IsOpen) - { - outRec->PolyNd->m_IsOpen = true; - polytree.AddChild(*outRec->PolyNd); - } - else if (outRec->FirstLeft && outRec->FirstLeft->PolyNd) - outRec->FirstLeft->PolyNd->AddChild(*outRec->PolyNd); - else - polytree.AddChild(*outRec->PolyNd); - } -} -//------------------------------------------------------------------------------ - -void SwapIntersectNodes(IntersectNode &int1, IntersectNode &int2) -{ - //just swap the contents (because fIntersectNodes is a single-linked-list) - IntersectNode inode = int1; //gets a copy of Int1 - int1.Edge1 = int2.Edge1; - int1.Edge2 = int2.Edge2; - int1.Pt = int2.Pt; - int2.Edge1 = inode.Edge1; - int2.Edge2 = inode.Edge2; - int2.Pt = inode.Pt; -} -//------------------------------------------------------------------------------ - -inline bool E2InsertsBeforeE1(TEdge &e1, TEdge &e2) -{ - if (e2.Curr.X == e1.Curr.X) - { - if (e2.Top.Y > e1.Top.Y) - return e2.Top.X < TopX(e1, e2.Top.Y); - else return e1.Top.X > TopX(e2, e1.Top.Y); - } - else return e2.Curr.X < e1.Curr.X; -} -//------------------------------------------------------------------------------ - -bool GetOverlap(const cInt a1, const cInt a2, const cInt b1, const cInt b2, - cInt& Left, cInt& Right) -{ - if (a1 < a2) - { - if (b1 < b2) {Left = std::max(a1,b1); Right = std::min(a2,b2);} - else {Left = std::max(a1,b2); Right = std::min(a2,b1);} - } - else - { - if (b1 < b2) {Left = std::max(a2,b1); Right = std::min(a1,b2);} - else {Left = std::max(a2,b2); Right = std::min(a1,b1);} - } - return Left < Right; -} -//------------------------------------------------------------------------------ - -inline void UpdateOutPtIdxs(OutRec& outrec) -{ - OutPt* op = outrec.Pts; - do - { - op->Idx = outrec.Idx; - op = op->Prev; - } - while(op != outrec.Pts); -} -//------------------------------------------------------------------------------ - -void Clipper::InsertEdgeIntoAEL(TEdge *edge, TEdge* startEdge) -{ - if(!m_ActiveEdges) - { - edge->PrevInAEL = 0; - edge->NextInAEL = 0; - m_ActiveEdges = edge; - } - else if(!startEdge && E2InsertsBeforeE1(*m_ActiveEdges, *edge)) - { - edge->PrevInAEL = 0; - edge->NextInAEL = m_ActiveEdges; - m_ActiveEdges->PrevInAEL = edge; - m_ActiveEdges = edge; - } - else - { - if(!startEdge) startEdge = m_ActiveEdges; - while(startEdge->NextInAEL && - !E2InsertsBeforeE1(*startEdge->NextInAEL , *edge)) - startEdge = startEdge->NextInAEL; - edge->NextInAEL = startEdge->NextInAEL; - if(startEdge->NextInAEL) startEdge->NextInAEL->PrevInAEL = edge; - edge->PrevInAEL = startEdge; - startEdge->NextInAEL = edge; - } -} -//---------------------------------------------------------------------- - -OutPt* DupOutPt(OutPt* outPt, bool InsertAfter) -{ - OutPt* result = new OutPt; - result->Pt = outPt->Pt; - result->Idx = outPt->Idx; - if (InsertAfter) - { - result->Next = outPt->Next; - result->Prev = outPt; - outPt->Next->Prev = result; - outPt->Next = result; - } - else - { - result->Prev = outPt->Prev; - result->Next = outPt; - outPt->Prev->Next = result; - outPt->Prev = result; - } - return result; -} -//------------------------------------------------------------------------------ - -bool JoinHorz(OutPt* op1, OutPt* op1b, OutPt* op2, OutPt* op2b, - const IntPoint Pt, bool DiscardLeft) -{ - Direction Dir1 = (op1->Pt.X > op1b->Pt.X ? dRightToLeft : dLeftToRight); - Direction Dir2 = (op2->Pt.X > op2b->Pt.X ? dRightToLeft : dLeftToRight); - if (Dir1 == Dir2) return false; - - //When DiscardLeft, we want Op1b to be on the Left of Op1, otherwise we - //want Op1b to be on the Right. (And likewise with Op2 and Op2b.) - //So, to facilitate this while inserting Op1b and Op2b ... - //when DiscardLeft, make sure we're AT or RIGHT of Pt before adding Op1b, - //otherwise make sure we're AT or LEFT of Pt. (Likewise with Op2b.) - if (Dir1 == dLeftToRight) - { - while (op1->Next->Pt.X <= Pt.X && - op1->Next->Pt.X >= op1->Pt.X && op1->Next->Pt.Y == Pt.Y) - op1 = op1->Next; - if (DiscardLeft && (op1->Pt.X != Pt.X)) op1 = op1->Next; - op1b = DupOutPt(op1, !DiscardLeft); - if (op1b->Pt != Pt) - { - op1 = op1b; - op1->Pt = Pt; - op1b = DupOutPt(op1, !DiscardLeft); - } - } - else - { - while (op1->Next->Pt.X >= Pt.X && - op1->Next->Pt.X <= op1->Pt.X && op1->Next->Pt.Y == Pt.Y) - op1 = op1->Next; - if (!DiscardLeft && (op1->Pt.X != Pt.X)) op1 = op1->Next; - op1b = DupOutPt(op1, DiscardLeft); - if (op1b->Pt != Pt) - { - op1 = op1b; - op1->Pt = Pt; - op1b = DupOutPt(op1, DiscardLeft); - } - } - - if (Dir2 == dLeftToRight) - { - while (op2->Next->Pt.X <= Pt.X && - op2->Next->Pt.X >= op2->Pt.X && op2->Next->Pt.Y == Pt.Y) - op2 = op2->Next; - if (DiscardLeft && (op2->Pt.X != Pt.X)) op2 = op2->Next; - op2b = DupOutPt(op2, !DiscardLeft); - if (op2b->Pt != Pt) - { - op2 = op2b; - op2->Pt = Pt; - op2b = DupOutPt(op2, !DiscardLeft); - }; - } else - { - while (op2->Next->Pt.X >= Pt.X && - op2->Next->Pt.X <= op2->Pt.X && op2->Next->Pt.Y == Pt.Y) - op2 = op2->Next; - if (!DiscardLeft && (op2->Pt.X != Pt.X)) op2 = op2->Next; - op2b = DupOutPt(op2, DiscardLeft); - if (op2b->Pt != Pt) - { - op2 = op2b; - op2->Pt = Pt; - op2b = DupOutPt(op2, DiscardLeft); - }; - }; - - if ((Dir1 == dLeftToRight) == DiscardLeft) - { - op1->Prev = op2; - op2->Next = op1; - op1b->Next = op2b; - op2b->Prev = op1b; - } - else - { - op1->Next = op2; - op2->Prev = op1; - op1b->Prev = op2b; - op2b->Next = op1b; - } - return true; -} -//------------------------------------------------------------------------------ - -bool Clipper::JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2) -{ - OutPt *op1 = j->OutPt1, *op1b; - OutPt *op2 = j->OutPt2, *op2b; - - //There are 3 kinds of joins for output polygons ... - //1. Horizontal joins where Join.OutPt1 & Join.OutPt2 are vertices anywhere - //along (horizontal) collinear edges (& Join.OffPt is on the same horizontal). - //2. Non-horizontal joins where Join.OutPt1 & Join.OutPt2 are at the same - //location at the Bottom of the overlapping segment (& Join.OffPt is above). - //3. StrictSimple joins where edges touch but are not collinear and where - //Join.OutPt1, Join.OutPt2 & Join.OffPt all share the same point. - bool isHorizontal = (j->OutPt1->Pt.Y == j->OffPt.Y); - - if (isHorizontal && (j->OffPt == j->OutPt1->Pt) && - (j->OffPt == j->OutPt2->Pt)) - { - //Strictly Simple join ... - if (outRec1 != outRec2) return false; - op1b = j->OutPt1->Next; - while (op1b != op1 && (op1b->Pt == j->OffPt)) - op1b = op1b->Next; - bool reverse1 = (op1b->Pt.Y > j->OffPt.Y); - op2b = j->OutPt2->Next; - while (op2b != op2 && (op2b->Pt == j->OffPt)) - op2b = op2b->Next; - bool reverse2 = (op2b->Pt.Y > j->OffPt.Y); - if (reverse1 == reverse2) return false; - if (reverse1) - { - op1b = DupOutPt(op1, false); - op2b = DupOutPt(op2, true); - op1->Prev = op2; - op2->Next = op1; - op1b->Next = op2b; - op2b->Prev = op1b; - j->OutPt1 = op1; - j->OutPt2 = op1b; - return true; - } else - { - op1b = DupOutPt(op1, true); - op2b = DupOutPt(op2, false); - op1->Next = op2; - op2->Prev = op1; - op1b->Prev = op2b; - op2b->Next = op1b; - j->OutPt1 = op1; - j->OutPt2 = op1b; - return true; - } - } - else if (isHorizontal) - { - //treat horizontal joins differently to non-horizontal joins since with - //them we're not yet sure where the overlapping is. OutPt1.Pt & OutPt2.Pt - //may be anywhere along the horizontal edge. - op1b = op1; - while (op1->Prev->Pt.Y == op1->Pt.Y && op1->Prev != op1b && op1->Prev != op2) - op1 = op1->Prev; - while (op1b->Next->Pt.Y == op1b->Pt.Y && op1b->Next != op1 && op1b->Next != op2) - op1b = op1b->Next; - if (op1b->Next == op1 || op1b->Next == op2) return false; //a flat 'polygon' - - op2b = op2; - while (op2->Prev->Pt.Y == op2->Pt.Y && op2->Prev != op2b && op2->Prev != op1b) - op2 = op2->Prev; - while (op2b->Next->Pt.Y == op2b->Pt.Y && op2b->Next != op2 && op2b->Next != op1) - op2b = op2b->Next; - if (op2b->Next == op2 || op2b->Next == op1) return false; //a flat 'polygon' - - cInt Left, Right; - //Op1 --> Op1b & Op2 --> Op2b are the extremites of the horizontal edges - if (!GetOverlap(op1->Pt.X, op1b->Pt.X, op2->Pt.X, op2b->Pt.X, Left, Right)) - return false; - - //DiscardLeftSide: when overlapping edges are joined, a spike will created - //which needs to be cleaned up. However, we don't want Op1 or Op2 caught up - //on the discard Side as either may still be needed for other joins ... - IntPoint Pt; - bool DiscardLeftSide; - if (op1->Pt.X >= Left && op1->Pt.X <= Right) - { - Pt = op1->Pt; DiscardLeftSide = (op1->Pt.X > op1b->Pt.X); - } - else if (op2->Pt.X >= Left&& op2->Pt.X <= Right) - { - Pt = op2->Pt; DiscardLeftSide = (op2->Pt.X > op2b->Pt.X); - } - else if (op1b->Pt.X >= Left && op1b->Pt.X <= Right) - { - Pt = op1b->Pt; DiscardLeftSide = op1b->Pt.X > op1->Pt.X; - } - else - { - Pt = op2b->Pt; DiscardLeftSide = (op2b->Pt.X > op2->Pt.X); - } - j->OutPt1 = op1; j->OutPt2 = op2; - return JoinHorz(op1, op1b, op2, op2b, Pt, DiscardLeftSide); - } else - { - //nb: For non-horizontal joins ... - // 1. Jr.OutPt1.Pt.Y == Jr.OutPt2.Pt.Y - // 2. Jr.OutPt1.Pt > Jr.OffPt.Y - - //make sure the polygons are correctly oriented ... - op1b = op1->Next; - while ((op1b->Pt == op1->Pt) && (op1b != op1)) op1b = op1b->Next; - bool Reverse1 = ((op1b->Pt.Y > op1->Pt.Y) || - !SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange)); - if (Reverse1) - { - op1b = op1->Prev; - while ((op1b->Pt == op1->Pt) && (op1b != op1)) op1b = op1b->Prev; - if ((op1b->Pt.Y > op1->Pt.Y) || - !SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange)) return false; - }; - op2b = op2->Next; - while ((op2b->Pt == op2->Pt) && (op2b != op2))op2b = op2b->Next; - bool Reverse2 = ((op2b->Pt.Y > op2->Pt.Y) || - !SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange)); - if (Reverse2) - { - op2b = op2->Prev; - while ((op2b->Pt == op2->Pt) && (op2b != op2)) op2b = op2b->Prev; - if ((op2b->Pt.Y > op2->Pt.Y) || - !SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange)) return false; - } - - if ((op1b == op1) || (op2b == op2) || (op1b == op2b) || - ((outRec1 == outRec2) && (Reverse1 == Reverse2))) return false; - - if (Reverse1) - { - op1b = DupOutPt(op1, false); - op2b = DupOutPt(op2, true); - op1->Prev = op2; - op2->Next = op1; - op1b->Next = op2b; - op2b->Prev = op1b; - j->OutPt1 = op1; - j->OutPt2 = op1b; - return true; - } else - { - op1b = DupOutPt(op1, true); - op2b = DupOutPt(op2, false); - op1->Next = op2; - op2->Prev = op1; - op1b->Prev = op2b; - op2b->Next = op1b; - j->OutPt1 = op1; - j->OutPt2 = op1b; - return true; - } - } -} -//---------------------------------------------------------------------- - -static OutRec* ParseFirstLeft(OutRec* FirstLeft) -{ - while (FirstLeft && !FirstLeft->Pts) - FirstLeft = FirstLeft->FirstLeft; - return FirstLeft; -} -//------------------------------------------------------------------------------ - -void Clipper::FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec) -{ - //tests if NewOutRec contains the polygon before reassigning FirstLeft - for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) - { - OutRec* outRec = m_PolyOuts[i]; - OutRec* firstLeft = ParseFirstLeft(outRec->FirstLeft); - if (outRec->Pts && firstLeft == OldOutRec) - { - if (Poly2ContainsPoly1(outRec->Pts, NewOutRec->Pts)) - outRec->FirstLeft = NewOutRec; - } - } -} -//---------------------------------------------------------------------- - -void Clipper::FixupFirstLefts2(OutRec* InnerOutRec, OutRec* OuterOutRec) -{ - //A polygon has split into two such that one is now the inner of the other. - //It's possible that these polygons now wrap around other polygons, so check - //every polygon that's also contained by OuterOutRec's FirstLeft container - //(including 0) to see if they've become inner to the new inner polygon ... - OutRec* orfl = OuterOutRec->FirstLeft; - for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) - { - OutRec* outRec = m_PolyOuts[i]; - - if (!outRec->Pts || outRec == OuterOutRec || outRec == InnerOutRec) - continue; - OutRec* firstLeft = ParseFirstLeft(outRec->FirstLeft); - if (firstLeft != orfl && firstLeft != InnerOutRec && firstLeft != OuterOutRec) - continue; - if (Poly2ContainsPoly1(outRec->Pts, InnerOutRec->Pts)) - outRec->FirstLeft = InnerOutRec; - else if (Poly2ContainsPoly1(outRec->Pts, OuterOutRec->Pts)) - outRec->FirstLeft = OuterOutRec; - else if (outRec->FirstLeft == InnerOutRec || outRec->FirstLeft == OuterOutRec) - outRec->FirstLeft = orfl; - } -} -//---------------------------------------------------------------------- -void Clipper::FixupFirstLefts3(OutRec* OldOutRec, OutRec* NewOutRec) -{ - //reassigns FirstLeft WITHOUT testing if NewOutRec contains the polygon - for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) - { - OutRec* outRec = m_PolyOuts[i]; - OutRec* firstLeft = ParseFirstLeft(outRec->FirstLeft); - if (outRec->Pts && firstLeft == OldOutRec) - outRec->FirstLeft = NewOutRec; - } -} -//---------------------------------------------------------------------- - -void Clipper::JoinCommonEdges() -{ - for (JoinList::size_type i = 0; i < m_Joins.size(); i++) - { - Join* join = m_Joins[i]; - - OutRec *outRec1 = GetOutRec(join->OutPt1->Idx); - OutRec *outRec2 = GetOutRec(join->OutPt2->Idx); - - if (!outRec1->Pts || !outRec2->Pts) continue; - if (outRec1->IsOpen || outRec2->IsOpen) continue; - - //get the polygon fragment with the correct hole state (FirstLeft) - //before calling JoinPoints() ... - OutRec *holeStateRec; - if (outRec1 == outRec2) holeStateRec = outRec1; - else if (OutRec1RightOfOutRec2(outRec1, outRec2)) holeStateRec = outRec2; - else if (OutRec1RightOfOutRec2(outRec2, outRec1)) holeStateRec = outRec1; - else holeStateRec = GetLowermostRec(outRec1, outRec2); - - if (!JoinPoints(join, outRec1, outRec2)) continue; - - if (outRec1 == outRec2) - { - //instead of joining two polygons, we've just created a new one by - //splitting one polygon into two. - outRec1->Pts = join->OutPt1; - outRec1->BottomPt = 0; - outRec2 = CreateOutRec(); - outRec2->Pts = join->OutPt2; - - //update all OutRec2.Pts Idx's ... - UpdateOutPtIdxs(*outRec2); - - if (Poly2ContainsPoly1(outRec2->Pts, outRec1->Pts)) - { - //outRec1 contains outRec2 ... - outRec2->IsHole = !outRec1->IsHole; - outRec2->FirstLeft = outRec1; - - if (m_UsingPolyTree) FixupFirstLefts2(outRec2, outRec1); - - if ((outRec2->IsHole ^ m_ReverseOutput) == (Area(*outRec2) > 0)) - ReversePolyPtLinks(outRec2->Pts); - - } else if (Poly2ContainsPoly1(outRec1->Pts, outRec2->Pts)) - { - //outRec2 contains outRec1 ... - outRec2->IsHole = outRec1->IsHole; - outRec1->IsHole = !outRec2->IsHole; - outRec2->FirstLeft = outRec1->FirstLeft; - outRec1->FirstLeft = outRec2; - - if (m_UsingPolyTree) FixupFirstLefts2(outRec1, outRec2); - - if ((outRec1->IsHole ^ m_ReverseOutput) == (Area(*outRec1) > 0)) - ReversePolyPtLinks(outRec1->Pts); - } - else - { - //the 2 polygons are completely separate ... - outRec2->IsHole = outRec1->IsHole; - outRec2->FirstLeft = outRec1->FirstLeft; - - //fixup FirstLeft pointers that may need reassigning to OutRec2 - if (m_UsingPolyTree) FixupFirstLefts1(outRec1, outRec2); - } - - } else - { - //joined 2 polygons together ... - - outRec2->Pts = 0; - outRec2->BottomPt = 0; - outRec2->Idx = outRec1->Idx; - - outRec1->IsHole = holeStateRec->IsHole; - if (holeStateRec == outRec2) - outRec1->FirstLeft = outRec2->FirstLeft; - outRec2->FirstLeft = outRec1; - - if (m_UsingPolyTree) FixupFirstLefts3(outRec2, outRec1); - } - } -} - -//------------------------------------------------------------------------------ -// ClipperOffset support functions ... -//------------------------------------------------------------------------------ - -DoublePoint GetUnitNormal(const IntPoint &pt1, const IntPoint &pt2) -{ - if(pt2.X == pt1.X && pt2.Y == pt1.Y) - return DoublePoint(0, 0); - - double Dx = (double)(pt2.X - pt1.X); - double dy = (double)(pt2.Y - pt1.Y); - double f = 1 *1.0/ std::sqrt( Dx*Dx + dy*dy ); - Dx *= f; - dy *= f; - return DoublePoint(dy, -Dx); -} - -//------------------------------------------------------------------------------ -// ClipperOffset class -//------------------------------------------------------------------------------ - -ClipperOffset::ClipperOffset(double miterLimit, double arcTolerance) -{ - this->MiterLimit = miterLimit; - this->ArcTolerance = arcTolerance; - m_lowest.X = -1; -} -//------------------------------------------------------------------------------ - -ClipperOffset::~ClipperOffset() -{ - Clear(); -} -//------------------------------------------------------------------------------ - -void ClipperOffset::Clear() -{ - for (int i = 0; i < m_polyNodes.ChildCount(); ++i) - delete m_polyNodes.Childs[i]; - m_polyNodes.Childs.clear(); - m_lowest.X = -1; -} -//------------------------------------------------------------------------------ - -void ClipperOffset::AddPath(const Path& path, JoinType joinType, EndType endType) -{ - int highI = (int)path.size() - 1; - if (highI < 0) return; - PolyNode* newNode = new PolyNode(); - newNode->m_jointype = joinType; - newNode->m_endtype = endType; - - //strip duplicate points from path and also get index to the lowest point ... - if (endType == etClosedLine || endType == etClosedPolygon) - while (highI > 0 && path[0] == path[highI]) highI--; - newNode->Contour.reserve(highI + 1); - newNode->Contour.push_back(path[0]); - int j = 0, k = 0; - for (int i = 1; i <= highI; i++) - if (newNode->Contour[j] != path[i]) - { - j++; - newNode->Contour.push_back(path[i]); - if (path[i].Y > newNode->Contour[k].Y || - (path[i].Y == newNode->Contour[k].Y && - path[i].X < newNode->Contour[k].X)) k = j; - } - if (endType == etClosedPolygon && j < 2) - { - delete newNode; - return; - } - m_polyNodes.AddChild(*newNode); - - //if this path's lowest pt is lower than all the others then update m_lowest - if (endType != etClosedPolygon) return; - if (m_lowest.X < 0) - m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k); - else - { - IntPoint ip = m_polyNodes.Childs[(int)m_lowest.X]->Contour[(int)m_lowest.Y]; - if (newNode->Contour[k].Y > ip.Y || - (newNode->Contour[k].Y == ip.Y && - newNode->Contour[k].X < ip.X)) - m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k); - } -} -//------------------------------------------------------------------------------ - -void ClipperOffset::AddPaths(const Paths& paths, JoinType joinType, EndType endType) -{ - for (Paths::size_type i = 0; i < paths.size(); ++i) - AddPath(paths[i], joinType, endType); -} -//------------------------------------------------------------------------------ - -void ClipperOffset::FixOrientations() -{ - //fixup orientations of all closed paths if the orientation of the - //closed path with the lowermost vertex is wrong ... - if (m_lowest.X >= 0 && - !Orientation(m_polyNodes.Childs[(int)m_lowest.X]->Contour)) - { - for (int i = 0; i < m_polyNodes.ChildCount(); ++i) - { - PolyNode& node = *m_polyNodes.Childs[i]; - if (node.m_endtype == etClosedPolygon || - (node.m_endtype == etClosedLine && Orientation(node.Contour))) - ReversePath(node.Contour); - } - } else - { - for (int i = 0; i < m_polyNodes.ChildCount(); ++i) - { - PolyNode& node = *m_polyNodes.Childs[i]; - if (node.m_endtype == etClosedLine && !Orientation(node.Contour)) - ReversePath(node.Contour); - } - } -} -//------------------------------------------------------------------------------ - -void ClipperOffset::Execute(Paths& solution, double delta) -{ - solution.clear(); - FixOrientations(); - DoOffset(delta); - - //now clean up 'corners' ... - Clipper clpr; - clpr.AddPaths(m_destPolys, ptSubject, true); - if (delta > 0) - { - clpr.Execute(ctUnion, solution, pftPositive, pftPositive); - } - else - { - IntRect r = clpr.GetBounds(); - Path outer(4); - outer[0] = IntPoint(r.left - 10, r.bottom + 10); - outer[1] = IntPoint(r.right + 10, r.bottom + 10); - outer[2] = IntPoint(r.right + 10, r.top - 10); - outer[3] = IntPoint(r.left - 10, r.top - 10); - - clpr.AddPath(outer, ptSubject, true); - clpr.ReverseSolution(true); - clpr.Execute(ctUnion, solution, pftNegative, pftNegative); - if (solution.size() > 0) solution.erase(solution.begin()); - } -} -//------------------------------------------------------------------------------ - -void ClipperOffset::Execute(PolyTree& solution, double delta) -{ - solution.Clear(); - FixOrientations(); - DoOffset(delta); - - //now clean up 'corners' ... - Clipper clpr; - clpr.AddPaths(m_destPolys, ptSubject, true); - if (delta > 0) - { - clpr.Execute(ctUnion, solution, pftPositive, pftPositive); - } - else - { - IntRect r = clpr.GetBounds(); - Path outer(4); - outer[0] = IntPoint(r.left - 10, r.bottom + 10); - outer[1] = IntPoint(r.right + 10, r.bottom + 10); - outer[2] = IntPoint(r.right + 10, r.top - 10); - outer[3] = IntPoint(r.left - 10, r.top - 10); - - clpr.AddPath(outer, ptSubject, true); - clpr.ReverseSolution(true); - clpr.Execute(ctUnion, solution, pftNegative, pftNegative); - //remove the outer PolyNode rectangle ... - if (solution.ChildCount() == 1 && solution.Childs[0]->ChildCount() > 0) - { - PolyNode* outerNode = solution.Childs[0]; - solution.Childs.reserve(outerNode->ChildCount()); - solution.Childs[0] = outerNode->Childs[0]; - solution.Childs[0]->Parent = outerNode->Parent; - for (int i = 1; i < outerNode->ChildCount(); ++i) - solution.AddChild(*outerNode->Childs[i]); - } - else - solution.Clear(); - } -} -//------------------------------------------------------------------------------ - -void ClipperOffset::DoOffset(double delta) -{ - m_destPolys.clear(); - m_delta = delta; - - //if Zero offset, just copy any CLOSED polygons to m_p and return ... - if (NEAR_ZERO(delta)) - { - m_destPolys.reserve(m_polyNodes.ChildCount()); - for (int i = 0; i < m_polyNodes.ChildCount(); i++) - { - PolyNode& node = *m_polyNodes.Childs[i]; - if (node.m_endtype == etClosedPolygon) - m_destPolys.push_back(node.Contour); - } - return; - } - - //see offset_triginometry3.svg in the documentation folder ... - if (MiterLimit > 2) m_miterLim = 2/(MiterLimit * MiterLimit); - else m_miterLim = 0.5; - - double y; - if (ArcTolerance <= 0.0) y = def_arc_tolerance; - else if (ArcTolerance > std::fabs(delta) * def_arc_tolerance) - y = std::fabs(delta) * def_arc_tolerance; - else y = ArcTolerance; - //see offset_triginometry2.svg in the documentation folder ... - double steps = pi / std::acos(1 - y / std::fabs(delta)); - if (steps > std::fabs(delta) * pi) - steps = std::fabs(delta) * pi; //ie excessive precision check - m_sin = std::sin(two_pi / steps); - m_cos = std::cos(two_pi / steps); - m_StepsPerRad = steps / two_pi; - if (delta < 0.0) m_sin = -m_sin; - - m_destPolys.reserve(m_polyNodes.ChildCount() * 2); - for (int i = 0; i < m_polyNodes.ChildCount(); i++) - { - PolyNode& node = *m_polyNodes.Childs[i]; - m_srcPoly = node.Contour; - - int len = (int)m_srcPoly.size(); - if (len == 0 || (delta <= 0 && (len < 3 || node.m_endtype != etClosedPolygon))) - continue; - - m_destPoly.clear(); - if (len == 1) - { - if (node.m_jointype == jtRound) - { - double X = 1.0, Y = 0.0; - for (cInt j = 1; j <= steps; j++) - { - m_destPoly.push_back(IntPoint( - Round(m_srcPoly[0].X + X * delta), - Round(m_srcPoly[0].Y + Y * delta))); - double X2 = X; - X = X * m_cos - m_sin * Y; - Y = X2 * m_sin + Y * m_cos; - } - } - else - { - double X = -1.0, Y = -1.0; - for (int j = 0; j < 4; ++j) - { - m_destPoly.push_back(IntPoint( - Round(m_srcPoly[0].X + X * delta), - Round(m_srcPoly[0].Y + Y * delta))); - if (X < 0) X = 1; - else if (Y < 0) Y = 1; - else X = -1; - } - } - m_destPolys.push_back(m_destPoly); - continue; - } - //build m_normals ... - m_normals.clear(); - m_normals.reserve(len); - for (int j = 0; j < len - 1; ++j) - m_normals.push_back(GetUnitNormal(m_srcPoly[j], m_srcPoly[j + 1])); - if (node.m_endtype == etClosedLine || node.m_endtype == etClosedPolygon) - m_normals.push_back(GetUnitNormal(m_srcPoly[len - 1], m_srcPoly[0])); - else - m_normals.push_back(DoublePoint(m_normals[len - 2])); - - if (node.m_endtype == etClosedPolygon) - { - int k = len - 1; - for (int j = 0; j < len; ++j) - OffsetPoint(j, k, node.m_jointype); - m_destPolys.push_back(m_destPoly); - } - else if (node.m_endtype == etClosedLine) - { - int k = len - 1; - for (int j = 0; j < len; ++j) - OffsetPoint(j, k, node.m_jointype); - m_destPolys.push_back(m_destPoly); - m_destPoly.clear(); - //re-build m_normals ... - DoublePoint n = m_normals[len -1]; - for (int j = len - 1; j > 0; j--) - m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y); - m_normals[0] = DoublePoint(-n.X, -n.Y); - k = 0; - for (int j = len - 1; j >= 0; j--) - OffsetPoint(j, k, node.m_jointype); - m_destPolys.push_back(m_destPoly); - } - else - { - int k = 0; - for (int j = 1; j < len - 1; ++j) - OffsetPoint(j, k, node.m_jointype); - - IntPoint pt1; - if (node.m_endtype == etOpenButt) - { - int j = len - 1; - pt1 = IntPoint((cInt)Round(m_srcPoly[j].X + m_normals[j].X * - delta), (cInt)Round(m_srcPoly[j].Y + m_normals[j].Y * delta)); - m_destPoly.push_back(pt1); - pt1 = IntPoint((cInt)Round(m_srcPoly[j].X - m_normals[j].X * - delta), (cInt)Round(m_srcPoly[j].Y - m_normals[j].Y * delta)); - m_destPoly.push_back(pt1); - } - else - { - int j = len - 1; - k = len - 2; - m_sinA = 0; - m_normals[j] = DoublePoint(-m_normals[j].X, -m_normals[j].Y); - if (node.m_endtype == etOpenSquare) - DoSquare(j, k); - else - DoRound(j, k); - } - - //re-build m_normals ... - for (int j = len - 1; j > 0; j--) - m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y); - m_normals[0] = DoublePoint(-m_normals[1].X, -m_normals[1].Y); - - k = len - 1; - for (int j = k - 1; j > 0; --j) OffsetPoint(j, k, node.m_jointype); - - if (node.m_endtype == etOpenButt) - { - pt1 = IntPoint((cInt)Round(m_srcPoly[0].X - m_normals[0].X * delta), - (cInt)Round(m_srcPoly[0].Y - m_normals[0].Y * delta)); - m_destPoly.push_back(pt1); - pt1 = IntPoint((cInt)Round(m_srcPoly[0].X + m_normals[0].X * delta), - (cInt)Round(m_srcPoly[0].Y + m_normals[0].Y * delta)); - m_destPoly.push_back(pt1); - } - else - { - k = 1; - m_sinA = 0; - if (node.m_endtype == etOpenSquare) - DoSquare(0, 1); - else - DoRound(0, 1); - } - m_destPolys.push_back(m_destPoly); - } - } -} -//------------------------------------------------------------------------------ - -void ClipperOffset::OffsetPoint(int j, int& k, JoinType jointype) -{ - //cross product ... - m_sinA = (m_normals[k].X * m_normals[j].Y - m_normals[j].X * m_normals[k].Y); - if (std::fabs(m_sinA * m_delta) < 1.0) - { - //dot product ... - double cosA = (m_normals[k].X * m_normals[j].X + m_normals[j].Y * m_normals[k].Y ); - if (cosA > 0) // angle => 0 degrees - { - m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta), - Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta))); - return; - } - //else angle => 180 degrees - } - else if (m_sinA > 1.0) m_sinA = 1.0; - else if (m_sinA < -1.0) m_sinA = -1.0; - - if (m_sinA * m_delta < 0) - { - m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta), - Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta))); - m_destPoly.push_back(m_srcPoly[j]); - m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[j].X * m_delta), - Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta))); - } - else - switch (jointype) - { - case jtMiter: - { - double r = 1 + (m_normals[j].X * m_normals[k].X + - m_normals[j].Y * m_normals[k].Y); - if (r >= m_miterLim) DoMiter(j, k, r); else DoSquare(j, k); - break; - } - case jtSquare: DoSquare(j, k); break; - case jtRound: DoRound(j, k); break; - } - k = j; -} -//------------------------------------------------------------------------------ - -void ClipperOffset::DoSquare(int j, int k) -{ - double dx = std::tan(std::atan2(m_sinA, - m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y) / 4); - m_destPoly.push_back(IntPoint( - Round(m_srcPoly[j].X + m_delta * (m_normals[k].X - m_normals[k].Y * dx)), - Round(m_srcPoly[j].Y + m_delta * (m_normals[k].Y + m_normals[k].X * dx)))); - m_destPoly.push_back(IntPoint( - Round(m_srcPoly[j].X + m_delta * (m_normals[j].X + m_normals[j].Y * dx)), - Round(m_srcPoly[j].Y + m_delta * (m_normals[j].Y - m_normals[j].X * dx)))); -} -//------------------------------------------------------------------------------ - -void ClipperOffset::DoMiter(int j, int k, double r) -{ - double q = m_delta / r; - m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + (m_normals[k].X + m_normals[j].X) * q), - Round(m_srcPoly[j].Y + (m_normals[k].Y + m_normals[j].Y) * q))); -} -//------------------------------------------------------------------------------ - -void ClipperOffset::DoRound(int j, int k) -{ - double a = std::atan2(m_sinA, - m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y); - int steps = std::max((int)Round(m_StepsPerRad * std::fabs(a)), 1); - - double X = m_normals[k].X, Y = m_normals[k].Y, X2; - for (int i = 0; i < steps; ++i) - { - m_destPoly.push_back(IntPoint( - Round(m_srcPoly[j].X + X * m_delta), - Round(m_srcPoly[j].Y + Y * m_delta))); - X2 = X; - X = X * m_cos - m_sin * Y; - Y = X2 * m_sin + Y * m_cos; - } - m_destPoly.push_back(IntPoint( - Round(m_srcPoly[j].X + m_normals[j].X * m_delta), - Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta))); -} - -//------------------------------------------------------------------------------ -// Miscellaneous public functions -//------------------------------------------------------------------------------ - -void Clipper::DoSimplePolygons() -{ - PolyOutList::size_type i = 0; - while (i < m_PolyOuts.size()) - { - OutRec* outrec = m_PolyOuts[i++]; - OutPt* op = outrec->Pts; - if (!op || outrec->IsOpen) continue; - do //for each Pt in Polygon until duplicate found do ... - { - OutPt* op2 = op->Next; - while (op2 != outrec->Pts) - { - if ((op->Pt == op2->Pt) && op2->Next != op && op2->Prev != op) - { - //split the polygon into two ... - OutPt* op3 = op->Prev; - OutPt* op4 = op2->Prev; - op->Prev = op4; - op4->Next = op; - op2->Prev = op3; - op3->Next = op2; - - outrec->Pts = op; - OutRec* outrec2 = CreateOutRec(); - outrec2->Pts = op2; - UpdateOutPtIdxs(*outrec2); - if (Poly2ContainsPoly1(outrec2->Pts, outrec->Pts)) - { - //OutRec2 is contained by OutRec1 ... - outrec2->IsHole = !outrec->IsHole; - outrec2->FirstLeft = outrec; - if (m_UsingPolyTree) FixupFirstLefts2(outrec2, outrec); - } - else - if (Poly2ContainsPoly1(outrec->Pts, outrec2->Pts)) - { - //OutRec1 is contained by OutRec2 ... - outrec2->IsHole = outrec->IsHole; - outrec->IsHole = !outrec2->IsHole; - outrec2->FirstLeft = outrec->FirstLeft; - outrec->FirstLeft = outrec2; - if (m_UsingPolyTree) FixupFirstLefts2(outrec, outrec2); - } - else - { - //the 2 polygons are separate ... - outrec2->IsHole = outrec->IsHole; - outrec2->FirstLeft = outrec->FirstLeft; - if (m_UsingPolyTree) FixupFirstLefts1(outrec, outrec2); - } - op2 = op; //ie get ready for the Next iteration - } - op2 = op2->Next; - } - op = op->Next; - } - while (op != outrec->Pts); - } -} -//------------------------------------------------------------------------------ - -void ReversePath(Path& p) -{ - std::reverse(p.begin(), p.end()); -} -//------------------------------------------------------------------------------ - -void ReversePaths(Paths& p) -{ - for (Paths::size_type i = 0; i < p.size(); ++i) - ReversePath(p[i]); -} -//------------------------------------------------------------------------------ - -void SimplifyPolygon(const Path &in_poly, Paths &out_polys, PolyFillType fillType) -{ - Clipper c; - c.StrictlySimple(true); - c.AddPath(in_poly, ptSubject, true); - c.Execute(ctUnion, out_polys, fillType, fillType); -} -//------------------------------------------------------------------------------ - -void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType) -{ - Clipper c; - c.StrictlySimple(true); - c.AddPaths(in_polys, ptSubject, true); - c.Execute(ctUnion, out_polys, fillType, fillType); -} -//------------------------------------------------------------------------------ - -void SimplifyPolygons(Paths &polys, PolyFillType fillType) -{ - SimplifyPolygons(polys, polys, fillType); -} -//------------------------------------------------------------------------------ - -inline double DistanceSqrd(const IntPoint& pt1, const IntPoint& pt2) -{ - double Dx = ((double)pt1.X - pt2.X); - double dy = ((double)pt1.Y - pt2.Y); - return (Dx*Dx + dy*dy); -} -//------------------------------------------------------------------------------ - -double DistanceFromLineSqrd( - const IntPoint& pt, const IntPoint& ln1, const IntPoint& ln2) -{ - //The equation of a line in general form (Ax + By + C = 0) - //given 2 points (x�,y�) & (x�,y�) is ... - //(y� - y�)x + (x� - x�)y + (y� - y�)x� - (x� - x�)y� = 0 - //A = (y� - y�); B = (x� - x�); C = (y� - y�)x� - (x� - x�)y� - //perpendicular distance of point (x�,y�) = (Ax� + By� + C)/Sqrt(A� + B�) - //see http://en.wikipedia.org/wiki/Perpendicular_distance - double A = double(ln1.Y - ln2.Y); - double B = double(ln2.X - ln1.X); - double C = A * ln1.X + B * ln1.Y; - C = A * pt.X + B * pt.Y - C; - return (C * C) / (A * A + B * B); -} -//--------------------------------------------------------------------------- - -bool SlopesNearCollinear(const IntPoint& pt1, - const IntPoint& pt2, const IntPoint& pt3, double distSqrd) -{ - //this function is more accurate when the point that's geometrically - //between the other 2 points is the one that's tested for distance. - //ie makes it more likely to pick up 'spikes' ... - if (Abs(pt1.X - pt2.X) > Abs(pt1.Y - pt2.Y)) - { - if ((pt1.X > pt2.X) == (pt1.X < pt3.X)) - return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd; - else if ((pt2.X > pt1.X) == (pt2.X < pt3.X)) - return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd; - else - return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd; - } - else - { - if ((pt1.Y > pt2.Y) == (pt1.Y < pt3.Y)) - return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd; - else if ((pt2.Y > pt1.Y) == (pt2.Y < pt3.Y)) - return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd; - else - return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd; - } -} -//------------------------------------------------------------------------------ - -bool PointsAreClose(IntPoint pt1, IntPoint pt2, double distSqrd) -{ - double Dx = (double)pt1.X - pt2.X; - double dy = (double)pt1.Y - pt2.Y; - return ((Dx * Dx) + (dy * dy) <= distSqrd); -} -//------------------------------------------------------------------------------ - -OutPt* ExcludeOp(OutPt* op) -{ - OutPt* result = op->Prev; - result->Next = op->Next; - op->Next->Prev = result; - result->Idx = 0; - return result; -} -//------------------------------------------------------------------------------ - -void CleanPolygon(const Path& in_poly, Path& out_poly, double distance) -{ - //distance = proximity in units/pixels below which vertices - //will be stripped. Default ~= sqrt(2). - - size_t size = in_poly.size(); - - if (size == 0) - { - out_poly.clear(); - return; - } - - OutPt* outPts = new OutPt[size]; - for (size_t i = 0; i < size; ++i) - { - outPts[i].Pt = in_poly[i]; - outPts[i].Next = &outPts[(i + 1) % size]; - outPts[i].Next->Prev = &outPts[i]; - outPts[i].Idx = 0; - } - - double distSqrd = distance * distance; - OutPt* op = &outPts[0]; - while (op->Idx == 0 && op->Next != op->Prev) - { - if (PointsAreClose(op->Pt, op->Prev->Pt, distSqrd)) - { - op = ExcludeOp(op); - size--; - } - else if (PointsAreClose(op->Prev->Pt, op->Next->Pt, distSqrd)) - { - ExcludeOp(op->Next); - op = ExcludeOp(op); - size -= 2; - } - else if (SlopesNearCollinear(op->Prev->Pt, op->Pt, op->Next->Pt, distSqrd)) - { - op = ExcludeOp(op); - size--; - } - else - { - op->Idx = 1; - op = op->Next; - } - } - - if (size < 3) size = 0; - out_poly.resize(size); - for (size_t i = 0; i < size; ++i) - { - out_poly[i] = op->Pt; - op = op->Next; - } - delete [] outPts; -} -//------------------------------------------------------------------------------ - -void CleanPolygon(Path& poly, double distance) -{ - CleanPolygon(poly, poly, distance); -} -//------------------------------------------------------------------------------ - -void CleanPolygons(const Paths& in_polys, Paths& out_polys, double distance) -{ - out_polys.resize(in_polys.size()); - for (Paths::size_type i = 0; i < in_polys.size(); ++i) - CleanPolygon(in_polys[i], out_polys[i], distance); -} -//------------------------------------------------------------------------------ - -void CleanPolygons(Paths& polys, double distance) -{ - CleanPolygons(polys, polys, distance); -} -//------------------------------------------------------------------------------ - -void Minkowski(const Path& poly, const Path& path, - Paths& solution, bool isSum, bool isClosed) -{ - int delta = (isClosed ? 1 : 0); - size_t polyCnt = poly.size(); - size_t pathCnt = path.size(); - Paths pp; - pp.reserve(pathCnt); - if (isSum) - for (size_t i = 0; i < pathCnt; ++i) - { - Path p; - p.reserve(polyCnt); - for (size_t j = 0; j < poly.size(); ++j) - p.push_back(IntPoint(path[i].X + poly[j].X, path[i].Y + poly[j].Y)); - pp.push_back(p); - } - else - for (size_t i = 0; i < pathCnt; ++i) - { - Path p; - p.reserve(polyCnt); - for (size_t j = 0; j < poly.size(); ++j) - p.push_back(IntPoint(path[i].X - poly[j].X, path[i].Y - poly[j].Y)); - pp.push_back(p); - } - - solution.clear(); - solution.reserve((pathCnt + delta) * (polyCnt + 1)); - for (size_t i = 0; i < pathCnt - 1 + delta; ++i) - for (size_t j = 0; j < polyCnt; ++j) - { - Path quad; - quad.reserve(4); - quad.push_back(pp[i % pathCnt][j % polyCnt]); - quad.push_back(pp[(i + 1) % pathCnt][j % polyCnt]); - quad.push_back(pp[(i + 1) % pathCnt][(j + 1) % polyCnt]); - quad.push_back(pp[i % pathCnt][(j + 1) % polyCnt]); - if (!Orientation(quad)) ReversePath(quad); - solution.push_back(quad); - } -} -//------------------------------------------------------------------------------ - -void MinkowskiSum(const Path& pattern, const Path& path, Paths& solution, bool pathIsClosed) -{ - Minkowski(pattern, path, solution, true, pathIsClosed); - Clipper c; - c.AddPaths(solution, ptSubject, true); - c.Execute(ctUnion, solution, pftNonZero, pftNonZero); -} -//------------------------------------------------------------------------------ - -void TranslatePath(const Path& input, Path& output, const IntPoint delta) -{ - //precondition: input != output - output.resize(input.size()); - for (size_t i = 0; i < input.size(); ++i) - output[i] = IntPoint(input[i].X + delta.X, input[i].Y + delta.Y); -} -//------------------------------------------------------------------------------ - -void MinkowskiSum(const Path& pattern, const Paths& paths, Paths& solution, bool pathIsClosed) -{ - Clipper c; - for (size_t i = 0; i < paths.size(); ++i) - { - Paths tmp; - Minkowski(pattern, paths[i], tmp, true, pathIsClosed); - c.AddPaths(tmp, ptSubject, true); - if (pathIsClosed) - { - Path tmp2; - TranslatePath(paths[i], tmp2, pattern[0]); - c.AddPath(tmp2, ptClip, true); - } - } - c.Execute(ctUnion, solution, pftNonZero, pftNonZero); -} -//------------------------------------------------------------------------------ - -void MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution) -{ - Minkowski(poly1, poly2, solution, false, true); - Clipper c; - c.AddPaths(solution, ptSubject, true); - c.Execute(ctUnion, solution, pftNonZero, pftNonZero); -} -//------------------------------------------------------------------------------ - -enum NodeType {ntAny, ntOpen, ntClosed}; - -void AddPolyNodeToPaths(const PolyNode& polynode, NodeType nodetype, Paths& paths) -{ - bool match = true; - if (nodetype == ntClosed) match = !polynode.IsOpen(); - else if (nodetype == ntOpen) return; - - if (!polynode.Contour.empty() && match) - paths.push_back(polynode.Contour); - for (int i = 0; i < polynode.ChildCount(); ++i) - AddPolyNodeToPaths(*polynode.Childs[i], nodetype, paths); -} -//------------------------------------------------------------------------------ - -void PolyTreeToPaths(const PolyTree& polytree, Paths& paths) -{ - paths.resize(0); - paths.reserve(polytree.Total()); - AddPolyNodeToPaths(polytree, ntAny, paths); -} -//------------------------------------------------------------------------------ - -void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths) -{ - paths.resize(0); - paths.reserve(polytree.Total()); - AddPolyNodeToPaths(polytree, ntClosed, paths); -} -//------------------------------------------------------------------------------ - -void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths) -{ - paths.resize(0); - paths.reserve(polytree.Total()); - //Open paths are top level only, so ... - for (int i = 0; i < polytree.ChildCount(); ++i) - if (polytree.Childs[i]->IsOpen()) - paths.push_back(polytree.Childs[i]->Contour); -} -//------------------------------------------------------------------------------ - -std::ostream& operator <<(std::ostream &s, const IntPoint &p) -{ - s << "(" << p.X << "," << p.Y << ")"; - return s; -} -//------------------------------------------------------------------------------ - -std::ostream& operator <<(std::ostream &s, const Path &p) -{ - if (p.empty()) return s; - Path::size_type last = p.size() -1; - for (Path::size_type i = 0; i < last; i++) - s << "(" << p[i].X << "," << p[i].Y << "), "; - s << "(" << p[last].X << "," << p[last].Y << ")\n"; - return s; -} -//------------------------------------------------------------------------------ - -std::ostream& operator <<(std::ostream &s, const Paths &p) -{ - for (Paths::size_type i = 0; i < p.size(); i++) - s << p[i]; - s << "\n"; - return s; -} -//------------------------------------------------------------------------------ - -} //ClipperLib namespace diff --git a/deploy/android_demo/app/src/main/cpp/ocr_clipper.hpp b/deploy/android_demo/app/src/main/cpp/ocr_clipper.hpp deleted file mode 100644 index 7129b535..00000000 --- a/deploy/android_demo/app/src/main/cpp/ocr_clipper.hpp +++ /dev/null @@ -1,547 +0,0 @@ -/******************************************************************************* -* * -* Author : Angus Johnson * -* Version : 6.4.2 * -* Date : 27 February 2017 * -* Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2017 * -* * -* License: * -* Use, modification & distribution is subject to Boost Software License Ver 1. * -* http://www.boost.org/LICENSE_1_0.txt * -* * -* Attributions: * -* The code in this library is an extension of Bala Vatti's clipping algorithm: * -* "A generic solution to polygon clipping" * -* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * -* http://portal.acm.org/citation.cfm?id=129906 * -* * -* Computer graphics and geometric modeling: implementation and algorithms * -* By Max K. Agoston * -* Springer; 1 edition (January 4, 2005) * -* http://books.google.com/books?q=vatti+clipping+agoston * -* * -* See also: * -* "Polygon Offsetting by Computing Winding Numbers" * -* Paper no. DETC2005-85513 pp. 565-575 * -* ASME 2005 International Design Engineering Technical Conferences * -* and Computers and Information in Engineering Conference (IDETC/CIE2005) * -* September 24-28, 2005 , Long Beach, California, USA * -* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * -* * -*******************************************************************************/ - -#ifndef clipper_hpp -#define clipper_hpp - -#define CLIPPER_VERSION "6.4.2" - -//use_int32: When enabled 32bit ints are used instead of 64bit ints. This -//improve performance but coordinate values are limited to the range +/- 46340 -//#define use_int32 - -//use_xyz: adds a Z member to IntPoint. Adds a minor cost to perfomance. -//#define use_xyz - -//use_lines: Enables line clipping. Adds a very minor cost to performance. -#define use_lines - -//use_deprecated: Enables temporary support for the obsolete functions -//#define use_deprecated - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace ClipperLib { - -enum ClipType { - ctIntersection, ctUnion, ctDifference, ctXor -}; -enum PolyType { - ptSubject, ptClip -}; -//By far the most widely used winding rules for polygon filling are -//EvenOdd & NonZero (GDI, GDI+, XLib, OpenGL, Cairo, AGG, Quartz, SVG, Gr32) -//Others rules include Positive, Negative and ABS_GTR_EQ_TWO (only in OpenGL) -//see http://glprogramming.com/red/chapter11.html -enum PolyFillType { - pftEvenOdd, pftNonZero, pftPositive, pftNegative -}; - -#ifdef use_int32 -typedef int cInt; -static cInt const loRange = 0x7FFF; -static cInt const hiRange = 0x7FFF; -#else -typedef signed long long cInt; -static cInt const loRange = 0x3FFFFFFF; -static cInt const hiRange = 0x3FFFFFFFFFFFFFFFLL; -typedef signed long long long64; //used by Int128 class -typedef unsigned long long ulong64; - -#endif - -struct IntPoint { - cInt X; - cInt Y; -#ifdef use_xyz - cInt Z; - IntPoint(cInt x = 0, cInt y = 0, cInt z = 0): X(x), Y(y), Z(z) {}; -#else - - IntPoint(cInt x = 0, cInt y = 0) : X(x), Y(y) {}; -#endif - - friend inline bool operator==(const IntPoint &a, const IntPoint &b) { - return a.X == b.X && a.Y == b.Y; - } - - friend inline bool operator!=(const IntPoint &a, const IntPoint &b) { - return a.X != b.X || a.Y != b.Y; - } -}; -//------------------------------------------------------------------------------ - -typedef std::vector Path; -typedef std::vector Paths; - -inline Path &operator<<(Path &poly, const IntPoint &p) { - poly.push_back(p); - return poly; -} - -inline Paths &operator<<(Paths &polys, const Path &p) { - polys.push_back(p); - return polys; -} - -std::ostream &operator<<(std::ostream &s, const IntPoint &p); - -std::ostream &operator<<(std::ostream &s, const Path &p); - -std::ostream &operator<<(std::ostream &s, const Paths &p); - -struct DoublePoint { - double X; - double Y; - - DoublePoint(double x = 0, double y = 0) : X(x), Y(y) {} - - DoublePoint(IntPoint ip) : X((double) ip.X), Y((double) ip.Y) {} -}; -//------------------------------------------------------------------------------ - -#ifdef use_xyz -typedef void (*ZFillCallback)(IntPoint& e1bot, IntPoint& e1top, IntPoint& e2bot, IntPoint& e2top, IntPoint& pt); -#endif - -enum InitOptions { - ioReverseSolution = 1, ioStrictlySimple = 2, ioPreserveCollinear = 4 -}; -enum JoinType { - jtSquare, jtRound, jtMiter -}; -enum EndType { - etClosedPolygon, etClosedLine, etOpenButt, etOpenSquare, etOpenRound -}; - -class PolyNode; - -typedef std::vector PolyNodes; - -class PolyNode { -public: - PolyNode(); - - virtual ~PolyNode() {}; - Path Contour; - PolyNodes Childs; - PolyNode *Parent; - - PolyNode *GetNext() const; - - bool IsHole() const; - - bool IsOpen() const; - - int ChildCount() const; - -private: - //PolyNode& operator =(PolyNode& other); - unsigned Index; //node index in Parent.Childs - bool m_IsOpen; - JoinType m_jointype; - EndType m_endtype; - - PolyNode *GetNextSiblingUp() const; - - void AddChild(PolyNode &child); - - friend class Clipper; //to access Index - friend class ClipperOffset; -}; - -class PolyTree : public PolyNode { -public: - ~PolyTree() { Clear(); }; - - PolyNode *GetFirst() const; - - void Clear(); - - int Total() const; - -private: - //PolyTree& operator =(PolyTree& other); - PolyNodes AllNodes; - - friend class Clipper; //to access AllNodes -}; - -bool Orientation(const Path &poly); - -double Area(const Path &poly); - -int PointInPolygon(const IntPoint &pt, const Path &path); - -void SimplifyPolygon(const Path &in_poly, Paths &out_polys, PolyFillType fillType = pftEvenOdd); - -void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType = pftEvenOdd); - -void SimplifyPolygons(Paths &polys, PolyFillType fillType = pftEvenOdd); - -void CleanPolygon(const Path &in_poly, Path &out_poly, double distance = 1.415); - -void CleanPolygon(Path &poly, double distance = 1.415); - -void CleanPolygons(const Paths &in_polys, Paths &out_polys, double distance = 1.415); - -void CleanPolygons(Paths &polys, double distance = 1.415); - -void MinkowskiSum(const Path &pattern, const Path &path, Paths &solution, bool pathIsClosed); - -void MinkowskiSum(const Path &pattern, const Paths &paths, Paths &solution, bool pathIsClosed); - -void MinkowskiDiff(const Path &poly1, const Path &poly2, Paths &solution); - -void PolyTreeToPaths(const PolyTree &polytree, Paths &paths); - -void ClosedPathsFromPolyTree(const PolyTree &polytree, Paths &paths); - -void OpenPathsFromPolyTree(PolyTree &polytree, Paths &paths); - -void ReversePath(Path &p); - -void ReversePaths(Paths &p); - -struct IntRect { - cInt left; - cInt top; - cInt right; - cInt bottom; -}; - -//enums that are used internally ... -enum EdgeSide { - esLeft = 1, esRight = 2 -}; - -//forward declarations (for stuff used internally) ... -struct TEdge; -struct IntersectNode; -struct LocalMinimum; -struct OutPt; -struct OutRec; -struct Join; - -typedef std::vector PolyOutList; -typedef std::vector EdgeList; -typedef std::vector JoinList; -typedef std::vector IntersectList; - -//------------------------------------------------------------------------------ - -//ClipperBase is the ancestor to the Clipper class. It should not be -//instantiated directly. This class simply abstracts the conversion of sets of -//polygon coordinates into edge objects that are stored in a LocalMinima list. -class ClipperBase { -public: - ClipperBase(); - - virtual ~ClipperBase(); - - virtual bool AddPath(const Path &pg, PolyType PolyTyp, bool Closed); - - bool AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed); - - virtual void Clear(); - - IntRect GetBounds(); - - bool PreserveCollinear() { return m_PreserveCollinear; }; - - void PreserveCollinear(bool value) { m_PreserveCollinear = value; }; -protected: - void DisposeLocalMinimaList(); - - TEdge *AddBoundsToLML(TEdge *e, bool IsClosed); - - virtual void Reset(); - - TEdge *ProcessBound(TEdge *E, bool IsClockwise); - - void InsertScanbeam(const cInt Y); - - bool PopScanbeam(cInt &Y); - - bool LocalMinimaPending(); - - bool PopLocalMinima(cInt Y, const LocalMinimum *&locMin); - - OutRec *CreateOutRec(); - - void DisposeAllOutRecs(); - - void DisposeOutRec(PolyOutList::size_type index); - - void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2); - - void DeleteFromAEL(TEdge *e); - - void UpdateEdgeIntoAEL(TEdge *&e); - - typedef std::vector MinimaList; - MinimaList::iterator m_CurrentLM; - MinimaList m_MinimaList; - - bool m_UseFullRange; - EdgeList m_edges; - bool m_PreserveCollinear; - bool m_HasOpenPaths; - PolyOutList m_PolyOuts; - TEdge *m_ActiveEdges; - - typedef std::priority_queue ScanbeamList; - ScanbeamList m_Scanbeam; -}; -//------------------------------------------------------------------------------ - -class Clipper : public virtual ClipperBase { -public: - Clipper(int initOptions = 0); - - bool Execute(ClipType clipType, - Paths &solution, - PolyFillType fillType = pftEvenOdd); - - bool Execute(ClipType clipType, - Paths &solution, - PolyFillType subjFillType, - PolyFillType clipFillType); - - bool Execute(ClipType clipType, - PolyTree &polytree, - PolyFillType fillType = pftEvenOdd); - - bool Execute(ClipType clipType, - PolyTree &polytree, - PolyFillType subjFillType, - PolyFillType clipFillType); - - bool ReverseSolution() { return m_ReverseOutput; }; - - void ReverseSolution(bool value) { m_ReverseOutput = value; }; - - bool StrictlySimple() { return m_StrictSimple; }; - - void StrictlySimple(bool value) { m_StrictSimple = value; }; - //set the callback function for z value filling on intersections (otherwise Z is 0) -#ifdef use_xyz - void ZFillFunction(ZFillCallback zFillFunc); -#endif -protected: - virtual bool ExecuteInternal(); - -private: - JoinList m_Joins; - JoinList m_GhostJoins; - IntersectList m_IntersectList; - ClipType m_ClipType; - typedef std::list MaximaList; - MaximaList m_Maxima; - TEdge *m_SortedEdges; - bool m_ExecuteLocked; - PolyFillType m_ClipFillType; - PolyFillType m_SubjFillType; - bool m_ReverseOutput; - bool m_UsingPolyTree; - bool m_StrictSimple; -#ifdef use_xyz - ZFillCallback m_ZFill; //custom callback -#endif - - void SetWindingCount(TEdge &edge); - - bool IsEvenOddFillType(const TEdge &edge) const; - - bool IsEvenOddAltFillType(const TEdge &edge) const; - - void InsertLocalMinimaIntoAEL(const cInt botY); - - void InsertEdgeIntoAEL(TEdge *edge, TEdge *startEdge); - - void AddEdgeToSEL(TEdge *edge); - - bool PopEdgeFromSEL(TEdge *&edge); - - void CopyAELToSEL(); - - void DeleteFromSEL(TEdge *e); - - void SwapPositionsInSEL(TEdge *edge1, TEdge *edge2); - - bool IsContributing(const TEdge &edge) const; - - bool IsTopHorz(const cInt XPos); - - void DoMaxima(TEdge *e); - - void ProcessHorizontals(); - - void ProcessHorizontal(TEdge *horzEdge); - - void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); - - OutPt *AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); - - OutRec *GetOutRec(int idx); - - void AppendPolygon(TEdge *e1, TEdge *e2); - - void IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &pt); - - OutPt *AddOutPt(TEdge *e, const IntPoint &pt); - - OutPt *GetLastOutPt(TEdge *e); - - bool ProcessIntersections(const cInt topY); - - void BuildIntersectList(const cInt topY); - - void ProcessIntersectList(); - - void ProcessEdgesAtTopOfScanbeam(const cInt topY); - - void BuildResult(Paths &polys); - - void BuildResult2(PolyTree &polytree); - - void SetHoleState(TEdge *e, OutRec *outrec); - - void DisposeIntersectNodes(); - - bool FixupIntersectionOrder(); - - void FixupOutPolygon(OutRec &outrec); - - void FixupOutPolyline(OutRec &outrec); - - bool IsHole(TEdge *e); - - bool FindOwnerFromSplitRecs(OutRec &outRec, OutRec *&currOrfl); - - void FixHoleLinkage(OutRec &outrec); - - void AddJoin(OutPt *op1, OutPt *op2, const IntPoint offPt); - - void ClearJoins(); - - void ClearGhostJoins(); - - void AddGhostJoin(OutPt *op, const IntPoint offPt); - - bool JoinPoints(Join *j, OutRec *outRec1, OutRec *outRec2); - - void JoinCommonEdges(); - - void DoSimplePolygons(); - - void FixupFirstLefts1(OutRec *OldOutRec, OutRec *NewOutRec); - - void FixupFirstLefts2(OutRec *InnerOutRec, OutRec *OuterOutRec); - - void FixupFirstLefts3(OutRec *OldOutRec, OutRec *NewOutRec); - -#ifdef use_xyz - void SetZ(IntPoint& pt, TEdge& e1, TEdge& e2); -#endif -}; -//------------------------------------------------------------------------------ - -class ClipperOffset { -public: - ClipperOffset(double miterLimit = 2.0, double roundPrecision = 0.25); - - ~ClipperOffset(); - - void AddPath(const Path &path, JoinType joinType, EndType endType); - - void AddPaths(const Paths &paths, JoinType joinType, EndType endType); - - void Execute(Paths &solution, double delta); - - void Execute(PolyTree &solution, double delta); - - void Clear(); - - double MiterLimit; - double ArcTolerance; -private: - Paths m_destPolys; - Path m_srcPoly; - Path m_destPoly; - std::vector m_normals; - double m_delta, m_sinA, m_sin, m_cos; - double m_miterLim, m_StepsPerRad; - IntPoint m_lowest; - PolyNode m_polyNodes; - - void FixOrientations(); - - void DoOffset(double delta); - - void OffsetPoint(int j, int &k, JoinType jointype); - - void DoSquare(int j, int k); - - void DoMiter(int j, int k, double r); - - void DoRound(int j, int k); -}; -//------------------------------------------------------------------------------ - -class clipperException : public std::exception { -public: - clipperException(const char *description) : m_descr(description) {} - - virtual ~clipperException() throw() {} - - virtual const char *what() const throw() { return m_descr.c_str(); } - -private: - std::string m_descr; -}; -//------------------------------------------------------------------------------ - -} //ClipperLib namespace - -#endif //clipper_hpp - - diff --git a/deploy/android_demo/app/src/main/cpp/ocr_crnn_process.cpp b/deploy/android_demo/app/src/main/cpp/ocr_crnn_process.cpp deleted file mode 100644 index 96f37b9b..00000000 --- a/deploy/android_demo/app/src/main/cpp/ocr_crnn_process.cpp +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "ocr_crnn_process.h" -#include -#include -#include -#include -#include -#include - -const std::string CHARACTER_TYPE = "ch"; -const int MAX_DICT_LENGTH = 6624; -const std::vector REC_IMAGE_SHAPE = {3, 32, 320}; - -static cv::Mat crnn_resize_norm_img(cv::Mat img, float wh_ratio) { - int imgC = REC_IMAGE_SHAPE[0]; - int imgW = REC_IMAGE_SHAPE[2]; - int imgH = REC_IMAGE_SHAPE[1]; - - if (CHARACTER_TYPE == "ch") - imgW = int(32 * wh_ratio); - - float ratio = float(img.cols) / float(img.rows); - int resize_w = 0; - if (ceilf(imgH * ratio) > imgW) - resize_w = imgW; - else - resize_w = int(ceilf(imgH * ratio)); - cv::Mat resize_img; - cv::resize(img, resize_img, cv::Size(resize_w, imgH), 0.f, 0.f, cv::INTER_CUBIC); - - resize_img.convertTo(resize_img, CV_32FC3, 1 / 255.f); - - for (int h = 0; h < resize_img.rows; h++) { - for (int w = 0; w < resize_img.cols; w++) { - resize_img.at(h, w)[0] = (resize_img.at(h, w)[0] - 0.5) * 2; - resize_img.at(h, w)[1] = (resize_img.at(h, w)[1] - 0.5) * 2; - resize_img.at(h, w)[2] = (resize_img.at(h, w)[2] - 0.5) * 2; - } - } - - cv::Mat dist; - cv::copyMakeBorder(resize_img, dist, 0, 0, 0, int(imgW - resize_w), cv::BORDER_CONSTANT, - {0, 0, 0}); - - return dist; - -} - -cv::Mat crnn_resize_img(const cv::Mat &img, float wh_ratio) { - int imgC = REC_IMAGE_SHAPE[0]; - int imgW = REC_IMAGE_SHAPE[2]; - int imgH = REC_IMAGE_SHAPE[1]; - - if (CHARACTER_TYPE == "ch") { - imgW = int(32 * wh_ratio); - } - - float ratio = float(img.cols) / float(img.rows); - int resize_w = 0; - if (ceilf(imgH * ratio) > imgW) - resize_w = imgW; - else - resize_w = int(ceilf(imgH * ratio)); - cv::Mat resize_img; - cv::resize(img, resize_img, cv::Size(resize_w, imgH)); - return resize_img; -} - - -cv::Mat get_rotate_crop_image(const cv::Mat &srcimage, const std::vector> &box) { - - std::vector> points = box; - - int x_collect[4] = {box[0][0], box[1][0], box[2][0], box[3][0]}; - int y_collect[4] = {box[0][1], box[1][1], box[2][1], box[3][1]}; - int left = int(*std::min_element(x_collect, x_collect + 4)); - int right = int(*std::max_element(x_collect, x_collect + 4)); - int top = int(*std::min_element(y_collect, y_collect + 4)); - int bottom = int(*std::max_element(y_collect, y_collect + 4)); - - cv::Mat img_crop; - srcimage(cv::Rect(left, top, right - left, bottom - top)).copyTo(img_crop); - - for (int i = 0; i < points.size(); i++) { - points[i][0] -= left; - points[i][1] -= top; - } - - int img_crop_width = int(sqrt(pow(points[0][0] - points[1][0], 2) + - pow(points[0][1] - points[1][1], 2))); - int img_crop_height = int(sqrt(pow(points[0][0] - points[3][0], 2) + - pow(points[0][1] - points[3][1], 2))); - - cv::Point2f pts_std[4]; - pts_std[0] = cv::Point2f(0., 0.); - pts_std[1] = cv::Point2f(img_crop_width, 0.); - pts_std[2] = cv::Point2f(img_crop_width, img_crop_height); - pts_std[3] = cv::Point2f(0.f, img_crop_height); - - cv::Point2f pointsf[4]; - pointsf[0] = cv::Point2f(points[0][0], points[0][1]); - pointsf[1] = cv::Point2f(points[1][0], points[1][1]); - pointsf[2] = cv::Point2f(points[2][0], points[2][1]); - pointsf[3] = cv::Point2f(points[3][0], points[3][1]); - - cv::Mat M = cv::getPerspectiveTransform(pointsf, pts_std); - - cv::Mat dst_img; - cv::warpPerspective(img_crop, dst_img, M, cv::Size(img_crop_width, img_crop_height), - cv::BORDER_REPLICATE); - - if (float(dst_img.rows) >= float(dst_img.cols) * 1.5) { - /* - cv::Mat srcCopy = cv::Mat(dst_img.rows, dst_img.cols, dst_img.depth()); - cv::transpose(dst_img, srcCopy); - cv::flip(srcCopy, srcCopy, 0); - return srcCopy; - */ - cv::transpose(dst_img, dst_img); - cv::flip(dst_img, dst_img, 0); - return dst_img; - } else { - return dst_img; - } - -} - diff --git a/deploy/android_demo/app/src/main/cpp/ocr_crnn_process.h b/deploy/android_demo/app/src/main/cpp/ocr_crnn_process.h deleted file mode 100644 index 99d0124a..00000000 --- a/deploy/android_demo/app/src/main/cpp/ocr_crnn_process.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// Created by fujiayi on 2020/7/3. -// -#pragma once - -#include -#include -#include "common.h" - -extern const std::vector REC_IMAGE_SHAPE; - -cv::Mat get_rotate_crop_image(const cv::Mat &srcimage, const std::vector> &box); - -cv::Mat crnn_resize_img(const cv::Mat &img, float wh_ratio); - -template -inline size_t argmax(ForwardIterator first, ForwardIterator last) { - return std::distance(first, std::max_element(first, last)); -} \ No newline at end of file diff --git a/deploy/android_demo/app/src/main/cpp/ocr_db_post_process.cpp b/deploy/android_demo/app/src/main/cpp/ocr_db_post_process.cpp deleted file mode 100644 index 7c3c60db..00000000 --- a/deploy/android_demo/app/src/main/cpp/ocr_db_post_process.cpp +++ /dev/null @@ -1,336 +0,0 @@ -// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include -#include -#include -#include "opencv2/core.hpp" -#include "opencv2/imgcodecs.hpp" -#include "opencv2/imgproc.hpp" -#include "ocr_clipper.hpp" - -static void getcontourarea(float **box, float unclip_ratio, float &distance) { - int pts_num = 4; - float area = 0.0f; - float dist = 0.0f; - for (int i = 0; i < pts_num; i++) { - area += box[i][0] * box[(i + 1) % pts_num][1] - box[i][1] * box[(i + 1) % pts_num][0]; - dist += sqrtf( - (box[i][0] - box[(i + 1) % pts_num][0]) * (box[i][0] - box[(i + 1) % pts_num][0]) + - (box[i][1] - box[(i + 1) % pts_num][1]) * (box[i][1] - box[(i + 1) % pts_num][1])); - } - area = fabs(float(area / 2.0)); - - distance = area * unclip_ratio / dist; - -} - -static cv::RotatedRect unclip(float **box) { - float unclip_ratio = 2.0; - float distance = 1.0; - - getcontourarea(box, unclip_ratio, distance); - - ClipperLib::ClipperOffset offset; - ClipperLib::Path p; - p << ClipperLib::IntPoint(int(box[0][0]), int(box[0][1])) - << ClipperLib::IntPoint(int(box[1][0]), int(box[1][1])) << - ClipperLib::IntPoint(int(box[2][0]), int(box[2][1])) - << ClipperLib::IntPoint(int(box[3][0]), int(box[3][1])); - offset.AddPath(p, ClipperLib::jtRound, ClipperLib::etClosedPolygon); - - ClipperLib::Paths soln; - offset.Execute(soln, distance); - std::vector points; - - for (int j = 0; j < soln.size(); j++) { - for (int i = 0; i < soln[soln.size() - 1].size(); i++) { - points.emplace_back(soln[j][i].X, soln[j][i].Y); - } - } - cv::RotatedRect res = cv::minAreaRect(points); - - return res; -} - -static float **Mat2Vec(cv::Mat mat) { - auto **array = new float *[mat.rows]; - for (int i = 0; i < mat.rows; ++i){ - array[i] = new float[mat.cols]; - } - for (int i = 0; i < mat.rows; ++i) { - for (int j = 0; j < mat.cols; ++j) { - array[i][j] = mat.at(i, j); - } - } - - return array; -} - -static void quickSort(float **s, int l, int r) { - if (l < r) { - int i = l, j = r; - float x = s[l][0]; - float *xp = s[l]; - while (i < j) { - while (i < j && s[j][0] >= x){ - j--; - } - if (i < j){ - std::swap(s[i++], s[j]); - } - while (i < j && s[i][0] < x){ - i++; - } - if (i < j){ - std::swap(s[j--], s[i]); - } - } - s[i] = xp; - quickSort(s, l, i - 1); - quickSort(s, i + 1, r); - } -} - -static void quickSort_vector(std::vector> &box, int l, int r, int axis) { - if (l < r) { - int i = l, j = r; - int x = box[l][axis]; - std::vector xp(box[l]); - while (i < j) { - while (i < j && box[j][axis] >= x){ - j--; - } - if (i < j){ - std::swap(box[i++], box[j]); - } - while (i < j && box[i][axis] < x){ - i++; - } - if (i < j){ - std::swap(box[j--], box[i]); - } - } - box[i] = xp; - quickSort_vector(box, l, i - 1, axis); - quickSort_vector(box, i + 1, r, axis); - } -} - -static std::vector> order_points_clockwise(std::vector> pts) { - std::vector> box = pts; - quickSort_vector(box, 0, int(box.size() - 1), 0); - std::vector> leftmost = {box[0], box[1]}; - std::vector> rightmost = {box[2], box[3]}; - - if (leftmost[0][1] > leftmost[1][1]){ - std::swap(leftmost[0], leftmost[1]); - } - - if (rightmost[0][1] > rightmost[1][1]){ - std::swap(rightmost[0], rightmost[1]); - } - - std::vector> rect = {leftmost[0], rightmost[0], rightmost[1], leftmost[1]}; - return rect; -} - -static float **get_mini_boxes(cv::RotatedRect box, float &ssid) { - ssid = box.size.width >= box.size.height ? box.size.height : box.size.width; - - cv::Mat points; - cv::boxPoints(box, points); - // sorted box points - auto array = Mat2Vec(points); - quickSort(array, 0, 3); - - float *idx1 = array[0], *idx2 = array[1], *idx3 = array[2], *idx4 = array[3]; - if (array[3][1] <= array[2][1]) { - idx2 = array[3]; - idx3 = array[2]; - } else { - idx2 = array[2]; - idx3 = array[3]; - } - if (array[1][1] <= array[0][1]) { - idx1 = array[1]; - idx4 = array[0]; - } else { - idx1 = array[0]; - idx4 = array[1]; - } - - array[0] = idx1; - array[1] = idx2; - array[2] = idx3; - array[3] = idx4; - - return array; -} - -template T clamp(T x, T min, T max) { - if (x > max){ - return max; - } - if (x < min){ - return min; - } - return x; -} - -static float clampf(float x, float min, float max) { - if (x > max) - return max; - if (x < min) - return min; - return x; -} - - -float box_score_fast(float **box_array, cv::Mat pred) { - auto array = box_array; - int width = pred.cols; - int height = pred.rows; - - float box_x[4] = {array[0][0], array[1][0], array[2][0], array[3][0]}; - float box_y[4] = {array[0][1], array[1][1], array[2][1], array[3][1]}; - - int xmin = clamp(int(std::floorf(*(std::min_element(box_x, box_x + 4)))), 0, width - 1); - int xmax = clamp(int(std::ceilf(*(std::max_element(box_x, box_x + 4)))), 0, width - 1); - int ymin = clamp(int(std::floorf(*(std::min_element(box_y, box_y + 4)))), 0, height - 1); - int ymax = clamp(int(std::ceilf(*(std::max_element(box_y, box_y + 4)))), 0, height - 1); - - cv::Mat mask; - mask = cv::Mat::zeros(ymax - ymin + 1, xmax - xmin + 1, CV_8UC1); - - cv::Point root_point[4]; - root_point[0] = cv::Point(int(array[0][0]) - xmin, int(array[0][1]) - ymin); - root_point[1] = cv::Point(int(array[1][0]) - xmin, int(array[1][1]) - ymin); - root_point[2] = cv::Point(int(array[2][0]) - xmin, int(array[2][1]) - ymin); - root_point[3] = cv::Point(int(array[3][0]) - xmin, int(array[3][1]) - ymin); - const cv::Point *ppt[1] = {root_point}; - int npt[] = {4}; - cv::fillPoly(mask, ppt, npt, 1, cv::Scalar(1)); - - cv::Mat croppedImg; - pred(cv::Rect(xmin, ymin, xmax - xmin + 1, ymax - ymin + 1)).copyTo(croppedImg); - - auto score = cv::mean(croppedImg, mask)[0]; - return score; -} - - -std::vector>> -boxes_from_bitmap(const cv::Mat& pred, const cv::Mat& bitmap) { - const int min_size = 3; - const int max_candidates = 1000; - const float box_thresh = 0.5; - - int width = bitmap.cols; - int height = bitmap.rows; - - std::vector> contours; - std::vector hierarchy; - - cv::findContours(bitmap, contours, hierarchy, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE); - - int num_contours = contours.size() >= max_candidates ? max_candidates : contours.size(); - - std::vector>> boxes; - - for (int _i = 0; _i < num_contours; _i++) { - float ssid; - cv::RotatedRect box = cv::minAreaRect(contours[_i]); - auto array = get_mini_boxes(box, ssid); - - auto box_for_unclip = array; - //end get_mini_box - - if (ssid < min_size) { - continue; - } - - float score; - score = box_score_fast(array, pred); - //end box_score_fast - if (score < box_thresh){ - continue; - } - - - // start for unclip - cv::RotatedRect points = unclip(box_for_unclip); - // end for unclip - - cv::RotatedRect clipbox = points; - auto cliparray = get_mini_boxes(clipbox, ssid); - - if (ssid < min_size + 2) continue; - - int dest_width = pred.cols; - int dest_height = pred.rows; - std::vector> intcliparray; - - for (int num_pt = 0; num_pt < 4; num_pt++) { - std::vector a{ - int(clampf(roundf(cliparray[num_pt][0] / float(width) * float(dest_width)), 0, - float(dest_width))), - int(clampf(roundf(cliparray[num_pt][1] / float(height) * float(dest_height)), 0, - float(dest_height)))}; - intcliparray.emplace_back(std::move(a)); - } - boxes.emplace_back(std::move(intcliparray)); - - }//end for - return boxes; -} - -int _max(int a, int b) { - return a >= b ? a : b; -} - -int _min(int a, int b) { - return a >= b ? b : a; -} - -std::vector>> -filter_tag_det_res(const std::vector>>& o_boxes, - float ratio_h, float ratio_w,const cv::Mat& srcimg) { - int oriimg_h = srcimg.rows; - int oriimg_w = srcimg.cols; - std::vector>> boxes{o_boxes}; - std::vector>> root_points; - for (int n = 0; n < boxes.size(); n++) { - boxes[n] = order_points_clockwise(boxes[n]); - for (int m = 0; m < boxes[0].size(); m++) { - boxes[n][m][0] /= ratio_w; - boxes[n][m][1] /= ratio_h; - - boxes[n][m][0] = int(_min(_max(boxes[n][m][0], 0), oriimg_w - 1)); - boxes[n][m][1] = int(_min(_max(boxes[n][m][1], 0), oriimg_h - 1)); - } - } - - for (int n = 0; n < boxes.size(); n++) { - int rect_width, rect_height; - rect_width = int(sqrt( - pow(boxes[n][0][0] - boxes[n][1][0], 2) + pow(boxes[n][0][1] - boxes[n][1][1], 2))); - rect_height = int(sqrt( - pow(boxes[n][0][0] - boxes[n][3][0], 2) + pow(boxes[n][0][1] - boxes[n][3][1], 2))); - if (rect_width <= 10 || rect_height <= 10) - continue; - root_points.push_back(boxes[n]); - } - return root_points; -} \ No newline at end of file diff --git a/deploy/android_demo/app/src/main/cpp/ocr_db_post_process.h b/deploy/android_demo/app/src/main/cpp/ocr_db_post_process.h deleted file mode 100644 index 86117512..00000000 --- a/deploy/android_demo/app/src/main/cpp/ocr_db_post_process.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// Created by fujiayi on 2020/7/2. -// -#pragma once -#include -#include - -std::vector>> -boxes_from_bitmap(const cv::Mat &pred, const cv::Mat &bitmap); - -std::vector>> -filter_tag_det_res( - const std::vector>> &o_boxes, - float ratio_h, - float ratio_w, - const cv::Mat &srcimg -); \ No newline at end of file diff --git a/deploy/android_demo/app/src/main/cpp/ocr_ppredictor.cpp b/deploy/android_demo/app/src/main/cpp/ocr_ppredictor.cpp deleted file mode 100644 index 3d014771..00000000 --- a/deploy/android_demo/app/src/main/cpp/ocr_ppredictor.cpp +++ /dev/null @@ -1,186 +0,0 @@ -// -// Created by fujiayi on 2020/7/1. -// - -#include "ocr_ppredictor.h" -#include "preprocess.h" -#include "common.h" -#include "ocr_db_post_process.h" -#include "ocr_crnn_process.h" - -namespace ppredictor { - -OCR_PPredictor::OCR_PPredictor(const OCR_Config &config) : _config(config) { - -} - -int -OCR_PPredictor::init(const std::string &det_model_content, const std::string &rec_model_content) { - _det_predictor = std::unique_ptr( - new PPredictor{_config.thread_num, NET_OCR, _config.mode}); - _det_predictor->init_nb(det_model_content); - - _rec_predictor = std::unique_ptr( - new PPredictor{_config.thread_num, NET_OCR_INTERNAL, _config.mode}); - _rec_predictor->init_nb(rec_model_content); - return RETURN_OK; -} - -int OCR_PPredictor::init_from_file(const std::string &det_model_path, const std::string &rec_model_path){ - _det_predictor = std::unique_ptr( - new PPredictor{_config.thread_num, NET_OCR, _config.mode}); - _det_predictor->init_from_file(det_model_path); - - _rec_predictor = std::unique_ptr( - new PPredictor{_config.thread_num, NET_OCR_INTERNAL, _config.mode}); - _rec_predictor->init_from_file(rec_model_path); - return RETURN_OK; -} -/** - * for debug use, show result of First Step - * @param filter_boxes - * @param boxes - * @param srcimg - */ -static void visual_img(const std::vector>> &filter_boxes, - const std::vector>> &boxes, - const cv::Mat &srcimg) { - // visualization - cv::Point rook_points[filter_boxes.size()][4]; - for (int n = 0; n < filter_boxes.size(); n++) { - for (int m = 0; m < filter_boxes[0].size(); m++) { - rook_points[n][m] = cv::Point(int(filter_boxes[n][m][0]), int(filter_boxes[n][m][1])); - } - } - - cv::Mat img_vis; - srcimg.copyTo(img_vis); - for (int n = 0; n < boxes.size(); n++) { - const cv::Point *ppt[1] = {rook_points[n]}; - int npt[] = {4}; - cv::polylines(img_vis, ppt, npt, 1, 1, CV_RGB(0, 255, 0), 2, 8, 0); - } - // 调试用,自行替换需要修改的路径 - cv::imwrite("/sdcard/1/vis.png", img_vis); -} - -std::vector -OCR_PPredictor::infer_ocr(const std::vector &dims, const float *input_data, int input_len, - int net_flag, cv::Mat &origin) { - - PredictorInput input = _det_predictor->get_first_input(); - input.set_dims(dims); - input.set_data(input_data, input_len); - std::vector results = _det_predictor->infer(); - PredictorOutput &res = results.at(0); - std::vector>> filtered_box - = calc_filtered_boxes(res.get_float_data(), res.get_size(), (int) dims[2], (int) dims[3], - origin); - LOGI("Filter_box size %ld", filtered_box.size()); - return infer_rec(filtered_box, origin); -} - -std::vector -OCR_PPredictor::infer_rec(const std::vector>> &boxes, - const cv::Mat &origin_img) { - std::vector mean = {0.5f, 0.5f, 0.5f}; - std::vector scale = {1 / 0.5f, 1 / 0.5f, 1 / 0.5f}; - std::vector dims = {1, 3, 0, 0}; - std::vector ocr_results; - - PredictorInput input = _rec_predictor->get_first_input(); - for (auto bp = boxes.crbegin(); bp != boxes.crend(); ++bp) { - const std::vector> &box = *bp; - cv::Mat crop_img = get_rotate_crop_image(origin_img, box); - float wh_ratio = float(crop_img.cols) / float(crop_img.rows); - cv::Mat input_image = crnn_resize_img(crop_img, wh_ratio); - input_image.convertTo(input_image, CV_32FC3, 1 / 255.0f); - const float *dimg = reinterpret_cast(input_image.data); - int input_size = input_image.rows * input_image.cols; - - dims[2] = input_image.rows; - dims[3] = input_image.cols; - input.set_dims(dims); - - neon_mean_scale(dimg, input.get_mutable_float_data(), input_size, mean, scale); - - std::vector results = _rec_predictor->infer(); - - OCRPredictResult res; - res.word_index = postprocess_rec_word_index(results.at(0)); - if (res.word_index.empty()) { - continue; - } - res.score = postprocess_rec_score(results.at(1)); - res.points = box; - ocr_results.emplace_back(std::move(res)); - } - LOGI("ocr_results finished %lu", ocr_results.size()); - return ocr_results; -} - -std::vector>> -OCR_PPredictor::calc_filtered_boxes(const float *pred, int pred_size, int output_height, - int output_width, const cv::Mat &origin) { - const double threshold = 0.3; - const double maxvalue = 1; - - cv::Mat pred_map = cv::Mat::zeros(output_height, output_width, CV_32F); - memcpy(pred_map.data, pred, pred_size * sizeof(float)); - cv::Mat cbuf_map; - pred_map.convertTo(cbuf_map, CV_8UC1); - - cv::Mat bit_map; - cv::threshold(cbuf_map, bit_map, threshold, maxvalue, cv::THRESH_BINARY); - - std::vector>> boxes = boxes_from_bitmap(pred_map, bit_map); - float ratio_h = output_height * 1.0f / origin.rows; - float ratio_w = output_width * 1.0f / origin.cols; - std::vector>> filter_boxes = filter_tag_det_res(boxes, ratio_h, - ratio_w, origin); - return filter_boxes; -} - -std::vector OCR_PPredictor::postprocess_rec_word_index(const PredictorOutput &res) { - const int *rec_idx = res.get_int_data(); - const std::vector> rec_idx_lod = res.get_lod(); - - std::vector pred_idx; - for (int n = int(rec_idx_lod[0][0]); n < int(rec_idx_lod[0][1] * 2); n += 2) { - pred_idx.emplace_back(rec_idx[n]); - } - return pred_idx; -} - -float OCR_PPredictor::postprocess_rec_score(const PredictorOutput &res) { - const float *predict_batch = res.get_float_data(); - const std::vector predict_shape = res.get_shape(); - const std::vector> predict_lod = res.get_lod(); - int blank = predict_shape[1]; - float score = 0.f; - int count = 0; - for (int n = predict_lod[0][0]; n < predict_lod[0][1] - 1; n++) { - int argmax_idx = argmax(predict_batch + n * predict_shape[1], - predict_batch + (n + 1) * predict_shape[1]); - float max_value = predict_batch[n * predict_shape[1] + argmax_idx]; - if (blank - 1 - argmax_idx > 1e-5) { - score += max_value; - count += 1; - } - - } - if (count == 0) { - LOGE("calc score count 0"); - } else { - score /= count; - } - LOGI("calc score: %f", score); - return score; - -} - - -NET_TYPE OCR_PPredictor::get_net_flag() const { - return NET_OCR; -} -} \ No newline at end of file diff --git a/deploy/android_demo/app/src/main/cpp/ocr_ppredictor.h b/deploy/android_demo/app/src/main/cpp/ocr_ppredictor.h deleted file mode 100644 index eb2bc3bc..00000000 --- a/deploy/android_demo/app/src/main/cpp/ocr_ppredictor.h +++ /dev/null @@ -1,112 +0,0 @@ -// -// Created by fujiayi on 2020/7/1. -// - -#pragma once - -#include -#include -#include -#include "ppredictor.h" - -namespace ppredictor { - -/** - * Config - */ -struct OCR_Config { - int thread_num = 4; // Thread num - paddle::lite_api::PowerMode mode = paddle::lite_api::LITE_POWER_HIGH; // PaddleLite Mode -}; - -/** - * PolyGone Result - */ -struct OCRPredictResult { - std::vector word_index; - std::vector> points; - float score; -}; - -/** - * OCR there are 2 models - * 1. First model(det),select polygones to show where are the texts - * 2. crop from the origin images, use these polygones to infer - */ -class OCR_PPredictor : public PPredictor_Interface { -public: - OCR_PPredictor(const OCR_Config &config); - - virtual ~OCR_PPredictor() { - - } - - /** - * 初始化二个模型的Predictor - * @param det_model_content - * @param rec_model_content - * @return - */ - int init(const std::string &det_model_content, const std::string &rec_model_content); - int init_from_file(const std::string &det_model_path, const std::string &rec_model_path); - /** - * Return OCR result - * @param dims - * @param input_data - * @param input_len - * @param net_flag - * @param origin - * @return - */ - virtual std::vector - infer_ocr(const std::vector &dims, const float *input_data, int input_len, - int net_flag, cv::Mat &origin); - - - virtual NET_TYPE get_net_flag() const; - - -private: - - /** - * calcul Polygone from the result image of first model - * @param pred - * @param output_height - * @param output_width - * @param origin - * @return - */ - std::vector>> - calc_filtered_boxes(const float *pred, int pred_size, int output_height, int output_width, - const cv::Mat &origin); - - /** - * infer for second model - * - * @param boxes - * @param origin - * @return - */ - std::vector - infer_rec(const std::vector>> &boxes, const cv::Mat &origin); - - /** - * Postprocess or sencod model to extract text - * @param res - * @return - */ - std::vector postprocess_rec_word_index(const PredictorOutput &res); - - /** - * calculate confidence of second model text result - * @param res - * @return - */ - float postprocess_rec_score(const PredictorOutput &res); - - std::unique_ptr _det_predictor; - std::unique_ptr _rec_predictor; - OCR_Config _config; - -}; -} diff --git a/deploy/android_demo/app/src/main/cpp/ppredictor.cpp b/deploy/android_demo/app/src/main/cpp/ppredictor.cpp deleted file mode 100644 index a9086bc6..00000000 --- a/deploy/android_demo/app/src/main/cpp/ppredictor.cpp +++ /dev/null @@ -1,70 +0,0 @@ -#include "ppredictor.h" -#include "common.h" - -namespace ppredictor { -PPredictor::PPredictor(int thread_num, int net_flag, paddle::lite_api::PowerMode mode) : - _thread_num(thread_num), _net_flag(net_flag), _mode(mode) { -} - -int PPredictor::init_nb(const std::string &model_content) { - paddle::lite_api::MobileConfig config; - config.set_model_from_buffer(model_content); - return _init(config); -} - -int PPredictor::init_from_file(const std::string &model_content){ - paddle::lite_api::MobileConfig config; - config.set_model_from_file(model_content); - return _init(config); -} - -template -int PPredictor::_init(ConfigT &config) { - config.set_threads(_thread_num); - config.set_power_mode(_mode); - _predictor = paddle::lite_api::CreatePaddlePredictor(config); - LOGI("paddle instance created"); - return RETURN_OK; -} - -PredictorInput PPredictor::get_input(int index) { - PredictorInput input{_predictor->GetInput(index), index, _net_flag}; - _is_input_get = true; - return input; -} - -std::vector PPredictor::get_inputs(int num) { - std::vector results; - for (int i = 0; i < num; i++) { - results.emplace_back(get_input(i)); - } - return results; -} - -PredictorInput PPredictor::get_first_input() { - return get_input(0); -} - -std::vector PPredictor::infer() { - LOGI("infer Run start %d", _net_flag); - std::vector results; - if (!_is_input_get) { - return results; - } - _predictor->Run(); - LOGI("infer Run end"); - - for (int i = 0; i < _predictor->GetOutputNames().size(); i++) { - std::unique_ptr output_tensor = _predictor->GetOutput(i); - LOGI("output tensor[%d] size %ld", i, product(output_tensor->shape())); - PredictorOutput result{std::move(output_tensor), i, _net_flag}; - results.emplace_back(std::move(result)); - } - return results; -} - -NET_TYPE PPredictor::get_net_flag() const { - return (NET_TYPE) _net_flag; -} - -} \ No newline at end of file diff --git a/deploy/android_demo/app/src/main/cpp/ppredictor.h b/deploy/android_demo/app/src/main/cpp/ppredictor.h deleted file mode 100644 index 1391109f..00000000 --- a/deploy/android_demo/app/src/main/cpp/ppredictor.h +++ /dev/null @@ -1,74 +0,0 @@ -#pragma once - -#include "paddle_api.h" -#include "predictor_input.h" -#include "predictor_output.h" - -namespace ppredictor { - -/** - * PaddleLite Preditor Common Interface - */ -class PPredictor_Interface { -public: - virtual ~PPredictor_Interface() { - - } - - - virtual NET_TYPE get_net_flag() const = 0; - -}; - -/** - * Common Predictor - */ -class PPredictor : public PPredictor_Interface { -public: - PPredictor(int thread_num, int net_flag = 0, - paddle::lite_api::PowerMode mode = paddle::lite_api::LITE_POWER_HIGH); - - virtual ~PPredictor() { - - } - - /** - * init paddlitelite opt model,nb format ,or use ini_paddle - * @param model_content - * @return 0 - */ - virtual int init_nb(const std::string &model_content); - - virtual int init_from_file(const std::string &model_content); - - std::vector infer(); - - std::shared_ptr get_predictor() { - return _predictor; - } - - virtual std::vector get_inputs(int num); - - virtual PredictorInput get_input(int index); - - virtual PredictorInput get_first_input(); - - virtual NET_TYPE get_net_flag() const; - -protected: - template - int _init(ConfigT &config); - -private: - int _thread_num; - paddle::lite_api::PowerMode _mode; - std::shared_ptr _predictor; - bool _is_input_get = false; - int _net_flag; - -}; - - -} - - diff --git a/deploy/android_demo/app/src/main/cpp/predictor_input.cpp b/deploy/android_demo/app/src/main/cpp/predictor_input.cpp deleted file mode 100644 index 9a1dd223..00000000 --- a/deploy/android_demo/app/src/main/cpp/predictor_input.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include "predictor_input.h" - -namespace ppredictor { - - -void PredictorInput::set_dims(std::vector dims) { - // yolov3 - if (_net_flag == 101 && _index == 1) { - _tensor->Resize({1, 2}); - _tensor->mutable_data()[0] = (int) dims.at(2); - _tensor->mutable_data()[1] = (int) dims.at(3); - } else { - _tensor->Resize(dims); - } - _is_dims_set = true; -} - -float *PredictorInput::get_mutable_float_data() { - if (!_is_dims_set) { - LOGE("PredictorInput::set_dims is not called"); - } - return _tensor->mutable_data(); -} - -void PredictorInput::set_data(const float *input_data, int input_float_len) { - float *input_raw_data = get_mutable_float_data(); - memcpy(input_raw_data, input_data, input_float_len * sizeof(float)); -} -} \ No newline at end of file diff --git a/deploy/android_demo/app/src/main/cpp/predictor_input.h b/deploy/android_demo/app/src/main/cpp/predictor_input.h deleted file mode 100644 index b48db189..00000000 --- a/deploy/android_demo/app/src/main/cpp/predictor_input.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include -#include -#include "common.h" - -namespace ppredictor { -class PredictorInput { -public: - PredictorInput(std::unique_ptr &&tensor, int index, int net_flag) : - _tensor(std::move(tensor)), _index(index),_net_flag(net_flag) { - - } - - - void set_dims(std::vector dims); - - float *get_mutable_float_data(); - - void set_data(const float *input_data, int input_float_len); - -private: - std::unique_ptr _tensor; - bool _is_dims_set = false; - int _index; - int _net_flag; -}; -} diff --git a/deploy/android_demo/app/src/main/cpp/predictor_output.cpp b/deploy/android_demo/app/src/main/cpp/predictor_output.cpp deleted file mode 100644 index 08155633..00000000 --- a/deploy/android_demo/app/src/main/cpp/predictor_output.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "predictor_output.h" -namespace ppredictor { -const float* PredictorOutput::get_float_data() const{ - return _tensor->data(); -} - -const int* PredictorOutput::get_int_data() const{ - return _tensor->data(); -} - -const std::vector> PredictorOutput::get_lod() const{ - return _tensor->lod(); -} - -int64_t PredictorOutput::get_size() const{ - if (_net_flag == NET_OCR) { - return _tensor->shape().at(2) * _tensor->shape().at(3); - } else { - return product(_tensor->shape()); - } -} - -const std::vector PredictorOutput::get_shape() const{ - return _tensor->shape(); - -} -} \ No newline at end of file diff --git a/deploy/android_demo/app/src/main/cpp/predictor_output.h b/deploy/android_demo/app/src/main/cpp/predictor_output.h deleted file mode 100644 index ec7086c6..00000000 --- a/deploy/android_demo/app/src/main/cpp/predictor_output.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include -#include -#include "common.h" - -namespace ppredictor { -class PredictorOutput { -public: - PredictorOutput(){ - - } - PredictorOutput(std::unique_ptr &&tensor, int index, int net_flag) : - _tensor(std::move(tensor)), _index(index), _net_flag(net_flag) { - - } - - const float* get_float_data() const; - const int* get_int_data() const; - int64_t get_size() const; - const std::vector> get_lod() const; - const std::vector get_shape() const; - - std::vector data; // return float, or use data_int - std::vector data_int; // several layers return int ,or use data - std::vector shape; // PaddleLite output shape - std::vector> lod; // PaddleLite output lod - -private: - std::unique_ptr _tensor; - int _index; - int _net_flag; -}; -} - diff --git a/deploy/android_demo/app/src/main/cpp/preprocess.cpp b/deploy/android_demo/app/src/main/cpp/preprocess.cpp deleted file mode 100644 index 5543cdd1..00000000 --- a/deploy/android_demo/app/src/main/cpp/preprocess.cpp +++ /dev/null @@ -1,84 +0,0 @@ -#include "preprocess.h" -#include - -cv::Mat bitmap_to_cv_mat(JNIEnv *env, jobject bitmap) { - AndroidBitmapInfo info; - int result = AndroidBitmap_getInfo(env, bitmap, &info); - if (result != ANDROID_BITMAP_RESULT_SUCCESS) { - LOGE("AndroidBitmap_getInfo failed, result: %d", result); - return cv::Mat{}; - } - if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) { - LOGE("Bitmap format is not RGBA_8888 !"); - return cv::Mat{}; - } - unsigned char *srcData = NULL; - AndroidBitmap_lockPixels(env, bitmap, (void **) &srcData); - cv::Mat mat = cv::Mat::zeros(info.height, info.width, CV_8UC4); - memcpy(mat.data, srcData, info.height * info.width * 4); - AndroidBitmap_unlockPixels(env, bitmap); - cv::cvtColor(mat, mat, cv::COLOR_RGBA2BGR); - /** - if (!cv::imwrite("/sdcard/1/copy.jpg", mat)){ - LOGE("Write image failed " ); - } - */ - - return mat; -} - -cv::Mat resize_img(const cv::Mat& img, int height, int width){ - if (img.rows == height && img.cols == width){ - return img; - } - cv::Mat new_img; - cv::resize(img, new_img, cv::Size(height, width)); - return new_img; -} - -// fill tensor with mean and scale and trans layout: nhwc -> nchw, neon speed up -void neon_mean_scale(const float* din, - float* dout, - int size, - const std::vector& mean, - const std::vector& scale) { - if (mean.size() != 3 || scale.size() != 3) { - LOGE("[ERROR] mean or scale size must equal to 3"); - return; - } - - float32x4_t vmean0 = vdupq_n_f32(mean[0]); - float32x4_t vmean1 = vdupq_n_f32(mean[1]); - float32x4_t vmean2 = vdupq_n_f32(mean[2]); - float32x4_t vscale0 = vdupq_n_f32(scale[0]); - float32x4_t vscale1 = vdupq_n_f32(scale[1]); - float32x4_t vscale2 = vdupq_n_f32(scale[2]); - - float* dout_c0 = dout; - float* dout_c1 = dout + size; - float* dout_c2 = dout + size * 2; - - int i = 0; - for (; i < size - 3; i += 4) { - float32x4x3_t vin3 = vld3q_f32(din); - float32x4_t vsub0 = vsubq_f32(vin3.val[0], vmean0); - float32x4_t vsub1 = vsubq_f32(vin3.val[1], vmean1); - float32x4_t vsub2 = vsubq_f32(vin3.val[2], vmean2); - float32x4_t vs0 = vmulq_f32(vsub0, vscale0); - float32x4_t vs1 = vmulq_f32(vsub1, vscale1); - float32x4_t vs2 = vmulq_f32(vsub2, vscale2); - vst1q_f32(dout_c0, vs0); - vst1q_f32(dout_c1, vs1); - vst1q_f32(dout_c2, vs2); - - din += 12; - dout_c0 += 4; - dout_c1 += 4; - dout_c2 += 4; - } - for (; i < size; i++) { - *(dout_c0++) = (*(din++) - mean[0]) * scale[0]; - *(dout_c1++) = (*(din++) - mean[1]) * scale[1]; - *(dout_c2++) = (*(din++) - mean[2]) * scale[2]; - } -} \ No newline at end of file diff --git a/deploy/android_demo/app/src/main/cpp/preprocess.h b/deploy/android_demo/app/src/main/cpp/preprocess.h deleted file mode 100644 index c256c440..00000000 --- a/deploy/android_demo/app/src/main/cpp/preprocess.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include -#include -#include "common.h" -cv::Mat bitmap_to_cv_mat(JNIEnv *env, jobject bitmap); - -cv::Mat resize_img(const cv::Mat& img, int height, int width); - -void neon_mean_scale(const float* din, - float* dout, - int size, - const std::vector& mean, - const std::vector& scale); diff --git a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/AppCompatPreferenceActivity.java b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/AppCompatPreferenceActivity.java deleted file mode 100644 index 49af0afe..00000000 --- a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/AppCompatPreferenceActivity.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.baidu.paddle.lite.demo.ocr; - -import android.content.res.Configuration; -import android.os.Bundle; -import android.preference.PreferenceActivity; -import android.view.MenuInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.annotation.LayoutRes; -import androidx.annotation.Nullable; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AppCompatDelegate; -import androidx.appcompat.widget.Toolbar; - -/** - * A {@link PreferenceActivity} which implements and proxies the necessary calls - * to be used with AppCompat. - *

- * This technique can be used with an {@link android.app.Activity} class, not just - * {@link PreferenceActivity}. - */ -public abstract class AppCompatPreferenceActivity extends PreferenceActivity { - private AppCompatDelegate mDelegate; - - @Override - protected void onCreate(Bundle savedInstanceState) { - getDelegate().installViewFactory(); - getDelegate().onCreate(savedInstanceState); - super.onCreate(savedInstanceState); - } - - @Override - protected void onPostCreate(Bundle savedInstanceState) { - super.onPostCreate(savedInstanceState); - getDelegate().onPostCreate(savedInstanceState); - } - - public ActionBar getSupportActionBar() { - return getDelegate().getSupportActionBar(); - } - - public void setSupportActionBar(@Nullable Toolbar toolbar) { - getDelegate().setSupportActionBar(toolbar); - } - - @Override - public MenuInflater getMenuInflater() { - return getDelegate().getMenuInflater(); - } - - @Override - public void setContentView(@LayoutRes int layoutResID) { - getDelegate().setContentView(layoutResID); - } - - @Override - public void setContentView(View view) { - getDelegate().setContentView(view); - } - - @Override - public void setContentView(View view, ViewGroup.LayoutParams params) { - getDelegate().setContentView(view, params); - } - - @Override - public void addContentView(View view, ViewGroup.LayoutParams params) { - getDelegate().addContentView(view, params); - } - - @Override - protected void onPostResume() { - super.onPostResume(); - getDelegate().onPostResume(); - } - - @Override - protected void onTitleChanged(CharSequence title, int color) { - super.onTitleChanged(title, color); - getDelegate().setTitle(title); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - getDelegate().onConfigurationChanged(newConfig); - } - - @Override - protected void onStop() { - super.onStop(); - getDelegate().onStop(); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - getDelegate().onDestroy(); - } - - public void invalidateOptionsMenu() { - getDelegate().invalidateOptionsMenu(); - } - - private AppCompatDelegate getDelegate() { - if (mDelegate == null) { - mDelegate = AppCompatDelegate.create(this, null); - } - return mDelegate; - } -} diff --git a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/MainActivity.java b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/MainActivity.java deleted file mode 100644 index afb261dc..00000000 --- a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/MainActivity.java +++ /dev/null @@ -1,473 +0,0 @@ -package com.baidu.paddle.lite.demo.ocr; - -import android.Manifest; -import android.app.ProgressDialog; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.pm.PackageManager; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.media.ExifInterface; -import android.net.Uri; -import android.os.Bundle; -import android.os.Environment; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Message; -import android.preference.PreferenceManager; -import android.provider.MediaStore; -import android.text.method.ScrollingMovementMethod; -import android.util.Log; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.widget.ImageView; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.appcompat.app.AppCompatActivity; -import androidx.core.app.ActivityCompat; -import androidx.core.content.ContextCompat; -import androidx.core.content.FileProvider; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.text.SimpleDateFormat; -import java.util.Date; - -public class MainActivity extends AppCompatActivity { - private static final String TAG = MainActivity.class.getSimpleName(); - public static final int OPEN_GALLERY_REQUEST_CODE = 0; - public static final int TAKE_PHOTO_REQUEST_CODE = 1; - - public static final int REQUEST_LOAD_MODEL = 0; - public static final int REQUEST_RUN_MODEL = 1; - public static final int RESPONSE_LOAD_MODEL_SUCCESSED = 0; - public static final int RESPONSE_LOAD_MODEL_FAILED = 1; - public static final int RESPONSE_RUN_MODEL_SUCCESSED = 2; - public static final int RESPONSE_RUN_MODEL_FAILED = 3; - - protected ProgressDialog pbLoadModel = null; - protected ProgressDialog pbRunModel = null; - - protected Handler receiver = null; // Receive messages from worker thread - protected Handler sender = null; // Send command to worker thread - protected HandlerThread worker = null; // Worker thread to load&run model - - // UI components of object detection - protected TextView tvInputSetting; - protected ImageView ivInputImage; - protected TextView tvOutputResult; - protected TextView tvInferenceTime; - - // Model settings of object detection - protected String modelPath = ""; - protected String labelPath = ""; - protected String imagePath = ""; - protected int cpuThreadNum = 1; - protected String cpuPowerMode = ""; - protected String inputColorFormat = ""; - protected long[] inputShape = new long[]{}; - protected float[] inputMean = new float[]{}; - protected float[] inputStd = new float[]{}; - protected float scoreThreshold = 0.1f; - private String currentPhotoPath; - - protected Predictor predictor = new Predictor(); - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - - // Clear all setting items to avoid app crashing due to the incorrect settings - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.clear(); - editor.commit(); - - // Prepare the worker thread for mode loading and inference - receiver = new Handler() { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case RESPONSE_LOAD_MODEL_SUCCESSED: - pbLoadModel.dismiss(); - onLoadModelSuccessed(); - break; - case RESPONSE_LOAD_MODEL_FAILED: - pbLoadModel.dismiss(); - Toast.makeText(MainActivity.this, "Load model failed!", Toast.LENGTH_SHORT).show(); - onLoadModelFailed(); - break; - case RESPONSE_RUN_MODEL_SUCCESSED: - pbRunModel.dismiss(); - onRunModelSuccessed(); - break; - case RESPONSE_RUN_MODEL_FAILED: - pbRunModel.dismiss(); - Toast.makeText(MainActivity.this, "Run model failed!", Toast.LENGTH_SHORT).show(); - onRunModelFailed(); - break; - default: - break; - } - } - }; - - worker = new HandlerThread("Predictor Worker"); - worker.start(); - sender = new Handler(worker.getLooper()) { - public void handleMessage(Message msg) { - switch (msg.what) { - case REQUEST_LOAD_MODEL: - // Load model and reload test image - if (onLoadModel()) { - receiver.sendEmptyMessage(RESPONSE_LOAD_MODEL_SUCCESSED); - } else { - receiver.sendEmptyMessage(RESPONSE_LOAD_MODEL_FAILED); - } - break; - case REQUEST_RUN_MODEL: - // Run model if model is loaded - if (onRunModel()) { - receiver.sendEmptyMessage(RESPONSE_RUN_MODEL_SUCCESSED); - } else { - receiver.sendEmptyMessage(RESPONSE_RUN_MODEL_FAILED); - } - break; - default: - break; - } - } - }; - - // Setup the UI components - tvInputSetting = findViewById(R.id.tv_input_setting); - ivInputImage = findViewById(R.id.iv_input_image); - tvInferenceTime = findViewById(R.id.tv_inference_time); - tvOutputResult = findViewById(R.id.tv_output_result); - tvInputSetting.setMovementMethod(ScrollingMovementMethod.getInstance()); - tvOutputResult.setMovementMethod(ScrollingMovementMethod.getInstance()); - } - - @Override - protected void onResume() { - super.onResume(); - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); - boolean settingsChanged = false; - String model_path = sharedPreferences.getString(getString(R.string.MODEL_PATH_KEY), - getString(R.string.MODEL_PATH_DEFAULT)); - String label_path = sharedPreferences.getString(getString(R.string.LABEL_PATH_KEY), - getString(R.string.LABEL_PATH_DEFAULT)); - String image_path = sharedPreferences.getString(getString(R.string.IMAGE_PATH_KEY), - getString(R.string.IMAGE_PATH_DEFAULT)); - settingsChanged |= !model_path.equalsIgnoreCase(modelPath); - settingsChanged |= !label_path.equalsIgnoreCase(labelPath); - settingsChanged |= !image_path.equalsIgnoreCase(imagePath); - int cpu_thread_num = Integer.parseInt(sharedPreferences.getString(getString(R.string.CPU_THREAD_NUM_KEY), - getString(R.string.CPU_THREAD_NUM_DEFAULT))); - settingsChanged |= cpu_thread_num != cpuThreadNum; - String cpu_power_mode = - sharedPreferences.getString(getString(R.string.CPU_POWER_MODE_KEY), - getString(R.string.CPU_POWER_MODE_DEFAULT)); - settingsChanged |= !cpu_power_mode.equalsIgnoreCase(cpuPowerMode); - String input_color_format = - sharedPreferences.getString(getString(R.string.INPUT_COLOR_FORMAT_KEY), - getString(R.string.INPUT_COLOR_FORMAT_DEFAULT)); - settingsChanged |= !input_color_format.equalsIgnoreCase(inputColorFormat); - long[] input_shape = - Utils.parseLongsFromString(sharedPreferences.getString(getString(R.string.INPUT_SHAPE_KEY), - getString(R.string.INPUT_SHAPE_DEFAULT)), ","); - float[] input_mean = - Utils.parseFloatsFromString(sharedPreferences.getString(getString(R.string.INPUT_MEAN_KEY), - getString(R.string.INPUT_MEAN_DEFAULT)), ","); - float[] input_std = - Utils.parseFloatsFromString(sharedPreferences.getString(getString(R.string.INPUT_STD_KEY) - , getString(R.string.INPUT_STD_DEFAULT)), ","); - settingsChanged |= input_shape.length != inputShape.length; - settingsChanged |= input_mean.length != inputMean.length; - settingsChanged |= input_std.length != inputStd.length; - if (!settingsChanged) { - for (int i = 0; i < input_shape.length; i++) { - settingsChanged |= input_shape[i] != inputShape[i]; - } - for (int i = 0; i < input_mean.length; i++) { - settingsChanged |= input_mean[i] != inputMean[i]; - } - for (int i = 0; i < input_std.length; i++) { - settingsChanged |= input_std[i] != inputStd[i]; - } - } - float score_threshold = - Float.parseFloat(sharedPreferences.getString(getString(R.string.SCORE_THRESHOLD_KEY), - getString(R.string.SCORE_THRESHOLD_DEFAULT))); - settingsChanged |= scoreThreshold != score_threshold; - if (settingsChanged) { - modelPath = model_path; - labelPath = label_path; - imagePath = image_path; - cpuThreadNum = cpu_thread_num; - cpuPowerMode = cpu_power_mode; - inputColorFormat = input_color_format; - inputShape = input_shape; - inputMean = input_mean; - inputStd = input_std; - scoreThreshold = score_threshold; - // Update UI - tvInputSetting.setText("Model: " + modelPath.substring(modelPath.lastIndexOf("/") + 1) + "\n" + "CPU" + - " Thread Num: " + Integer.toString(cpuThreadNum) + "\n" + "CPU Power Mode: " + cpuPowerMode); - tvInputSetting.scrollTo(0, 0); - // Reload model if configure has been changed - loadModel(); - } - } - - public void loadModel() { - pbLoadModel = ProgressDialog.show(this, "", "Loading model...", false, false); - sender.sendEmptyMessage(REQUEST_LOAD_MODEL); - } - - public void runModel() { - pbRunModel = ProgressDialog.show(this, "", "Running model...", false, false); - sender.sendEmptyMessage(REQUEST_RUN_MODEL); - } - - public boolean onLoadModel() { - return predictor.init(MainActivity.this, modelPath, labelPath, cpuThreadNum, - cpuPowerMode, - inputColorFormat, - inputShape, inputMean, - inputStd, scoreThreshold); - } - - public boolean onRunModel() { - return predictor.isLoaded() && predictor.runModel(); - } - - public void onLoadModelSuccessed() { - // Load test image from path and run model - try { - if (imagePath.isEmpty()) { - return; - } - Bitmap image = null; - // Read test image file from custom path if the first character of mode path is '/', otherwise read test - // image file from assets - if (!imagePath.substring(0, 1).equals("/")) { - InputStream imageStream = getAssets().open(imagePath); - image = BitmapFactory.decodeStream(imageStream); - } else { - if (!new File(imagePath).exists()) { - return; - } - image = BitmapFactory.decodeFile(imagePath); - } - if (image != null && predictor.isLoaded()) { - predictor.setInputImage(image); - runModel(); - } - } catch (IOException e) { - Toast.makeText(MainActivity.this, "Load image failed!", Toast.LENGTH_SHORT).show(); - e.printStackTrace(); - } - } - - public void onLoadModelFailed() { - } - - public void onRunModelSuccessed() { - // Obtain results and update UI - tvInferenceTime.setText("Inference time: " + predictor.inferenceTime() + " ms"); - Bitmap outputImage = predictor.outputImage(); - if (outputImage != null) { - ivInputImage.setImageBitmap(outputImage); - } - tvOutputResult.setText(predictor.outputResult()); - tvOutputResult.scrollTo(0, 0); - } - - public void onRunModelFailed() { - } - - public void onImageChanged(Bitmap image) { - // Rerun model if users pick test image from gallery or camera - if (image != null && predictor.isLoaded()) { - predictor.setInputImage(image); - runModel(); - } - } - - public void onSettingsClicked() { - startActivity(new Intent(MainActivity.this, SettingsActivity.class)); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.menu_action_options, menu); - return true; - } - - public boolean onPrepareOptionsMenu(Menu menu) { - boolean isLoaded = predictor.isLoaded(); - menu.findItem(R.id.open_gallery).setEnabled(isLoaded); - menu.findItem(R.id.take_photo).setEnabled(isLoaded); - return super.onPrepareOptionsMenu(menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - finish(); - break; - case R.id.open_gallery: - if (requestAllPermissions()) { - openGallery(); - } - break; - case R.id.take_photo: - if (requestAllPermissions()) { - takePhoto(); - } - break; - case R.id.settings: - if (requestAllPermissions()) { - // Make sure we have SDCard r&w permissions to load model from SDCard - onSettingsClicked(); - } - break; - } - return super.onOptionsItemSelected(item); - } - - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, - @NonNull int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - if (grantResults[0] != PackageManager.PERMISSION_GRANTED || grantResults[1] != PackageManager.PERMISSION_GRANTED) { - Toast.makeText(this, "Permission Denied", Toast.LENGTH_SHORT).show(); - } - } - - private boolean requestAllPermissions() { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) - != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, - Manifest.permission.CAMERA) - != PackageManager.PERMISSION_GRANTED) { - ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.CAMERA}, - 0); - return false; - } - return true; - } - - private void openGallery() { - Intent intent = new Intent(Intent.ACTION_PICK, null); - intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*"); - startActivityForResult(intent, OPEN_GALLERY_REQUEST_CODE); - } - - private void takePhoto() { - Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); - // Ensure that there's a camera activity to handle the intent - if (takePictureIntent.resolveActivity(getPackageManager()) != null) { - // Create the File where the photo should go - File photoFile = null; - try { - photoFile = createImageFile(); - } catch (IOException ex) { - Log.e("MainActitity", ex.getMessage(), ex); - Toast.makeText(MainActivity.this, - "Create Camera temp file failed: " + ex.getMessage(), Toast.LENGTH_SHORT).show(); - } - // Continue only if the File was successfully created - if (photoFile != null) { - Log.i(TAG, "FILEPATH " + getExternalFilesDir("Pictures").getAbsolutePath()); - Uri photoURI = FileProvider.getUriForFile(this, - "com.baidu.paddle.lite.demo.ocr.fileprovider", - photoFile); - currentPhotoPath = photoFile.getAbsolutePath(); - takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI); - startActivityForResult(takePictureIntent, TAKE_PHOTO_REQUEST_CODE); - Log.i(TAG, "startActivityForResult finished"); - } - } - - } - - private File createImageFile() throws IOException { - // Create an image file name - String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); - String imageFileName = "JPEG_" + timeStamp + "_"; - File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES); - File image = File.createTempFile( - imageFileName, /* prefix */ - ".bmp", /* suffix */ - storageDir /* directory */ - ); - - return image; - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (resultCode == RESULT_OK) { - switch (requestCode) { - case OPEN_GALLERY_REQUEST_CODE: - if (data == null) { - break; - } - try { - ContentResolver resolver = getContentResolver(); - Uri uri = data.getData(); - Bitmap image = MediaStore.Images.Media.getBitmap(resolver, uri); - String[] proj = {MediaStore.Images.Media.DATA}; - Cursor cursor = managedQuery(uri, proj, null, null, null); - cursor.moveToFirst(); - onImageChanged(image); - } catch (IOException e) { - Log.e(TAG, e.toString()); - } - break; - case TAKE_PHOTO_REQUEST_CODE: - if (currentPhotoPath != null) { - ExifInterface exif = null; - try { - exif = new ExifInterface(currentPhotoPath); - } catch (IOException e) { - e.printStackTrace(); - } - int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, - ExifInterface.ORIENTATION_UNDEFINED); - Log.i(TAG, "rotation " + orientation); - Bitmap image = BitmapFactory.decodeFile(currentPhotoPath); - image = Utils.rotateBitmap(image, orientation); - onImageChanged(image); - } else { - Log.e(TAG, "currentPhotoPath is null"); - } - break; - default: - break; - } - } - } - - @Override - protected void onDestroy() { - if (predictor != null) { - predictor.releaseModel(); - } - worker.quit(); - super.onDestroy(); - } -} diff --git a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/MiniActivity.java b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/MiniActivity.java deleted file mode 100644 index d5608911..00000000 --- a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/MiniActivity.java +++ /dev/null @@ -1,157 +0,0 @@ -package com.baidu.paddle.lite.demo.ocr; - -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Message; -import android.util.Log; -import android.view.View; -import android.widget.Button; -import android.widget.ImageView; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.appcompat.app.AppCompatActivity; - -import java.io.IOException; -import java.io.InputStream; - -public class MiniActivity extends AppCompatActivity { - - - public static final int REQUEST_LOAD_MODEL = 0; - public static final int REQUEST_RUN_MODEL = 1; - public static final int REQUEST_UNLOAD_MODEL = 2; - public static final int RESPONSE_LOAD_MODEL_SUCCESSED = 0; - public static final int RESPONSE_LOAD_MODEL_FAILED = 1; - public static final int RESPONSE_RUN_MODEL_SUCCESSED = 2; - public static final int RESPONSE_RUN_MODEL_FAILED = 3; - - private static final String TAG = "MiniActivity"; - - protected Handler receiver = null; // Receive messages from worker thread - protected Handler sender = null; // Send command to worker thread - protected HandlerThread worker = null; // Worker thread to load&run model - protected volatile Predictor predictor = null; - - private String assetModelDirPath = "models/ocr_v1_for_cpu"; - private String assetlabelFilePath = "labels/ppocr_keys_v1.txt"; - - private Button button; - private ImageView imageView; // image result - private TextView textView; // text result - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_mini); - - Log.i(TAG, "SHOW in Logcat"); - - // Prepare the worker thread for mode loading and inference - worker = new HandlerThread("Predictor Worker"); - worker.start(); - sender = new Handler(worker.getLooper()) { - public void handleMessage(Message msg) { - switch (msg.what) { - case REQUEST_LOAD_MODEL: - // Load model and reload test image - if (!onLoadModel()) { - runOnUiThread(new Runnable() { - @Override - public void run() { - Toast.makeText(MiniActivity.this, "Load model failed!", Toast.LENGTH_SHORT).show(); - } - }); - } - break; - case REQUEST_RUN_MODEL: - // Run model if model is loaded - final boolean isSuccessed = onRunModel(); - runOnUiThread(new Runnable() { - @Override - public void run() { - if (isSuccessed){ - onRunModelSuccessed(); - }else{ - Toast.makeText(MiniActivity.this, "Run model failed!", Toast.LENGTH_SHORT).show(); - } - } - }); - break; - } - } - }; - sender.sendEmptyMessage(REQUEST_LOAD_MODEL); // corresponding to REQUEST_LOAD_MODEL, to call onLoadModel() - - imageView = findViewById(R.id.imageView); - textView = findViewById(R.id.sample_text); - button = findViewById(R.id.button); - button.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - sender.sendEmptyMessage(REQUEST_RUN_MODEL); - } - }); - - - } - - @Override - protected void onDestroy() { - onUnloadModel(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - worker.quitSafely(); - } else { - worker.quit(); - } - super.onDestroy(); - } - - /** - * call in onCreate, model init - * - * @return - */ - private boolean onLoadModel() { - if (predictor == null) { - predictor = new Predictor(); - } - return predictor.init(this, assetModelDirPath, assetlabelFilePath); - } - - /** - * init engine - * call in onCreate - * - * @return - */ - private boolean onRunModel() { - try { - String assetImagePath = "images/5.jpg"; - InputStream imageStream = getAssets().open(assetImagePath); - Bitmap image = BitmapFactory.decodeStream(imageStream); - // Input is Bitmap - predictor.setInputImage(image); - return predictor.isLoaded() && predictor.runModel(); - } catch (IOException e) { - e.printStackTrace(); - return false; - } - } - - private void onRunModelSuccessed() { - Log.i(TAG, "onRunModelSuccessed"); - textView.setText(predictor.outputResult); - imageView.setImageBitmap(predictor.outputImage); - } - - private void onUnloadModel() { - if (predictor != null) { - predictor.releaseModel(); - } - } -} diff --git a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/OCRPredictorNative.java b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/OCRPredictorNative.java deleted file mode 100644 index 2e78a3ec..00000000 --- a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/OCRPredictorNative.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.baidu.paddle.lite.demo.ocr; - -import android.graphics.Bitmap; -import android.util.Log; - -import java.util.ArrayList; -import java.util.concurrent.atomic.AtomicBoolean; - -public class OCRPredictorNative { - - private static final AtomicBoolean isSOLoaded = new AtomicBoolean(); - - public static void loadLibrary() throws RuntimeException { - if (!isSOLoaded.get() && isSOLoaded.compareAndSet(false, true)) { - try { - System.loadLibrary("Native"); - } catch (Throwable e) { - RuntimeException exception = new RuntimeException( - "Load libNative.so failed, please check it exists in apk file.", e); - throw exception; - } - } - } - - private Config config; - - private long nativePointer = 0; - - public OCRPredictorNative(Config config) { - this.config = config; - loadLibrary(); - nativePointer = init(config.detModelFilename, config.recModelFilename, - config.cpuThreadNum, config.cpuPower); - Log.i("OCRPredictorNative", "load success " + nativePointer); - - } - - public void release() { - if (nativePointer != 0) { - nativePointer = 0; - destory(nativePointer); - } - } - - public ArrayList runImage(float[] inputData, int width, int height, int channels, Bitmap originalImage) { - Log.i("OCRPredictorNative", "begin to run image " + inputData.length + " " + width + " " + height); - float[] dims = new float[]{1, channels, height, width}; - float[] rawResults = forward(nativePointer, inputData, dims, originalImage); - ArrayList results = postprocess(rawResults); - return results; - } - - public static class Config { - public int cpuThreadNum; - public String cpuPower; - public String detModelFilename; - public String recModelFilename; - - } - - protected native long init(String detModelPath, String recModelPath, int threadNum, String cpuMode); - - protected native float[] forward(long pointer, float[] buf, float[] ddims, Bitmap originalImage); - - protected native void destory(long pointer); - - private ArrayList postprocess(float[] raw) { - ArrayList results = new ArrayList(); - int begin = 0; - - while (begin < raw.length) { - int point_num = Math.round(raw[begin]); - int word_num = Math.round(raw[begin + 1]); - OcrResultModel model = parse(raw, begin + 2, point_num, word_num); - begin += 2 + 1 + point_num * 2 + word_num; - results.add(model); - } - - return results; - } - - private OcrResultModel parse(float[] raw, int begin, int pointNum, int wordNum) { - int current = begin; - OcrResultModel model = new OcrResultModel(); - model.setConfidence(raw[current]); - current++; - for (int i = 0; i < pointNum; i++) { - model.addPoints(Math.round(raw[current + i * 2]), Math.round(raw[current + i * 2 + 1])); - } - current += (pointNum * 2); - for (int i = 0; i < wordNum; i++) { - int index = Math.round(raw[current + i]); - model.addWordIndex(index); - } - Log.i("OCRPredictorNative", "word finished " + wordNum); - return model; - } - - -} diff --git a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/OcrResultModel.java b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/OcrResultModel.java deleted file mode 100644 index 9494574e..00000000 --- a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/OcrResultModel.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.baidu.paddle.lite.demo.ocr; - -import android.graphics.Point; - -import java.util.ArrayList; -import java.util.List; - -public class OcrResultModel { - private List points; - private List wordIndex; - private String label; - private float confidence; - - public OcrResultModel() { - super(); - points = new ArrayList<>(); - wordIndex = new ArrayList<>(); - } - - public void addPoints(int x, int y) { - Point point = new Point(x, y); - points.add(point); - } - - public void addWordIndex(int index) { - wordIndex.add(index); - } - - public List getPoints() { - return points; - } - - public List getWordIndex() { - return wordIndex; - } - - public String getLabel() { - return label; - } - - public void setLabel(String label) { - this.label = label; - } - - public float getConfidence() { - return confidence; - } - - public void setConfidence(float confidence) { - this.confidence = confidence; - } -} diff --git a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/Predictor.java b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/Predictor.java deleted file mode 100644 index 078bba28..00000000 --- a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/Predictor.java +++ /dev/null @@ -1,355 +0,0 @@ -package com.baidu.paddle.lite.demo.ocr; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.Point; -import android.util.Log; - -import java.io.File; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Vector; - -import static android.graphics.Color.*; - -public class Predictor { - private static final String TAG = Predictor.class.getSimpleName(); - public boolean isLoaded = false; - public int warmupIterNum = 1; - public int inferIterNum = 1; - public int cpuThreadNum = 4; - public String cpuPowerMode = "LITE_POWER_HIGH"; - public String modelPath = ""; - public String modelName = ""; - protected OCRPredictorNative paddlePredictor = null; - protected float inferenceTime = 0; - // Only for object detection - protected Vector wordLabels = new Vector(); - protected String inputColorFormat = "BGR"; - protected long[] inputShape = new long[]{1, 3, 960}; - protected float[] inputMean = new float[]{0.485f, 0.456f, 0.406f}; - protected float[] inputStd = new float[]{1.0f / 0.229f, 1.0f / 0.224f, 1.0f / 0.225f}; - protected float scoreThreshold = 0.1f; - protected Bitmap inputImage = null; - protected Bitmap outputImage = null; - protected volatile String outputResult = ""; - protected float preprocessTime = 0; - protected float postprocessTime = 0; - - - public Predictor() { - } - - public boolean init(Context appCtx, String modelPath, String labelPath) { - isLoaded = loadModel(appCtx, modelPath, cpuThreadNum, cpuPowerMode); - if (!isLoaded) { - return false; - } - isLoaded = loadLabel(appCtx, labelPath); - return isLoaded; - } - - - public boolean init(Context appCtx, String modelPath, String labelPath, int cpuThreadNum, String cpuPowerMode, - String inputColorFormat, - long[] inputShape, float[] inputMean, - float[] inputStd, float scoreThreshold) { - if (inputShape.length != 3) { - Log.e(TAG, "Size of input shape should be: 3"); - return false; - } - if (inputMean.length != inputShape[1]) { - Log.e(TAG, "Size of input mean should be: " + Long.toString(inputShape[1])); - return false; - } - if (inputStd.length != inputShape[1]) { - Log.e(TAG, "Size of input std should be: " + Long.toString(inputShape[1])); - return false; - } - if (inputShape[0] != 1) { - Log.e(TAG, "Only one batch is supported in the image classification demo, you can use any batch size in " + - "your Apps!"); - return false; - } - if (inputShape[1] != 1 && inputShape[1] != 3) { - Log.e(TAG, "Only one/three channels are supported in the image classification demo, you can use any " + - "channel size in your Apps!"); - return false; - } - if (!inputColorFormat.equalsIgnoreCase("BGR")) { - Log.e(TAG, "Only BGR color format is supported."); - return false; - } - boolean isLoaded = init(appCtx, modelPath, labelPath); - if (!isLoaded) { - return false; - } - this.inputColorFormat = inputColorFormat; - this.inputShape = inputShape; - this.inputMean = inputMean; - this.inputStd = inputStd; - this.scoreThreshold = scoreThreshold; - return true; - } - - protected boolean loadModel(Context appCtx, String modelPath, int cpuThreadNum, String cpuPowerMode) { - // Release model if exists - releaseModel(); - - // Load model - if (modelPath.isEmpty()) { - return false; - } - String realPath = modelPath; - if (!modelPath.substring(0, 1).equals("/")) { - // Read model files from custom path if the first character of mode path is '/' - // otherwise copy model to cache from assets - realPath = appCtx.getCacheDir() + "/" + modelPath; - Utils.copyDirectoryFromAssets(appCtx, modelPath, realPath); - } - if (realPath.isEmpty()) { - return false; - } - - OCRPredictorNative.Config config = new OCRPredictorNative.Config(); - config.cpuThreadNum = cpuThreadNum; - config.detModelFilename = realPath + File.separator + "ch_det_mv3_db_opt.nb"; - config.recModelFilename = realPath + File.separator + "ch_rec_mv3_crnn_opt.nb"; - Log.e("Predictor", "model path" + config.detModelFilename + " ; " + config.recModelFilename); - config.cpuPower = cpuPowerMode; - paddlePredictor = new OCRPredictorNative(config); - - this.cpuThreadNum = cpuThreadNum; - this.cpuPowerMode = cpuPowerMode; - this.modelPath = realPath; - this.modelName = realPath.substring(realPath.lastIndexOf("/") + 1); - return true; - } - - public void releaseModel() { - if (paddlePredictor != null) { - paddlePredictor.release(); - paddlePredictor = null; - } - isLoaded = false; - cpuThreadNum = 1; - cpuPowerMode = "LITE_POWER_HIGH"; - modelPath = ""; - modelName = ""; - } - - protected boolean loadLabel(Context appCtx, String labelPath) { - wordLabels.clear(); - // Load word labels from file - try { - InputStream assetsInputStream = appCtx.getAssets().open(labelPath); - int available = assetsInputStream.available(); - byte[] lines = new byte[available]; - assetsInputStream.read(lines); - assetsInputStream.close(); - String words = new String(lines); - String[] contents = words.split("\n"); - for (String content : contents) { - wordLabels.add(content); - } - Log.i(TAG, "Word label size: " + wordLabels.size()); - } catch (Exception e) { - Log.e(TAG, e.getMessage()); - return false; - } - return true; - } - - - public boolean runModel() { - if (inputImage == null || !isLoaded()) { - return false; - } - - // Pre-process image, and feed input tensor with pre-processed data - - Bitmap scaleImage = Utils.resizeWithStep(inputImage, Long.valueOf(inputShape[2]).intValue(), 32); - - Date start = new Date(); - int channels = (int) inputShape[1]; - int width = scaleImage.getWidth(); - int height = scaleImage.getHeight(); - float[] inputData = new float[channels * width * height]; - if (channels == 3) { - int[] channelIdx = null; - if (inputColorFormat.equalsIgnoreCase("RGB")) { - channelIdx = new int[]{0, 1, 2}; - } else if (inputColorFormat.equalsIgnoreCase("BGR")) { - channelIdx = new int[]{2, 1, 0}; - } else { - Log.i(TAG, "Unknown color format " + inputColorFormat + ", only RGB and BGR color format is " + - "supported!"); - return false; - } - int[] channelStride = new int[]{width * height, width * height * 2}; - int p = scaleImage.getPixel(scaleImage.getWidth() - 1, scaleImage.getHeight() - 1); - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - int color = scaleImage.getPixel(x, y); - float[] rgb = new float[]{(float) red(color) / 255.0f, (float) green(color) / 255.0f, - (float) blue(color) / 255.0f}; - inputData[y * width + x] = (rgb[channelIdx[0]] - inputMean[0]) / inputStd[0]; - inputData[y * width + x + channelStride[0]] = (rgb[channelIdx[1]] - inputMean[1]) / inputStd[1]; - inputData[y * width + x + channelStride[1]] = (rgb[channelIdx[2]] - inputMean[2]) / inputStd[2]; - - } - } - } else if (channels == 1) { - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - int color = inputImage.getPixel(x, y); - float gray = (float) (red(color) + green(color) + blue(color)) / 3.0f / 255.0f; - inputData[y * width + x] = (gray - inputMean[0]) / inputStd[0]; - } - } - } else { - Log.i(TAG, "Unsupported channel size " + Integer.toString(channels) + ", only channel 1 and 3 is " + - "supported!"); - return false; - } - float[] pixels = inputData; - Log.i(TAG, "pixels " + pixels[0] + " " + pixels[1] + " " + pixels[2] + " " + pixels[3] - + " " + pixels[pixels.length / 2] + " " + pixels[pixels.length / 2 + 1] + " " + pixels[pixels.length - 2] + " " + pixels[pixels.length - 1]); - Date end = new Date(); - preprocessTime = (float) (end.getTime() - start.getTime()); - - // Warm up - for (int i = 0; i < warmupIterNum; i++) { - paddlePredictor.runImage(inputData, width, height, channels, inputImage); - } - warmupIterNum = 0; // do not need warm - // Run inference - start = new Date(); - ArrayList results = paddlePredictor.runImage(inputData, width, height, channels, inputImage); - end = new Date(); - inferenceTime = (end.getTime() - start.getTime()) / (float) inferIterNum; - - results = postprocess(results); - Log.i(TAG, "[stat] Preprocess Time: " + preprocessTime - + " ; Inference Time: " + inferenceTime + " ;Box Size " + results.size()); - drawResults(results); - - return true; - } - - - public boolean isLoaded() { - return paddlePredictor != null && isLoaded; - } - - public String modelPath() { - return modelPath; - } - - public String modelName() { - return modelName; - } - - public int cpuThreadNum() { - return cpuThreadNum; - } - - public String cpuPowerMode() { - return cpuPowerMode; - } - - public float inferenceTime() { - return inferenceTime; - } - - public Bitmap inputImage() { - return inputImage; - } - - public Bitmap outputImage() { - return outputImage; - } - - public String outputResult() { - return outputResult; - } - - public float preprocessTime() { - return preprocessTime; - } - - public float postprocessTime() { - return postprocessTime; - } - - - public void setInputImage(Bitmap image) { - if (image == null) { - return; - } - this.inputImage = image.copy(Bitmap.Config.ARGB_8888, true); - } - - private ArrayList postprocess(ArrayList results) { - for (OcrResultModel r : results) { - StringBuffer word = new StringBuffer(); - for (int index : r.getWordIndex()) { - if (index >= 0 && index < wordLabels.size()) { - word.append(wordLabels.get(index)); - } else { - Log.e(TAG, "Word index is not in label list:" + index); - word.append("×"); - } - } - r.setLabel(word.toString()); - } - return results; - } - - private void drawResults(ArrayList results) { - StringBuffer outputResultSb = new StringBuffer(""); - for (int i = 0; i < results.size(); i++) { - OcrResultModel result = results.get(i); - StringBuilder sb = new StringBuilder(""); - sb.append(result.getLabel()); - sb.append(" ").append(result.getConfidence()); - sb.append("; Points: "); - for (Point p : result.getPoints()) { - sb.append("(").append(p.x).append(",").append(p.y).append(") "); - } - Log.i(TAG, sb.toString()); // show LOG in Logcat panel - outputResultSb.append(i + 1).append(": ").append(result.getLabel()).append("\n"); - } - outputResult = outputResultSb.toString(); - outputImage = inputImage; - Canvas canvas = new Canvas(outputImage); - Paint paintFillAlpha = new Paint(); - paintFillAlpha.setStyle(Paint.Style.FILL); - paintFillAlpha.setColor(Color.parseColor("#3B85F5")); - paintFillAlpha.setAlpha(50); - - Paint paint = new Paint(); - paint.setColor(Color.parseColor("#3B85F5")); - paint.setStrokeWidth(5); - paint.setStyle(Paint.Style.STROKE); - - for (OcrResultModel result : results) { - Path path = new Path(); - List points = result.getPoints(); - path.moveTo(points.get(0).x, points.get(0).y); - for (int i = points.size() - 1; i >= 0; i--) { - Point p = points.get(i); - path.lineTo(p.x, p.y); - } - canvas.drawPath(path, paint); - canvas.drawPath(path, paintFillAlpha); - } - } - -} diff --git a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/SettingsActivity.java b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/SettingsActivity.java deleted file mode 100644 index b3653ccc..00000000 --- a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/SettingsActivity.java +++ /dev/null @@ -1,201 +0,0 @@ -package com.baidu.paddle.lite.demo.ocr; - -import android.content.SharedPreferences; -import android.os.Bundle; -import android.preference.CheckBoxPreference; -import android.preference.EditTextPreference; -import android.preference.ListPreference; - -import androidx.appcompat.app.ActionBar; - -import java.util.ArrayList; -import java.util.List; - - -public class SettingsActivity extends AppCompatPreferenceActivity implements SharedPreferences.OnSharedPreferenceChangeListener { - ListPreference lpChoosePreInstalledModel = null; - CheckBoxPreference cbEnableCustomSettings = null; - EditTextPreference etModelPath = null; - EditTextPreference etLabelPath = null; - EditTextPreference etImagePath = null; - ListPreference lpCPUThreadNum = null; - ListPreference lpCPUPowerMode = null; - ListPreference lpInputColorFormat = null; - EditTextPreference etInputShape = null; - EditTextPreference etInputMean = null; - EditTextPreference etInputStd = null; - EditTextPreference etScoreThreshold = null; - - List preInstalledModelPaths = null; - List preInstalledLabelPaths = null; - List preInstalledImagePaths = null; - List preInstalledInputShapes = null; - List preInstalledCPUThreadNums = null; - List preInstalledCPUPowerModes = null; - List preInstalledInputColorFormats = null; - List preInstalledInputMeans = null; - List preInstalledInputStds = null; - List preInstalledScoreThresholds = null; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - addPreferencesFromResource(R.xml.settings); - ActionBar supportActionBar = getSupportActionBar(); - if (supportActionBar != null) { - supportActionBar.setDisplayHomeAsUpEnabled(true); - } - - // Initialized pre-installed models - preInstalledModelPaths = new ArrayList(); - preInstalledLabelPaths = new ArrayList(); - preInstalledImagePaths = new ArrayList(); - preInstalledInputShapes = new ArrayList(); - preInstalledCPUThreadNums = new ArrayList(); - preInstalledCPUPowerModes = new ArrayList(); - preInstalledInputColorFormats = new ArrayList(); - preInstalledInputMeans = new ArrayList(); - preInstalledInputStds = new ArrayList(); - preInstalledScoreThresholds = new ArrayList(); - // Add ssd_mobilenet_v1_pascalvoc_for_cpu - preInstalledModelPaths.add(getString(R.string.MODEL_PATH_DEFAULT)); - preInstalledLabelPaths.add(getString(R.string.LABEL_PATH_DEFAULT)); - preInstalledImagePaths.add(getString(R.string.IMAGE_PATH_DEFAULT)); - preInstalledCPUThreadNums.add(getString(R.string.CPU_THREAD_NUM_DEFAULT)); - preInstalledCPUPowerModes.add(getString(R.string.CPU_POWER_MODE_DEFAULT)); - preInstalledInputColorFormats.add(getString(R.string.INPUT_COLOR_FORMAT_DEFAULT)); - preInstalledInputShapes.add(getString(R.string.INPUT_SHAPE_DEFAULT)); - preInstalledInputMeans.add(getString(R.string.INPUT_MEAN_DEFAULT)); - preInstalledInputStds.add(getString(R.string.INPUT_STD_DEFAULT)); - preInstalledScoreThresholds.add(getString(R.string.SCORE_THRESHOLD_DEFAULT)); - - // Setup UI components - lpChoosePreInstalledModel = - (ListPreference) findPreference(getString(R.string.CHOOSE_PRE_INSTALLED_MODEL_KEY)); - String[] preInstalledModelNames = new String[preInstalledModelPaths.size()]; - for (int i = 0; i < preInstalledModelPaths.size(); i++) { - preInstalledModelNames[i] = - preInstalledModelPaths.get(i).substring(preInstalledModelPaths.get(i).lastIndexOf("/") + 1); - } - lpChoosePreInstalledModel.setEntries(preInstalledModelNames); - lpChoosePreInstalledModel.setEntryValues(preInstalledModelPaths.toArray(new String[preInstalledModelPaths.size()])); - cbEnableCustomSettings = - (CheckBoxPreference) findPreference(getString(R.string.ENABLE_CUSTOM_SETTINGS_KEY)); - etModelPath = (EditTextPreference) findPreference(getString(R.string.MODEL_PATH_KEY)); - etModelPath.setTitle("Model Path (SDCard: " + Utils.getSDCardDirectory() + ")"); - etLabelPath = (EditTextPreference) findPreference(getString(R.string.LABEL_PATH_KEY)); - etImagePath = (EditTextPreference) findPreference(getString(R.string.IMAGE_PATH_KEY)); - lpCPUThreadNum = - (ListPreference) findPreference(getString(R.string.CPU_THREAD_NUM_KEY)); - lpCPUPowerMode = - (ListPreference) findPreference(getString(R.string.CPU_POWER_MODE_KEY)); - lpInputColorFormat = - (ListPreference) findPreference(getString(R.string.INPUT_COLOR_FORMAT_KEY)); - etInputShape = (EditTextPreference) findPreference(getString(R.string.INPUT_SHAPE_KEY)); - etInputMean = (EditTextPreference) findPreference(getString(R.string.INPUT_MEAN_KEY)); - etInputStd = (EditTextPreference) findPreference(getString(R.string.INPUT_STD_KEY)); - etScoreThreshold = (EditTextPreference) findPreference(getString(R.string.SCORE_THRESHOLD_KEY)); - } - - private void reloadPreferenceAndUpdateUI() { - SharedPreferences sharedPreferences = getPreferenceScreen().getSharedPreferences(); - boolean enableCustomSettings = - sharedPreferences.getBoolean(getString(R.string.ENABLE_CUSTOM_SETTINGS_KEY), false); - String modelPath = sharedPreferences.getString(getString(R.string.CHOOSE_PRE_INSTALLED_MODEL_KEY), - getString(R.string.MODEL_PATH_DEFAULT)); - int modelIdx = lpChoosePreInstalledModel.findIndexOfValue(modelPath); - if (modelIdx >= 0 && modelIdx < preInstalledModelPaths.size()) { - if (!enableCustomSettings) { - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putString(getString(R.string.MODEL_PATH_KEY), preInstalledModelPaths.get(modelIdx)); - editor.putString(getString(R.string.LABEL_PATH_KEY), preInstalledLabelPaths.get(modelIdx)); - editor.putString(getString(R.string.IMAGE_PATH_KEY), preInstalledImagePaths.get(modelIdx)); - editor.putString(getString(R.string.CPU_THREAD_NUM_KEY), preInstalledCPUThreadNums.get(modelIdx)); - editor.putString(getString(R.string.CPU_POWER_MODE_KEY), preInstalledCPUPowerModes.get(modelIdx)); - editor.putString(getString(R.string.INPUT_COLOR_FORMAT_KEY), - preInstalledInputColorFormats.get(modelIdx)); - editor.putString(getString(R.string.INPUT_SHAPE_KEY), preInstalledInputShapes.get(modelIdx)); - editor.putString(getString(R.string.INPUT_MEAN_KEY), preInstalledInputMeans.get(modelIdx)); - editor.putString(getString(R.string.INPUT_STD_KEY), preInstalledInputStds.get(modelIdx)); - editor.putString(getString(R.string.SCORE_THRESHOLD_KEY), - preInstalledScoreThresholds.get(modelIdx)); - editor.commit(); - } - lpChoosePreInstalledModel.setSummary(modelPath); - } - cbEnableCustomSettings.setChecked(enableCustomSettings); - etModelPath.setEnabled(enableCustomSettings); - etLabelPath.setEnabled(enableCustomSettings); - etImagePath.setEnabled(enableCustomSettings); - lpCPUThreadNum.setEnabled(enableCustomSettings); - lpCPUPowerMode.setEnabled(enableCustomSettings); - lpInputColorFormat.setEnabled(enableCustomSettings); - etInputShape.setEnabled(enableCustomSettings); - etInputMean.setEnabled(enableCustomSettings); - etInputStd.setEnabled(enableCustomSettings); - etScoreThreshold.setEnabled(enableCustomSettings); - modelPath = sharedPreferences.getString(getString(R.string.MODEL_PATH_KEY), - getString(R.string.MODEL_PATH_DEFAULT)); - String labelPath = sharedPreferences.getString(getString(R.string.LABEL_PATH_KEY), - getString(R.string.LABEL_PATH_DEFAULT)); - String imagePath = sharedPreferences.getString(getString(R.string.IMAGE_PATH_KEY), - getString(R.string.IMAGE_PATH_DEFAULT)); - String cpuThreadNum = sharedPreferences.getString(getString(R.string.CPU_THREAD_NUM_KEY), - getString(R.string.CPU_THREAD_NUM_DEFAULT)); - String cpuPowerMode = sharedPreferences.getString(getString(R.string.CPU_POWER_MODE_KEY), - getString(R.string.CPU_POWER_MODE_DEFAULT)); - String inputColorFormat = sharedPreferences.getString(getString(R.string.INPUT_COLOR_FORMAT_KEY), - getString(R.string.INPUT_COLOR_FORMAT_DEFAULT)); - String inputShape = sharedPreferences.getString(getString(R.string.INPUT_SHAPE_KEY), - getString(R.string.INPUT_SHAPE_DEFAULT)); - String inputMean = sharedPreferences.getString(getString(R.string.INPUT_MEAN_KEY), - getString(R.string.INPUT_MEAN_DEFAULT)); - String inputStd = sharedPreferences.getString(getString(R.string.INPUT_STD_KEY), - getString(R.string.INPUT_STD_DEFAULT)); - String scoreThreshold = sharedPreferences.getString(getString(R.string.SCORE_THRESHOLD_KEY), - getString(R.string.SCORE_THRESHOLD_DEFAULT)); - etModelPath.setSummary(modelPath); - etModelPath.setText(modelPath); - etLabelPath.setSummary(labelPath); - etLabelPath.setText(labelPath); - etImagePath.setSummary(imagePath); - etImagePath.setText(imagePath); - lpCPUThreadNum.setValue(cpuThreadNum); - lpCPUThreadNum.setSummary(cpuThreadNum); - lpCPUPowerMode.setValue(cpuPowerMode); - lpCPUPowerMode.setSummary(cpuPowerMode); - lpInputColorFormat.setValue(inputColorFormat); - lpInputColorFormat.setSummary(inputColorFormat); - etInputShape.setSummary(inputShape); - etInputShape.setText(inputShape); - etInputMean.setSummary(inputMean); - etInputMean.setText(inputMean); - etInputStd.setSummary(inputStd); - etInputStd.setText(inputStd); - etScoreThreshold.setText(scoreThreshold); - etScoreThreshold.setSummary(scoreThreshold); - } - - @Override - protected void onResume() { - super.onResume(); - getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this); - reloadPreferenceAndUpdateUI(); - } - - @Override - protected void onPause() { - super.onPause(); - getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this); - } - - @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - if (key.equals(getString(R.string.CHOOSE_PRE_INSTALLED_MODEL_KEY))) { - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putBoolean(getString(R.string.ENABLE_CUSTOM_SETTINGS_KEY), false); - editor.commit(); - } - reloadPreferenceAndUpdateUI(); - } -} diff --git a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/Utils.java b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/Utils.java deleted file mode 100644 index ef468057..00000000 --- a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/Utils.java +++ /dev/null @@ -1,159 +0,0 @@ -package com.baidu.paddle.lite.demo.ocr; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Matrix; -import android.media.ExifInterface; -import android.os.Environment; - -import java.io.*; - -public class Utils { - private static final String TAG = Utils.class.getSimpleName(); - - public static void copyFileFromAssets(Context appCtx, String srcPath, String dstPath) { - if (srcPath.isEmpty() || dstPath.isEmpty()) { - return; - } - InputStream is = null; - OutputStream os = null; - try { - is = new BufferedInputStream(appCtx.getAssets().open(srcPath)); - os = new BufferedOutputStream(new FileOutputStream(new File(dstPath))); - byte[] buffer = new byte[1024]; - int length = 0; - while ((length = is.read(buffer)) != -1) { - os.write(buffer, 0, length); - } - } catch (FileNotFoundException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } finally { - try { - os.close(); - is.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - public static void copyDirectoryFromAssets(Context appCtx, String srcDir, String dstDir) { - if (srcDir.isEmpty() || dstDir.isEmpty()) { - return; - } - try { - if (!new File(dstDir).exists()) { - new File(dstDir).mkdirs(); - } - for (String fileName : appCtx.getAssets().list(srcDir)) { - String srcSubPath = srcDir + File.separator + fileName; - String dstSubPath = dstDir + File.separator + fileName; - if (new File(srcSubPath).isDirectory()) { - copyDirectoryFromAssets(appCtx, srcSubPath, dstSubPath); - } else { - copyFileFromAssets(appCtx, srcSubPath, dstSubPath); - } - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - public static float[] parseFloatsFromString(String string, String delimiter) { - String[] pieces = string.trim().toLowerCase().split(delimiter); - float[] floats = new float[pieces.length]; - for (int i = 0; i < pieces.length; i++) { - floats[i] = Float.parseFloat(pieces[i].trim()); - } - return floats; - } - - public static long[] parseLongsFromString(String string, String delimiter) { - String[] pieces = string.trim().toLowerCase().split(delimiter); - long[] longs = new long[pieces.length]; - for (int i = 0; i < pieces.length; i++) { - longs[i] = Long.parseLong(pieces[i].trim()); - } - return longs; - } - - public static String getSDCardDirectory() { - return Environment.getExternalStorageDirectory().getAbsolutePath(); - } - - public static boolean isSupportedNPU() { - return false; - // String hardware = android.os.Build.HARDWARE; - // return hardware.equalsIgnoreCase("kirin810") || hardware.equalsIgnoreCase("kirin990"); - } - - public static Bitmap resizeWithStep(Bitmap bitmap, int maxLength, int step) { - int width = bitmap.getWidth(); - int height = bitmap.getHeight(); - int maxWH = Math.max(width, height); - float ratio = 1; - int newWidth = width; - int newHeight = height; - if (maxWH > maxLength) { - ratio = maxLength * 1.0f / maxWH; - newWidth = (int) Math.floor(ratio * width); - newHeight = (int) Math.floor(ratio * height); - } - - newWidth = newWidth - newWidth % step; - if (newWidth == 0) { - newWidth = step; - } - newHeight = newHeight - newHeight % step; - if (newHeight == 0) { - newHeight = step; - } - return Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true); - } - - public static Bitmap rotateBitmap(Bitmap bitmap, int orientation) { - - Matrix matrix = new Matrix(); - switch (orientation) { - case ExifInterface.ORIENTATION_NORMAL: - return bitmap; - case ExifInterface.ORIENTATION_FLIP_HORIZONTAL: - matrix.setScale(-1, 1); - break; - case ExifInterface.ORIENTATION_ROTATE_180: - matrix.setRotate(180); - break; - case ExifInterface.ORIENTATION_FLIP_VERTICAL: - matrix.setRotate(180); - matrix.postScale(-1, 1); - break; - case ExifInterface.ORIENTATION_TRANSPOSE: - matrix.setRotate(90); - matrix.postScale(-1, 1); - break; - case ExifInterface.ORIENTATION_ROTATE_90: - matrix.setRotate(90); - break; - case ExifInterface.ORIENTATION_TRANSVERSE: - matrix.setRotate(-90); - matrix.postScale(-1, 1); - break; - case ExifInterface.ORIENTATION_ROTATE_270: - matrix.setRotate(-90); - break; - default: - return bitmap; - } - try { - Bitmap bmRotated = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); - bitmap.recycle(); - return bmRotated; - } - catch (OutOfMemoryError e) { - e.printStackTrace(); - return null; - } - } -} diff --git a/deploy/android_demo/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/deploy/android_demo/app/src/main/res/drawable-v24/ic_launcher_foreground.xml deleted file mode 100644 index 1f6bb290..00000000 --- a/deploy/android_demo/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - diff --git a/deploy/android_demo/app/src/main/res/drawable/ic_launcher_background.xml b/deploy/android_demo/app/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index 0d025f9b..00000000 --- a/deploy/android_demo/app/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/deploy/android_demo/app/src/main/res/layout/activity_main.xml b/deploy/android_demo/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index adc70e20..00000000 --- a/deploy/android_demo/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/deploy/android_demo/app/src/main/res/layout/activity_mini.xml b/deploy/android_demo/app/src/main/res/layout/activity_mini.xml deleted file mode 100644 index ec4622ae..00000000 --- a/deploy/android_demo/app/src/main/res/layout/activity_mini.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - -