Repository: FeiGeChuanShu/ncnn_paddleocr Branch: main Commit: 3e66eb7df5d8 Files: 29 Total size: 318.6 KB Directory structure: gitextract_zxhxyjoe/ ├── README.md ├── app/ │ ├── build.gradle │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── assets/ │ │ ├── ch_PP-OCRv3_det.param │ │ ├── ch_PP-OCRv3_rec.param │ │ ├── cls-sim-op.param │ │ ├── det-sim-op.param │ │ ├── paddleocr_keys.txt │ │ ├── pdocrv2.0_det-op.param │ │ ├── pdocrv2.0_rec-op.param │ │ └── rec-sim-op.param │ ├── java/ │ │ └── com/ │ │ └── tencent/ │ │ └── paddleocrncnn/ │ │ ├── MainActivity.java │ │ └── PaddleOCRNcnn.java │ ├── jni/ │ │ ├── CMakeLists.txt │ │ ├── clipper.cpp │ │ ├── clipper.hpp │ │ ├── common.cpp │ │ ├── common.h │ │ └── paddleocr_ncnn.cpp │ └── res/ │ ├── layout/ │ │ └── main.xml │ ├── values/ │ │ └── strings.xml │ └── xml/ │ └── file_paths.xml ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── local.properties └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: README.md ================================================ # ncnn_paddleocr This is a sample paddleocr ncnn android project, it depends on ncnn library and opencv https://github.com/Tencent/ncnn https://github.com/nihui/opencv-mobile convert paddleocr light model to ncnn,you can use it by ncnn. the infer code you can use chineseocr_lite project. PS:if you use angle model plz change the input shape dstHeight from 32 to 48 # model support ## text detection 1.mv3dbnet-sim-op(paddleocr_mobile) 2.pdocrv2.0_det-op(PP-OCRv2) 3.ch_PP-OCRv3_det(PP-OCRv3) 4.ch_PP-OCRv4_det(PP-OCRv4) [model](https://github.com/FeiGeChuanShu/ncnn_ppstructure) ## text angle cls 1.angle-sim-op ## text recognition 1.mv3rec-sim-op(paddleocr_mobile) 2.pdocrv2.0_rec-op(PP-OCRv2) 3.ch_PP-OCRv3_rec(PP-OCRv3) 4.ch_PP-OCRv4_rec(PP-OCRv4) [model](https://github.com/FeiGeChuanShu/ncnn_ppstructure) ## how to build and run ### step1 https://github.com/Tencent/ncnn/releases * Download ncnn-YYYYMMDD-android-vulkan.zip or build ncnn for android yourself * Extract ncnn-YYYYMMDD-android-vulkan.zip into **app/src/main/jni** and change the **ncnn_DIR** path to yours in **app/src/main/jni/CMakeLists.txt** ### step2 https://github.com/nihui/opencv-mobile * Download opencv-mobile-XYZ-android.zip * Extract opencv-mobile-XYZ-android.zip into **app/src/main/jni** and change the **OpenCV_DIR** path to yours in **app/src/main/jni/CMakeLists.txt** ### step3 * Open this project with Android Studio, build it and enjoy! ## screenshot ![](screenshot.png) 1.https://github.com/DayBreak-u/chineseocr_lite/tree/onnx/cpp_projects/OcrLiteNcnn 2.https://github.com/frotms/PaddleOCR2Pytorch 3.https://github.com/PaddlePaddle/PaddleOCR#PP-OCRv2 4.https://github.com/nihui/ncnn-android-yolov5 5.https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.5 ================================================ FILE: app/build.gradle ================================================ apply plugin: 'com.android.application' android { compileSdkVersion 24 buildToolsVersion "29.0.2" defaultConfig { applicationId "com.tencent.paddleocrncnn" archivesBaseName = "$applicationId" ndk { moduleName "ncnn" abiFilters "armeabi-v7a", "arm64-v8a" } minSdkVersion 24 } externalNativeBuild { cmake { version "3.10.2" path file('src/main/jni/CMakeLists.txt') } } dependencies { implementation 'com.android.support:support-v4:24.0.0' } } ================================================ FILE: app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: app/src/main/assets/ch_PP-OCRv3_det.param ================================================ 7767517 153 185 Input x 0 1 input Convolution Conv_0 1 1 input hardswish_0.tmp_0 0=8 1=3 3=2 4=1 5=1 6=216 9=6 -23310=2,1.666667e-01,5.000000e-01 Split splitncnn_0 1 2 hardswish_0.tmp_0 hardswish_0.tmp_0_splitncnn_0 hardswish_0.tmp_0_splitncnn_1 Convolution Conv_1 1 1 hardswish_0.tmp_0_splitncnn_1 relu_0.tmp_0 0=8 1=1 5=1 6=64 9=1 ConvolutionDepthWise Conv_2 1 1 relu_0.tmp_0 relu_1.tmp_0 0=8 1=3 4=1 5=1 6=72 7=8 9=1 Convolution Conv_3 1 1 relu_1.tmp_0 conv2d_213.tmp_0 0=8 1=1 5=1 6=64 BinaryOp Add_1 2 1 hardswish_0.tmp_0_splitncnn_0 conv2d_213.tmp_0 elementwise_add_0 Convolution Conv_4 1 1 elementwise_add_0 relu_2.tmp_0 0=32 1=1 5=1 6=256 9=1 ConvolutionDepthWise Conv_5 1 1 relu_2.tmp_0 relu_3.tmp_0 0=32 1=3 3=2 4=1 5=1 6=288 7=32 9=1 Convolution Conv_6 1 1 relu_3.tmp_0 conv2d_215.tmp_0 0=16 1=1 5=1 6=512 Split splitncnn_1 1 2 conv2d_215.tmp_0 conv2d_215.tmp_0_splitncnn_0 conv2d_215.tmp_0_splitncnn_1 Convolution Conv_7 1 1 conv2d_215.tmp_0_splitncnn_1 relu_4.tmp_0 0=40 1=1 5=1 6=640 9=1 ConvolutionDepthWise Conv_8 1 1 relu_4.tmp_0 relu_5.tmp_0 0=40 1=3 4=1 5=1 6=360 7=40 9=1 Convolution Conv_9 1 1 relu_5.tmp_0 conv2d_217.tmp_0 0=16 1=1 5=1 6=640 BinaryOp Add_2 2 1 conv2d_215.tmp_0_splitncnn_0 conv2d_217.tmp_0 elementwise_add_1 Split splitncnn_2 1 2 elementwise_add_1 elementwise_add_1_splitncnn_0 elementwise_add_1_splitncnn_1 Convolution Conv_10 1 1 elementwise_add_1_splitncnn_1 relu_6.tmp_0 0=40 1=1 5=1 6=640 9=1 ConvolutionDepthWise Conv_11 1 1 relu_6.tmp_0 relu_7.tmp_0 0=40 1=5 3=2 4=2 5=1 6=1000 7=40 9=1 Convolution Conv_12 1 1 relu_7.tmp_0 conv2d_219.tmp_0 0=24 1=1 5=1 6=960 Split splitncnn_3 1 2 conv2d_219.tmp_0 conv2d_219.tmp_0_splitncnn_0 conv2d_219.tmp_0_splitncnn_1 Convolution Conv_13 1 1 conv2d_219.tmp_0_splitncnn_1 relu_8.tmp_0 0=64 1=1 5=1 6=1536 9=1 ConvolutionDepthWise Conv_14 1 1 relu_8.tmp_0 relu_9.tmp_0 0=64 1=5 4=2 5=1 6=1600 7=64 9=1 Convolution Conv_15 1 1 relu_9.tmp_0 conv2d_221.tmp_0 0=24 1=1 5=1 6=1536 BinaryOp Add_3 2 1 conv2d_219.tmp_0_splitncnn_0 conv2d_221.tmp_0 elementwise_add_2 Split splitncnn_4 1 2 elementwise_add_2 elementwise_add_2_splitncnn_0 elementwise_add_2_splitncnn_1 Convolution Conv_16 1 1 elementwise_add_2_splitncnn_1 relu_10.tmp_0 0=64 1=1 5=1 6=1536 9=1 ConvolutionDepthWise Conv_17 1 1 relu_10.tmp_0 relu_11.tmp_0 0=64 1=5 4=2 5=1 6=1600 7=64 9=1 Convolution Conv_18 1 1 relu_11.tmp_0 conv2d_223.tmp_0 0=24 1=1 5=1 6=1536 BinaryOp Add_4 2 1 elementwise_add_2_splitncnn_0 conv2d_223.tmp_0 elementwise_add_3 Split splitncnn_5 1 2 elementwise_add_3 elementwise_add_3_splitncnn_0 elementwise_add_3_splitncnn_1 Convolution Conv_19 1 1 elementwise_add_3_splitncnn_1 hardswish_1.tmp_0 0=120 1=1 5=1 6=2880 9=6 -23310=2,1.666667e-01,5.000000e-01 ConvolutionDepthWise Conv_20 1 1 hardswish_1.tmp_0 hardswish_2.tmp_0 0=120 1=3 3=2 4=1 5=1 6=1080 7=120 9=6 -23310=2,1.666667e-01,5.000000e-01 Convolution Conv_21 1 1 hardswish_2.tmp_0 conv2d_225.tmp_0 0=40 1=1 5=1 6=4800 Split splitncnn_6 1 2 conv2d_225.tmp_0 conv2d_225.tmp_0_splitncnn_0 conv2d_225.tmp_0_splitncnn_1 Convolution Conv_22 1 1 conv2d_225.tmp_0_splitncnn_1 hardswish_3.tmp_0 0=104 1=1 5=1 6=4160 9=6 -23310=2,1.666667e-01,5.000000e-01 ConvolutionDepthWise Conv_23 1 1 hardswish_3.tmp_0 hardswish_4.tmp_0 0=104 1=3 4=1 5=1 6=936 7=104 9=6 -23310=2,1.666667e-01,5.000000e-01 Convolution Conv_24 1 1 hardswish_4.tmp_0 conv2d_227.tmp_0 0=40 1=1 5=1 6=4160 BinaryOp Add_9 2 1 conv2d_225.tmp_0_splitncnn_0 conv2d_227.tmp_0 elementwise_add_4 Split splitncnn_7 1 2 elementwise_add_4 elementwise_add_4_splitncnn_0 elementwise_add_4_splitncnn_1 Convolution Conv_25 1 1 elementwise_add_4_splitncnn_1 hardswish_5.tmp_0 0=96 1=1 5=1 6=3840 9=6 -23310=2,1.666667e-01,5.000000e-01 ConvolutionDepthWise Conv_26 1 1 hardswish_5.tmp_0 hardswish_6.tmp_0 0=96 1=3 4=1 5=1 6=864 7=96 9=6 -23310=2,1.666667e-01,5.000000e-01 Convolution Conv_27 1 1 hardswish_6.tmp_0 conv2d_229.tmp_0 0=40 1=1 5=1 6=3840 BinaryOp Add_12 2 1 elementwise_add_4_splitncnn_0 conv2d_229.tmp_0 elementwise_add_5 Split splitncnn_8 1 2 elementwise_add_5 elementwise_add_5_splitncnn_0 elementwise_add_5_splitncnn_1 Convolution Conv_28 1 1 elementwise_add_5_splitncnn_1 hardswish_7.tmp_0 0=96 1=1 5=1 6=3840 9=6 -23310=2,1.666667e-01,5.000000e-01 ConvolutionDepthWise Conv_29 1 1 hardswish_7.tmp_0 hardswish_8.tmp_0 0=96 1=3 4=1 5=1 6=864 7=96 9=6 -23310=2,1.666667e-01,5.000000e-01 Convolution Conv_30 1 1 hardswish_8.tmp_0 conv2d_231.tmp_0 0=40 1=1 5=1 6=3840 BinaryOp Add_15 2 1 elementwise_add_5_splitncnn_0 conv2d_231.tmp_0 elementwise_add_6 Convolution Conv_31 1 1 elementwise_add_6 hardswish_9.tmp_0 0=240 1=1 5=1 6=9600 9=6 -23310=2,1.666667e-01,5.000000e-01 ConvolutionDepthWise Conv_32 1 1 hardswish_9.tmp_0 hardswish_10.tmp_0 0=240 1=3 4=1 5=1 6=2160 7=240 9=6 -23310=2,1.666667e-01,5.000000e-01 Convolution Conv_33 1 1 hardswish_10.tmp_0 conv2d_233.tmp_0 0=56 1=1 5=1 6=13440 Split splitncnn_9 1 2 conv2d_233.tmp_0 conv2d_233.tmp_0_splitncnn_0 conv2d_233.tmp_0_splitncnn_1 Convolution Conv_34 1 1 conv2d_233.tmp_0_splitncnn_1 hardswish_11.tmp_0 0=336 1=1 5=1 6=18816 9=6 -23310=2,1.666667e-01,5.000000e-01 ConvolutionDepthWise Conv_35 1 1 hardswish_11.tmp_0 hardswish_12.tmp_0 0=336 1=3 4=1 5=1 6=3024 7=336 9=6 -23310=2,1.666667e-01,5.000000e-01 Convolution Conv_36 1 1 hardswish_12.tmp_0 conv2d_235.tmp_0 0=56 1=1 5=1 6=18816 BinaryOp Add_20 2 1 conv2d_233.tmp_0_splitncnn_0 conv2d_235.tmp_0 elementwise_add_7 Split splitncnn_10 1 2 elementwise_add_7 elementwise_add_7_splitncnn_0 elementwise_add_7_splitncnn_1 Convolution Conv_37 1 1 elementwise_add_7_splitncnn_1 hardswish_13.tmp_0 0=336 1=1 5=1 6=18816 9=6 -23310=2,1.666667e-01,5.000000e-01 ConvolutionDepthWise Conv_38 1 1 hardswish_13.tmp_0 hardswish_14.tmp_0 0=336 1=5 3=2 4=2 5=1 6=8400 7=336 9=6 -23310=2,1.666667e-01,5.000000e-01 Convolution Conv_39 1 1 hardswish_14.tmp_0 conv2d_237.tmp_0 0=80 1=1 5=1 6=26880 Split splitncnn_11 1 2 conv2d_237.tmp_0 conv2d_237.tmp_0_splitncnn_0 conv2d_237.tmp_0_splitncnn_1 Convolution Conv_40 1 1 conv2d_237.tmp_0_splitncnn_1 hardswish_15.tmp_0 0=480 1=1 5=1 6=38400 9=6 -23310=2,1.666667e-01,5.000000e-01 ConvolutionDepthWise Conv_41 1 1 hardswish_15.tmp_0 hardswish_16.tmp_0 0=480 1=5 4=2 5=1 6=12000 7=480 9=6 -23310=2,1.666667e-01,5.000000e-01 Convolution Conv_42 1 1 hardswish_16.tmp_0 conv2d_239.tmp_0 0=80 1=1 5=1 6=38400 BinaryOp Add_25 2 1 conv2d_237.tmp_0_splitncnn_0 conv2d_239.tmp_0 elementwise_add_8 Split splitncnn_12 1 2 elementwise_add_8 elementwise_add_8_splitncnn_0 elementwise_add_8_splitncnn_1 Convolution Conv_43 1 1 elementwise_add_8_splitncnn_1 hardswish_17.tmp_0 0=480 1=1 5=1 6=38400 9=6 -23310=2,1.666667e-01,5.000000e-01 ConvolutionDepthWise Conv_44 1 1 hardswish_17.tmp_0 hardswish_18.tmp_0 0=480 1=5 4=2 5=1 6=12000 7=480 9=6 -23310=2,1.666667e-01,5.000000e-01 Convolution Conv_45 1 1 hardswish_18.tmp_0 conv2d_241.tmp_0 0=80 1=1 5=1 6=38400 BinaryOp Add_28 2 1 elementwise_add_8_splitncnn_0 conv2d_241.tmp_0 elementwise_add_9 Convolution Conv_46 1 1 elementwise_add_9 hardswish_19.tmp_0 0=480 1=1 5=1 6=38400 9=6 -23310=2,1.666667e-01,5.000000e-01 Convolution Conv_47 1 1 hardswish_19.tmp_0 conv2d_243.tmp_0 0=96 1=1 6=46080 Split splitncnn_13 1 3 conv2d_243.tmp_0 conv2d_243.tmp_0_splitncnn_0 conv2d_243.tmp_0_splitncnn_1 conv2d_243.tmp_0_splitncnn_2 Pooling GlobalAveragePool_0 1 1 conv2d_243.tmp_0_splitncnn_2 pool2d_0.tmp_0 0=1 4=1 InnerProduct Conv_48 1 1 pool2d_0.tmp_0 relu_12.tmp_0 0=24 1=1 2=2304 9=1 InnerProduct Conv_49 1 1 relu_12.tmp_0 conv2d_245.tmp_0 0=96 1=1 2=2304 HardSigmoid HardSigmoid_0 1 1 conv2d_245.tmp_0 hardsigmoid_0.tmp_0 BinaryOp Mul_20 2 1 conv2d_243.tmp_0_splitncnn_1 hardsigmoid_0.tmp_0 tmp_0 0=2 BinaryOp Add_32 2 1 conv2d_243.tmp_0_splitncnn_0 tmp_0 tmp_1 Split splitncnn_14 1 2 tmp_1 tmp_1_splitncnn_0 tmp_1_splitncnn_1 Convolution Conv_50 1 1 elementwise_add_7_splitncnn_0 conv2d_246.tmp_0 0=96 1=1 6=5376 Split splitncnn_15 1 3 conv2d_246.tmp_0 conv2d_246.tmp_0_splitncnn_0 conv2d_246.tmp_0_splitncnn_1 conv2d_246.tmp_0_splitncnn_2 Pooling GlobalAveragePool_1 1 1 conv2d_246.tmp_0_splitncnn_2 pool2d_1.tmp_0 0=1 4=1 InnerProduct Conv_51 1 1 pool2d_1.tmp_0 relu_13.tmp_0 0=24 1=1 2=2304 9=1 InnerProduct Conv_52 1 1 relu_13.tmp_0 conv2d_248.tmp_0 0=96 1=1 2=2304 HardSigmoid HardSigmoid_1 1 1 conv2d_248.tmp_0 hardsigmoid_1.tmp_0 BinaryOp Mul_21 2 1 conv2d_246.tmp_0_splitncnn_1 hardsigmoid_1.tmp_0 tmp_2 0=2 BinaryOp Add_35 2 1 conv2d_246.tmp_0_splitncnn_0 tmp_2 tmp_3 Convolution Conv_53 1 1 elementwise_add_3_splitncnn_0 conv2d_249.tmp_0 0=96 1=1 6=2304 Split splitncnn_16 1 3 conv2d_249.tmp_0 conv2d_249.tmp_0_splitncnn_0 conv2d_249.tmp_0_splitncnn_1 conv2d_249.tmp_0_splitncnn_2 Pooling GlobalAveragePool_2 1 1 conv2d_249.tmp_0_splitncnn_2 pool2d_2.tmp_0 0=1 4=1 InnerProduct Conv_54 1 1 pool2d_2.tmp_0 relu_14.tmp_0 0=24 1=1 2=2304 9=1 InnerProduct Conv_55 1 1 relu_14.tmp_0 conv2d_251.tmp_0 0=96 1=1 2=2304 HardSigmoid HardSigmoid_2 1 1 conv2d_251.tmp_0 hardsigmoid_2.tmp_0 BinaryOp Mul_22 2 1 conv2d_249.tmp_0_splitncnn_1 hardsigmoid_2.tmp_0 tmp_4 0=2 BinaryOp Add_38 2 1 conv2d_249.tmp_0_splitncnn_0 tmp_4 tmp_5 Convolution Conv_56 1 1 elementwise_add_1_splitncnn_0 conv2d_252.tmp_0 0=96 1=1 6=1536 Split splitncnn_17 1 3 conv2d_252.tmp_0 conv2d_252.tmp_0_splitncnn_0 conv2d_252.tmp_0_splitncnn_1 conv2d_252.tmp_0_splitncnn_2 Pooling GlobalAveragePool_3 1 1 conv2d_252.tmp_0_splitncnn_2 pool2d_3.tmp_0 0=1 4=1 InnerProduct Conv_57 1 1 pool2d_3.tmp_0 relu_15.tmp_0 0=24 1=1 2=2304 9=1 InnerProduct Conv_58 1 1 relu_15.tmp_0 conv2d_254.tmp_0 0=96 1=1 2=2304 HardSigmoid HardSigmoid_3 1 1 conv2d_254.tmp_0 hardsigmoid_3.tmp_0 BinaryOp Mul_23 2 1 conv2d_252.tmp_0_splitncnn_1 hardsigmoid_3.tmp_0 tmp_6 0=2 BinaryOp Add_41 2 1 conv2d_252.tmp_0_splitncnn_0 tmp_6 tmp_7 Interp Resize_0 1 1 tmp_1_splitncnn_1 nearest_interp_v2_0.tmp_0 0=1 1=2.000000e+00 2=2.000000e+00 BinaryOp Add_42 2 1 tmp_3 nearest_interp_v2_0.tmp_0 tmp_8 Split splitncnn_18 1 2 tmp_8 tmp_8_splitncnn_0 tmp_8_splitncnn_1 Interp Resize_1 1 1 tmp_8_splitncnn_1 nearest_interp_v2_1.tmp_0 0=1 1=2.000000e+00 2=2.000000e+00 BinaryOp Add_43 2 1 tmp_5 nearest_interp_v2_1.tmp_0 tmp_9 Split splitncnn_19 1 2 tmp_9 tmp_9_splitncnn_0 tmp_9_splitncnn_1 Interp Resize_2 1 1 tmp_9_splitncnn_1 nearest_interp_v2_2.tmp_0 0=1 1=2.000000e+00 2=2.000000e+00 BinaryOp Add_44 2 1 tmp_7 nearest_interp_v2_2.tmp_0 tmp_10 Convolution Conv_59 1 1 tmp_1_splitncnn_0 conv2d_255.tmp_0 0=24 1=3 4=1 6=20736 Split splitncnn_20 1 3 conv2d_255.tmp_0 conv2d_255.tmp_0_splitncnn_0 conv2d_255.tmp_0_splitncnn_1 conv2d_255.tmp_0_splitncnn_2 Pooling GlobalAveragePool_4 1 1 conv2d_255.tmp_0_splitncnn_2 pool2d_4.tmp_0 0=1 4=1 InnerProduct Conv_60 1 1 pool2d_4.tmp_0 relu_16.tmp_0 0=6 1=1 2=144 9=1 InnerProduct Conv_61 1 1 relu_16.tmp_0 conv2d_257.tmp_0 0=24 1=1 2=144 HardSigmoid HardSigmoid_4 1 1 conv2d_257.tmp_0 hardsigmoid_4.tmp_0 BinaryOp Mul_24 2 1 conv2d_255.tmp_0_splitncnn_1 hardsigmoid_4.tmp_0 tmp_11 0=2 BinaryOp Add_47 2 1 conv2d_255.tmp_0_splitncnn_0 tmp_11 tmp_12 Convolution Conv_62 1 1 tmp_8_splitncnn_0 conv2d_258.tmp_0 0=24 1=3 4=1 6=20736 Split splitncnn_21 1 3 conv2d_258.tmp_0 conv2d_258.tmp_0_splitncnn_0 conv2d_258.tmp_0_splitncnn_1 conv2d_258.tmp_0_splitncnn_2 Pooling GlobalAveragePool_5 1 1 conv2d_258.tmp_0_splitncnn_2 pool2d_5.tmp_0 0=1 4=1 InnerProduct Conv_63 1 1 pool2d_5.tmp_0 relu_17.tmp_0 0=6 1=1 2=144 9=1 InnerProduct Conv_64 1 1 relu_17.tmp_0 conv2d_260.tmp_0 0=24 1=1 2=144 HardSigmoid HardSigmoid_5 1 1 conv2d_260.tmp_0 hardsigmoid_5.tmp_0 BinaryOp Mul_25 2 1 conv2d_258.tmp_0_splitncnn_1 hardsigmoid_5.tmp_0 tmp_13 0=2 BinaryOp Add_50 2 1 conv2d_258.tmp_0_splitncnn_0 tmp_13 tmp_14 Convolution Conv_65 1 1 tmp_9_splitncnn_0 conv2d_261.tmp_0 0=24 1=3 4=1 6=20736 Split splitncnn_22 1 3 conv2d_261.tmp_0 conv2d_261.tmp_0_splitncnn_0 conv2d_261.tmp_0_splitncnn_1 conv2d_261.tmp_0_splitncnn_2 Pooling GlobalAveragePool_6 1 1 conv2d_261.tmp_0_splitncnn_2 pool2d_6.tmp_0 0=1 4=1 InnerProduct Conv_66 1 1 pool2d_6.tmp_0 relu_18.tmp_0 0=6 1=1 2=144 9=1 InnerProduct Conv_67 1 1 relu_18.tmp_0 conv2d_263.tmp_0 0=24 1=1 2=144 HardSigmoid HardSigmoid_6 1 1 conv2d_263.tmp_0 hardsigmoid_6.tmp_0 BinaryOp Mul_26 2 1 conv2d_261.tmp_0_splitncnn_1 hardsigmoid_6.tmp_0 tmp_15 0=2 BinaryOp Add_53 2 1 conv2d_261.tmp_0_splitncnn_0 tmp_15 tmp_16 Convolution Conv_68 1 1 tmp_10 conv2d_264.tmp_0 0=24 1=3 4=1 6=20736 Split splitncnn_23 1 3 conv2d_264.tmp_0 conv2d_264.tmp_0_splitncnn_0 conv2d_264.tmp_0_splitncnn_1 conv2d_264.tmp_0_splitncnn_2 Pooling GlobalAveragePool_7 1 1 conv2d_264.tmp_0_splitncnn_2 pool2d_7.tmp_0 0=1 4=1 InnerProduct Conv_69 1 1 pool2d_7.tmp_0 relu_19.tmp_0 0=6 1=1 2=144 9=1 InnerProduct Conv_70 1 1 relu_19.tmp_0 conv2d_266.tmp_0 0=24 1=1 2=144 HardSigmoid HardSigmoid_7 1 1 conv2d_266.tmp_0 hardsigmoid_7.tmp_0 BinaryOp Mul_27 2 1 conv2d_264.tmp_0_splitncnn_1 hardsigmoid_7.tmp_0 tmp_17 0=2 BinaryOp Add_56 2 1 conv2d_264.tmp_0_splitncnn_0 tmp_17 tmp_18 Interp Resize_3 1 1 tmp_12 nearest_interp_v2_3.tmp_0 0=1 1=8.000000e+00 2=8.000000e+00 Interp Resize_4 1 1 tmp_14 nearest_interp_v2_4.tmp_0 0=1 1=4.000000e+00 2=4.000000e+00 Interp Resize_5 1 1 tmp_16 nearest_interp_v2_5.tmp_0 0=1 1=2.000000e+00 2=2.000000e+00 Concat Concat_0 4 1 nearest_interp_v2_3.tmp_0 nearest_interp_v2_4.tmp_0 nearest_interp_v2_5.tmp_0 tmp_18 concat_0.tmp_0 Convolution Conv_71 1 1 concat_0.tmp_0 batch_norm_47.tmp_4 0=24 1=3 4=1 5=1 6=20736 9=1 Deconvolution ConvTranspose_0 1 1 batch_norm_47.tmp_4 elementwise_add_10.tmp_0 0=24 1=2 3=2 5=1 6=2304 BatchNorm BatchNormalization_48 1 1 elementwise_add_10.tmp_0 batch_norm_48.tmp_3 0=24 ReLU Relu_21 1 1 batch_norm_48.tmp_3 batch_norm_48.tmp_4 Deconvolution ConvTranspose_1 1 1 batch_norm_48.tmp_4 output 0=1 1=2 3=2 5=1 6=96 9=4 ================================================ FILE: app/src/main/assets/ch_PP-OCRv3_rec.param ================================================ 7767517 116 127 Input input 0 1 input Convolution Conv_0 1 1 input batch_norm_27.tmp_4 0=16 1=3 3=2 4=1 5=1 6=432 9=6 -23310=2,1.666667e-01,5.000000e-01 ConvolutionDepthWise Conv_1 1 1 batch_norm_27.tmp_4 batch_norm_28.tmp_4 0=16 1=3 4=1 5=1 6=144 7=16 9=6 -23310=2,1.666667e-01,5.000000e-01 Convolution Conv_2 1 1 batch_norm_28.tmp_4 batch_norm_29.tmp_4 0=32 1=1 5=1 6=512 9=6 -23310=2,1.666667e-01,5.000000e-01 ConvolutionDepthWise Conv_3 1 1 batch_norm_29.tmp_4 batch_norm_30.tmp_4 0=32 1=3 4=1 5=1 6=288 7=32 9=6 -23310=2,1.666667e-01,5.000000e-01 Convolution Conv_4 1 1 batch_norm_30.tmp_4 batch_norm_31.tmp_4 0=64 1=1 5=1 6=2048 9=6 -23310=2,1.666667e-01,5.000000e-01 ConvolutionDepthWise Conv_5 1 1 batch_norm_31.tmp_4 batch_norm_32.tmp_4 0=64 1=3 4=1 5=1 6=576 7=64 9=6 -23310=2,1.666667e-01,5.000000e-01 Convolution Conv_6 1 1 batch_norm_32.tmp_4 batch_norm_33.tmp_4 0=64 1=1 5=1 6=4096 9=6 -23310=2,1.666667e-01,5.000000e-01 ConvolutionDepthWise Conv_7 1 1 batch_norm_33.tmp_4 batch_norm_34.tmp_4 0=64 1=3 13=2 4=1 5=1 6=576 7=64 9=6 -23310=2,1.666667e-01,5.000000e-01 Convolution Conv_8 1 1 batch_norm_34.tmp_4 batch_norm_35.tmp_4 0=128 1=1 5=1 6=8192 9=6 -23310=2,1.666667e-01,5.000000e-01 ConvolutionDepthWise Conv_9 1 1 batch_norm_35.tmp_4 batch_norm_36.tmp_4 0=128 1=3 4=1 5=1 6=1152 7=128 9=6 -23310=2,1.666667e-01,5.000000e-01 Convolution Conv_10 1 1 batch_norm_36.tmp_4 batch_norm_37.tmp_4 0=128 1=1 5=1 6=16384 9=6 -23310=2,1.666667e-01,5.000000e-01 ConvolutionDepthWise Conv_11 1 1 batch_norm_37.tmp_4 batch_norm_38.tmp_4 0=128 1=3 13=2 4=1 5=1 6=1152 7=128 9=6 -23310=2,1.666667e-01,5.000000e-01 Convolution Conv_12 1 1 batch_norm_38.tmp_4 batch_norm_39.tmp_4 0=256 1=1 5=1 6=32768 9=6 -23310=2,1.666667e-01,5.000000e-01 ConvolutionDepthWise Conv_13 1 1 batch_norm_39.tmp_4 batch_norm_40.tmp_4 0=256 1=5 4=2 5=1 6=6400 7=256 9=6 -23310=2,1.666667e-01,5.000000e-01 Convolution Conv_14 1 1 batch_norm_40.tmp_4 batch_norm_41.tmp_4 0=256 1=1 5=1 6=65536 9=6 -23310=2,1.666667e-01,5.000000e-01 ConvolutionDepthWise Conv_15 1 1 batch_norm_41.tmp_4 batch_norm_42.tmp_4 0=256 1=5 4=2 5=1 6=6400 7=256 9=6 -23310=2,1.666667e-01,5.000000e-01 Convolution Conv_16 1 1 batch_norm_42.tmp_4 batch_norm_43.tmp_4 0=256 1=1 5=1 6=65536 9=6 -23310=2,1.666667e-01,5.000000e-01 ConvolutionDepthWise Conv_17 1 1 batch_norm_43.tmp_4 batch_norm_44.tmp_4 0=256 1=5 4=2 5=1 6=6400 7=256 9=6 -23310=2,1.666667e-01,5.000000e-01 Convolution Conv_18 1 1 batch_norm_44.tmp_4 batch_norm_45.tmp_4 0=256 1=1 5=1 6=65536 9=6 -23310=2,1.666667e-01,5.000000e-01 ConvolutionDepthWise Conv_19 1 1 batch_norm_45.tmp_4 batch_norm_46.tmp_4 0=256 1=5 4=2 5=1 6=6400 7=256 9=6 -23310=2,1.666667e-01,5.000000e-01 Convolution Conv_20 1 1 batch_norm_46.tmp_4 batch_norm_47.tmp_4 0=256 1=1 5=1 6=65536 9=6 -23310=2,1.666667e-01,5.000000e-01 ConvolutionDepthWise Conv_21 1 1 batch_norm_47.tmp_4 batch_norm_48.tmp_4 0=256 1=5 4=2 5=1 6=6400 7=256 9=6 -23310=2,1.666667e-01,5.000000e-01 Convolution Conv_22 1 1 batch_norm_48.tmp_4 batch_norm_49.tmp_4 0=256 1=1 5=1 6=65536 9=6 -23310=2,1.666667e-01,5.000000e-01 ConvolutionDepthWise Conv_23 1 1 batch_norm_49.tmp_4 batch_norm_50.tmp_4 0=256 1=5 13=2 4=2 5=1 6=6400 7=256 9=6 -23310=2,1.666667e-01,5.000000e-01 Split splitncnn_0 1 2 batch_norm_50.tmp_4 batch_norm_50.tmp_4_splitncnn_0 batch_norm_50.tmp_4_splitncnn_1 Pooling GlobalAveragePool_0 1 1 batch_norm_50.tmp_4_splitncnn_1 pool2d_3.tmp_0 0=1 4=1 InnerProduct Conv_24 1 1 pool2d_3.tmp_0 relu_2.tmp_0 0=64 1=1 2=16384 9=1 InnerProduct Conv_25 1 1 relu_2.tmp_0 conv2d_110.tmp_0 0=256 1=1 2=16384 HardSigmoid HardSigmoid_0 1 1 conv2d_110.tmp_0 hardsigmoid_2.tmp_0 0=1.666667e-01 BinaryOp Mul_24 2 1 batch_norm_50.tmp_4_splitncnn_0 hardsigmoid_2.tmp_0 elementwise_mul_2 0=2 Convolution Conv_26 1 1 elementwise_mul_2 batch_norm_51.tmp_4 0=512 1=1 5=1 6=131072 9=6 -23310=2,1.666667e-01,5.000000e-01 ConvolutionDepthWise Conv_27 1 1 batch_norm_51.tmp_4 batch_norm_52.tmp_4 0=512 1=5 3=2 13=1 4=2 5=1 6=12800 7=512 9=6 -23310=2,1.666667e-01,5.000000e-01 Split splitncnn_1 1 2 batch_norm_52.tmp_4 batch_norm_52.tmp_4_splitncnn_0 batch_norm_52.tmp_4_splitncnn_1 Pooling GlobalAveragePool_1 1 1 batch_norm_52.tmp_4_splitncnn_1 pool2d_4.tmp_0 0=1 4=1 InnerProduct Conv_28 1 1 pool2d_4.tmp_0 relu_3.tmp_0 0=128 1=1 2=65536 9=1 InnerProduct Conv_29 1 1 relu_3.tmp_0 conv2d_113.tmp_0 0=512 1=1 2=65536 HardSigmoid HardSigmoid_1 1 1 conv2d_113.tmp_0 hardsigmoid_3.tmp_0 0=1.666667e-01 BinaryOp Mul_27 2 1 batch_norm_52.tmp_4_splitncnn_0 hardsigmoid_3.tmp_0 elementwise_mul_3 0=2 Convolution Conv_30 1 1 elementwise_mul_3 batch_norm_53.tmp_4 0=512 1=1 5=1 6=262144 9=6 -23310=2,1.666667e-01,5.000000e-01 Pooling AveragePool_0 1 1 batch_norm_53.tmp_4 pool2d_5.tmp_0 0=1 1=2 2=2 5=1 Split splitncnn_2 1 2 pool2d_5.tmp_0 pool2d_5.tmp_0_splitncnn_0 pool2d_5.tmp_0_splitncnn_1 Convolution Conv_31 1 1 pool2d_5.tmp_0_splitncnn_1 conv2d_115.tmp_0 0=64 1=3 4=1 5=1 6=294912 Swish Mul_29 1 1 conv2d_115.tmp_0 swish_21.tmp_0 Convolution Conv_32 1 1 swish_21.tmp_0 conv2d_116.tmp_0 0=120 1=1 5=1 6=7680 Swish Mul_30 1 1 conv2d_116.tmp_0 swish_22.tmp_0 Reshape Reshape_4 1 1 swish_22.tmp_0 flatten_1.tmp_0 0=-1 1=120 Permute Transpose_0 1 1 flatten_1.tmp_0 transpose_9.tmp_0 0=1 Split splitncnn_3 1 2 transpose_9.tmp_0 transpose_9.tmp_0_splitncnn_0 transpose_9.tmp_0_splitncnn_1 LayerNorm Add_32 1 1 transpose_9.tmp_0_splitncnn_1 layer_norm_15.tmp_2 0=120 1=1.000000e-05 InnerProduct MatMul_0 1 1 layer_norm_15.tmp_2 linear_35.tmp_1 0=360 1=1 2=43200 Reshape Reshape_7 1 1 linear_35.tmp_1 reshape2_5.tmp_0 0=15 1=8 2=-1 11=3 Permute Transpose_1 1 1 reshape2_5.tmp_0 transpose_10.tmp_0 0=8 Split splitncnn_4 1 3 transpose_10.tmp_0 transpose_10.tmp_0_splitncnn_0 transpose_10.tmp_0_splitncnn_1 transpose_10.tmp_0_splitncnn_2 Crop Slice_2 1 1 transpose_10.tmp_0_splitncnn_2 Slice_2 -23309=1,0 -23310=1,1 -23311=1,0 Reshape Squeeze_0 1 1 Slice_2 transpose_10.tmp_0_slice_0 0=15 1=-1 2=8 BinaryOp Mul_32 1 1 transpose_10.tmp_0_slice_0 Mul_32 0=2 1=1 2=2.581989e-01 Crop Slice_3 1 1 transpose_10.tmp_0_splitncnn_1 Slice_3 -23309=1,1 -23310=1,2 -23311=1,0 Reshape Squeeze_1 1 1 Slice_3 transpose_10.tmp_0_slice_1 0=15 1=-1 2=8 Crop Slice_4 1 1 transpose_10.tmp_0_splitncnn_0 Slice_4 -23309=1,2 -23310=1,3 -23311=1,0 Reshape Squeeze_2 1 1 Slice_4 transpose_10.tmp_0_slice_2 0=15 1=-1 2=8 Permute Transpose_2 1 1 transpose_10.tmp_0_slice_1 transpose_11.tmp_0 0=1 MatMul MatMul_1 2 1 Mul_32 transpose_11.tmp_0 matmul_v2_4.tmp_0 Softmax Softmax_0 1 1 matmul_v2_4.tmp_0 softmax_3.tmp_0 0=-1 1=1 MatMul MatMul_2 2 1 softmax_3.tmp_0 transpose_10.tmp_0_slice_2 matmul_v2_5.tmp_0 Permute Transpose_3 1 1 matmul_v2_5.tmp_0 transpose_12.tmp_0 0=2 Reshape Reshape_8 1 1 transpose_12.tmp_0 reshape2_6.tmp_0 0=120 1=-1 InnerProduct MatMul_3 1 1 reshape2_6.tmp_0 linear_36.tmp_1 0=120 1=1 2=14400 BinaryOp Add_36 2 1 transpose_9.tmp_0_splitncnn_0 linear_36.tmp_1 tmp_7 Split splitncnn_5 1 2 tmp_7 tmp_7_splitncnn_0 tmp_7_splitncnn_1 LayerNorm Add_38 1 1 tmp_7_splitncnn_1 layer_norm_16.tmp_2 0=120 1=1.000000e-05 InnerProduct MatMul_4 1 1 layer_norm_16.tmp_2 linear_37.tmp_1 0=240 1=1 2=28800 Swish Mul_34 1 1 linear_37.tmp_1 swish_23.tmp_0 InnerProduct MatMul_5 1 1 swish_23.tmp_0 linear_38.tmp_1 0=120 1=1 2=28800 BinaryOp Add_41 2 1 tmp_7_splitncnn_0 linear_38.tmp_1 tmp_8 Split splitncnn_6 1 2 tmp_8 tmp_8_splitncnn_0 tmp_8_splitncnn_1 LayerNorm Add_43 1 1 tmp_8_splitncnn_1 layer_norm_17.tmp_2 0=120 1=1.000000e-05 InnerProduct MatMul_6 1 1 layer_norm_17.tmp_2 linear_39.tmp_1 0=360 1=1 2=43200 Reshape Reshape_13 1 1 linear_39.tmp_1 reshape2_7.tmp_0 0=15 1=8 2=-1 11=3 Permute Transpose_4 1 1 reshape2_7.tmp_0 transpose_13.tmp_0 0=8 Split splitncnn_7 1 3 transpose_13.tmp_0 transpose_13.tmp_0_splitncnn_0 transpose_13.tmp_0_splitncnn_1 transpose_13.tmp_0_splitncnn_2 Crop Slice_7 1 1 transpose_13.tmp_0_splitncnn_2 Slice_7 -23309=1,0 -23310=1,1 -23311=1,0 Reshape Squeeze_3 1 1 Slice_7 transpose_13.tmp_0_slice_0 0=15 1=-1 2=8 BinaryOp Mul_36 1 1 transpose_13.tmp_0_slice_0 Mul_36 0=2 1=1 2=2.581989e-01 Crop Slice_8 1 1 transpose_13.tmp_0_splitncnn_1 Slice_8 -23309=1,1 -23310=1,2 -23311=1,0 Reshape Squeeze_4 1 1 Slice_8 transpose_13.tmp_0_slice_1 0=15 1=-1 2=8 Crop Slice_9 1 1 transpose_13.tmp_0_splitncnn_0 Slice_9 -23309=1,2 -23310=1,3 -23311=1,0 Reshape Squeeze_5 1 1 Slice_9 transpose_13.tmp_0_slice_2 0=15 1=-1 2=8 Permute Transpose_5 1 1 transpose_13.tmp_0_slice_1 transpose_14.tmp_0 0=1 MatMul MatMul_7 2 1 Mul_36 transpose_14.tmp_0 matmul_v2_6.tmp_0 Softmax Softmax_1 1 1 matmul_v2_6.tmp_0 softmax_4.tmp_0 0=2 1=1 MatMul MatMul_8 2 1 softmax_4.tmp_0 transpose_13.tmp_0_slice_2 matmul_v2_7.tmp_0 Permute Transpose_6 1 1 matmul_v2_7.tmp_0 transpose_15.tmp_0 0=2 Reshape Reshape_14 1 1 transpose_15.tmp_0 reshape2_8.tmp_0 0=120 1=-1 InnerProduct MatMul_9 1 1 reshape2_8.tmp_0 linear_40.tmp_1 0=120 1=1 2=14400 BinaryOp Add_47 2 1 tmp_8_splitncnn_0 linear_40.tmp_1 tmp_10 Split splitncnn_8 1 2 tmp_10 tmp_10_splitncnn_0 tmp_10_splitncnn_1 LayerNorm Add_49 1 1 tmp_10_splitncnn_1 layer_norm_18.tmp_2 0=120 1=1.000000e-05 InnerProduct MatMul_10 1 1 layer_norm_18.tmp_2 linear_41.tmp_1 0=240 1=1 2=28800 Swish Mul_38 1 1 linear_41.tmp_1 swish_24.tmp_0 InnerProduct MatMul_11 1 1 swish_24.tmp_0 linear_42.tmp_1 0=120 1=1 2=28800 BinaryOp Add_52 2 1 tmp_10_splitncnn_0 linear_42.tmp_1 tmp_11 LayerNorm Add_54 1 1 tmp_11 layer_norm_19.tmp_2 0=120 1=1.000000e-06 Reshape Reshape_19 1 1 layer_norm_19.tmp_2 reshape2_9.tmp_0 0=120 1=-1 2=1 Permute Transpose_7 1 1 reshape2_9.tmp_0 transpose_16.tmp_0 0=4 Convolution Conv_33 1 1 transpose_16.tmp_0 conv2d_117.tmp_0 0=512 1=1 5=1 6=61440 Swish Mul_40 1 1 conv2d_117.tmp_0 swish_25.tmp_0 Concat Concat_1 2 1 pool2d_5.tmp_0_splitncnn_0 swish_25.tmp_0 concat_1.tmp_0 Convolution Conv_34 1 1 concat_1.tmp_0 conv2d_118.tmp_0 0=64 1=3 4=1 5=1 6=589824 Swish Mul_41 1 1 conv2d_118.tmp_0 swish_26.tmp_0 Convolution Conv_35 1 1 swish_26.tmp_0 conv2d_119.tmp_0 0=64 1=1 5=1 6=4096 Swish Mul_42 1 1 conv2d_119.tmp_0 swish_27.tmp_0 Permute Transpose_8 1 1 swish_27.tmp_0 squeeze_1.tmp_0 0=3 Squeeze Squeeze_6 1 1 squeeze_1.tmp_0 transpose_17.tmp_0 -23303=1,0 InnerProduct MatMul_12 1 1 transpose_17.tmp_0 linear_43.tmp_1 0=6625 1=1 2=424000 Softmax Softmax_2 1 1 linear_43.tmp_1 out 0=-1 1=1 ================================================ FILE: app/src/main/assets/cls-sim-op.param ================================================ 7767517 135 151 Input input 0 1 input Convolution Conv_0 1 1 input 250 0=8 1=3 3=2 4=1 5=1 6=216 HardSwish Div_9 1 1 250 258 0=1.666667e-01 Convolution Conv_10 1 1 258 261 0=8 1=1 5=1 6=64 9=1 ConvolutionDepthWise Conv_13 1 1 261 264 0=8 1=3 13=2 4=1 5=1 6=72 7=8 9=1 Split splitncnn_0 1 2 264 264_splitncnn_0 264_splitncnn_1 Pooling GlobalAveragePool_16 1 1 264_splitncnn_1 265 0=1 4=1 InnerProduct Conv_17 1 1 265 267 0=2 1=1 2=16 9=1 InnerProduct Conv_19 1 1 267 268 0=8 1=1 2=16 BinaryOp Mul_21 1 1 268 270 0=2 1=1 2=1.200000e+00 HardSigmoid Div_28 1 1 270 277 0=1.666667e-01 BinaryOp Mul_29 2 1 264_splitncnn_0 277 278 0=2 Convolution Conv_30 1 1 278 280 0=8 1=1 5=1 6=64 Convolution Conv_32 1 1 280 283 0=24 1=1 5=1 6=192 9=1 ConvolutionDepthWise Conv_35 1 1 283 286 0=24 1=3 13=2 4=1 5=1 6=216 7=24 9=1 Convolution Conv_38 1 1 286 288 0=8 1=1 5=1 6=192 Split splitncnn_1 1 2 288 288_splitncnn_0 288_splitncnn_1 Convolution Conv_40 1 1 288_splitncnn_1 291 0=32 1=1 5=1 6=256 9=1 ConvolutionDepthWise Conv_43 1 1 291 294 0=32 1=3 4=1 5=1 6=288 7=32 9=1 Convolution Conv_46 1 1 294 296 0=8 1=1 5=1 6=256 BinaryOp Add_48 2 1 288_splitncnn_0 296 297 Convolution Conv_49 1 1 297 299 0=32 1=1 5=1 6=256 HardSwish Div_58 1 1 299 307 0=1.666667e-01 ConvolutionDepthWise Conv_59 1 1 307 309 0=32 1=5 13=2 4=2 5=1 6=800 7=32 HardSwish Div_68 1 1 309 317 0=1.666667e-01 Split splitncnn_2 1 2 317 317_splitncnn_0 317_splitncnn_1 Pooling GlobalAveragePool_69 1 1 317_splitncnn_1 318 0=1 4=1 InnerProduct Conv_70 1 1 318 320 0=8 1=1 2=256 9=1 InnerProduct Conv_72 1 1 320 321 0=32 1=1 2=256 BinaryOp Mul_74 1 1 321 323 0=2 1=1 2=1.200000e+00 HardSigmoid Div_81 1 1 323 330 0=1.666667e-01 BinaryOp Mul_82 2 1 317_splitncnn_0 330 331 0=2 Convolution Conv_83 1 1 331 333 0=16 1=1 5=1 6=512 Split splitncnn_3 1 2 333 333_splitncnn_0 333_splitncnn_1 Convolution Conv_85 1 1 333_splitncnn_1 335 0=88 1=1 5=1 6=1408 HardSwish Div_94 1 1 335 343 0=1.666667e-01 ConvolutionDepthWise Conv_95 1 1 343 345 0=88 1=5 4=2 5=1 6=2200 7=88 HardSwish Div_104 1 1 345 353 0=1.666667e-01 Split splitncnn_4 1 2 353 353_splitncnn_0 353_splitncnn_1 Pooling GlobalAveragePool_105 1 1 353_splitncnn_1 354 0=1 4=1 InnerProduct Conv_106 1 1 354 356 0=22 1=1 2=1936 9=1 InnerProduct Conv_108 1 1 356 357 0=88 1=1 2=1936 BinaryOp Mul_110 1 1 357 359 0=2 1=1 2=1.200000e+00 HardSigmoid Div_117 1 1 359 366 0=1.666667e-01 BinaryOp Mul_118 2 1 353_splitncnn_0 366 367 0=2 Convolution Conv_119 1 1 367 369 0=16 1=1 5=1 6=1408 BinaryOp Add_121 2 1 333_splitncnn_0 369 370 Split splitncnn_5 1 2 370 370_splitncnn_0 370_splitncnn_1 Convolution Conv_122 1 1 370_splitncnn_1 372 0=88 1=1 5=1 6=1408 HardSwish Div_131 1 1 372 380 0=1.666667e-01 ConvolutionDepthWise Conv_132 1 1 380 382 0=88 1=5 4=2 5=1 6=2200 7=88 HardSwish Div_141 1 1 382 390 0=1.666667e-01 Split splitncnn_6 1 2 390 390_splitncnn_0 390_splitncnn_1 Pooling GlobalAveragePool_142 1 1 390_splitncnn_1 391 0=1 4=1 InnerProduct Conv_143 1 1 391 393 0=22 1=1 2=1936 9=1 InnerProduct Conv_145 1 1 393 394 0=88 1=1 2=1936 BinaryOp Mul_147 1 1 394 396 0=2 1=1 2=1.200000e+00 HardSigmoid Div_154 1 1 396 403 0=1.666667e-01 BinaryOp Mul_155 2 1 390_splitncnn_0 403 404 0=2 Convolution Conv_156 1 1 404 406 0=16 1=1 5=1 6=1408 BinaryOp Add_158 2 1 370_splitncnn_0 406 407 Split splitncnn_7 1 2 407 407_splitncnn_0 407_splitncnn_1 Convolution Conv_159 1 1 407_splitncnn_1 409 0=40 1=1 5=1 6=640 HardSwish Div_168 1 1 409 417 0=1.666667e-01 ConvolutionDepthWise Conv_169 1 1 417 419 0=40 1=5 4=2 5=1 6=1000 7=40 HardSwish Div_178 1 1 419 427 0=1.666667e-01 Split splitncnn_8 1 2 427 427_splitncnn_0 427_splitncnn_1 Pooling GlobalAveragePool_179 1 1 427_splitncnn_1 428 0=1 4=1 InnerProduct Conv_180 1 1 428 430 0=10 1=1 2=400 9=1 InnerProduct Conv_182 1 1 430 431 0=40 1=1 2=400 BinaryOp Mul_184 1 1 431 433 0=2 1=1 2=1.200000e+00 HardSigmoid Div_191 1 1 433 440 0=1.666667e-01 BinaryOp Mul_192 2 1 427_splitncnn_0 440 441 0=2 Convolution Conv_193 1 1 441 443 0=16 1=1 5=1 6=640 BinaryOp Add_195 2 1 407_splitncnn_0 443 444 Split splitncnn_9 1 2 444 444_splitncnn_0 444_splitncnn_1 Convolution Conv_196 1 1 444_splitncnn_1 446 0=48 1=1 5=1 6=768 HardSwish Div_205 1 1 446 454 0=1.666667e-01 ConvolutionDepthWise Conv_206 1 1 454 456 0=48 1=5 4=2 5=1 6=1200 7=48 HardSwish Div_215 1 1 456 464 0=1.666667e-01 Split splitncnn_10 1 2 464 464_splitncnn_0 464_splitncnn_1 Pooling GlobalAveragePool_216 1 1 464_splitncnn_1 465 0=1 4=1 InnerProduct Conv_217 1 1 465 467 0=12 1=1 2=576 9=1 InnerProduct Conv_219 1 1 467 468 0=48 1=1 2=576 BinaryOp Mul_221 1 1 468 470 0=2 1=1 2=1.200000e+00 HardSigmoid Div_228 1 1 470 477 0=1.666667e-01 BinaryOp Mul_229 2 1 464_splitncnn_0 477 478 0=2 Convolution Conv_230 1 1 478 480 0=16 1=1 5=1 6=768 BinaryOp Add_232 2 1 444_splitncnn_0 480 481 Convolution Conv_233 1 1 481 483 0=104 1=1 5=1 6=1664 HardSwish Div_242 1 1 483 491 0=1.666667e-01 ConvolutionDepthWise Conv_243 1 1 491 493 0=104 1=5 13=2 4=2 5=1 6=2600 7=104 HardSwish Div_252 1 1 493 501 0=1.666667e-01 Split splitncnn_11 1 2 501 501_splitncnn_0 501_splitncnn_1 Pooling GlobalAveragePool_253 1 1 501_splitncnn_1 502 0=1 4=1 InnerProduct Conv_254 1 1 502 504 0=26 1=1 2=2704 9=1 InnerProduct Conv_256 1 1 504 505 0=104 1=1 2=2704 BinaryOp Mul_258 1 1 505 507 0=2 1=1 2=1.200000e+00 HardSigmoid Div_265 1 1 507 514 0=1.666667e-01 BinaryOp Mul_266 2 1 501_splitncnn_0 514 515 0=2 Convolution Conv_267 1 1 515 517 0=32 1=1 5=1 6=3328 Split splitncnn_12 1 2 517 517_splitncnn_0 517_splitncnn_1 Convolution Conv_269 1 1 517_splitncnn_1 519 0=200 1=1 5=1 6=6400 HardSwish Div_278 1 1 519 527 0=1.666667e-01 ConvolutionDepthWise Conv_279 1 1 527 529 0=200 1=5 4=2 5=1 6=5000 7=200 HardSwish Div_288 1 1 529 537 0=1.666667e-01 Split splitncnn_13 1 2 537 537_splitncnn_0 537_splitncnn_1 Pooling GlobalAveragePool_289 1 1 537_splitncnn_1 538 0=1 4=1 InnerProduct Conv_290 1 1 538 540 0=50 1=1 2=10000 9=1 InnerProduct Conv_292 1 1 540 541 0=200 1=1 2=10000 BinaryOp Mul_294 1 1 541 543 0=2 1=1 2=1.200000e+00 HardSigmoid Div_301 1 1 543 550 0=1.666667e-01 BinaryOp Mul_302 2 1 537_splitncnn_0 550 551 0=2 Convolution Conv_303 1 1 551 553 0=32 1=1 5=1 6=6400 BinaryOp Add_305 2 1 517_splitncnn_0 553 554 Split splitncnn_14 1 2 554 554_splitncnn_0 554_splitncnn_1 Convolution Conv_306 1 1 554_splitncnn_1 556 0=200 1=1 5=1 6=6400 HardSwish Div_315 1 1 556 564 0=1.666667e-01 ConvolutionDepthWise Conv_316 1 1 564 566 0=200 1=5 4=2 5=1 6=5000 7=200 HardSwish Div_325 1 1 566 574 0=1.666667e-01 Split splitncnn_15 1 2 574 574_splitncnn_0 574_splitncnn_1 Pooling GlobalAveragePool_326 1 1 574_splitncnn_1 575 0=1 4=1 InnerProduct Conv_327 1 1 575 577 0=50 1=1 2=10000 9=1 InnerProduct Conv_329 1 1 577 578 0=200 1=1 2=10000 BinaryOp Mul_331 1 1 578 580 0=2 1=1 2=1.200000e+00 HardSigmoid Div_338 1 1 580 587 0=1.666667e-01 BinaryOp Mul_339 2 1 574_splitncnn_0 587 588 0=2 Convolution Conv_340 1 1 588 590 0=32 1=1 5=1 6=6400 BinaryOp Add_342 2 1 554_splitncnn_0 590 591 Convolution Conv_343 1 1 591 593 0=200 1=1 5=1 6=6400 HardSwish Div_352 1 1 593 601 0=1.666667e-01 Pooling MaxPool_353 1 1 601 602 1=2 2=2 5=1 Pooling GlobalAveragePool_354 1 1 602 613 0=1 4=1 InnerProduct Gemm_365 1 1 613 614 0=2 1=1 2=400 Softmax Softmax_366 1 1 614 out ================================================ FILE: app/src/main/assets/det-sim-op.param ================================================ 7767517 115 131 Input input 0 1 input0 Convolution Conv_0 1 1 input0 322 0=8 1=3 3=2 4=1 5=1 6=216 HardSwish Div_9 1 1 322 330 0=1.666667e-01 Split splitncnn_0 1 2 330 330_splitncnn_0 330_splitncnn_1 Convolution Conv_10 1 1 330_splitncnn_1 333 0=8 1=1 5=1 6=64 9=1 ConvolutionDepthWise Conv_13 1 1 333 336 0=8 1=3 4=1 5=1 6=72 7=8 9=1 Convolution Conv_16 1 1 336 338 0=8 1=1 5=1 6=64 BinaryOp Add_18 2 1 330_splitncnn_0 338 339 Convolution Conv_19 1 1 339 342 0=32 1=1 5=1 6=256 9=1 ConvolutionDepthWise Conv_22 1 1 342 345 0=32 1=3 3=2 4=1 5=1 6=288 7=32 9=1 Convolution Conv_25 1 1 345 347 0=16 1=1 5=1 6=512 Split splitncnn_1 1 2 347 347_splitncnn_0 347_splitncnn_1 Convolution Conv_27 1 1 347_splitncnn_1 350 0=40 1=1 5=1 6=640 9=1 ConvolutionDepthWise Conv_30 1 1 350 353 0=40 1=3 4=1 5=1 6=360 7=40 9=1 Convolution Conv_33 1 1 353 355 0=16 1=1 5=1 6=640 BinaryOp Add_35 2 1 347_splitncnn_0 355 356 Split splitncnn_2 1 2 356 356_splitncnn_0 356_splitncnn_1 Convolution Conv_36 1 1 356_splitncnn_1 359 0=40 1=1 5=1 6=640 9=1 ConvolutionDepthWise Conv_39 1 1 359 362 0=40 1=5 3=2 4=2 5=1 6=1000 7=40 9=1 Convolution Conv_42 1 1 362 364 0=24 1=1 5=1 6=960 Split splitncnn_3 1 2 364 364_splitncnn_0 364_splitncnn_1 Convolution Conv_44 1 1 364_splitncnn_1 367 0=64 1=1 5=1 6=1536 9=1 ConvolutionDepthWise Conv_47 1 1 367 370 0=64 1=5 4=2 5=1 6=1600 7=64 9=1 Convolution Conv_50 1 1 370 372 0=24 1=1 5=1 6=1536 BinaryOp Add_52 2 1 364_splitncnn_0 372 373 Split splitncnn_4 1 2 373 373_splitncnn_0 373_splitncnn_1 Convolution Conv_53 1 1 373_splitncnn_1 376 0=64 1=1 5=1 6=1536 9=1 ConvolutionDepthWise Conv_56 1 1 376 379 0=64 1=5 4=2 5=1 6=1600 7=64 9=1 Convolution Conv_59 1 1 379 381 0=24 1=1 5=1 6=1536 BinaryOp Add_61 2 1 373_splitncnn_0 381 382 Split splitncnn_5 1 2 382 382_splitncnn_0 382_splitncnn_1 Convolution Conv_62 1 1 382_splitncnn_1 384 0=120 1=1 5=1 6=2880 HardSwish Div_71 1 1 384 392 0=1.666667e-01 ConvolutionDepthWise Conv_72 1 1 392 394 0=120 1=3 3=2 4=1 5=1 6=1080 7=120 HardSwish Div_81 1 1 394 402 0=1.666667e-01 Convolution Conv_82 1 1 402 404 0=40 1=1 5=1 6=4800 Split splitncnn_6 1 2 404 404_splitncnn_0 404_splitncnn_1 Convolution Conv_84 1 1 404_splitncnn_1 406 0=104 1=1 5=1 6=4160 HardSwish Div_93 1 1 406 414 0=1.666667e-01 ConvolutionDepthWise Conv_94 1 1 414 416 0=104 1=3 4=1 5=1 6=936 7=104 HardSwish Div_103 1 1 416 424 0=1.666667e-01 Convolution Conv_104 1 1 424 426 0=40 1=1 5=1 6=4160 BinaryOp Add_106 2 1 404_splitncnn_0 426 427 Split splitncnn_7 1 2 427 427_splitncnn_0 427_splitncnn_1 Convolution Conv_107 1 1 427_splitncnn_1 429 0=96 1=1 5=1 6=3840 HardSwish Div_116 1 1 429 437 0=1.666667e-01 ConvolutionDepthWise Conv_117 1 1 437 439 0=96 1=3 4=1 5=1 6=864 7=96 HardSwish Div_126 1 1 439 447 0=1.666667e-01 Convolution Conv_127 1 1 447 449 0=40 1=1 5=1 6=3840 BinaryOp Add_129 2 1 427_splitncnn_0 449 450 Split splitncnn_8 1 2 450 450_splitncnn_0 450_splitncnn_1 Convolution Conv_130 1 1 450_splitncnn_1 452 0=96 1=1 5=1 6=3840 HardSwish Div_139 1 1 452 460 0=1.666667e-01 ConvolutionDepthWise Conv_140 1 1 460 462 0=96 1=3 4=1 5=1 6=864 7=96 HardSwish Div_149 1 1 462 470 0=1.666667e-01 Convolution Conv_150 1 1 470 472 0=40 1=1 5=1 6=3840 BinaryOp Add_152 2 1 450_splitncnn_0 472 473 Convolution Conv_153 1 1 473 475 0=240 1=1 5=1 6=9600 HardSwish Div_162 1 1 475 483 0=1.666667e-01 ConvolutionDepthWise Conv_163 1 1 483 485 0=240 1=3 4=1 5=1 6=2160 7=240 HardSwish Div_172 1 1 485 493 0=1.666667e-01 Convolution Conv_173 1 1 493 495 0=56 1=1 5=1 6=13440 Split splitncnn_9 1 2 495 495_splitncnn_0 495_splitncnn_1 Convolution Conv_175 1 1 495_splitncnn_1 497 0=336 1=1 5=1 6=18816 HardSwish Div_184 1 1 497 505 0=1.666667e-01 ConvolutionDepthWise Conv_185 1 1 505 507 0=336 1=3 4=1 5=1 6=3024 7=336 HardSwish Div_194 1 1 507 515 0=1.666667e-01 Convolution Conv_195 1 1 515 517 0=56 1=1 5=1 6=18816 BinaryOp Add_197 2 1 495_splitncnn_0 517 518 Split splitncnn_10 1 2 518 518_splitncnn_0 518_splitncnn_1 Convolution Conv_198 1 1 518_splitncnn_1 520 0=336 1=1 5=1 6=18816 HardSwish Div_207 1 1 520 528 0=1.666667e-01 ConvolutionDepthWise Conv_208 1 1 528 530 0=336 1=5 3=2 4=2 5=1 6=8400 7=336 HardSwish Div_217 1 1 530 538 0=1.666667e-01 Convolution Conv_218 1 1 538 540 0=80 1=1 5=1 6=26880 Split splitncnn_11 1 2 540 540_splitncnn_0 540_splitncnn_1 Convolution Conv_220 1 1 540_splitncnn_1 542 0=480 1=1 5=1 6=38400 HardSwish Div_229 1 1 542 550 0=1.666667e-01 ConvolutionDepthWise Conv_230 1 1 550 552 0=480 1=5 4=2 5=1 6=12000 7=480 HardSwish Div_239 1 1 552 560 0=1.666667e-01 Convolution Conv_240 1 1 560 562 0=80 1=1 5=1 6=38400 BinaryOp Add_242 2 1 540_splitncnn_0 562 563 Split splitncnn_12 1 2 563 563_splitncnn_0 563_splitncnn_1 Convolution Conv_243 1 1 563_splitncnn_1 565 0=480 1=1 5=1 6=38400 HardSwish Div_252 1 1 565 573 0=1.666667e-01 ConvolutionDepthWise Conv_253 1 1 573 575 0=480 1=5 4=2 5=1 6=12000 7=480 HardSwish Div_262 1 1 575 583 0=1.666667e-01 Convolution Conv_263 1 1 583 585 0=80 1=1 5=1 6=38400 BinaryOp Add_265 2 1 563_splitncnn_0 585 586 Convolution Conv_266 1 1 586 588 0=480 1=1 5=1 6=38400 HardSwish Div_275 1 1 588 596 0=1.666667e-01 Convolution Conv_276 1 1 596 597 0=96 1=1 6=46080 Split splitncnn_13 1 2 597 597_splitncnn_0 597_splitncnn_1 Convolution Conv_277 1 1 518_splitncnn_0 598 0=96 1=1 6=5376 Convolution Conv_278 1 1 382_splitncnn_0 599 0=96 1=1 6=2304 Convolution Conv_279 1 1 356_splitncnn_0 600 0=96 1=1 6=1536 Interp Resize_281 1 1 597_splitncnn_1 610 0=1 1=2.000000e+00 2=2.000000e+00 BinaryOp Add_282 2 1 598 610 611 Split splitncnn_14 1 2 611 611_splitncnn_0 611_splitncnn_1 Interp Resize_284 1 1 611_splitncnn_1 621 0=1 1=2.000000e+00 2=2.000000e+00 BinaryOp Add_285 2 1 599 621 622 Split splitncnn_15 1 2 622 622_splitncnn_0 622_splitncnn_1 Interp Resize_287 1 1 622_splitncnn_1 632 0=1 1=2.000000e+00 2=2.000000e+00 BinaryOp Add_288 2 1 600 632 633 Convolution Conv_289 1 1 597_splitncnn_0 634 0=24 1=3 4=1 6=20736 Convolution Conv_290 1 1 611_splitncnn_0 635 0=24 1=3 4=1 6=20736 Convolution Conv_291 1 1 622_splitncnn_0 636 0=24 1=3 4=1 6=20736 Convolution Conv_292 1 1 633 637 0=24 1=3 4=1 6=20736 Interp Resize_294 1 1 634 647 0=1 1=8.000000e+00 2=8.000000e+00 Interp Resize_296 1 1 635 657 0=1 1=4.000000e+00 2=4.000000e+00 Interp Resize_298 1 1 636 667 0=1 1=2.000000e+00 2=2.000000e+00 Concat Concat_299 4 1 647 657 667 637 668 Convolution Conv_300 1 1 668 671 0=24 1=3 4=1 5=1 6=20736 9=1 Deconvolution ConvTranspose_303 1 1 671 674 0=24 1=2 3=2 5=1 6=2304 9=1 Deconvolution ConvTranspose_306 1 1 674 out1 0=1 1=2 3=2 5=1 6=96 9=4 ================================================ FILE: app/src/main/assets/paddleocr_keys.txt ================================================ ' 疗 绚 诚 娇 溜 题 贿 者 廖 更 纳 加 奉 公 一 就 汴 计 与 路 房 原 妇 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 羙 悵 燙 曉 員 組 徹 艷 痠 鋼 鼙 縮 細 嚒 爯 ≠ 維 " 鱻 壇 厍 帰 浥 犇 薡 軎 ² 應 醜 刪 緻 鶴 賜 噁 軌 尨 镔 鷺 槗 彌 葚 濛 請 溇 緹 賢 訪 獴 瑅 資 縤 陣 蕟 栢 韻 祼 恁 伢 謝 劃 涑 總 衖 踺 砋 凉 籃 駿 苼 瘋 昽 紡 驊 腎 ﹗ 響 杋 剛 嚴 禪 歓 槍 傘 檸 檫 炣 勢 鏜 鎢 銑 尐 減 奪 惡 θ 僮 婭 臘 ū ì 殻 鉄 ∑ 蛲 焼 緖 續 紹 懮 ================================================ FILE: app/src/main/assets/pdocrv2.0_det-op.param ================================================ 7767517 115 131 Input input 0 1 input0 Convolution Conv_0 1 1 input0 647 0=8 1=3 3=2 4=1 5=1 6=216 HardSwish Div_8 1 1 647 330 0=1.666667e-01 Split splitncnn_0 1 2 330 330_splitncnn_0 330_splitncnn_1 Convolution Conv_9 1 1 330_splitncnn_1 333 0=8 1=1 5=1 6=64 9=1 ConvolutionDepthWise Conv_11 1 1 333 336 0=8 1=3 4=1 5=1 6=72 7=8 9=1 Convolution Conv_13 1 1 336 656 0=8 1=1 5=1 6=64 BinaryOp Add_14 2 1 330_splitncnn_0 656 339 Convolution Conv_15 1 1 339 342 0=32 1=1 5=1 6=256 9=1 ConvolutionDepthWise Conv_17 1 1 342 345 0=32 1=3 3=2 4=1 5=1 6=288 7=32 9=1 Convolution Conv_19 1 1 345 665 0=16 1=1 5=1 6=512 Split splitncnn_1 1 2 665 665_splitncnn_0 665_splitncnn_1 Convolution Conv_20 1 1 665_splitncnn_1 350 0=40 1=1 5=1 6=640 9=1 ConvolutionDepthWise Conv_22 1 1 350 353 0=40 1=3 4=1 5=1 6=360 7=40 9=1 Convolution Conv_24 1 1 353 674 0=16 1=1 5=1 6=640 BinaryOp Add_25 2 1 665_splitncnn_0 674 356 Split splitncnn_2 1 2 356 356_splitncnn_0 356_splitncnn_1 Convolution Conv_26 1 1 356_splitncnn_1 359 0=40 1=1 5=1 6=640 9=1 ConvolutionDepthWise Conv_28 1 1 359 362 0=40 1=5 3=2 4=2 5=1 6=1000 7=40 9=1 Convolution Conv_30 1 1 362 683 0=24 1=1 5=1 6=960 Split splitncnn_3 1 2 683 683_splitncnn_0 683_splitncnn_1 Convolution Conv_31 1 1 683_splitncnn_1 367 0=64 1=1 5=1 6=1536 9=1 ConvolutionDepthWise Conv_33 1 1 367 370 0=64 1=5 4=2 5=1 6=1600 7=64 9=1 Convolution Conv_35 1 1 370 692 0=24 1=1 5=1 6=1536 BinaryOp Add_36 2 1 683_splitncnn_0 692 373 Split splitncnn_4 1 2 373 373_splitncnn_0 373_splitncnn_1 Convolution Conv_37 1 1 373_splitncnn_1 376 0=64 1=1 5=1 6=1536 9=1 ConvolutionDepthWise Conv_39 1 1 376 379 0=64 1=5 4=2 5=1 6=1600 7=64 9=1 Convolution Conv_41 1 1 379 701 0=24 1=1 5=1 6=1536 BinaryOp Add_42 2 1 373_splitncnn_0 701 382 Split splitncnn_5 1 2 382 382_splitncnn_0 382_splitncnn_1 Convolution Conv_43 1 1 382_splitncnn_1 704 0=120 1=1 5=1 6=2880 HardSwish Div_51 1 1 704 392 0=1.666667e-01 ConvolutionDepthWise Conv_52 1 1 392 707 0=120 1=3 3=2 4=1 5=1 6=1080 7=120 HardSwish Div_60 1 1 707 402 0=1.666667e-01 Convolution Conv_61 1 1 402 710 0=40 1=1 5=1 6=4800 Split splitncnn_6 1 2 710 710_splitncnn_0 710_splitncnn_1 Convolution Conv_62 1 1 710_splitncnn_1 713 0=104 1=1 5=1 6=4160 HardSwish Div_70 1 1 713 414 0=1.666667e-01 ConvolutionDepthWise Conv_71 1 1 414 716 0=104 1=3 4=1 5=1 6=936 7=104 HardSwish Div_79 1 1 716 424 0=1.666667e-01 Convolution Conv_80 1 1 424 719 0=40 1=1 5=1 6=4160 BinaryOp Add_81 2 1 710_splitncnn_0 719 427 Split splitncnn_7 1 2 427 427_splitncnn_0 427_splitncnn_1 Convolution Conv_82 1 1 427_splitncnn_1 722 0=96 1=1 5=1 6=3840 HardSwish Div_90 1 1 722 437 0=1.666667e-01 ConvolutionDepthWise Conv_91 1 1 437 725 0=96 1=3 4=1 5=1 6=864 7=96 HardSwish Div_99 1 1 725 447 0=1.666667e-01 Convolution Conv_100 1 1 447 728 0=40 1=1 5=1 6=3840 BinaryOp Add_101 2 1 427_splitncnn_0 728 450 Split splitncnn_8 1 2 450 450_splitncnn_0 450_splitncnn_1 Convolution Conv_102 1 1 450_splitncnn_1 731 0=96 1=1 5=1 6=3840 HardSwish Div_110 1 1 731 460 0=1.666667e-01 ConvolutionDepthWise Conv_111 1 1 460 734 0=96 1=3 4=1 5=1 6=864 7=96 HardSwish Div_119 1 1 734 470 0=1.666667e-01 Convolution Conv_120 1 1 470 737 0=40 1=1 5=1 6=3840 BinaryOp Add_121 2 1 450_splitncnn_0 737 473 Convolution Conv_122 1 1 473 740 0=240 1=1 5=1 6=9600 HardSwish Div_130 1 1 740 483 0=1.666667e-01 ConvolutionDepthWise Conv_131 1 1 483 743 0=240 1=3 4=1 5=1 6=2160 7=240 HardSwish Div_139 1 1 743 493 0=1.666667e-01 Convolution Conv_140 1 1 493 746 0=56 1=1 5=1 6=13440 Split splitncnn_9 1 2 746 746_splitncnn_0 746_splitncnn_1 Convolution Conv_141 1 1 746_splitncnn_1 749 0=336 1=1 5=1 6=18816 HardSwish Div_149 1 1 749 505 0=1.666667e-01 ConvolutionDepthWise Conv_150 1 1 505 752 0=336 1=3 4=1 5=1 6=3024 7=336 HardSwish Div_158 1 1 752 515 0=1.666667e-01 Convolution Conv_159 1 1 515 755 0=56 1=1 5=1 6=18816 BinaryOp Add_160 2 1 746_splitncnn_0 755 518 Split splitncnn_10 1 2 518 518_splitncnn_0 518_splitncnn_1 Convolution Conv_161 1 1 518_splitncnn_1 758 0=336 1=1 5=1 6=18816 HardSwish Div_169 1 1 758 528 0=1.666667e-01 ConvolutionDepthWise Conv_170 1 1 528 761 0=336 1=5 3=2 4=2 5=1 6=8400 7=336 HardSwish Div_178 1 1 761 538 0=1.666667e-01 Convolution Conv_179 1 1 538 764 0=80 1=1 5=1 6=26880 Split splitncnn_11 1 2 764 764_splitncnn_0 764_splitncnn_1 Convolution Conv_180 1 1 764_splitncnn_1 767 0=480 1=1 5=1 6=38400 HardSwish Div_188 1 1 767 550 0=1.666667e-01 ConvolutionDepthWise Conv_189 1 1 550 770 0=480 1=5 4=2 5=1 6=12000 7=480 HardSwish Div_197 1 1 770 560 0=1.666667e-01 Convolution Conv_198 1 1 560 773 0=80 1=1 5=1 6=38400 BinaryOp Add_199 2 1 764_splitncnn_0 773 563 Split splitncnn_12 1 2 563 563_splitncnn_0 563_splitncnn_1 Convolution Conv_200 1 1 563_splitncnn_1 776 0=480 1=1 5=1 6=38400 HardSwish Div_208 1 1 776 573 0=1.666667e-01 ConvolutionDepthWise Conv_209 1 1 573 779 0=480 1=5 4=2 5=1 6=12000 7=480 HardSwish Div_217 1 1 779 583 0=1.666667e-01 Convolution Conv_218 1 1 583 782 0=80 1=1 5=1 6=38400 BinaryOp Add_219 2 1 563_splitncnn_0 782 586 Convolution Conv_220 1 1 586 785 0=480 1=1 5=1 6=38400 HardSwish Div_228 1 1 785 596 0=1.666667e-01 Convolution Conv_229 1 1 596 597 0=96 1=1 6=46080 Split splitncnn_13 1 2 597 597_splitncnn_0 597_splitncnn_1 Convolution Conv_230 1 1 518_splitncnn_0 598 0=96 1=1 6=5376 Convolution Conv_231 1 1 382_splitncnn_0 599 0=96 1=1 6=2304 Convolution Conv_232 1 1 356_splitncnn_0 600 0=96 1=1 6=1536 Interp Resize_234 1 1 597_splitncnn_1 605 0=1 1=2.000000e+00 2=2.000000e+00 BinaryOp Add_235 2 1 598 605 606 Split splitncnn_14 1 2 606 606_splitncnn_0 606_splitncnn_1 Interp Resize_237 1 1 606_splitncnn_1 611 0=1 1=2.000000e+00 2=2.000000e+00 BinaryOp Add_238 2 1 599 611 612 Split splitncnn_15 1 2 612 612_splitncnn_0 612_splitncnn_1 Interp Resize_240 1 1 612_splitncnn_1 617 0=1 1=2.000000e+00 2=2.000000e+00 BinaryOp Add_241 2 1 600 617 618 Convolution Conv_242 1 1 597_splitncnn_0 619 0=24 1=3 4=1 6=20736 Convolution Conv_243 1 1 606_splitncnn_0 620 0=24 1=3 4=1 6=20736 Convolution Conv_244 1 1 612_splitncnn_0 621 0=24 1=3 4=1 6=20736 Convolution Conv_245 1 1 618 622 0=24 1=3 4=1 6=20736 Interp Resize_247 1 1 619 627 0=1 1=8.000000e+00 2=8.000000e+00 Interp Resize_249 1 1 620 632 0=1 1=4.000000e+00 2=4.000000e+00 Interp Resize_251 1 1 621 637 0=1 1=2.000000e+00 2=2.000000e+00 Concat Concat_252 4 1 627 632 637 622 638 Convolution Conv_253 1 1 638 641 0=24 1=3 4=1 5=1 6=20736 9=1 Deconvolution ConvTranspose_255 1 1 641 644 0=24 1=2 3=2 5=1 6=2304 9=1 Deconvolution ConvTranspose_258 1 1 644 out1 0=1 1=2 3=2 5=1 6=96 9=4 ================================================ FILE: app/src/main/assets/pdocrv2.0_rec-op.param ================================================ 7767517 75 77 Input input 0 1 input Convolution Conv_0 1 1 input 779 0=16 1=3 3=2 4=1 5=1 6=432 HardSwish Div_8 1 1 779 200 0=1.666667e-01 ConvolutionDepthWise Conv_9 1 1 200 782 0=16 1=3 4=1 5=1 6=144 7=16 HardSwish Div_17 1 1 782 210 0=1.666667e-01 Convolution Conv_18 1 1 210 785 0=32 1=1 5=1 6=512 HardSwish Div_26 1 1 785 220 0=1.666667e-01 ConvolutionDepthWise Conv_27 1 1 220 788 0=32 1=3 4=1 5=1 6=288 7=32 HardSwish Div_35 1 1 788 230 0=1.666667e-01 Convolution Conv_36 1 1 230 791 0=64 1=1 5=1 6=2048 HardSwish Div_44 1 1 791 240 0=1.666667e-01 ConvolutionDepthWise Conv_45 1 1 240 794 0=64 1=3 4=1 5=1 6=576 7=64 HardSwish Div_53 1 1 794 250 0=1.666667e-01 Convolution Conv_54 1 1 250 797 0=64 1=1 5=1 6=4096 HardSwish Div_62 1 1 797 260 0=1.666667e-01 ConvolutionDepthWise Conv_63 1 1 260 800 0=64 1=3 13=2 4=1 5=1 6=576 7=64 HardSwish Div_71 1 1 800 270 0=1.666667e-01 Convolution Conv_72 1 1 270 803 0=128 1=1 5=1 6=8192 HardSwish Div_80 1 1 803 280 0=1.666667e-01 ConvolutionDepthWise Conv_81 1 1 280 806 0=128 1=3 4=1 5=1 6=1152 7=128 HardSwish Div_89 1 1 806 290 0=1.666667e-01 Convolution Conv_90 1 1 290 809 0=128 1=1 5=1 6=16384 HardSwish Div_98 1 1 809 300 0=1.666667e-01 ConvolutionDepthWise Conv_99 1 1 300 812 0=128 1=3 13=2 4=1 5=1 6=1152 7=128 HardSwish Div_107 1 1 812 310 0=1.666667e-01 Convolution Conv_108 1 1 310 815 0=256 1=1 5=1 6=32768 HardSwish Div_116 1 1 815 320 0=1.666667e-01 ConvolutionDepthWise Conv_117 1 1 320 818 0=256 1=5 4=2 5=1 6=6400 7=256 HardSwish Div_125 1 1 818 330 0=1.666667e-01 Convolution Conv_126 1 1 330 821 0=256 1=1 5=1 6=65536 HardSwish Div_134 1 1 821 340 0=1.666667e-01 ConvolutionDepthWise Conv_135 1 1 340 824 0=256 1=5 4=2 5=1 6=6400 7=256 HardSwish Div_143 1 1 824 350 0=1.666667e-01 Convolution Conv_144 1 1 350 827 0=256 1=1 5=1 6=65536 HardSwish Div_152 1 1 827 360 0=1.666667e-01 ConvolutionDepthWise Conv_153 1 1 360 830 0=256 1=5 4=2 5=1 6=6400 7=256 HardSwish Div_161 1 1 830 370 0=1.666667e-01 Convolution Conv_162 1 1 370 833 0=256 1=1 5=1 6=65536 HardSwish Div_170 1 1 833 380 0=1.666667e-01 ConvolutionDepthWise Conv_171 1 1 380 836 0=256 1=5 4=2 5=1 6=6400 7=256 HardSwish Div_179 1 1 836 390 0=1.666667e-01 Convolution Conv_180 1 1 390 839 0=256 1=1 5=1 6=65536 HardSwish Div_188 1 1 839 400 0=1.666667e-01 ConvolutionDepthWise Conv_189 1 1 400 842 0=256 1=5 4=2 5=1 6=6400 7=256 HardSwish Div_197 1 1 842 410 0=1.666667e-01 Convolution Conv_198 1 1 410 845 0=256 1=1 5=1 6=65536 HardSwish Div_206 1 1 845 420 0=1.666667e-01 ConvolutionDepthWise Conv_207 1 1 420 848 0=256 1=5 13=2 4=2 5=1 6=6400 7=256 HardSwish Div_215 1 1 848 430 0=1.666667e-01 Split splitncnn_0 1 2 430 430_splitncnn_0 430_splitncnn_1 Pooling GlobalAveragePool_216 1 1 430_splitncnn_1 431 0=1 4=1 InnerProduct Conv_217 1 1 431 433 0=64 1=1 2=16384 9=1 InnerProduct Conv_219 1 1 433 434 0=256 1=1 2=16384 HardSigmoid Div_226 1 1 434 441 0=1.666667e-01 BinaryOp Mul_227 2 1 430_splitncnn_0 441 442 0=2 Convolution Conv_228 1 1 442 851 0=512 1=1 5=1 6=131072 HardSwish Div_236 1 1 851 452 0=1.666667e-01 ConvolutionDepthWise Conv_237 1 1 452 854 0=512 1=5 4=2 5=1 6=12800 7=512 HardSwish Div_245 1 1 854 462 0=1.666667e-01 Split splitncnn_1 1 2 462 462_splitncnn_0 462_splitncnn_1 Pooling GlobalAveragePool_246 1 1 462_splitncnn_1 463 0=1 4=1 InnerProduct Conv_247 1 1 463 465 0=128 1=1 2=65536 9=1 InnerProduct Conv_249 1 1 465 466 0=512 1=1 2=65536 HardSigmoid Div_256 1 1 466 473 0=1.666667e-01 BinaryOp Mul_257 2 1 462_splitncnn_0 473 474 0=2 Convolution Conv_258 1 1 474 857 0=512 1=1 5=1 6=262144 HardSwish Div_266 1 1 857 484 0=1.666667e-01 Pooling MaxPool_267 1 1 484 485 1=2 2=2 5=1 Reshape Reshape_281 1 1 485 499 0=-1 1=512 Permute Transpose_289 1 1 499 511 0=1 LSTM LSTM_298 1 1 511 641 0=64 1=262144 2=2 LSTM LSTM_310 1 1 641 771 0=64 1=65536 2=2 InnerProduct MatMul_315 1 1 771 774 0=96 1=1 2=12288 InnerProduct MatMul_317 1 1 774 777 0=6625 1=1 2=636000 Softmax Softmax_319 1 1 777 out 0=1 1=1 ================================================ FILE: app/src/main/assets/rec-sim-op.param ================================================ 7767517 138 154 Input input 0 1 input Convolution Conv_0 1 1 input 266 0=8 1=3 3=2 4=1 5=1 6=216 HardSwish Div_9 1 1 266 274 0=1.666667e-01 Convolution Conv_10 1 1 274 277 0=8 1=1 5=1 6=64 9=1 ConvolutionDepthWise Conv_13 1 1 277 280 0=8 1=3 4=1 5=1 6=72 7=8 9=1 Split splitncnn_0 1 2 280 280_splitncnn_0 280_splitncnn_1 Pooling GlobalAveragePool_16 1 1 280_splitncnn_1 281 0=1 4=1 InnerProduct Conv_17 1 1 281 283 0=2 1=1 2=16 9=1 InnerProduct Conv_19 1 1 283 284 0=8 1=1 2=16 BinaryOp Mul_21 1 1 284 286 0=2 1=1 2=1.200000e+00 HardSigmoid Div_28 1 1 286 293 0=1.666667e-01 BinaryOp Mul_29 2 1 280_splitncnn_0 293 294 0=2 Convolution Conv_30 1 1 294 296 0=8 1=1 5=1 6=64 Convolution Conv_32 1 1 296 299 0=40 1=1 5=1 6=320 9=1 ConvolutionDepthWise Conv_35 1 1 299 302 0=40 1=3 13=2 4=1 5=1 6=360 7=40 9=1 Convolution Conv_38 1 1 302 304 0=16 1=1 5=1 6=640 Split splitncnn_1 1 2 304 304_splitncnn_0 304_splitncnn_1 Convolution Conv_40 1 1 304_splitncnn_1 307 0=48 1=1 5=1 6=768 9=1 ConvolutionDepthWise Conv_43 1 1 307 310 0=48 1=3 4=1 5=1 6=432 7=48 9=1 Convolution Conv_46 1 1 310 312 0=16 1=1 5=1 6=768 BinaryOp Add_48 2 1 304_splitncnn_0 312 313 Convolution Conv_49 1 1 313 315 0=48 1=1 5=1 6=768 HardSwish Div_58 1 1 315 323 0=1.666667e-01 ConvolutionDepthWise Conv_59 1 1 323 325 0=48 1=5 13=2 4=2 5=1 6=1200 7=48 HardSwish Div_68 1 1 325 333 0=1.666667e-01 Split splitncnn_2 1 2 333 333_splitncnn_0 333_splitncnn_1 Pooling GlobalAveragePool_69 1 1 333_splitncnn_1 334 0=1 4=1 InnerProduct Conv_70 1 1 334 336 0=12 1=1 2=576 9=1 InnerProduct Conv_72 1 1 336 337 0=48 1=1 2=576 BinaryOp Mul_74 1 1 337 339 0=2 1=1 2=1.200000e+00 HardSigmoid Div_81 1 1 339 346 0=1.666667e-01 BinaryOp Mul_82 2 1 333_splitncnn_0 346 347 0=2 Convolution Conv_83 1 1 347 349 0=24 1=1 5=1 6=1152 Split splitncnn_3 1 2 349 349_splitncnn_0 349_splitncnn_1 Convolution Conv_85 1 1 349_splitncnn_1 351 0=120 1=1 5=1 6=2880 HardSwish Div_94 1 1 351 359 0=1.666667e-01 ConvolutionDepthWise Conv_95 1 1 359 361 0=120 1=5 4=2 5=1 6=3000 7=120 HardSwish Div_104 1 1 361 369 0=1.666667e-01 Split splitncnn_4 1 2 369 369_splitncnn_0 369_splitncnn_1 Pooling GlobalAveragePool_105 1 1 369_splitncnn_1 370 0=1 4=1 InnerProduct Conv_106 1 1 370 372 0=30 1=1 2=3600 9=1 InnerProduct Conv_108 1 1 372 373 0=120 1=1 2=3600 BinaryOp Mul_110 1 1 373 375 0=2 1=1 2=1.200000e+00 HardSigmoid Div_117 1 1 375 382 0=1.666667e-01 BinaryOp Mul_118 2 1 369_splitncnn_0 382 383 0=2 Convolution Conv_119 1 1 383 385 0=24 1=1 5=1 6=2880 BinaryOp Add_121 2 1 349_splitncnn_0 385 386 Split splitncnn_5 1 2 386 386_splitncnn_0 386_splitncnn_1 Convolution Conv_122 1 1 386_splitncnn_1 388 0=120 1=1 5=1 6=2880 HardSwish Div_131 1 1 388 396 0=1.666667e-01 ConvolutionDepthWise Conv_132 1 1 396 398 0=120 1=5 4=2 5=1 6=3000 7=120 HardSwish Div_141 1 1 398 406 0=1.666667e-01 Split splitncnn_6 1 2 406 406_splitncnn_0 406_splitncnn_1 Pooling GlobalAveragePool_142 1 1 406_splitncnn_1 407 0=1 4=1 InnerProduct Conv_143 1 1 407 409 0=30 1=1 2=3600 9=1 InnerProduct Conv_145 1 1 409 410 0=120 1=1 2=3600 BinaryOp Mul_147 1 1 410 412 0=2 1=1 2=1.200000e+00 HardSigmoid Div_154 1 1 412 419 0=1.666667e-01 BinaryOp Mul_155 2 1 406_splitncnn_0 419 420 0=2 Convolution Conv_156 1 1 420 422 0=24 1=1 5=1 6=2880 BinaryOp Add_158 2 1 386_splitncnn_0 422 423 Split splitncnn_7 1 2 423 423_splitncnn_0 423_splitncnn_1 Convolution Conv_159 1 1 423_splitncnn_1 425 0=64 1=1 5=1 6=1536 HardSwish Div_168 1 1 425 433 0=1.666667e-01 ConvolutionDepthWise Conv_169 1 1 433 435 0=64 1=5 4=2 5=1 6=1600 7=64 HardSwish Div_178 1 1 435 443 0=1.666667e-01 Split splitncnn_8 1 2 443 443_splitncnn_0 443_splitncnn_1 Pooling GlobalAveragePool_179 1 1 443_splitncnn_1 444 0=1 4=1 InnerProduct Conv_180 1 1 444 446 0=16 1=1 2=1024 9=1 InnerProduct Conv_182 1 1 446 447 0=64 1=1 2=1024 BinaryOp Mul_184 1 1 447 449 0=2 1=1 2=1.200000e+00 HardSigmoid Div_191 1 1 449 456 0=1.666667e-01 BinaryOp Mul_192 2 1 443_splitncnn_0 456 457 0=2 Convolution Conv_193 1 1 457 459 0=24 1=1 5=1 6=1536 BinaryOp Add_195 2 1 423_splitncnn_0 459 460 Split splitncnn_9 1 2 460 460_splitncnn_0 460_splitncnn_1 Convolution Conv_196 1 1 460_splitncnn_1 462 0=72 1=1 5=1 6=1728 HardSwish Div_205 1 1 462 470 0=1.666667e-01 ConvolutionDepthWise Conv_206 1 1 470 472 0=72 1=5 4=2 5=1 6=1800 7=72 HardSwish Div_215 1 1 472 480 0=1.666667e-01 Split splitncnn_10 1 2 480 480_splitncnn_0 480_splitncnn_1 Pooling GlobalAveragePool_216 1 1 480_splitncnn_1 481 0=1 4=1 InnerProduct Conv_217 1 1 481 483 0=18 1=1 2=1296 9=1 InnerProduct Conv_219 1 1 483 484 0=72 1=1 2=1296 BinaryOp Mul_221 1 1 484 486 0=2 1=1 2=1.200000e+00 HardSigmoid Div_228 1 1 486 493 0=1.666667e-01 BinaryOp Mul_229 2 1 480_splitncnn_0 493 494 0=2 Convolution Conv_230 1 1 494 496 0=24 1=1 5=1 6=1728 BinaryOp Add_232 2 1 460_splitncnn_0 496 497 Convolution Conv_233 1 1 497 499 0=144 1=1 5=1 6=3456 HardSwish Div_242 1 1 499 507 0=1.666667e-01 ConvolutionDepthWise Conv_243 1 1 507 509 0=144 1=5 13=2 4=2 5=1 6=3600 7=144 HardSwish Div_252 1 1 509 517 0=1.666667e-01 Split splitncnn_11 1 2 517 517_splitncnn_0 517_splitncnn_1 Pooling GlobalAveragePool_253 1 1 517_splitncnn_1 518 0=1 4=1 InnerProduct Conv_254 1 1 518 520 0=36 1=1 2=5184 9=1 InnerProduct Conv_256 1 1 520 521 0=144 1=1 2=5184 BinaryOp Mul_258 1 1 521 523 0=2 1=1 2=1.200000e+00 HardSigmoid Div_265 1 1 523 530 0=1.666667e-01 BinaryOp Mul_266 2 1 517_splitncnn_0 530 531 0=2 Convolution Conv_267 1 1 531 533 0=48 1=1 5=1 6=6912 Split splitncnn_12 1 2 533 533_splitncnn_0 533_splitncnn_1 Convolution Conv_269 1 1 533_splitncnn_1 535 0=288 1=1 5=1 6=13824 HardSwish Div_278 1 1 535 543 0=1.666667e-01 ConvolutionDepthWise Conv_279 1 1 543 545 0=288 1=5 4=2 5=1 6=7200 7=288 HardSwish Div_288 1 1 545 553 0=1.666667e-01 Split splitncnn_13 1 2 553 553_splitncnn_0 553_splitncnn_1 Pooling GlobalAveragePool_289 1 1 553_splitncnn_1 554 0=1 4=1 InnerProduct Conv_290 1 1 554 556 0=72 1=1 2=20736 9=1 InnerProduct Conv_292 1 1 556 557 0=288 1=1 2=20736 BinaryOp Mul_294 1 1 557 559 0=2 1=1 2=1.200000e+00 HardSigmoid Div_301 1 1 559 566 0=1.666667e-01 BinaryOp Mul_302 2 1 553_splitncnn_0 566 567 0=2 Convolution Conv_303 1 1 567 569 0=48 1=1 5=1 6=13824 BinaryOp Add_305 2 1 533_splitncnn_0 569 570 Split splitncnn_14 1 2 570 570_splitncnn_0 570_splitncnn_1 Convolution Conv_306 1 1 570_splitncnn_1 572 0=288 1=1 5=1 6=13824 HardSwish Div_315 1 1 572 580 0=1.666667e-01 ConvolutionDepthWise Conv_316 1 1 580 582 0=288 1=5 4=2 5=1 6=7200 7=288 HardSwish Div_325 1 1 582 590 0=1.666667e-01 Split splitncnn_15 1 2 590 590_splitncnn_0 590_splitncnn_1 Pooling GlobalAveragePool_326 1 1 590_splitncnn_1 591 0=1 4=1 InnerProduct Conv_327 1 1 591 593 0=72 1=1 2=20736 9=1 InnerProduct Conv_329 1 1 593 594 0=288 1=1 2=20736 BinaryOp Mul_331 1 1 594 596 0=2 1=1 2=1.200000e+00 HardSigmoid Div_338 1 1 596 603 0=1.666667e-01 BinaryOp Mul_339 2 1 590_splitncnn_0 603 604 0=2 Convolution Conv_340 1 1 604 606 0=48 1=1 5=1 6=13824 BinaryOp Add_342 2 1 570_splitncnn_0 606 607 Convolution Conv_343 1 1 607 609 0=288 1=1 5=1 6=13824 HardSwish Div_352 1 1 609 617 0=1.666667e-01 Pooling MaxPool_353 1 1 617 618 1=2 2=2 5=1 Reshape Squeeze_354 1 1 618 619 0=-1 1=288 Permute Transpose_362 1 1 619 631 0=1 LSTM LSTM_371 1 1 631 761 0=48 1=110592 2=2 LSTM LSTM_383 1 1 761 891 0=48 1=36864 2=2 InnerProduct MatMul_388 1 1 891 894 0=6625 1=1 2=636000 Softmax Softmax_390 1 1 894 out 0=1 1=1 ================================================ FILE: app/src/main/java/com/tencent/paddleocrncnn/MainActivity.java ================================================ // Tencent is pleased to support the open source community by making ncnn available. // // Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. // // Licensed under the BSD 3-Clause License (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // https://opensource.org/licenses/BSD-3-Clause // // 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.tencent.paddleocrncnn; import android.os.Build; import android.Manifest; import android.os.Environment; import android.app.Activity; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.media.ExifInterface; import android.graphics.Matrix; import android.net.Uri; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.ImageView; import java.io.FileNotFoundException; import java.io.IOException; import java.io.File; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v4.content.FileProvider; import android.provider.MediaStore; import android.content.pm.PackageManager; public class MainActivity extends Activity { private static final int TAKE_PHOTO = 1; private static final int SELECT_IMAGE = 2; private ImageView imageView; //private Bitmap bitmap = null; private Bitmap yourSelectedImage = null; private final String filePath = Environment.getExternalStorageDirectory() + File.separator + "output_image.jpg"; private Uri imageUri; private PaddleOCRNcnn paddleocrncnn = new PaddleOCRNcnn(); @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (grantResults != null && grantResults.length != 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { switch (requestCode) { case 1: { requestCamera(); } break; } } } /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); boolean ret_init = paddleocrncnn.Init(getAssets()); if (!ret_init) { Log.e("MainActivity", "paddleocrncnn Init failed"); } imageView = (ImageView) findViewById(R.id.imageView); Button buttonImage = (Button) findViewById(R.id.buttonImage); buttonImage.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View arg0) { Intent i = new Intent(Intent.ACTION_PICK); i.setType("image/*"); startActivityForResult(i, SELECT_IMAGE); } }); Button buttonCamera = (Button) findViewById(R.id.buttonCamera); buttonCamera.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View arg0) { requestPermission(); } }); Button buttonDetect = (Button) findViewById(R.id.buttonDetect); buttonDetect.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View arg0) { if (yourSelectedImage == null) return; Bitmap bitmap = yourSelectedImage.copy(Bitmap.Config.ARGB_8888, true); PaddleOCRNcnn.Obj[] objects = paddleocrncnn.Detect(bitmap, false); showObjects(objects); } }); Button buttonDetectGPU = (Button) findViewById(R.id.buttonDetectGPU); buttonDetectGPU.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View arg0) { if (yourSelectedImage == null) return; Bitmap bitmap = yourSelectedImage.copy(Bitmap.Config.ARGB_8888, true); PaddleOCRNcnn.Obj[] objects = paddleocrncnn.Detect(bitmap, true); showObjects(objects); } }); } private void requestPermission() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA}, 1); } else { requestCamera(); } } private void requestCamera() { File outputImage = new File(filePath); try { if (!outputImage.getParentFile().exists()) { outputImage.getParentFile().mkdirs(); } if (outputImage.exists()) { outputImage.delete(); } outputImage.createNewFile(); if (Build.VERSION.SDK_INT >= 24) { imageUri = FileProvider.getUriForFile(this, "com.tencent.paddleocrncnn.fileprovider", outputImage); } else { imageUri = Uri.fromFile(outputImage); } Intent intent = new Intent("android.media.action.IMAGE_CAPTURE"); intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri); startActivityForResult(intent, TAKE_PHOTO); } catch (IOException e) { e.printStackTrace(); } } private void showObjects(PaddleOCRNcnn.Obj[] objects) { if (objects == null) { imageView.setImageBitmap(yourSelectedImage); return; } // draw objects on bitmap Bitmap rgba = yourSelectedImage.copy(Bitmap.Config.ARGB_8888, true); final int[] colors = new int[] { Color.rgb( 54, 67, 244), Color.rgb( 99, 30, 233), Color.rgb(176, 39, 156), Color.rgb(183, 58, 103), Color.rgb(181, 81, 63), Color.rgb(243, 150, 33), Color.rgb(244, 169, 3), Color.rgb(212, 188, 0), Color.rgb(136, 150, 0), Color.rgb( 80, 175, 76), Color.rgb( 74, 195, 139), Color.rgb( 57, 220, 205), Color.rgb( 59, 235, 255), Color.rgb( 7, 193, 255), Color.rgb( 0, 152, 255), Color.rgb( 34, 87, 255), Color.rgb( 72, 85, 121), Color.rgb(158, 158, 158), Color.rgb(139, 125, 96) }; Canvas canvas = new Canvas(rgba); Paint paint = new Paint(); paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(4); Paint textbgpaint = new Paint(); textbgpaint.setColor(Color.WHITE); textbgpaint.setStyle(Paint.Style.FILL); Paint textpaint = new Paint(); textpaint.setColor(Color.BLACK); textpaint.setTextSize(56); textpaint.setTextAlign(Paint.Align.LEFT); for (int i = 0; i < objects.length; i++) { paint.setColor(colors[i % 19]); //canvas.drawRect(objects[i].x, objects[i].y, objects[i].x + objects[i].w, objects[i].y + objects[i].h, paint); canvas.drawLine(objects[i].x0,objects[i].y0,objects[i].x1,objects[i].y1,paint); canvas.drawLine(objects[i].x1,objects[i].y1,objects[i].x2,objects[i].y2,paint); canvas.drawLine(objects[i].x2,objects[i].y2,objects[i].x3,objects[i].y3,paint); canvas.drawLine(objects[i].x3,objects[i].y3,objects[i].x0,objects[i].y0,paint); // draw filled text inside image { String text = objects[i].label;// + " = " + String.format("%.1f", objects[i].prob * 100) + "%"; float text_width = textpaint.measureText(text); float text_height = - textpaint.ascent() + textpaint.descent(); float x = objects[i].x0; float y = objects[i].y0 - text_height; if (y < 0) y = 0; if (x + text_width > rgba.getWidth()) x = rgba.getWidth() - text_width; canvas.drawRect(x, y, x + text_width, y + text_height, textbgpaint); canvas.drawText(text, x, y - textpaint.ascent(), textpaint); } } imageView.setImageBitmap(rgba); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { case TAKE_PHOTO: if (resultCode == RESULT_OK) { try { yourSelectedImage = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri)); imageView.setImageBitmap(yourSelectedImage); } catch (FileNotFoundException e) { e.printStackTrace(); Log.e("MainActivity", "FileNotFoundException"); } } break; case SELECT_IMAGE: if (resultCode == RESULT_OK && null != data) { Uri selectedImage = data.getData(); try { if (requestCode == SELECT_IMAGE) { yourSelectedImage = decodeUri(selectedImage); imageView.setImageBitmap(yourSelectedImage); } } catch (FileNotFoundException e) { Log.e("MainActivity", "FileNotFoundException"); return; } } default: break; } } private Bitmap decodeUri(Uri selectedImage) throws FileNotFoundException { // Decode image size BitmapFactory.Options o = new BitmapFactory.Options(); o.inJustDecodeBounds = true; BitmapFactory.decodeStream(getContentResolver().openInputStream(selectedImage), null, o); // The new size we want to scale to final int REQUIRED_SIZE = 640; // Find the correct scale value. It should be the power of 2. int width_tmp = o.outWidth, height_tmp = o.outHeight; int scale = 1; while (true) { if (width_tmp / 2 < REQUIRED_SIZE || height_tmp / 2 < REQUIRED_SIZE) { break; } width_tmp /= 2; height_tmp /= 2; scale *= 2; } // Decode with inSampleSize BitmapFactory.Options o2 = new BitmapFactory.Options(); o2.inSampleSize = scale; Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(selectedImage), null, o2); // Rotate according to EXIF int rotate = 0; try { ExifInterface exif = new ExifInterface(getContentResolver().openInputStream(selectedImage)); int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); switch (orientation) { case ExifInterface.ORIENTATION_ROTATE_270: rotate = 270; break; case ExifInterface.ORIENTATION_ROTATE_180: rotate = 180; break; case ExifInterface.ORIENTATION_ROTATE_90: rotate = 90; break; } } catch (IOException e) { Log.e("MainActivity", "ExifInterface IOException"); } Matrix matrix = new Matrix(); matrix.postRotate(rotate); return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); } } ================================================ FILE: app/src/main/java/com/tencent/paddleocrncnn/PaddleOCRNcnn.java ================================================ // Tencent is pleased to support the open source community by making ncnn available. // // Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. // // Licensed under the BSD 3-Clause License (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // https://opensource.org/licenses/BSD-3-Clause // // 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.tencent.paddleocrncnn; import android.content.res.AssetManager; import android.graphics.Bitmap; public class PaddleOCRNcnn { public native boolean Init(AssetManager mgr); public class Obj { public float x0; public float y0; public float x1; public float y1; public float x2; public float y2; public float x3; public float y3; public String label; public float prob; } public native Obj[] Detect(Bitmap bitmap, boolean use_gpu); static { System.loadLibrary("paddleocrncnn"); } } ================================================ FILE: app/src/main/jni/CMakeLists.txt ================================================ project(paddleocrncnn) cmake_minimum_required(VERSION 3.4.1) set(OpenCV_DIR ${CMAKE_SOURCE_DIR}/opencv-mobile-4.5.1-android/sdk/native/jni) find_package(OpenCV REQUIRED core imgproc) set(ncnn_DIR ${CMAKE_SOURCE_DIR}/ncnn-20210720-android-vulkan/${ANDROID_ABI}/lib/cmake/ncnn) find_package(ncnn REQUIRED) add_library(paddleocrncnn SHARED paddleocr_ncnn.cpp common.cpp clipper.cpp) target_link_libraries(paddleocrncnn ncnn ${OpenCV_LIBS} jnigraphics) ================================================ FILE: app/src/main/jni/clipper.cpp ================================================ /******************************************************************************* * * * 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 "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) return;//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) return false;//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; // return false;//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) return;//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) return false;//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 return;//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 ================================================ FILE: app/src/main/jni/clipper.hpp ================================================ /******************************************************************************* * * * 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< IntPoint > Path; typedef std::vector< Path > 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< PolyNode* > 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 < OutRec* > PolyOutList; typedef std::vector < TEdge* > EdgeList; typedef std::vector < Join* > JoinList; typedef std::vector < IntersectNode* > 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 ================================================ FILE: app/src/main/jni/common.cpp ================================================ #include "common.h" #include #include bool cvPointCompare(const cv::Point& a, const cv::Point& b) { return a.x < b.x; } bool compareBoxWidth(const TextBox &a, const TextBox& b) { return abs(a.boxPoint[0].x-a.boxPoint[1].x)>abs(b.boxPoint[0].x-b.boxPoint[1].x); } std::vector getMinBoxes(const std::vector& inVec, float& minSideLen, float& allEdgeSize) { std::vector minBoxVec; cv::RotatedRect textRect = cv::minAreaRect(inVec); cv::Mat boxPoints2f; cv::boxPoints(textRect, boxPoints2f); float* p1 = (float*)boxPoints2f.data; std::vector tmpVec; for (int i = 0; i < 4; ++i, p1 += 2) { tmpVec.emplace_back(int(p1[0]), int(p1[1])); } std::sort(tmpVec.begin(), tmpVec.end(), cvPointCompare); minBoxVec.clear(); int index1, index2, index3, index4; if (tmpVec[1].y > tmpVec[0].y) { index1 = 0; index4 = 1; } else { index1 = 1; index4 = 0; } if (tmpVec[3].y > tmpVec[2].y) { index2 = 2; index3 = 3; } else { index2 = 3; index3 = 2; } minBoxVec.clear(); minBoxVec.push_back(tmpVec[index1]); minBoxVec.push_back(tmpVec[index2]); minBoxVec.push_back(tmpVec[index3]); minBoxVec.push_back(tmpVec[index4]); minSideLen = (std::min)(textRect.size.width, textRect.size.height); allEdgeSize = 2.f * (textRect.size.width + textRect.size.height); return minBoxVec; } float boxScoreFast(const cv::Mat & inMat, const std::vector & inBox) { std::vector box = inBox; int width = inMat.cols; int height = inMat.rows; int maxX = -1, minX = 1000000, maxY = -1, minY = 1000000; for (int i = 0; i < box.size(); ++i) { if (maxX < box[i].x) maxX = box[i].x; if (minX > box[i].x) minX = box[i].x; if (maxY < box[i].y) maxY = box[i].y; if (minY > box[i].y) minY = box[i].y; } maxX = (std::min)((std::max)(maxX, 0), width - 1); minX = (std::max)((std::min)(minX, width - 1), 0); maxY = (std::min)((std::max)(maxY, 0), height - 1); minY = (std::max)((std::min)(minY, height - 1), 0); for (int i = 0; i < box.size(); ++i) { box[i].x = box[i].x - minX; box[i].y = box[i].y - minY; } std::vector> maskBox; maskBox.push_back(box); cv::Mat maskMat(maxY - minY + 1, maxX - minX + 1, CV_8UC1, cv::Scalar(0, 0, 0)); cv::fillPoly(maskMat, maskBox, cv::Scalar(1, 1, 1), 1); return cv::mean(inMat(cv::Rect(cv::Point(minX, minY), cv::Point(maxX + 1, maxY + 1))).clone(), maskMat).val[0]; } std::vector unClip(const std::vector & inBox, float perimeter, float unClipRatio) { std::vector outBox; ClipperLib::Path poly; for (int i = 0; i < inBox.size(); ++i) { poly.push_back(ClipperLib::IntPoint(inBox[i].x, inBox[i].y)); } double distance = unClipRatio * ClipperLib::Area(poly) / (double)perimeter; ClipperLib::ClipperOffset clipperOffset; clipperOffset.AddPath(poly, ClipperLib::JoinType::jtRound, ClipperLib::EndType::etClosedPolygon); ClipperLib::Paths polys; polys.push_back(poly); clipperOffset.Execute(polys, distance); outBox.clear(); std::vector rsVec; for (int i = 0; i < polys.size(); ++i) { ClipperLib::Path tmpPoly = polys[i]; for (int j = 0; j < tmpPoly.size(); ++j) { outBox.emplace_back(tmpPoly[j].X, tmpPoly[j].Y); } } return outBox; } cv::Mat getRotateCropImage(const cv::Mat& src, std::vector box) { cv::Mat image; src.copyTo(image); std::vector points = box; int collectX[4] = { box[0].x, box[1].x, box[2].x, box[3].x }; int collectY[4] = { box[0].y, box[1].y, box[2].y, box[3].y }; int left = int(*std::min_element(collectX, collectX + 4)); int right = int(*std::max_element(collectX, collectX + 4)); int top = int(*std::min_element(collectY, collectY + 4)); int bottom = int(*std::max_element(collectY, collectY + 4)); cv::Mat imgCrop; image(cv::Rect(left, top, right - left, bottom - top)).copyTo(imgCrop); for (int i = 0; i < points.size(); i++) { points[i].x -= left; points[i].y -= top; } int imgCropWidth = int(sqrt(pow(points[0].x - points[1].x, 2) + pow(points[0].y - points[1].y, 2))); int imgCropHeight = int(sqrt(pow(points[0].x - points[3].x, 2) + pow(points[0].y - points[3].y, 2))); cv::Point2f ptsDst[4]; ptsDst[0] = cv::Point2f(0., 0.); ptsDst[1] = cv::Point2f(imgCropWidth, 0.); ptsDst[2] = cv::Point2f(imgCropWidth, imgCropHeight); ptsDst[3] = cv::Point2f(0.f, imgCropHeight); cv::Point2f ptsSrc[4]; ptsSrc[0] = cv::Point2f(points[0].x, points[0].y); ptsSrc[1] = cv::Point2f(points[1].x, points[1].y); ptsSrc[2] = cv::Point2f(points[2].x, points[2].y); ptsSrc[3] = cv::Point2f(points[3].x, points[3].y); cv::Mat M = cv::getPerspectiveTransform(ptsSrc, ptsDst); cv::Mat partImg; cv::warpPerspective(imgCrop, partImg, M, cv::Size(imgCropWidth, imgCropHeight), cv::BORDER_REPLICATE); if (float(partImg.rows) >= float(partImg.cols) * 1.5) { cv::Mat srcCopy = cv::Mat(partImg.rows, partImg.cols, partImg.depth()); cv::transpose(partImg, srcCopy); cv::flip(srcCopy, srcCopy, 0); return srcCopy; } else { return partImg; } } std::vector getPartImages(const cv::Mat& src, std::vector& textBoxes) { std::sort(textBoxes.begin(),textBoxes.end(),compareBoxWidth); std::vector partImages; if(textBoxes.size() > 0) { for (int i = 0; i < textBoxes.size(); ++i) { cv::Mat partImg = getRotateCropImage(src, textBoxes[i].boxPoint); partImages.emplace_back(partImg); } } return partImages; } cv::Mat matRotateClockWise180(cv::Mat src) { flip(src, src, 0); flip(src, src, 1); return src; } cv::Mat makePadding(cv::Mat& src, const int padding) { if (padding <= 0) return src; cv::Scalar paddingScalar = { 255, 255, 255 }; cv::Mat paddingSrc; cv::copyMakeBorder(src, paddingSrc, padding, padding, padding, padding, cv::BORDER_ISOLATED, paddingScalar); return paddingSrc; } ================================================ FILE: app/src/main/jni/common.h ================================================ #ifndef __COMMON_H_ #define __COMMON_H_ #include #include #include "clipper.hpp" #include struct TextBox { std::vector boxPoint; float score; std::string text; }; struct TextLine { std::string text; std::vector charScores; }; struct Angle { int index; float score; }; std::vector getMinBoxes(const std::vector& inVec, float& minSideLen, float& allEdgeSize); float boxScoreFast(const cv::Mat& inMat, const std::vector& inBox); std::vector unClip(const std::vector& inBox, float perimeter, float unClipRatio); cv::Mat getRotateCropImage(const cv::Mat& src, std::vector box); std::vector getPartImages(const cv::Mat& src, std::vector& textBoxes); cv::Mat matRotateClockWise180(cv::Mat src); cv::Mat makePadding(cv::Mat& src, const int padding); #endif ================================================ FILE: app/src/main/jni/paddleocr_ncnn.cpp ================================================ // Tencent is pleased to support the open source community by making ncnn available. // // Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. // // Licensed under the BSD 3-Clause License (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // https://opensource.org/licenses/BSD-3-Clause // // 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 #include #include #include // ncnn #include "layer.h" #include "net.h" #include "benchmark.h" #include "common.h" static ncnn::UnlockedPoolAllocator g_blob_pool_allocator; static ncnn::PoolAllocator g_workspace_pool_allocator; const int dstHeight = 32;//when use PP-OCRv3 it should be 48 ncnn::Net dbNet; ncnn::Net crnnNet; std::vector keys; char *readKeysFromAssets(AAssetManager *mgr) { if (mgr == NULL) { return NULL; } char *buffer; AAsset *asset = AAssetManager_open(mgr, "paddleocr_keys.txt", AASSET_MODE_UNKNOWN); if (asset == NULL) { return NULL; } off_t bufferSize = AAsset_getLength(asset); buffer = (char *) malloc(bufferSize + 1); buffer[bufferSize] = 0; int numBytesRead = AAsset_read(asset, buffer, bufferSize); AAsset_close(asset); return buffer; } std::vector findRsBoxes(const cv::Mat& fMapMat, const cv::Mat& norfMapMat, const float boxScoreThresh, const float unClipRatio) { float minArea = 3; std::vector rsBoxes; rsBoxes.clear(); std::vector> contours; cv::findContours(norfMapMat, contours, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE); for (int i = 0; i < contours.size(); ++i) { float minSideLen, perimeter; std::vector minBox = getMinBoxes(contours[i], minSideLen, perimeter); if (minSideLen < minArea) continue; float score = boxScoreFast(fMapMat, contours[i]); if (score < boxScoreThresh) continue; //---use clipper start--- std::vector clipBox = unClip(minBox, perimeter, unClipRatio); std::vector clipMinBox = getMinBoxes(clipBox, minSideLen, perimeter); //---use clipper end--- if (minSideLen < minArea + 2) continue; for (int j = 0; j < clipMinBox.size(); ++j) { clipMinBox[j].x = (clipMinBox[j].x / 1.0); clipMinBox[j].x = (std::min)((std::max)(clipMinBox[j].x, 0), norfMapMat.cols); clipMinBox[j].y = (clipMinBox[j].y / 1.0); clipMinBox[j].y = (std::min)((std::max)(clipMinBox[j].y, 0), norfMapMat.rows); } rsBoxes.emplace_back(TextBox{ clipMinBox, score }); } reverse(rsBoxes.begin(), rsBoxes.end()); return rsBoxes; } std::vector getTextBoxes(const cv::Mat & src, float boxScoreThresh, float boxThresh, float unClipRatio) { int width = src.cols; int height = src.rows; int target_size = 640; // pad to multiple of 32 int w = width; int h = height; float scale = 1.f; if (w > h) { scale = (float)target_size / w; w = target_size; h = h * scale; } else { scale = (float)target_size / h; h = target_size; w = w * scale; } ncnn::Mat input = ncnn::Mat::from_pixels_resize(src.data, ncnn::Mat::PIXEL_RGB, width, height, w, h); // pad to target_size rectangle int wpad = (w + 31) / 32 * 32 - w; int hpad = (h + 31) / 32 * 32 - h; ncnn::Mat in_pad; ncnn::copy_make_border(input, in_pad, hpad / 2, hpad - hpad / 2, wpad / 2, wpad - wpad / 2, ncnn::BORDER_CONSTANT, 0.f); const float meanValues[3] = { 0.485 * 255, 0.456 * 255, 0.406 * 255 }; const float normValues[3] = { 1.0 / 0.229 / 255.0, 1.0 / 0.224 / 255.0, 1.0 / 0.225 / 255.0 }; in_pad.substract_mean_normalize(meanValues, normValues); ncnn::Extractor extractor = dbNet.create_extractor(); extractor.input("input0", in_pad); ncnn::Mat out; extractor.extract("out1", out); cv::Mat fMapMat(in_pad.h, in_pad.w, CV_32FC1, (float*)out.data); cv::Mat norfMapMat; norfMapMat = fMapMat > boxThresh; cv::dilate(norfMapMat, norfMapMat, cv::Mat(), cv::Point(-1, -1), 1); std::vector result = findRsBoxes(fMapMat, norfMapMat, boxScoreThresh, 2.0f); for(int i = 0; i < result.size(); i++) { for(int j = 0; j < result[i].boxPoint.size(); j++) { float x = (result[i].boxPoint[j].x-(wpad/2))/scale; float y = (result[i].boxPoint[j].y-(hpad/2))/scale; x = std::max(std::min(x,(float)(width-1)),0.f); y = std::max(std::min(y,(float)(height-1)),0.f); result[i].boxPoint[j].x = x; result[i].boxPoint[j].y = y; } } return result; } template inline static size_t argmax(ForwardIterator first, ForwardIterator last) { return std::distance(first, std::max_element(first, last)); } TextLine scoreToTextLine(const std::vector& outputData, int h, int w) { int keySize = keys.size(); std::string strRes; std::vector scores; int lastIndex = 0; int maxIndex; float maxValue; for (int i = 0; i < h; i++) { maxIndex = 0; maxValue = -1000.f; maxIndex = int(argmax(outputData.begin()+i*w, outputData.begin()+i*w+w)); maxValue = float(*std::max_element(outputData.begin()+i*w, outputData.begin()+i*w+w));// / partition; if (maxIndex > 0 && maxIndex < keySize && (!(i > 0 && maxIndex == lastIndex))) { scores.emplace_back(maxValue); strRes.append(keys[maxIndex - 1]); } lastIndex = maxIndex; } return { strRes, scores }; } TextLine getTextLine(const cv::Mat & src) { float scale = (float)dstHeight / (float)src.rows; int dstWidth = int((float)src.cols * scale); cv::Mat srcResize; cv::resize(src, srcResize, cv::Size(dstWidth, dstHeight)); //if you use PP-OCRv3 you should change PIXEL_RGB to PIXEL_RGB2BGR ncnn::Mat input = ncnn::Mat::from_pixels(srcResize.data, ncnn::Mat::PIXEL_RGB,srcResize.cols, srcResize.rows); const float mean_vals[3] = { 127.5, 127.5, 127.5 }; const float norm_vals[3] = { 1.0 / 127.5, 1.0 / 127.5, 1.0 / 127.5 }; input.substract_mean_normalize(mean_vals, norm_vals); ncnn::Extractor extractor = crnnNet.create_extractor(); //extractor.set_num_threads(2); extractor.input("input", input); ncnn::Mat out; extractor.extract("out", out); float* floatArray = (float*)out.data; std::vector outputData(floatArray, floatArray + out.h * out.w); TextLine res = scoreToTextLine(outputData, out.h, out.w); return res; } std::vector getTextLines(std::vector & partImg) { int size = partImg.size(); std::vector textLines(size); for (int i = 0; i < size; ++i) { TextLine textLine = getTextLine(partImg[i]); textLines[i] = textLine; } return textLines; } extern "C" { // FIXME DeleteGlobalRef is missing for objCls static jclass objCls = NULL; static jmethodID constructortorId; static jfieldID x0Id; static jfieldID y0Id; static jfieldID x1Id; static jfieldID y1Id; static jfieldID x2Id; static jfieldID y2Id; static jfieldID x3Id; static jfieldID y3Id; static jfieldID labelId; static jfieldID probId; JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { __android_log_print(ANDROID_LOG_DEBUG, "PaddleOCRNcnn", "JNI_OnLoad"); ncnn::create_gpu_instance(); return JNI_VERSION_1_4; } JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved) { __android_log_print(ANDROID_LOG_DEBUG, "PaddleOCRNcnn", "JNI_OnUnload"); ncnn::destroy_gpu_instance(); } // public native boolean Init(AssetManager mgr); JNIEXPORT jboolean JNICALL Java_com_tencent_paddleocrncnn_PaddleOCRNcnn_Init(JNIEnv* env, jobject thiz, jobject assetManager) { ncnn::Option opt; opt.lightmode = true; opt.num_threads = 4; opt.blob_allocator = &g_blob_pool_allocator; opt.workspace_allocator = &g_workspace_pool_allocator; opt.use_packing_layout = true; // use vulkan compute if (ncnn::get_gpu_count() != 0) opt.use_vulkan_compute = true; AAssetManager* mgr = AAssetManager_fromJava(env, assetManager); dbNet.opt = opt; crnnNet.opt = opt; // init param { int ret = dbNet.load_param(mgr, "pdocrv2.0_det-op.param"); if (ret != 0) { __android_log_print(ANDROID_LOG_WARN, "PaddleocrNcnn", "load_dbNet_param failed"); return JNI_FALSE; } ret = crnnNet.load_param(mgr, "pdocrv2.0_rec-op.param"); if (ret != 0) { __android_log_print(ANDROID_LOG_WARN, "PaddleocrNcnn", "load_crnnNet_param failed"); return JNI_FALSE; } } // init bin { int ret = dbNet.load_model(mgr, "pdocrv2.0_det-op.bin"); if (ret != 0) { __android_log_print(ANDROID_LOG_WARN, "PaddleocrNcnn", "load_dbNet_model failed"); return JNI_FALSE; } ret = crnnNet.load_model(mgr, "pdocrv2.0_rec-op.bin"); if (ret != 0) { __android_log_print(ANDROID_LOG_WARN, "PaddleocrNcnn", "load_crnnNet_model failed"); return JNI_FALSE; } } //load keys char *buffer = readKeysFromAssets(mgr); if (buffer != NULL) { std::istringstream inStr(buffer); std::string line; int size = 0; while (getline(inStr, line)) { keys.emplace_back(line); size++; } free(buffer); } else { return false; } // init jni glue jclass localObjCls = env->FindClass("com/tencent/paddleocrncnn/PaddleOCRNcnn$Obj"); objCls = reinterpret_cast(env->NewGlobalRef(localObjCls)); constructortorId = env->GetMethodID(objCls, "", "(Lcom/tencent/paddleocrncnn/PaddleOCRNcnn;)V"); x0Id = env->GetFieldID(objCls, "x0", "F"); y0Id = env->GetFieldID(objCls, "y0", "F"); x1Id = env->GetFieldID(objCls, "x1", "F"); y1Id = env->GetFieldID(objCls, "y1", "F"); x2Id = env->GetFieldID(objCls, "x2", "F"); y2Id = env->GetFieldID(objCls, "y2", "F"); x3Id = env->GetFieldID(objCls, "x3", "F"); y3Id = env->GetFieldID(objCls, "y3", "F"); labelId = env->GetFieldID(objCls, "label", "Ljava/lang/String;"); probId = env->GetFieldID(objCls, "prob", "F"); return JNI_TRUE; } // public native Obj[] Detect(Bitmap bitmap, boolean use_gpu); JNIEXPORT jobjectArray JNICALL Java_com_tencent_paddleocrncnn_PaddleOCRNcnn_Detect(JNIEnv* env, jobject thiz, jobject bitmap, jboolean use_gpu) { if (use_gpu == JNI_TRUE && ncnn::get_gpu_count() == 0) { return NULL; //return env->NewStringUTF("no vulkan capable gpu"); } AndroidBitmapInfo info; AndroidBitmap_getInfo(env, bitmap, &info); const int width = info.width; const int height = info.height; if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) return NULL; ncnn::Mat in = ncnn::Mat::from_android_bitmap(env, bitmap, ncnn::Mat::PIXEL_RGB); cv::Mat rgb = cv::Mat::zeros(in.h,in.w,CV_8UC3); in.to_pixels(rgb.data, ncnn::Mat::PIXEL_RGB); std::vector objects; objects = getTextBoxes(rgb, 0.4, 0.3, 2.0); std::vector partImages = getPartImages(rgb, objects); std::vector textLines = getTextLines(partImages); if(textLines.size() > 0) { for(int i = 0; i < textLines.size(); i++) objects[i].text = textLines[i].text; } // objects to Obj[] jobjectArray jObjArray = env->NewObjectArray(objects.size(), objCls, NULL); for (size_t i=0; iNewObject(objCls, constructortorId, thiz); float x0 = objects[i].boxPoint[0].x; float y0 = objects[i].boxPoint[0].y; float x1 = objects[i].boxPoint[1].x; float y1 = objects[i].boxPoint[1].y; float x2 = objects[i].boxPoint[2].x; float y2 = objects[i].boxPoint[2].y; float x3 = objects[i].boxPoint[3].x; float y3 = objects[i].boxPoint[3].y; env->SetFloatField(jObj, x0Id, x0); env->SetFloatField(jObj, y0Id, y0); env->SetFloatField(jObj, x1Id, x1); env->SetFloatField(jObj, y1Id, y1); env->SetFloatField(jObj, x2Id, x2); env->SetFloatField(jObj, y2Id, y2); env->SetFloatField(jObj, x3Id, x3); env->SetFloatField(jObj, y3Id, y3); env->SetObjectField(jObj, labelId, env->NewStringUTF(objects[i].text.c_str())); env->SetFloatField(jObj, probId, objects[i].score); env->SetObjectArrayElement(jObjArray, i, jObj); } return jObjArray; } } ================================================ FILE: app/src/main/res/layout/main.xml ================================================